Aller au contenu
s3nd.sh

Votre bucket · Un code · Pas de compte

Envoyez n’importe quoi avec un code.

s3nd dépose un fichier dans un bucket S3 qui vous appartient et vous remet huit caractères. Quiconque a le code le récupère, depuis n’importe quelle machine, jusqu’à son expiration. Depuis un terminal, depuis votre propre app, ou depuis curl.

transfer · drop/K7QP2M4Xvotre bucketexpire 1h
you@s3nd.sh $ s3nd put ./report.pdf
report.pdf · 284 kB · expires in 1 hour
$ s3nd get k7qp-2m4xn’importe quelle autre machine
Wrote ./report.pdf · 284 kB
$ s3nd rm k7qp-2m4x
Burned K7QP2M4X brûlé
$
  • s3nd.sh
  • Pas de compte
  • Pas de relais
  • Rien à déployer
  • Votre bucket
  • AWS S3
  • Cloudflare R2
  • MinIO
  • Scaleway
  • Wasabi
  • Expire tout seul
  • Codes de 40 bits
  • CLI · Bibliothèque · React
  • MIT

01Put. Code. Get.

Tout le produit tient en trois commandes.

Les octets vont d’une machine à votre bucket, puis de votre bucket à l’autre machine. Rien ne transite par le serveur de quelqu’un d’autre, et personne ne s’inscrit nulle part.

  1. put

    Déposez-le dans votre bucket.

    Un fichier, un dossier sous forme d’archive, tout ce qui arrive sur stdin. Un seul PutObject dans un bucket à vous, sous un code neuf, avec une expiration inscrite sur l’objet. Le code est affiché et rien d’autre, donc ça se compose.

  2. code

    Dictez-le au téléphone.

    Huit caractères, quarante bits, ni I, ni L, ni O, ni U. Écrivez-le sur un post-it, tapez-le dans la mauvaise casse avec un tiret au milieu : il se résout quand même. Le serveur le choisit et le réserve avec une écriture conditionnelle.

  3. get

    Récupérez-le n’importe où.

    Toute machine qui a le code et l’accès au bucket, ou un jeton pour votre serveur, récupère le fichier. Jusqu’à l’expiration du transfert, ou jusqu’à ce que vous le brûliez. L’autre machine n’avait pas besoin d’être allumée au moment de l’envoi.

02Trois façons d’envoyer

Un terminal, votre propre app, ou n’importe quoi qui parle HTTP.

Une seule primitive sous les trois. Le package côté serveur détient les clés ; tout ce qui tourne dans un navigateur ne dépend que de fetch.

Depuis un terminal

@s3nd/cli

Directement vers le bucket avec les identifiants de cette machine, rien à déployer. Ou via votre serveur avec un jeton et --remote. Une commande doctor prouve que la configuration fonctionne.

$ s3nd put ./build.tar.gz
build.tar.gz · 41 MB · expires in 1 day
K7QP2M4X

$ tar cz ./photos | s3nd put - --name photos.tar.gz
$ CODE=$(s3nd put ./report.pdf)     # code on stdout, the rest on stderr

# any other machine
$ s3nd get k7qp-2m4x
Wrote ./build.tar.gz · 41 MB
Toutes les commandes

Dans votre app

s3nd@s3nd/react

Un seul fichier de route sert le protocole à quatre routes sur votre domaine, avec votre authentification. Les hooks React envoient un fichier, relisent un code, et réparent ce que l’utilisateur a tapé.

// app/api/transfers/[[...route]]/route.ts
import { createBucket, createTransferHandler } from 's3nd'

export const { GET, POST, DELETE } = createTransferHandler({
  bucket: createBucket({ bucket: 'drop' }),
  expiresIn: 24 * 3600,
  authorize: (request) => request.headers.get('authorization') === `Bearer ${process.env.TOKEN}`,
})

// In the browser, with @s3nd/react:
const { sendFile, transfer } = useSendTransfer()
await sendFile(file) // → transfer.code

Tout ce qui parle HTTP

@s3nd/protocol

Le protocole, ce sont quatre routes et un format d’erreur, écrits noir sur blanc. curl fonctionne, un client Go fonctionne, et un serveur Rails fonctionne avec tous les clients s3nd.

$ curl -X POST https://drop.example.com/api/transfers \
    -H "Authorization: Bearer $TOKEN" \
    -H "Content-Type: application/pdf" \
    -H "X-S3nd-Filename: report.pdf" \
    --data-binary @report.pdf
{ "code": "K7QP2M4X", "kind": "file", "size": 290816, "expiresAt": "…" }

$ curl -LOJ -H "Authorization: Bearer $TOKEN" \
    https://drop.example.com/api/transfers/K7QP2M4X/raw
La spec du protocole

03Votre bucket

Personne au milieu.

Tous les autres outils du domaine font tourner un relais, hébergent vos fichiers, ou demandent un compte. s3nd est une fine couche au-dessus du stockage objet que vous payez déjà.

Votre bucket

Les octets vont d’une machine à votre bucket et de votre bucket à l’autre. Rien d’autre dans le tableau.

Pas de relais

De la machine au bucket, du bucket à la machine. La durabilité de votre fournisseur, sa facture, et sur R2 aucun frais de sortie.

Pas de compte

Un code, c’est toute la poignée de main. Sur votre propre serveur, un jeton par personne est le maximum d’identité que s3nd demandera jamais.

Rien à déployer

La CLI parle directement au bucket. Un serveur n’entre en scène que lorsqu’un navigateur l’exige, et c’est un seul fichier de route.

Expire tout seul

Chaque transfert porte une expiration, vérifiée à chaque lecture. Une règle de cycle de vie supprime l’objet, et s3nd doctor vérifie que vous en avez une.

04Codes de synchronisation

Un code que l’on peut dicter au téléphone.

Toute l’expérience d’un transfert, c’est quelqu’un qui lit un code sur un écran et le tape sur un autre. Tout dans le code découle de là.

Ce que codes.normalize() cherche

Complet. Séparateurs retirés, casse repliée, et O, I et L lus comme 0, 1 et 1, parce que la base32 Crockford n’a ni O, ni I, ni L avec lesquels les confondre.

Alphabet 0123456789ABCDEFGHJKMNPQRSTVWXYZ · 8 caractères · 40 bits

Par défaut, huit caractères en base32 Crockford : ni I, ni L, ni O, ni U, pour qu’un code survive au papier, au clavier d’un téléphone et à un appel. La génération et la normalisation vivent sur le même objet, donc les deux côtés ne peuvent jamais être en désaccord sur l’alphabet.

Quatre chiffres pour une app pensée pour le téléphone, douze caractères alphanumériques pour un dépôt de longue durée : les deux moitiés sont configurables, et entropyBits vous dit ce que le code vaut face à une attaque par devinette, pour que la limitation de débit fasse le reste.

Un code est un jeton au porteur. Donnez-lui une expiration courte, limitez le débit de la route de lecture, et pour les données sensibles, chiffrez avant que quoi que ce soit n’atteigne le bucket.

05Et aussi

Pas seulement des fichiers. Tout l’état d’une app.

Un transfert peut être des données structurées autant que des octets. C’est ainsi qu’une app local-first sans comptes emporte sa base de données sur le nouveau téléphone de l’utilisateur : le navigateur exporte IndexedDB, le serveur en fait un snapshot, l’autre téléphone tape le code.

L’ancien téléphone exporte, votre serveur fait un snapshot, le nouveau téléphone tape le code et voit ce qu’il s’apprête à restaurer.

import { createBucket } from 's3nd'

const store = createBucket({ bucket: 'my-bucket', prefix: 'snapshots' })

// On the old phone: hand the user a code.
const code = store.codes.create() // "K7QP2M4X"
await store.putSnapshot(code, state, { app: 'notes', version: 3, expiresIn: 3600 })

// On the new phone: they type it in.
const snapshot = await store.getSnapshot(store.codes.normalize(typed), { maxVersion: 3 })
snapshot?.data // → ready to write back into IndexedDB

Une enveloppe qui se décrit elle-même

Le nom de votre app, votre version de schéma, l’appareil, une expiration, puis les données, gzippées. Une restauration refuse un snapshot issu d’une version plus récente au lieu de le mal lire, et montre quand et où il a été fait avant de remplacer quoi que ce soit.

Deux appareils, une sauvegarde, aucune perte silencieuse

Passez l’ETag lu en dernier comme ifMatch, et un appareil qui écrit après un autre reçoit une erreur au lieu d’écraser son travail.

07Comparé

Pourquoi pas croc, WeTransfer ou rclone ?

Parfois, ce sont eux la bonne réponse. Les pages de comparaison le disent, outil par outil, dans les termes de l’autre outil.

08Questions

Celles qui reviennent.

01Qu’est-ce que s3nd ?

Une façon d’envoyer n’importe quoi avec un code. s3nd put dépose un fichier dans un bucket S3 qui vous appartient et affiche huit caractères ; s3nd get sur n’importe quelle autre machine, avec le code, le télécharge. La même primitive tourne dans votre propre app sous forme de bibliothèque et de hooks React, et elle transporte des données structurées autant que des fichiers. Comment ça marche

02En quoi est-ce différent de WeTransfer, croc ou Magic Wormhole ?

Les octets vont dans votre bucket et nulle part ailleurs : pas de service hébergé, pas de relais, pas de compte d’un côté ni de l’autre. Et contrairement à un transfert pair-à-pair en direct, l’expéditeur s’en va et le code est utilisé plus tard, jusqu’à son expiration. Les pages de comparaison vont outil par outil et disent quand l’autre est le meilleur choix. Comparer

03Ai-je besoin d’un serveur ?

Pas pour des machines que vous contrôlez : la CLI parle directement au bucket avec les identifiants de cette machine, et il n’y a rien à déployer. Il vous en faut un dès qu’un navigateur participe, parce qu’un navigateur ne peut pas détenir des identifiants S3. s3nd livre ce serveur sous forme d’un seul fichier de route, et la CLI lui parle avec un jeton plutôt qu’avec des clés. Sans serveur

04Quels fournisseurs de stockage fonctionnent ?

AWS S3 et tout ce qui parle l’API S3 : Cloudflare R2, MinIO, Scaleway Object Storage, Wasabi, Ceph, Garage et les autres. Définissez un endpoint et s3nd bascule les deux valeurs par défaut que ces fournisseurs attendent. s3nd init écrit une configuration de départ par fournisseur, et s3nd doctor prouve qu’elle fonctionne avant que vous ne vous y fiiez. Fournisseurs de stockage

05Un code, c’est sûr ?

Un code est un jeton au porteur : quiconque l’a peut lire ce transfert-là tant qu’il vit. Par défaut, huit caractères en base32 Crockford, quarante bits, ce qui est solide quand un transfert expire dans la journée et que la route de lecture est limitée en débit. Pour les données sensibles, chiffrez avant d’envoyer ; s3nd stocke les octets que vous lui donnez, tels quels. Codes de synchronisation

06Quelle taille peut faire un fichier ?

Depuis la CLI directement vers le bucket, un fichier monte en un seul PutObject, donc il est borné par la mémoire de la machine qui envoie plutôt que par une limite de requête : des centaines de mégaoctets passent, les archives de plusieurs gigaoctets attendent le multipart, qui est sur la feuille de route. Via votre serveur, la limite de requête du runtime s’applique, 4,5 Mo sur les fonctions Vercel et 6 Mo sur Lambda, sauf à présigner. Limites

07Que se passe-t-il quand un transfert expire ?

Il n’est plus jamais remis : l’expiration est vérifiée à chaque lecture, et un code expiré répond le même NOT_FOUND qu’un code qui n’a jamais existé. L’objet lui-même est supprimé par une règle de cycle de vie sur votre bucket, dont s3nd doctor vérifie la présence, parce qu’un bucket qui se remplit discrètement de transferts expirés est la façon la plus courante de se tromper.

08Peut-il déplacer les données d’une app, pas seulement des fichiers ?

Oui. Un snapshot est de l’état structuré enveloppé avec le nom de votre app, une version de schéma et une expiration, gzippé, et stocké sous un code. Une app local-first exporte son IndexedDB, le poste, et l’utilisateur tape le code sur son autre téléphone : pas de compte, et la version qui reçoit refuse un snapshot d’un schéma plus récent. C’est de là que s3nd est parti. Migrer une app sur un nouvel appareil

09Combien ça coûte ?

Rien. Chaque package est sous licence MIT et il n’y a aucun service hébergé au milieu. La seule facture est ce que votre fournisseur de stockage demande pour quelques objets qui expirent, et sur Cloudflare R2 la sortie est gratuite.

10Sur quels runtimes ça tourne ?

La bibliothèque et la CLI ont besoin de Node 20 ou plus, parce que le SDK AWS l’exige. Le handler de transfert prend une Request et renvoie une Response, donc il s’insère dans Next.js, Hono, Bun.serve, Deno ou un worker sans adaptateur. Les packages navigateur ne dépendent que de fetch, donc ils tournent dans un navigateur, un worker ou React Native.

Déposez un fichier. Donnez le code.

Pointez-le sur le bucket que vous payez déjà. Rien à déployer, aucune inscription, personne au milieu.