Releases: thebkht/cliplink
Release list
@thebkht/rtc-file-transfer 1.3.0
📦 https://www.npmjs.com/package/@thebkht/rtc-file-transfer
- A device that has a file can pass it on. With
keepReceived, a finished download stays in theresumestore instead of being deleted, andseed(entries)offers what the store holds — after a reload too — under a fresh offer id and the original digest. Nothing is hashed again, and the bytes are read back only when a peer asks. Seeded items areseeded: true. - Offers of the same content are one item. Matched by
digest, they collapse into one incoming item whosesourceslists every peer offering it;peerIdis the one a download uses. A source leaving only revokes the item when it was the last. Offers without a digest keep a row each, as before. - A download continues at another peer when its source goes away or runs out, keeping its sink and every verified block. That is a handoff, not a failure: nothing is reported. Sources that let the download down are tried last, and handoffs that make no progress are bounded.
- The
partialcapability lets a sender offer a verified prefix. The offer'shavesays how much, and the sender ends with{"t":"part","b":N}instead of"done". WithseedWhileDownloading(on by default) a stored download offers its prefix while it is still arriving.hellonow carriescaps, since offers are broadcast and that is where a sender learns which peers can take a partial one. ResumeProvidergains optionallist,readandkeep, implemented byopfsResume, which also takes atag.ResumeKeymay carrymimeandpath. A file stored with a differentsegmentBytesnow starts over rather than resuming into misaligned parts.prefixRangein/sinks: the Range arithmetic for playing a file while it downloads.- A device no longer lists an incoming offer for content it is itself offering.
FileItem.verifiedBytes: how much of a download has been verified and written, which on a slow disk trailsbytes. Cleared when the download ends.- A growing prefix is re-announced at most every 5 s, and a finished download is offered on even when no prefix was. A content item that is withdrawn stays withdrawn for the session.
- A peer's capabilities are learned from its offers and requests as well as its
hello, and a source that returns under the same offer is taken back. - A download whose finished file is shorter than its size, or fails its digest, fails and forgets what was stored of it.
- New exports:
ItemSource,StoredFile,StoredMeta,prefixRange,PrefixRange.
All additive. Every new field is optional and every new behaviour is negotiated, so a 1.2 peer — or a v1 one — interoperates exactly as before.
@thebkht/rtc-file-transfer 1.2.0
📦 https://www.npmjs.com/package/@thebkht/rtc-file-transfer
- A failed connection is restarted once before the transfer is given up on.
connectionState === "failed"went straight to anatfailure, so the things that routinely break a candidate pair mid-file — moving from Wi-Fi to Ethernet, a phone changing cell, a NAT binding expiring — ended a transfer that the network could still have carried. The sender now re-offers withiceRestart, which gathers fresh candidates over the existing connection. Only the sender offers, along the path it already used, so the restart is answered by a peer running an older version with no new message type and no capability to negotiate. One attempt, not a retry loop. limits.iceRestartMs(8 s) bounds how long a restarted connection has to come back. Shorter thanstallMsby default, so a restart that does not take still reportsnatrather thanstalled.iceServerswas already resolved per connection, so a restart dials with refreshed TURN credentials without further work.
No wire changes. An ICE restart is an ordinary rtc-description offer.
@thebkht/cliplink 0.2.1
📦 https://www.npmjs.com/package/@thebkht/cliplink
- A saved room is now matched by its code and its server, not by the code alone. A room code is six characters and unique only to the server that issued it, so the same code names different rooms on a dev server and on a deployment.
findSavedRoomconsulted only the code, so joining a code against one origin could be handed the key--savehad written for another — which fails to decrypt, and is the wrong room's key besides. The origin was already recorded inrooms.json; it is now read. Saving matches the same way, so the same code can be held for two servers at once rather than one evicting the other. Trailing slashes, host case and a default port do not make two origins different; a path does.rooms --forgetstill takes a code alone, and so now forgets it on every server it was saved against, rather than the first one found.
@thebkht/cliplink 0.2.0
📦 https://www.npmjs.com/package/@thebkht/cliplink
cliplink rooms.--savecould write to the saved-rooms file but nothing could read it back, so seeing what a machine had kept, or removing one entry, meant openingrooms.jsonby hand.roomslists code, origin and age;--forgettakes one,--forget-alltakes them all, and--prunedrops what has expired. The listing withholds keys unless--show-keysasks — keeping keys scarce is the whole reason--saveis opt-in per run, and a command run to remind yourself of a room code should not put key material into scrollback or a screen share. It says on stderr that it is withholding, so the omission cannot be read as the room having no key. Forgetting every room removes the file rather than leaving an empty one behind.cliplink link. The share link and QR were printed only whensendcreated a room, so getting a second device onto a room already in progress meant scrolling back to find them.linkresolves a room exactly assendandrecvdo and prints what they print. It opens no socket and asks the server nothing — the code and the key are both in hand once the session resolves — so it answers for a room that has already expired. It never creates a room: minting one just to print its link would leave a room on the server nobody asked for.recv --json. Every clip was decrypted and all of it but the text thrown away, so a script could read what was said but not who said it, when, or which clip it was.--jsonprints one object per line:id,text,senderId,ts. The fields are listed rather than the clip stringified whole, so a field added to the wire type does not appear in the output format without someone deciding it should.recv --last <n>and--all.recvalways started at the newest clip, so anything sent before it was running was unreachable from the terminal. The clips are in the connect response either way, so this is a choice of where the cursor starts rather than a new request. Replayed clips take the same path as one arriving later, so--oneand--jsonmean the same thing for both, and the shared cursor still refuses to print a clip twice.recv --timeout <secs>.recv --onewaited forever, so a script that expected a clip and did not get one hung rather than failing. It exits 1 when nothing arrived and 0 when something did: nothing arriving writes nothing to stdout, and an empty clip and a clip that never came look alike to a caller that only reads the pipe.NO_COLORand--no-color.renderQrhas taken acoloroption since it was written and no caller ever passed one. Colour stays the default because it is what makes the QR scannable — block characters are drawn in the foreground colour, so on a dark terminal an unstyled QR comes out inverted and many scanners refuse it.- Clustered short flags.
-q1was an unknown option. Only a run whose every letter is a known short flag is expanded, so-50and-kSECRETstill reach the unknown-option error rather than being taken apart into letters; a flag that takes a value has to come last. - A failing poll is reported once per run of failures rather than once per poll. A server that is down stays down, and a line every 1.5 seconds for as long as that lasts buried both the clips already received and the first line that said why. Reaching the room again is noted, so a failure that comes and goes is still visible.
- A room saved by
--saveon creation now records when it expires, which is what--prunereads. A room saved on a join was never told its lifetime, so it falls back to the longest a room may be configured to live — a bound rather than a reading.
No wire changes. The protocol, the encryption and both transports are untouched.
@thebkht/rtc-file-transfer 1.1.0
📦 https://www.npmjs.com/package/@thebkht/rtc-file-transfer
- Progress on files you are sending.
FileItem.byteshas always been the receiver's count, so a sender could see that a file was being pulled and by how many devices, but not how far along any of them was — there was no way to draw an upload bar. Outgoing items now carryoutgoingTransfers, one entry per device pulling the file, withbytes,bytesPerSecondandetaMs. A single number would have to pick one receiver and be wrong about the rest. Each entry'scommittedsays what its count means: with theflowcapability it is what the receiver has written, and without it what the sender has handed to the channel, which can run ahead. A device joins the list when its transfer opens, before any byte moves, and leaves when it ends. getItems()andgetItem(id)read the current state back, for a caller that would otherwise mirror everyonItemsChange. Both return copies.iceServersalso takes a function, called for each peer connection. TURN credentials are usually short-lived, and an array is read once at construction — a transfer started an hour later dialled with credentials that had expired. Synchronous, becauserequestreturns a boolean and awaiting would make it async.FileTransferManageris a declared type rather thanReturnType<typeof createFileTransferManager>. The published surface no longer widens as a side effect of adding a property, the doc comments survive into the type, and it can be implemented or stubbed.- Fixed:
offerFilestold an{ file, path }entry from a bare file withinstanceof File, so aFilefrom another realm — an iframe, a worker, a polyfill, Node's own — was destructured as an entry and its bytes went missing. The check is structural now. - New export:
OutgoingTransfer.
Everything here is additive. No wire messages change, and a 1.0.x peer interoperates in both directions exactly as before.
@thebkht/rtc-file-transfer 1.0.1
📦 https://www.npmjs.com/package/@thebkht/rtc-file-transfer
- Fixed:
resumewas implemented asthis.request(…), so pulling it off the manager —const { resume } = createFileTransferManager(…), which is how every other method here is meant to be used — threw. No test had ever calledresume, which is how it reached 1.0.0; there is now one that drives a pause and a resume entirely through destructured methods. ResumeProvider,ResumeStateandResumeKeyare exported. They were declared inmanager.tsand re-exported by neither entrypoint, so the custom resume store the README describes could not be typed.
@thebkht/cliplink 0.1.1
📦 https://www.npmjs.com/package/@thebkht/cliplink
- Room codes now come from the CSPRNG.
generateRoomCodedrew fromMath.random, whose output is predictable from a handful of earlier draws. A room code is the room's only identifier, and for a room opened by its code alone it is the whole of the key material, sincederiveOpenRoomKeytakes nothing else. The format is unchanged, so this is not a wire change and existing codes still resolve. --ttlis checked against the 3600–86400 the help text advertises before the request goes out, through thevalidateRoomTtlthe server already uses.--ttlalongside a room named byCLIPLINK_ROOMnow says so, rather than reporting that "this run joins one" when the command line shows no room at all.resolveBaseUrlis exported, for anyone implementingTransportClientagainst a lazy origin.- The package ships its own
LICENSE. There was none, so the tarball carried no licence text and bothREADME.mdandCLI.mdlinked above the package root to a file that was not there.
@thebkht/cliplink 0.1.0
📦 https://www.npmjs.com/package/@thebkht/cliplink
First release. The CLIPLINK room protocol, extracted from the web app so the
app and the CLI share one implementation of the encryption rather than one
each.
- The protocol. End-to-end encryption (HKDF from the room key, AES-GCM per
clip), the room wire types, room codes, QR encoding, and hand-written
validation. Wire protocol v1 is fixed: a client on any version talks to a
room served by any other, and the tests pin ciphertexts from earlier builds. - Both transports.
createHttpClientpolls,createWebSocketTransport
holds a socket open, and both implementTransportClient, so a client writes
its retry and backoff logic once. Each takes its origin as a parameter —
nothing here assumes a browser, andwindowappears nowhere in it. - The
cliplinkCLI, shipped in this package as abin.cliplink send
creates a room and prints a link and QR code for the other device;
cliplink recvprints clips as they arrive. Importing the library never
loads it, so a browser bundle pays nothing for the CLI. - Requires Node 22 or newer, and has no third-party dependencies. The CLI talks
to a room over Node's ownWebSocketglobal rather than carryingws, and
the library uses Web Crypto, which is global from the same versions.
@thebkht/rtc-file-transfer 1.0.0
📦 https://www.npmjs.com/package/@thebkht/rtc-file-transfer
1.0.0 is the commitment that what is there now stays there:
- The API follows semver. Exported functions and types, option names,
FileItemfields, notice types and failure codes change only in a major. New failure codes and newFileItemfields can still appear in a minor. - Wire protocol v1 is permanent. A peer on any version interoperates with a peer on any other, in both directions, and the test suite now pins that with a pair of transfers against a manager created as
capabilities: []— a 0.1.0 peer, as far as the wire is concerned. Future features arrive as capabilities and optional fields, never as changes to existing messages. - README: a "Compared with" table checked against each package's own source, a browser support matrix, an honest list of what this doesn't solve (TURN, tab lifetime, mobile backgrounding, SCTP throughput, integrity vs. authenticity), quick-starts for simple-peer, PeerJS and Trystero, and a stability section spelling the above out.
- Fixed: a manager disposed mid-block no longer checkpoints that block into a
ResumeProvider. Its sink has already been aborted, so those bytes were never there to resume from, and a store that recorded them sent the next load back to an offset its file did not reach. This was reachable from 0.10.0 whenever a page tore a manager down mid-transfer. - New
examples/two-tabs.html: a complete client — offer, download, pause, resume across a reload — in one static file, not published to npm. - CONTRIBUTING documents how a capability is added.
@thebkht/rtc-file-transfer 0.9.0
📦 https://www.npmjs.com/package/@thebkht/rtc-file-transfer
- New capability
flow: the receiver credits the sender with what its sink has committed, and the sender stops once it iswindowBytes(16 MB) ahead. Memory is now bounded at both ends, so a slow disk can't pile up in the receiver's. Requiresblocks; peers without it fall back tobufferedAmountalone. pause(id)andresume(id): pause an incoming download and keep every verified block. The item's status becomespaused, with no failure notice, and resuming continues into the same sink. Needsblocksandresumeon both sides;pausereturns false otherwise.- New limit
windowBytes, new statuspaused, new failure codepaused(sent to the sender as the reason; the paused item carries no error).