@byokit/pair 0.10.0 → 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +5 -0
- package/dist/channel.js +1 -1
- package/dist/check.js +1 -1
- package/dist/device.d.ts +9 -9
- package/dist/device.js +3 -3
- package/dist/host.js +5 -5
- package/dist/index.js +8 -8
- package/dist/migrate.js +1 -1
- package/dist/node.js +1 -1
- package/dist/pairing.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.10.1 (2026-10-16)
|
|
6
|
+
|
|
7
|
+
- FIX: the browser pairing test opens the pairing page over the test browser's own private CDP pipe once Chromium answers there, not from the launch command line. On a slow first start Chromium's network service quits after 15 s unanswered and took the launch page's request with it, so the test silently sat out its 180 s deadline; the page now loads after Chromium restarts the service, and a page Chromium cannot open fails at once with Chromium's own navigation error.
|
|
8
|
+
- FIX: pairing and setup checks split "can't reach the computer" from "the computer is slow": a socket that never opens within the timeout reports `unreachable` ("Check the phone and computer are on the same network, and that no firewall is blocking the connection."), while an open socket with no answer reports the new `slow` code ("Your computer is slow to answer. Wait a moment and try again."). Consumers relay the codes unchanged.
|
|
9
|
+
|
|
5
10
|
## 0.10.0 (2026-10-12)
|
|
6
11
|
|
|
7
12
|
- DOCS: the "Checking reachability before pairing" section now shows both sides: setup self-checks its own advertised URLs with `check()` before printing the tick, and the phone re-checks the scanned offer before consent. The probe still spends nothing, so a checked offer pairs afterwards.
|
package/dist/channel.js
CHANGED
|
@@ -14,7 +14,7 @@ import Noise from 'noise-handshake';
|
|
|
14
14
|
import { chacha20poly1305 } from '@noble/ciphers/chacha.js';
|
|
15
15
|
import dh from 'noise-handshake/dh.js';
|
|
16
16
|
import sodium from 'sodium-universal';
|
|
17
|
-
import { CONFIRM_WORDS } from
|
|
17
|
+
import { CONFIRM_WORDS } from './confirm-words.js';
|
|
18
18
|
/** Mixed into every handshake, so a v2 of this protocol can never be confused with v1. */
|
|
19
19
|
export const PROLOGUE = 'byokit-link-v1';
|
|
20
20
|
const CHUNK = 60_000; // a Noise message is at most 65535 bytes; larger payloads go in pieces
|
package/dist/check.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// A per-URL reachability probe for onboarding: before pairing, the phone checks the offer's URLs (and the host
|
|
2
2
|
// checks its own advertised URL) without claiming or burning the one-time offer. No platform APIs: the same code
|
|
3
3
|
// runs in Node, browsers and React Native on the platform WebSocket, like `dial` in device.ts.
|
|
4
|
-
import { LINK_WORDS } from
|
|
4
|
+
import { LINK_WORDS } from './device.js';
|
|
5
5
|
/** The first frame a probe sends. The host answers with LINK_PROBE_OK and closes; it touches no ticket, code,
|
|
6
6
|
// grant or counter, so a checked offer still pairs afterwards. Relayed devices speak bare frames, so the same
|
|
7
7
|
// exchange works through a relay with no relay change. */
|
package/dist/device.d.ts
CHANGED
|
@@ -44,22 +44,22 @@ export declare const LINK_WORDS: {
|
|
|
44
44
|
readonly unreachable: "Can't reach your computer. Check the phone and computer are on the same network, and that no firewall is blocking the connection.";
|
|
45
45
|
readonly 'wrong-host': "Something other than your computer answered, so this device didn't connect.";
|
|
46
46
|
readonly 'wrong-code': "That code didn't match. Check it, or show a new one on your computer.";
|
|
47
|
-
readonly expired:
|
|
48
|
-
readonly declined:
|
|
49
|
-
readonly full:
|
|
47
|
+
readonly expired: 'That pairing code has run out. Show a new one on your computer.';
|
|
48
|
+
readonly declined: 'Your computer said no to this device.';
|
|
49
|
+
readonly full: 'Your computer has all the devices it allows. Remove one there first.';
|
|
50
50
|
readonly 'not-paired': "Your computer doesn't know this device any more. Pair it again.";
|
|
51
|
-
readonly removed:
|
|
52
|
-
readonly 'view-only':
|
|
51
|
+
readonly removed: 'This device was removed on your computer.';
|
|
52
|
+
readonly 'view-only': 'This device can watch but not make changes.';
|
|
53
53
|
readonly timeout: "Your computer didn't answer in time.";
|
|
54
|
-
readonly slow:
|
|
54
|
+
readonly slow: 'Your computer is slow to answer. Wait a moment and try again.';
|
|
55
55
|
readonly failed: "Your computer couldn't do that.";
|
|
56
|
-
readonly busy:
|
|
57
|
-
readonly stopped:
|
|
56
|
+
readonly busy: 'Your computer is still catching up. Try again in a moment.';
|
|
57
|
+
readonly stopped: 'This link is stopped. Try connecting again.';
|
|
58
58
|
readonly 'not-supported': "The app on your computer can't do this yet. Update it there.";
|
|
59
59
|
readonly ended: "This device's access has run out. Pair it again on your computer.";
|
|
60
60
|
readonly 'not-allowed': "This device isn't allowed to do that.";
|
|
61
61
|
readonly 'too-late': "That reached your computer too late, so it wasn't done.";
|
|
62
|
-
readonly unsupported:
|
|
62
|
+
readonly unsupported: 'Your computer needs an update for that.';
|
|
63
63
|
};
|
|
64
64
|
export type LinkProblem = keyof typeof LINK_WORDS;
|
|
65
65
|
/** A failure with a plain `message` and a `code` for the app. `sealed` means the host said it inside the encrypted
|
package/dist/device.js
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
// the host's credentials. It pairs once (scan or typed code), then keeps one socket open, reconnecting on its own, and
|
|
3
3
|
// its requests survive a reconnect: each carries a key, so a retried tap runs once. Uses only the platform's
|
|
4
4
|
// WebSocket, so the same code runs in browsers, React Native and Node.
|
|
5
|
-
import { Handshake, b64, b64url, keyPair, keyPairFrom, random, unb64url } from
|
|
6
|
-
import { cleanName, codeKey, decodeCompactOffer, parseCode, parseOffer, parseV1Offer, shortKey, COMPACT_TAG } from
|
|
7
|
-
import { Streams } from
|
|
5
|
+
import { Handshake, b64, b64url, keyPair, keyPairFrom, random, unb64url } from './channel.js';
|
|
6
|
+
import { cleanName, codeKey, decodeCompactOffer, parseCode, parseOffer, parseV1Offer, shortKey, COMPACT_TAG } from './pairing.js';
|
|
7
|
+
import { Streams } from './stream.js';
|
|
8
8
|
/** Every way the link can fail, in words a person can act on. */
|
|
9
9
|
export const LINK_WORDS = {
|
|
10
10
|
unreachable: "Can't reach your computer. Check the phone and computer are on the same network, and that no firewall is blocking the connection.",
|
package/dist/host.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
// The host: the computer that owns the credentials. It pairs devices (scanned or typed code, then a person says yes),
|
|
2
2
|
// keeps one durable grant per device, answers their requests, and removes them. It never listens on anything itself:
|
|
3
3
|
// the app hands it WebSockets (`accept`), or one relay socket that carries many devices (`relay`).
|
|
4
|
-
import { Handshake, b64, b64url, firstFrame, hostId, messageBytes, random, unb64url } from
|
|
5
|
-
import { bindCode, cleanName, codeKey, encodeCompactOffer, newCode, normalizeCode, offerText } from
|
|
6
|
-
import { PublicLinkError } from
|
|
7
|
-
import { LINK_PROBE, LINK_PROBE_OK } from
|
|
8
|
-
import { MAX_STREAMS, Streams } from
|
|
4
|
+
import { Handshake, b64, b64url, firstFrame, hostId, messageBytes, random, unb64url } from './channel.js';
|
|
5
|
+
import { bindCode, cleanName, codeKey, encodeCompactOffer, newCode, normalizeCode, offerText } from './pairing.js';
|
|
6
|
+
import { PublicLinkError } from './device.js';
|
|
7
|
+
import { LINK_PROBE, LINK_PROBE_OK } from './check.js';
|
|
8
|
+
import { MAX_STREAMS, Streams } from './stream.js';
|
|
9
9
|
const HANDSHAKE_MS = 15_000;
|
|
10
10
|
const MAX_TRIES = 5; // wrong codes before every open code is withdrawn
|
|
11
11
|
const MAX_ANSWERS = 1000;
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
export { hostId, keyPair, keyPairFrom, b64url, unb64url } from
|
|
2
|
-
export { Host, } from
|
|
3
|
-
export { DeviceLink, LINK_WORDS, LinkError, PublicLinkError, pairWithCode, pairWithOffer, pendingGrant, } from
|
|
4
|
-
export { cleanName, decodeCompactOffer, decodeOffer, encodeCompactOffer, encodeOffer, normalizeCode, offerText, parseOffer, parseV1Offer, COMPACT_TAG } from
|
|
5
|
-
export { LinkStream, NDJSON_LINE_TOO_LONG, NDJSON_MAX_LINE, WINDOW, ndjson } from
|
|
6
|
-
export { check, LINK_PROBE, LINK_PROBE_OK } from
|
|
7
|
-
export { browserDeviceStore, browserDeviceStores, secureDeviceStore, secureDeviceStores } from
|
|
8
|
-
export { migrateGrant, GrantMigrationError } from
|
|
1
|
+
export { hostId, keyPair, keyPairFrom, b64url, unb64url } from './channel.js';
|
|
2
|
+
export { Host, } from './host.js';
|
|
3
|
+
export { DeviceLink, LINK_WORDS, LinkError, PublicLinkError, pairWithCode, pairWithOffer, pendingGrant, } from './device.js';
|
|
4
|
+
export { cleanName, decodeCompactOffer, decodeOffer, encodeCompactOffer, encodeOffer, normalizeCode, offerText, parseOffer, parseV1Offer, COMPACT_TAG } from './pairing.js';
|
|
5
|
+
export { LinkStream, NDJSON_LINE_TOO_LONG, NDJSON_MAX_LINE, WINDOW, ndjson } from './stream.js';
|
|
6
|
+
export { check, LINK_PROBE, LINK_PROBE_OK } from './check.js';
|
|
7
|
+
export { browserDeviceStore, browserDeviceStores, secureDeviceStore, secureDeviceStores } from './stores.js';
|
|
8
|
+
export { migrateGrant, GrantMigrationError } from './migrate.js';
|
package/dist/migrate.js
CHANGED
package/dist/node.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Node only (`@byokit/pair/node`): host key and device grant files. Kept out of the main entry for browsers and React Native.
|
|
2
2
|
import { closeSync, fsyncSync, linkSync, lstatSync, mkdirSync, openSync, readFileSync, renameSync, rmSync, unlinkSync, unwatchFile, watchFile, writeFileSync, writeSync } from 'node:fs';
|
|
3
3
|
import { dirname } from 'node:path';
|
|
4
|
-
import { b64url, keyPair, keyPairFrom, random, unb64url } from
|
|
4
|
+
import { b64url, keyPair, keyPairFrom, random, unb64url } from './channel.js';
|
|
5
5
|
/** The host's key pair, kept in `path` (0600, in a 0700 folder, written atomically). The first call makes it.
|
|
6
6
|
* A file that exists but can't be read as a key is never replaced: a new key would silently cut off every paired
|
|
7
7
|
* device, so it throws and a person decides. */
|
package/dist/pairing.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// What pairing puts in front of a person: the QR's text (or a link that carries it), and the typed code.
|
|
2
2
|
import b4a from 'b4a';
|
|
3
|
-
import { b64url, hash, random, unb64url } from
|
|
3
|
+
import { b64url, hash, random, unb64url } from './channel.js';
|
|
4
4
|
const TAG = 'byokit-link:1:';
|
|
5
5
|
const UNSAFE = /[\u0000-\u001f\u007f\u200e\u200f\u202a-\u202e\u2066-\u2069]/gu; // control and direction-flipping characters
|
|
6
6
|
/** A device or host name as a person should see it: no hidden characters, at most 60 of them. */
|
package/package.json
CHANGED