pairlink 0.3.0
pairlink: ^0.3.0 copied to clipboard
Local-network pairing between two phones over TCP: an out-of-band secret, a nonce-bound HMAC handshake, replay rejection, heartbeats and takeover. Pure Dart.
Changelog #
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
0.3.0 - 2026-09-13 #
Added #
SharedandSignal: two application messages this package carries without reading — a named value one peer shares with the other, and a named one-shot request. Both directions, throughshare(key:, value:)andsignal(name)on either session, arriving asOutletPeerShared/OutletPeerSignalandPanelPeerShared/PanelPeerSignal. A consumer can add a wire feature without a protocol bump or a release here.Sharedis state: re-asserted on everyattachrather than queued. A receiver that acts on one must ignore a repeat — re-assertion means the link came back, not that anything changed, and one that restarts a timer per delivery lets a flapping link extend it without end.Signalis never stored or queued and is dropped with the link: a command delivered late is executed at the wrong moment.- Values are scalars only (
bool,num,String, null):ArgumentErroron send,FrameError(malformed)on receipt. The MAC covers a canonicalization that sorts top-level keys only. A real check, not an assertion, which release builds strip. - Both are signed like every post-handshake frame and join no unsigned-acceptance set. An unknown
keyornameis ignored, never refused.
Changed #
- Breaking:
kPairlinkVersionis 4. The session key binds the version, so 3 and 4 cannot pair; a mismatch is still a cleanReject(versionMismatch). - Breaking: the new sealed subclasses of
PairFrame,OutletEventandPanelEventbreak every exhaustive switch over them until it handles them.
0.2.0 - 2026-09-05 #
Added #
PairEndpoint.idandPairSecret.pairId({required brand}): a public name for a pairing, the first four bytes of HMAC-SHA256 over the secret, in hex. Null for a typed code by construction — thirty-two bits of a keyed hash over 128 random bits reveal nothing, while the same hash over four digits would be a ten-thousand-row lookup table. It lets two phones that remember each other recognise each other BEFORE they connect, so a search never spends a stranger's attempt budget on a handshake that could not verify.
Fixed #
PanelSessionreported one dropped socket TWICE. The transport'sonDoneemittedPanelDisconnectedwithout detaching, so the idle timer stayed armed and fired again fifteen seconds later. A consumer that reacts to a drop with a bounded retry loop got the second report after it had finished, and started over with a fresh budget — the "stopped trying to reconnect, then connected on its own" shape seen on two phones on 2026-09-05.PanelSessionreported a handshake that never completed as a drop.attachalready says so by returning false; the extra event made a failed reconnect attempt look like a fresh outage.
0.1.0 - 2026-09-04 #
Added #
- First released version: the v3 wire protocol
(
PROTOCOL.md),OutletSession,PanelSession,PairInvite,PairSecret, the socket transport and the discovery contracts, with the skeptic test suite. PairIdentity: the application'sbrand, mDNSserviceTypeand QRschemeare one value the application declares and passes in. Two applications on this protocol never share a key space.
Changed #
- Breaking.
OutletSession,PanelSessionandPairInviterequire anidentity;PairInvite.tryParsetakes one;PairSecret.deriveKeytakes abrand. The constantskWireBrandandkServiceTypeare gone.