@yefengr/pi-reach 0.0.0-stage → 0.0.2
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/LICENSE +22 -0
- package/README.md +110 -2
- package/dist/actions/handlers.d.ts +143 -0
- package/dist/actions/handlers.js +200 -0
- package/dist/actions/handlers.js.map +1 -0
- package/dist/actions/registry.d.ts +39 -0
- package/dist/actions/registry.js +58 -0
- package/dist/actions/registry.js.map +1 -0
- package/dist/actions/session_new_bridge.d.ts +35 -0
- package/dist/actions/session_new_bridge.js +88 -0
- package/dist/actions/session_new_bridge.js.map +1 -0
- package/dist/commands.d.ts +29 -0
- package/dist/commands.js +169 -0
- package/dist/commands.js.map +1 -0
- package/dist/config.d.ts +61 -0
- package/dist/config.js +115 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +618 -0
- package/dist/index.js.map +1 -0
- package/dist/pairing/crypto.d.ts +15 -0
- package/dist/pairing/crypto.js +72 -0
- package/dist/pairing/crypto.js.map +1 -0
- package/dist/pairing/identity-keyring.d.ts +22 -0
- package/dist/pairing/identity-keyring.js +94 -0
- package/dist/pairing/identity-keyring.js.map +1 -0
- package/dist/pairing/identity-lock.d.ts +14 -0
- package/dist/pairing/identity-lock.js +116 -0
- package/dist/pairing/identity-lock.js.map +1 -0
- package/dist/pairing/owner_storage.d.ts +61 -0
- package/dist/pairing/owner_storage.js +263 -0
- package/dist/pairing/owner_storage.js.map +1 -0
- package/dist/pairing/qr.d.ts +39 -0
- package/dist/pairing/qr.js +113 -0
- package/dist/pairing/qr.js.map +1 -0
- package/dist/pairing/storage.d.ts +36 -0
- package/dist/pairing/storage.js +263 -0
- package/dist/pairing/storage.js.map +1 -0
- package/dist/protocol/types.d.ts +225 -0
- package/dist/protocol/types.js +2 -0
- package/dist/protocol/types.js.map +1 -0
- package/dist/protocol/v2/codec.d.ts +6 -0
- package/dist/protocol/v2/codec.js +4 -0
- package/dist/protocol/v2/codec.js.map +1 -0
- package/dist/protocol/v2/index.d.ts +4 -0
- package/dist/protocol/v2/index.js +5 -0
- package/dist/protocol/v2/index.js.map +1 -0
- package/dist/protocol/v2/marker.d.ts +45 -0
- package/dist/protocol/v2/marker.js +47 -0
- package/dist/protocol/v2/marker.js.map +1 -0
- package/dist/protocol/v2/schemas.d.ts +3 -0
- package/dist/protocol/v2/schemas.js +4 -0
- package/dist/protocol/v2/schemas.js.map +1 -0
- package/dist/protocol/v2/session_state.d.ts +79 -0
- package/dist/protocol/v2/session_state.js +166 -0
- package/dist/protocol/v2/session_state.js.map +1 -0
- package/dist/runtime/endpoint_metadata.d.ts +14 -0
- package/dist/runtime/endpoint_metadata.js +18 -0
- package/dist/runtime/endpoint_metadata.js.map +1 -0
- package/dist/runtime/extension_owner.d.ts +12 -0
- package/dist/runtime/extension_owner.js +80 -0
- package/dist/runtime/extension_owner.js.map +1 -0
- package/dist/runtime/footer.d.ts +6 -0
- package/dist/runtime/footer.js +21 -0
- package/dist/runtime/footer.js.map +1 -0
- package/dist/runtime/owner_router.d.ts +16 -0
- package/dist/runtime/owner_router.js +41 -0
- package/dist/runtime/owner_router.js.map +1 -0
- package/dist/runtime/pairing_coordinator.d.ts +42 -0
- package/dist/runtime/pairing_coordinator.js +136 -0
- package/dist/runtime/pairing_coordinator.js.map +1 -0
- package/dist/runtime/relay_lifecycle.d.ts +64 -0
- package/dist/runtime/relay_lifecycle.js +275 -0
- package/dist/runtime/relay_lifecycle.js.map +1 -0
- package/dist/session/local_config.d.ts +7 -0
- package/dist/session/local_config.js +52 -0
- package/dist/session/local_config.js.map +1 -0
- package/dist/timeline/history.d.ts +30 -0
- package/dist/timeline/history.js +170 -0
- package/dist/timeline/history.js.map +1 -0
- package/dist/timeline/partial_delta.d.ts +2 -0
- package/dist/timeline/partial_delta.js +16 -0
- package/dist/timeline/partial_delta.js.map +1 -0
- package/dist/timeline/run_end.d.ts +16 -0
- package/dist/timeline/run_end.js +23 -0
- package/dist/timeline/run_end.js.map +1 -0
- package/dist/timeline/runtime.d.ts +89 -0
- package/dist/timeline/runtime.js +546 -0
- package/dist/timeline/runtime.js.map +1 -0
- package/dist/timeline/sequence.d.ts +7 -0
- package/dist/timeline/sequence.js +16 -0
- package/dist/timeline/sequence.js.map +1 -0
- package/dist/timeline/tool_lifecycle.d.ts +36 -0
- package/dist/timeline/tool_lifecycle.js +171 -0
- package/dist/timeline/tool_lifecycle.js.map +1 -0
- package/dist/timeline/user_delivery.d.ts +96 -0
- package/dist/timeline/user_delivery.js +360 -0
- package/dist/timeline/user_delivery.js.map +1 -0
- package/dist/timeline/user_delivery_binding.d.ts +43 -0
- package/dist/timeline/user_delivery_binding.js +107 -0
- package/dist/timeline/user_delivery_binding.js.map +1 -0
- package/dist/timeline/v2_service.d.ts +82 -0
- package/dist/timeline/v2_service.js +512 -0
- package/dist/timeline/v2_service.js.map +1 -0
- package/dist/transport/peer_channel.d.ts +34 -0
- package/dist/transport/peer_channel.js +116 -0
- package/dist/transport/peer_channel.js.map +1 -0
- package/dist/transport/relay_client.d.ts +54 -0
- package/dist/transport/relay_client.js +270 -0
- package/dist/transport/relay_client.js.map +1 -0
- package/dist/vendor/protocol/constants.d.ts +4 -0
- package/dist/vendor/protocol/constants.js +4 -0
- package/dist/vendor/protocol/encoding.d.ts +9 -0
- package/dist/vendor/protocol/encoding.js +66 -0
- package/dist/vendor/protocol/outer/codec.d.ts +13 -0
- package/dist/vendor/protocol/outer/codec.js +105 -0
- package/dist/vendor/protocol/outer/index.d.ts +4 -0
- package/dist/vendor/protocol/outer/index.js +4 -0
- package/dist/vendor/protocol/outer/types.d.ts +120 -0
- package/dist/vendor/protocol/outer/types.js +1 -0
- package/dist/vendor/protocol/session/codec.d.ts +41 -0
- package/dist/vendor/protocol/session/codec.js +236 -0
- package/dist/vendor/protocol/session/frames.d.ts +5220 -0
- package/dist/vendor/protocol/session/frames.js +83 -0
- package/dist/vendor/protocol/session/index.d.ts +3 -0
- package/dist/vendor/protocol/session/index.js +3 -0
- package/dist/vendor/protocol/session/schema.d.ts +858 -0
- package/dist/vendor/protocol/session/schema.js +256 -0
- package/package.json +74 -3
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import { writeFileSync } from "node:fs";
|
|
2
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import { canonicalizeEd25519PublicKey } from "./crypto.js";
|
|
6
|
+
const PEERS_PATH = join(homedir(), ".pi", "pi-reach", "peers.json");
|
|
7
|
+
export async function listPeers() {
|
|
8
|
+
try {
|
|
9
|
+
const raw = await readFile(PEERS_PATH, "utf8");
|
|
10
|
+
const parsed = JSON.parse(raw);
|
|
11
|
+
return Array.isArray(parsed.peers) ? parsed.peers : [];
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return [];
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Strict container read used by serialized storage mutations. Valid records
|
|
19
|
+
* remain verbatim so one corrupt entry never erases other pairings.
|
|
20
|
+
*/
|
|
21
|
+
async function _readPeerContainerStrict() {
|
|
22
|
+
let raw;
|
|
23
|
+
try {
|
|
24
|
+
raw = await readFile(PEERS_PATH, "utf8");
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
if (typeof error === "object" &&
|
|
28
|
+
error !== null &&
|
|
29
|
+
"code" in error &&
|
|
30
|
+
error.code === "ENOENT") {
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
throw error;
|
|
34
|
+
}
|
|
35
|
+
const parsed = JSON.parse(raw);
|
|
36
|
+
if (!Array.isArray(parsed.peers)) {
|
|
37
|
+
throw new Error("Invalid peers.json container");
|
|
38
|
+
}
|
|
39
|
+
return parsed.peers;
|
|
40
|
+
}
|
|
41
|
+
let _peerMutationQueue = Promise.resolve();
|
|
42
|
+
const _ownerSlotTokens = new Map();
|
|
43
|
+
const _ownerStorageTokenBrand = Symbol("owner-storage-token");
|
|
44
|
+
function _ownerSlotKey(rawOwnerPubkey) {
|
|
45
|
+
if (typeof rawOwnerPubkey !== "string") {
|
|
46
|
+
// Invalid non-string records remain isolated; their quarantine slots never
|
|
47
|
+
// collide with a valid canonical Owner identity.
|
|
48
|
+
return `raw:quarantine:${typeof rawOwnerPubkey}`;
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
return `owner:${canonicalizeEd25519PublicKey(rawOwnerPubkey, "Owner record")}`;
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return `raw:string:${rawOwnerPubkey}`;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function _tokenForSlot(slot) {
|
|
58
|
+
const existing = _ownerSlotTokens.get(slot);
|
|
59
|
+
if (existing)
|
|
60
|
+
return existing;
|
|
61
|
+
const token = Object.freeze({ [_ownerStorageTokenBrand]: true });
|
|
62
|
+
_ownerSlotTokens.set(slot, token);
|
|
63
|
+
return token;
|
|
64
|
+
}
|
|
65
|
+
function _invalidateOwnerSlot(rawOwnerPubkey) {
|
|
66
|
+
const slot = _ownerSlotKey(rawOwnerPubkey);
|
|
67
|
+
const token = Object.freeze({ [_ownerStorageTokenBrand]: true });
|
|
68
|
+
_ownerSlotTokens.set(slot, token);
|
|
69
|
+
return token;
|
|
70
|
+
}
|
|
71
|
+
function _serializePeerMutation(mutation) {
|
|
72
|
+
const result = _peerMutationQueue.then(mutation, mutation);
|
|
73
|
+
_peerMutationQueue = result.then(() => undefined, () => undefined);
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
export function addPeer(record) {
|
|
77
|
+
return _serializePeerMutation(async () => {
|
|
78
|
+
const peers = await listPeers();
|
|
79
|
+
const idx = peers.findIndex((peer) => !!peer &&
|
|
80
|
+
typeof peer === "object" &&
|
|
81
|
+
peer.remote_epk === record.remote_epk);
|
|
82
|
+
const previousRecord = idx >= 0 ? peers[idx] : undefined;
|
|
83
|
+
if (idx >= 0) {
|
|
84
|
+
peers[idx] = record; // idempotent re-pair
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
peers.push(record);
|
|
88
|
+
}
|
|
89
|
+
await mkdir(dirname(PEERS_PATH), { recursive: true });
|
|
90
|
+
await writeFile(PEERS_PATH, JSON.stringify({ peers }, null, 2));
|
|
91
|
+
// A successful re-pair is a new storage provenance event even when the
|
|
92
|
+
// record bytes happen to be identical.
|
|
93
|
+
const token = _invalidateOwnerSlot(record.remote_epk);
|
|
94
|
+
return idx >= 0
|
|
95
|
+
? { record, token, previousRecord }
|
|
96
|
+
: { record, token };
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Returns the set of distinct `remote_epk` values in peers.json.
|
|
101
|
+
*
|
|
102
|
+
* Each `remote_epk` is the paired Owner's Ed25519 public key and represents a
|
|
103
|
+
* machine-scoped authorization entry.
|
|
104
|
+
*/
|
|
105
|
+
export async function listOwnerPubkeys() {
|
|
106
|
+
const peers = await listPeers();
|
|
107
|
+
const seen = new Set();
|
|
108
|
+
for (const peer of peers) {
|
|
109
|
+
if (!peer || typeof peer !== "object") {
|
|
110
|
+
seen.add(peer);
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
seen.add(peer.remote_epk);
|
|
114
|
+
}
|
|
115
|
+
return [...seen];
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Atomically snapshots raw Owner handles and their canonical-slot provenance.
|
|
119
|
+
* The token is process-local and opaque to callers.
|
|
120
|
+
*/
|
|
121
|
+
export function snapshotOwnerPubkeys() {
|
|
122
|
+
return _serializePeerMutation(async () => {
|
|
123
|
+
const peers = await _readPeerContainerStrict();
|
|
124
|
+
const rawOwners = new Set();
|
|
125
|
+
for (const peer of peers) {
|
|
126
|
+
if (!peer || typeof peer !== "object") {
|
|
127
|
+
rawOwners.add(peer);
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
rawOwners.add(peer.remote_epk);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return [...rawOwners].map((rawOwnerPubkey) => ({
|
|
134
|
+
rawOwnerPubkey,
|
|
135
|
+
token: _tokenForSlot(_ownerSlotKey(rawOwnerPubkey)),
|
|
136
|
+
}));
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Removes one exact raw handle only when its snapshot provenance still owns
|
|
141
|
+
* the target canonical Owner slot. The final authority/token checks and sync
|
|
142
|
+
* write share the existing serialized mutation lane.
|
|
143
|
+
*/
|
|
144
|
+
export function conditionalRemovePeer(remoteEpk, expectedToken, canCommit) {
|
|
145
|
+
return _serializePeerMutation(async () => {
|
|
146
|
+
const slot = _ownerSlotKey(remoteEpk);
|
|
147
|
+
// Provenance belongs to the canonical Owner slot, not the exact raw
|
|
148
|
+
// spelling. A stale slot must therefore win over an absent old spelling.
|
|
149
|
+
if (_tokenForSlot(slot) !== expectedToken)
|
|
150
|
+
return { outcome: "stale" };
|
|
151
|
+
const peers = await _readPeerContainerStrict();
|
|
152
|
+
const filtered = peers.filter((peer) => !peer ||
|
|
153
|
+
typeof peer !== "object" ||
|
|
154
|
+
peer.remote_epk !== remoteEpk);
|
|
155
|
+
if (filtered.length === peers.length)
|
|
156
|
+
return { outcome: "not_found" };
|
|
157
|
+
await mkdir(dirname(PEERS_PATH), { recursive: true });
|
|
158
|
+
// No await may intervene between the final token/authority checks and
|
|
159
|
+
// synchronous write, preserving the lane's fail-closed commit boundary.
|
|
160
|
+
if (_tokenForSlot(slot) !== expectedToken)
|
|
161
|
+
return { outcome: "stale" };
|
|
162
|
+
if (canCommit) {
|
|
163
|
+
let authorized = false;
|
|
164
|
+
try {
|
|
165
|
+
authorized = canCommit();
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
return { outcome: "no_authority" };
|
|
169
|
+
}
|
|
170
|
+
if (!authorized)
|
|
171
|
+
return { outcome: "no_authority" };
|
|
172
|
+
}
|
|
173
|
+
writeFileSync(PEERS_PATH, JSON.stringify({ peers: filtered }, null, 2));
|
|
174
|
+
return { outcome: "removed", nextToken: _invalidateOwnerSlot(remoteEpk) };
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Compensates a specific addPeer write only while that write still owns its
|
|
179
|
+
* canonical Owner slot. The receipt's raw written record must still be
|
|
180
|
+
* present, so an out-of-band replacement cannot be removed or overwritten.
|
|
181
|
+
*/
|
|
182
|
+
export function conditionalRollbackPeer(receipt, canCommit) {
|
|
183
|
+
return _serializePeerMutation(async () => {
|
|
184
|
+
const slot = _ownerSlotKey(receipt.record.remote_epk);
|
|
185
|
+
if (_tokenForSlot(slot) !== receipt.token)
|
|
186
|
+
return { outcome: "stale" };
|
|
187
|
+
const peers = await _readPeerContainerStrict();
|
|
188
|
+
const idx = peers.findIndex((peer) => {
|
|
189
|
+
try {
|
|
190
|
+
return JSON.stringify(peer) === JSON.stringify(receipt.record);
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
if (idx < 0)
|
|
197
|
+
return { outcome: "not_found" };
|
|
198
|
+
const restoringPreviousRecord = "previousRecord" in receipt;
|
|
199
|
+
const nextPeers = [...peers];
|
|
200
|
+
if (restoringPreviousRecord) {
|
|
201
|
+
nextPeers[idx] = receipt.previousRecord;
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
nextPeers.splice(idx, 1);
|
|
205
|
+
}
|
|
206
|
+
await mkdir(dirname(PEERS_PATH), { recursive: true });
|
|
207
|
+
// No await may intervene between the final token/authority checks and
|
|
208
|
+
// synchronous write, preserving the lane's fail-closed commit boundary.
|
|
209
|
+
if (_tokenForSlot(slot) !== receipt.token)
|
|
210
|
+
return { outcome: "stale" };
|
|
211
|
+
if (canCommit) {
|
|
212
|
+
let authorized = false;
|
|
213
|
+
try {
|
|
214
|
+
authorized = canCommit();
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
return { outcome: "no_authority" };
|
|
218
|
+
}
|
|
219
|
+
if (!authorized)
|
|
220
|
+
return { outcome: "no_authority" };
|
|
221
|
+
}
|
|
222
|
+
writeFileSync(PEERS_PATH, JSON.stringify({ peers: nextPeers }, null, 2));
|
|
223
|
+
return {
|
|
224
|
+
outcome: restoringPreviousRecord ? "restored" : "removed",
|
|
225
|
+
nextToken: _invalidateOwnerSlot(receipt.record.remote_epk),
|
|
226
|
+
};
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
export function removePeer(remoteEpk, canCommit) {
|
|
230
|
+
return _serializePeerMutation(async () => {
|
|
231
|
+
const peers = await listPeers();
|
|
232
|
+
const filtered = peers.filter((peer) => !peer ||
|
|
233
|
+
typeof peer !== "object" ||
|
|
234
|
+
peer.remote_epk !== remoteEpk);
|
|
235
|
+
if (filtered.length === peers.length)
|
|
236
|
+
return false;
|
|
237
|
+
await mkdir(dirname(PEERS_PATH), { recursive: true });
|
|
238
|
+
const serialized = JSON.stringify({ peers: filtered }, null, 2);
|
|
239
|
+
if (canCommit) {
|
|
240
|
+
// Guarded commits are atomic with their final authority check: fail
|
|
241
|
+
// closed on false/throw, then synchronously persist the small JSON rewrite.
|
|
242
|
+
let authorized = false;
|
|
243
|
+
try {
|
|
244
|
+
authorized = canCommit();
|
|
245
|
+
}
|
|
246
|
+
catch {
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
if (!authorized)
|
|
250
|
+
return false;
|
|
251
|
+
writeFileSync(PEERS_PATH, serialized);
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
// Manual removals keep the established asynchronous storage behavior.
|
|
255
|
+
await writeFile(PEERS_PATH, serialized);
|
|
256
|
+
}
|
|
257
|
+
const removed = true;
|
|
258
|
+
if (removed)
|
|
259
|
+
_invalidateOwnerSlot(remoteEpk);
|
|
260
|
+
return removed;
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
//# sourceMappingURL=owner_storage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"owner_storage.js","sourceRoot":"","sources":["../../src/pairing/owner_storage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACxC,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,4BAA4B,EAAE,MAAM,aAAa,CAAC;AAE3D,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,YAAY,CAAC,CAAC;AAQpE,MAAM,CAAC,KAAK,UAAU,SAAS;IAC7B,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;QAC/C,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAwB,CAAC;QACtD,OAAO,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAqB,CAAC,CAAC,CAAC,EAAE,CAAC;IACzE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,KAAK,UAAU,wBAAwB;IACrC,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,GAAG,GAAG,MAAM,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IAC3C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IACE,OAAO,KAAK,KAAK,QAAQ;YACzB,KAAK,KAAK,IAAI;YACd,MAAM,IAAI,KAAK;YACd,KAA4B,CAAC,IAAI,KAAK,QAAQ,EAC/C,CAAC;YACD,OAAO,EAAE,CAAC;QACZ,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;IACD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAwB,CAAC;IACtD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;QACjC,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC;IAClD,CAAC;IACD,OAAO,MAAM,CAAC,KAAK,CAAC;AACtB,CAAC;AAED,IAAI,kBAAkB,GAAkB,OAAO,CAAC,OAAO,EAAE,CAAC;AAC1D,MAAM,gBAAgB,GAAG,IAAI,GAAG,EAA6B,CAAC;AAC9D,MAAM,uBAAuB,GAAkB,MAAM,CAAC,qBAAqB,CAAC,CAAC;AA4B7E,SAAS,aAAa,CAAC,cAAuB;IAC5C,IAAI,OAAO,cAAc,KAAK,QAAQ,EAAE,CAAC;QACvC,2EAA2E;QAC3E,iDAAiD;QACjD,OAAO,kBAAkB,OAAO,cAAc,EAAE,CAAC;IACnD,CAAC;IACD,IAAI,CAAC;QACH,OAAO,SAAS,4BAA4B,CAAC,cAAc,EAAE,cAAc,CAAC,EAAE,CAAC;IACjF,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,cAAc,cAAc,EAAE,CAAC;IACxC,CAAC;AACH,CAAC;AAED,SAAS,aAAa,CAAC,IAAY;IACjC,MAAM,QAAQ,GAAG,gBAAgB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC5C,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAC9B,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,uBAAuB,CAAC,EAAE,IAAI,EAAE,CAAsB,CAAC;IACtF,gBAAgB,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAClC,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,oBAAoB,CAAC,cAAuB;IACnD,MAAM,IAAI,GAAG,aAAa,CAAC,cAAc,CAAC,CAAC;IAC3C,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,uBAAuB,CAAC,EAAE,IAAI,EAAE,CAAsB,CAAC;IACtF,gBAAgB,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAClC,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,sBAAsB,CAAI,QAA0B;IAC3D,MAAM,MAAM,GAAG,kBAAkB,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAC3D,kBAAkB,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IACnE,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,MAAkB;IACxC,OAAO,sBAAsB,CAAC,KAAK,IAAI,EAAE;QACvC,MAAM,KAAK,GAAG,MAAM,SAAS,EAAe,CAAC;QAC7C,MAAM,GAAG,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,IAAI,EAAE,EAAE,CACnC,CAAC,CAAC,IAAI;YACN,OAAO,IAAI,KAAK,QAAQ;YACvB,IAAiC,CAAC,UAAU,KAAK,MAAM,CAAC,UAAU,CACpE,CAAC;QACF,MAAM,cAAc,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACzD,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC;YACb,KAAK,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC,qBAAqB;QAC5C,CAAC;aAAM,CAAC;YACN,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACrB,CAAC;QACD,MAAM,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACtD,MAAM,SAAS,CAAC,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QAChE,uEAAuE;QACvE,uCAAuC;QACvC,MAAM,KAAK,GAAG,oBAAoB,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QACtD,OAAO,GAAG,IAAI,CAAC;YACb,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,cAAc,EAAE;YACnC,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IACxB,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB;IACpC,MAAM,KAAK,GAAG,MAAM,SAAS,EAAe,CAAC;IAC7C,MAAM,IAAI,GAAG,IAAI,GAAG,EAAW,CAAC;IAChC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;YACtC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YACf,SAAS;QACX,CAAC;QACD,IAAI,CAAC,GAAG,CAAE,IAAiC,CAAC,UAAU,CAAC,CAAC;IAC1D,CAAC;IACD,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC;AACnB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,oBAAoB;IAClC,OAAO,sBAAsB,CAAC,KAAK,IAAI,EAAE;QACvC,MAAM,KAAK,GAAG,MAAM,wBAAwB,EAAE,CAAC;QAC/C,MAAM,SAAS,GAAG,IAAI,GAAG,EAAW,CAAC;QACrC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;gBACtC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YACtB,CAAC;iBAAM,CAAC;gBACN,SAAS,CAAC,GAAG,CAAE,IAAiC,CAAC,UAAU,CAAC,CAAC;YAC/D,CAAC;QACH,CAAC;QACD,OAAO,CAAC,GAAG,SAAS,CAAC,CAAC,GAAG,CAAC,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC;YAC7C,cAAc;YACd,KAAK,EAAE,aAAa,CAAC,aAAa,CAAC,cAAc,CAAC,CAAC;SACpD,CAAC,CAAC,CAAC;IACN,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CACnC,SAAiB,EACjB,aAAgC,EAChC,SAAyB;IAEzB,OAAO,sBAAsB,CAAC,KAAK,IAAI,EAAE;QACvC,MAAM,IAAI,GAAG,aAAa,CAAC,SAAS,CAAC,CAAC;QACtC,oEAAoE;QACpE,yEAAyE;QACzE,IAAI,aAAa,CAAC,IAAI,CAAC,KAAK,aAAa;YAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;QACvE,MAAM,KAAK,GAAG,MAAM,wBAAwB,EAAE,CAAC;QAC/C,MAAM,QAAQ,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CACrC,CAAC,IAAI;YACL,OAAO,IAAI,KAAK,QAAQ;YACvB,IAAiC,CAAC,UAAU,KAAK,SAAS,CAC5D,CAAC;QACF,IAAI,QAAQ,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM;YAAE,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC;QACtE,MAAM,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACtD,sEAAsE;QACtE,wEAAwE;QACxE,IAAI,aAAa,CAAC,IAAI,CAAC,KAAK,aAAa;YAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;QACvE,IAAI,SAAS,EAAE,CAAC;YACd,IAAI,UAAU,GAAG,KAAK,CAAC;YACvB,IAAI,CAAC;gBAAC,UAAU,GAAG,SAAS,EAAE,CAAC;YAAC,CAAC;YAAC,MAAM,CAAC;gBAAC,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,CAAC;YAAC,CAAC;YAC/E,IAAI,CAAC,UAAU;gBAAE,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,CAAC;QACtD,CAAC;QACD,aAAa,CAAC,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QACxE,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,oBAAoB,CAAC,SAAS,CAAC,EAAE,CAAC;IAC5E,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,uBAAuB,CACrC,OAAyB,EACzB,SAAyB;IAEzB,OAAO,sBAAsB,CAAC,KAAK,IAAI,EAAE;QACvC,MAAM,IAAI,GAAG,aAAa,CAAC,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QACtD,IAAI,aAAa,CAAC,IAAI,CAAC,KAAK,OAAO,CAAC,KAAK;YAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;QAEvE,MAAM,KAAK,GAAG,MAAM,wBAAwB,EAAE,CAAC;QAC/C,MAAM,GAAG,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,IAAI,EAAE,EAAE;YACnC,IAAI,CAAC;gBACH,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;YACjE,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,KAAK,CAAC;YACf,CAAC;QACH,CAAC,CAAC,CAAC;QACH,IAAI,GAAG,GAAG,CAAC;YAAE,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC;QAE7C,MAAM,uBAAuB,GAAG,gBAAgB,IAAI,OAAO,CAAC;QAC5D,MAAM,SAAS,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC;QAC7B,IAAI,uBAAuB,EAAE,CAAC;YAC5B,SAAS,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,cAAc,CAAC;QAC1C,CAAC;aAAM,CAAC;YACN,SAAS,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QAC3B,CAAC;QAED,MAAM,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACtD,sEAAsE;QACtE,wEAAwE;QACxE,IAAI,aAAa,CAAC,IAAI,CAAC,KAAK,OAAO,CAAC,KAAK;YAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;QACvE,IAAI,SAAS,EAAE,CAAC;YACd,IAAI,UAAU,GAAG,KAAK,CAAC;YACvB,IAAI,CAAC;gBAAC,UAAU,GAAG,SAAS,EAAE,CAAC;YAAC,CAAC;YAAC,MAAM,CAAC;gBAAC,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,CAAC;YAAC,CAAC;YAC/E,IAAI,CAAC,UAAU;gBAAE,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,CAAC;QACtD,CAAC;QACD,aAAa,CAAC,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QACzE,OAAO;YACL,OAAO,EAAE,uBAAuB,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS;YACzD,SAAS,EAAE,oBAAoB,CAAC,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC;SAC3D,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,UAAU,CACxB,SAAiB,EACjB,SAAyB;IAEzB,OAAO,sBAAsB,CAAC,KAAK,IAAI,EAAE;QACvC,MAAM,KAAK,GAAG,MAAM,SAAS,EAAe,CAAC;QAC7C,MAAM,QAAQ,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CACrC,CAAC,IAAI;YACL,OAAO,IAAI,KAAK,QAAQ;YACvB,IAAiC,CAAC,UAAU,KAAK,SAAS,CAC5D,CAAC;QACF,IAAI,QAAQ,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM;YAAE,OAAO,KAAK,CAAC;QACnD,MAAM,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAEtD,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;QAChE,IAAI,SAAS,EAAE,CAAC;YACd,oEAAoE;YACpE,4EAA4E;YAC5E,IAAI,UAAU,GAAG,KAAK,CAAC;YACvB,IAAI,CAAC;gBAAC,UAAU,GAAG,SAAS,EAAE,CAAC;YAAC,CAAC;YAAC,MAAM,CAAC;gBAAC,OAAO,KAAK,CAAC;YAAC,CAAC;YACzD,IAAI,CAAC,UAAU;gBAAE,OAAO,KAAK,CAAC;YAC9B,aAAa,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;QACxC,CAAC;aAAM,CAAC;YACN,sEAAsE;YACtE,MAAM,SAAS,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;QAC1C,CAAC;QACD,MAAM,OAAO,GAAG,IAAI,CAAC;QACrB,IAAI,OAAO;YAAE,oBAAoB,CAAC,SAAS,CAAC,CAAC;QAC7C,OAAO,OAAO,CAAC;IACjB,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { PAIRING_INVITE_TTL_MS, PAIR_TTL_MAX_MS, PAIR_TTL_MIN_MS } from "../vendor/protocol/outer/index.js";
|
|
2
|
+
export { PAIRING_INVITE_TTL_MS, PAIR_TTL_MAX_MS, PAIR_TTL_MIN_MS };
|
|
3
|
+
export declare const PAIRING_CODE_LENGTH = 8;
|
|
4
|
+
export declare const PAIRING_CODE_PATTERN: RegExp;
|
|
5
|
+
export declare function clampPairTtlMs(ttlMs: number): number;
|
|
6
|
+
export type PairingInvite = Readonly<{
|
|
7
|
+
code: string;
|
|
8
|
+
expiresAt: number;
|
|
9
|
+
}>;
|
|
10
|
+
export type CodeReservation = Readonly<{
|
|
11
|
+
code: string;
|
|
12
|
+
ownerId: string;
|
|
13
|
+
requestId: string;
|
|
14
|
+
}>;
|
|
15
|
+
export type CodeReservationResult<T = unknown> = {
|
|
16
|
+
status: "reserved";
|
|
17
|
+
reservation: CodeReservation;
|
|
18
|
+
} | {
|
|
19
|
+
status: "committed";
|
|
20
|
+
completion: T;
|
|
21
|
+
} | {
|
|
22
|
+
status: "expired" | "consumed" | "unknown";
|
|
23
|
+
};
|
|
24
|
+
export declare class QRSession {
|
|
25
|
+
private active;
|
|
26
|
+
generateCode(): string;
|
|
27
|
+
issueCode(ttlMs?: number): PairingInvite;
|
|
28
|
+
issueInvite(ttlMs?: number): PairingInvite;
|
|
29
|
+
getActiveInvite(): PairingInvite | null;
|
|
30
|
+
reserveCode<T = unknown>(code: string, ownerId: string, requestId: string): CodeReservationResult<T>;
|
|
31
|
+
commitCode<T>(reservation: CodeReservation, completion: T): boolean;
|
|
32
|
+
releaseCode(reservation: CodeReservation): boolean;
|
|
33
|
+
isReservationCurrent(reservation: CodeReservation): boolean;
|
|
34
|
+
consumeCode(code: string): "ok" | "expired" | "consumed" | "unknown";
|
|
35
|
+
clear(): void;
|
|
36
|
+
}
|
|
37
|
+
export declare const qrSession: QRSession;
|
|
38
|
+
export declare function renderQRAscii(code: string): string;
|
|
39
|
+
export declare function displayQR(code: string): void;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { PAIRING_INVITE_TTL_MS, PAIR_TTL_MAX_MS, PAIR_TTL_MIN_MS } from "../vendor/protocol/outer/index.js";
|
|
3
|
+
import qrTerminal from "qrcode-terminal";
|
|
4
|
+
export { PAIRING_INVITE_TTL_MS, PAIR_TTL_MAX_MS, PAIR_TTL_MIN_MS };
|
|
5
|
+
export const PAIRING_CODE_LENGTH = 8;
|
|
6
|
+
export const PAIRING_CODE_PATTERN = /^[0-9A-HJKMNP-TV-Z]{8}$/;
|
|
7
|
+
const CROCKFORD_BASE32 = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
8
|
+
export function clampPairTtlMs(ttlMs) {
|
|
9
|
+
if (!Number.isFinite(ttlMs))
|
|
10
|
+
return PAIRING_INVITE_TTL_MS;
|
|
11
|
+
return Math.min(PAIR_TTL_MAX_MS, Math.max(PAIR_TTL_MIN_MS, Math.floor(ttlMs)));
|
|
12
|
+
}
|
|
13
|
+
export class QRSession {
|
|
14
|
+
active = null;
|
|
15
|
+
generateCode() {
|
|
16
|
+
const random = randomBytes(PAIRING_CODE_LENGTH);
|
|
17
|
+
let code = "";
|
|
18
|
+
for (const byte of random)
|
|
19
|
+
code += CROCKFORD_BASE32[byte & 31];
|
|
20
|
+
return code;
|
|
21
|
+
}
|
|
22
|
+
issueCode(ttlMs = PAIRING_INVITE_TTL_MS) {
|
|
23
|
+
const invite = Object.freeze({ code: this.generateCode(), expiresAt: Date.now() + ttlMs });
|
|
24
|
+
this.active = { ...invite, consumed: false };
|
|
25
|
+
return invite;
|
|
26
|
+
}
|
|
27
|
+
issueInvite(ttlMs = PAIRING_INVITE_TTL_MS) {
|
|
28
|
+
return this.issueCode(ttlMs);
|
|
29
|
+
}
|
|
30
|
+
getActiveInvite() {
|
|
31
|
+
if (!this.active)
|
|
32
|
+
return null;
|
|
33
|
+
return { code: this.active.code, expiresAt: this.active.expiresAt };
|
|
34
|
+
}
|
|
35
|
+
reserveCode(code, ownerId, requestId) {
|
|
36
|
+
const active = this.active;
|
|
37
|
+
if (!active || active.code !== code)
|
|
38
|
+
return { status: "unknown" };
|
|
39
|
+
const existing = active.reservation;
|
|
40
|
+
if (existing) {
|
|
41
|
+
if (existing.reservation.ownerId !== ownerId || existing.reservation.requestId !== requestId) {
|
|
42
|
+
return { status: "consumed" };
|
|
43
|
+
}
|
|
44
|
+
// A committed result belongs to the exact Owner/request and remains
|
|
45
|
+
// replayable until this invite is replaced or cleared, even after its TTL.
|
|
46
|
+
if (existing.status === "committed") {
|
|
47
|
+
return { status: "committed", completion: existing.completion };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (Date.now() >= active.expiresAt)
|
|
51
|
+
return { status: "expired" };
|
|
52
|
+
if (active.consumed)
|
|
53
|
+
return { status: "consumed" };
|
|
54
|
+
if (existing) {
|
|
55
|
+
if (existing.status === "reserved")
|
|
56
|
+
return { status: "reserved", reservation: existing.reservation };
|
|
57
|
+
// A released reservation remains private to the original attempt. A
|
|
58
|
+
// retry by that exact Owner/request may reacquire it; nobody else can.
|
|
59
|
+
}
|
|
60
|
+
const reservation = existing?.status === "released"
|
|
61
|
+
? Object.freeze({ code, ownerId, requestId })
|
|
62
|
+
: existing?.reservation ?? Object.freeze({ code, ownerId, requestId });
|
|
63
|
+
active.reservation = { reservation, status: "reserved" };
|
|
64
|
+
return { status: "reserved", reservation };
|
|
65
|
+
}
|
|
66
|
+
commitCode(reservation, completion) {
|
|
67
|
+
const active = this.active;
|
|
68
|
+
const state = active?.reservation;
|
|
69
|
+
if (!active || !state || state.reservation !== reservation || state.status !== "reserved")
|
|
70
|
+
return false;
|
|
71
|
+
if (active.code !== reservation.code || Date.now() >= active.expiresAt)
|
|
72
|
+
return false;
|
|
73
|
+
state.status = "committed";
|
|
74
|
+
state.completion = completion;
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
releaseCode(reservation) {
|
|
78
|
+
const active = this.active;
|
|
79
|
+
const state = active?.reservation;
|
|
80
|
+
if (!active || !state || state.reservation !== reservation || state.status !== "reserved")
|
|
81
|
+
return false;
|
|
82
|
+
state.status = "released";
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
isReservationCurrent(reservation) {
|
|
86
|
+
const active = this.active;
|
|
87
|
+
const state = active?.reservation;
|
|
88
|
+
return !!active && !!state && state.reservation === reservation && state.status === "reserved" && active.code === reservation.code && Date.now() < active.expiresAt;
|
|
89
|
+
}
|
|
90
|
+
consumeCode(code) {
|
|
91
|
+
if (!this.active || this.active.code !== code)
|
|
92
|
+
return "unknown";
|
|
93
|
+
if (this.active.consumed || this.active.reservation)
|
|
94
|
+
return "consumed";
|
|
95
|
+
if (Date.now() >= this.active.expiresAt)
|
|
96
|
+
return "expired";
|
|
97
|
+
this.active.consumed = true;
|
|
98
|
+
return "ok";
|
|
99
|
+
}
|
|
100
|
+
clear() {
|
|
101
|
+
this.active = null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
export const qrSession = new QRSession();
|
|
105
|
+
export function renderQRAscii(code) {
|
|
106
|
+
let out = "";
|
|
107
|
+
qrTerminal.generate(code, { small: true }, (qrcode) => { out = qrcode; });
|
|
108
|
+
return out;
|
|
109
|
+
}
|
|
110
|
+
export function displayQR(code) {
|
|
111
|
+
process.stderr.write(`\nScan to pair:\n\n${renderQRAscii(code)}\n`);
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=qr.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"qr.js","sourceRoot":"","sources":["../../src/pairing/qr.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type Ed25519Keypair } from "./crypto.js";
|
|
2
|
+
import { type KeyStoreBackend } from "./identity-keyring.js";
|
|
3
|
+
export { addPeer, listPeers, listOwnerPubkeys, snapshotOwnerPubkeys, conditionalRemovePeer, conditionalRollbackPeer, removePeer, } from "./owner_storage.js";
|
|
4
|
+
export type { PeerRecord, OwnerStorageToken, OwnerStorageSnapshotRecord, ConditionalPeerRemoval, PeerWriteReceipt, ConditionalPeerRollback, } from "./owner_storage.js";
|
|
5
|
+
export { _setKeyringOperationTimeoutForTest, _setNativeBindingErrorForTest, KeyringMutationTimeoutError, KeyringReadTimeoutError, } from "./identity-keyring.js";
|
|
6
|
+
export type { KeyStoreBackend } from "./identity-keyring.js";
|
|
7
|
+
export { IdentityLockTimeoutError } from "./identity-lock.js";
|
|
8
|
+
export declare class KeyringUnavailableError extends Error {
|
|
9
|
+
constructor(cause: unknown);
|
|
10
|
+
}
|
|
11
|
+
export declare class PairedIdentityMissingError extends Error {
|
|
12
|
+
constructor(pairedCount: number, cause: unknown);
|
|
13
|
+
}
|
|
14
|
+
type IdentityFileErrorCategory = "read_failed" | "invalid_format";
|
|
15
|
+
export declare class IdentityFileError extends Error {
|
|
16
|
+
readonly identityPath: string;
|
|
17
|
+
readonly category: IdentityFileErrorCategory;
|
|
18
|
+
constructor(identityPath: string, category: IdentityFileErrorCategory);
|
|
19
|
+
}
|
|
20
|
+
export declare class KeyringIdentityError extends Error {
|
|
21
|
+
readonly service: string;
|
|
22
|
+
constructor(service: string);
|
|
23
|
+
}
|
|
24
|
+
export declare function _setKeyStoreBackendForTest(value: KeyStoreBackend | null): void;
|
|
25
|
+
export declare function _setKeyringExpectedForTest(value: boolean | null): void;
|
|
26
|
+
export declare function _setKeyringRetryForTest(attempts: number | null, delayMs?: number): void;
|
|
27
|
+
export declare function _setIdentityLockTimingForTest(waitTimeoutMs: number | null, pollIntervalMs?: number): void;
|
|
28
|
+
/**
|
|
29
|
+
* Returns the stable Host identity. Existing file identity wins; otherwise all
|
|
30
|
+
* keyring reads, generation, and persistence run under the fixed
|
|
31
|
+
* cross-process identity lock. Every source is read again after lock acquisition.
|
|
32
|
+
*/
|
|
33
|
+
export declare function getOrCreateEd25519Keypair(): Promise<Ed25519Keypair>;
|
|
34
|
+
export declare const _IDENTITY_FILE_FOR_TEST: string;
|
|
35
|
+
export declare const _IDENTITY_LOCK_FILE_FOR_TEST: string;
|
|
36
|
+
export declare const _unlinkIdentityFileForTest: () => Promise<void>;
|