@yefengr/remote-pi 0.9.7 → 0.9.9
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/README.md +12 -2
- 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/qr.d.ts +2 -3
- package/dist/pairing/qr.js +2 -3
- package/dist/pairing/qr.js.map +1 -1
- package/dist/pairing/storage.d.ts +19 -61
- package/dist/pairing/storage.js +196 -314
- package/dist/pairing/storage.js.map +1 -1
- package/dist/protocol/types.d.ts +2 -52
- package/dist/protocol/v2/codec.d.ts +5 -25
- package/dist/protocol/v2/codec.js +3 -206
- package/dist/protocol/v2/codec.js.map +1 -1
- package/dist/protocol/v2/index.d.ts +1 -0
- package/dist/protocol/v2/index.js +1 -0
- package/dist/protocol/v2/index.js.map +1 -1
- package/dist/protocol/v2/marker.d.ts +40 -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 -2024
- package/dist/protocol/v2/schemas.js +3 -433
- package/dist/protocol/v2/schemas.js.map +1 -1
- package/dist/runtime/owner_router.js +5 -1
- package/dist/runtime/owner_router.js.map +1 -1
- package/dist/transport/peer_channel.d.ts +2 -11
- package/dist/transport/peer_channel.js +7 -11
- package/dist/transport/peer_channel.js.map +1 -1
- package/dist/transport/relay_client.d.ts +2 -8
- package/dist/transport/relay_client.js +3 -2
- package/dist/transport/relay_client.js.map +1 -1
- 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 +5750 -0
- package/dist/vendor/protocol/session/frames.js +406 -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 +1060 -0
- package/dist/vendor/protocol/session/schema.js +302 -0
- package/package.json +17 -25
package/README.md
CHANGED
|
@@ -80,6 +80,7 @@ Only `http://` and `https://` are accepted at the command boundary; WebSocket co
|
|
|
80
80
|
- Pairing and revocation update the Relay endpoint ACL with `authorized_owner_ids`.
|
|
81
81
|
- Relay loss enters reconnecting state; the Extension does not restart Pi to recover.
|
|
82
82
|
- Device private keys, pairing tokens, encrypted payloads, and message bodies are not logged.
|
|
83
|
+
- Concurrent Pi processes coordinate device identity initialization through a local lock. If initialization is interrupted, follow the [identity storage and lock recovery rules](../docs/reference/protocol/pairing.md#host); do not delete identity or pairing data to retry.
|
|
83
84
|
|
|
84
85
|
## Migration from older releases
|
|
85
86
|
|
|
@@ -104,14 +105,23 @@ Do not remove `~/.pi/remote/identity.json` or `peers.json`: they retain the exis
|
|
|
104
105
|
|
|
105
106
|
## Development
|
|
106
107
|
|
|
108
|
+
Install dependencies from the repository root with `pnpm install --frozen-lockfile`.
|
|
109
|
+
The root workspace owns dependency catalogs, build approvals, and the lockfile. The private workspace package [`@remote-pi/protocol`](../packages/protocol/) provides the shared protocol build artifacts; the Extension uses it as a development dependency, while installed users receive vendored artifacts and need neither the workspace nor a separately published shared package. Its module and distribution boundary is defined in [ARCHITECTURE](../docs/ARCHITECTURE.md#工程与构建边界).
|
|
110
|
+
|
|
111
|
+
The root `prepare` script and an Extension `pnpm build` build the shared package first. After changing shared sources, run `pnpm --filter @remote-pi/protocol build` from the repository root before an Extension-only `typecheck` or `test`, or use the corresponding root command.
|
|
112
|
+
|
|
113
|
+
Run these commands from `pi-extension/`, or use the `@yefengr/remote-pi` package filter:
|
|
114
|
+
|
|
107
115
|
```bash
|
|
108
|
-
pnpm install
|
|
109
116
|
pnpm typecheck
|
|
110
117
|
pnpm test
|
|
111
118
|
pnpm build
|
|
112
119
|
```
|
|
113
120
|
|
|
114
|
-
|
|
121
|
+
The development toolchain is pinned by the root configuration. The published
|
|
122
|
+
extension retains its Node 20+ runtime requirement. TypeScript ESM imports must
|
|
123
|
+
use `.js` extensions. `prepack` builds before packaging; `pnpm pack` resolves
|
|
124
|
+
catalog and workspace references, and `publish-npm.sh` uploads the inspected tarball.
|
|
115
125
|
|
|
116
126
|
## License
|
|
117
127
|
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { IDENTITY_LOCK_RELEASE_AFTER, type IdentityLockDeferredRelease } from "./identity-lock.js";
|
|
2
|
+
export interface KeyStoreBackend {
|
|
3
|
+
read(service: string, account: string): Promise<string | undefined>;
|
|
4
|
+
write(service: string, account: string, value: string): Promise<void>;
|
|
5
|
+
delete(service: string, account: string): Promise<boolean>;
|
|
6
|
+
}
|
|
7
|
+
export declare class KeyringReadTimeoutError extends Error {
|
|
8
|
+
constructor(op: string, timeoutMs: number);
|
|
9
|
+
}
|
|
10
|
+
export declare class KeyringMutationTimeoutError extends Error implements IdentityLockDeferredRelease {
|
|
11
|
+
readonly [IDENTITY_LOCK_RELEASE_AFTER]: Promise<void>;
|
|
12
|
+
constructor(op: string, timeoutMs: number, settled: Promise<void>);
|
|
13
|
+
}
|
|
14
|
+
export declare function _setKeyringOperationTimeoutForTest(timeoutMs: number | null): void;
|
|
15
|
+
export declare function _runKeyringOperationWithTimeoutForTest<T>(operation: Promise<T>, op: string, mutating: boolean, timeoutMs: number): Promise<T>;
|
|
16
|
+
export declare function nativeBindingUnavailable(): boolean;
|
|
17
|
+
export declare function _setNativeBindingErrorForTest(error: unknown): void;
|
|
18
|
+
export declare class NapiKeyringBackend implements KeyStoreBackend {
|
|
19
|
+
read(service: string, account: string): Promise<string | undefined>;
|
|
20
|
+
write(service: string, account: string, value: string): Promise<void>;
|
|
21
|
+
delete(service: string, account: string): Promise<boolean>;
|
|
22
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { IDENTITY_LOCK_RELEASE_AFTER, } from "./identity-lock.js";
|
|
2
|
+
export class KeyringReadTimeoutError extends Error {
|
|
3
|
+
constructor(op, timeoutMs) {
|
|
4
|
+
super(`keyring ${op} timed out after ${timeoutMs}ms`);
|
|
5
|
+
this.name = "KeyringReadTimeoutError";
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
export class KeyringMutationTimeoutError extends Error {
|
|
9
|
+
[IDENTITY_LOCK_RELEASE_AFTER];
|
|
10
|
+
constructor(op, timeoutMs, settled) {
|
|
11
|
+
super(`keyring ${op} timed out after ${timeoutMs}ms; the underlying operation is still pending`);
|
|
12
|
+
this.name = "KeyringMutationTimeoutError";
|
|
13
|
+
this[IDENTITY_LOCK_RELEASE_AFTER] = settled.then(() => undefined, () => undefined);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
let keyringOperationTimeoutMs = 3_000;
|
|
17
|
+
export function _setKeyringOperationTimeoutForTest(timeoutMs) {
|
|
18
|
+
keyringOperationTimeoutMs = timeoutMs ?? 3_000;
|
|
19
|
+
}
|
|
20
|
+
export function _runKeyringOperationWithTimeoutForTest(operation, op, mutating, timeoutMs) {
|
|
21
|
+
return withKeyringTimeout(operation, op, mutating, timeoutMs);
|
|
22
|
+
}
|
|
23
|
+
function withKeyringTimeout(operation, op, mutating, timeoutMs = keyringOperationTimeoutMs) {
|
|
24
|
+
const settled = operation.then(() => undefined, () => undefined);
|
|
25
|
+
return new Promise((resolve, reject) => {
|
|
26
|
+
const timer = setTimeout(() => {
|
|
27
|
+
reject(mutating
|
|
28
|
+
? new KeyringMutationTimeoutError(op, timeoutMs, settled)
|
|
29
|
+
: new KeyringReadTimeoutError(op, timeoutMs));
|
|
30
|
+
}, timeoutMs);
|
|
31
|
+
operation.then((value) => {
|
|
32
|
+
clearTimeout(timer);
|
|
33
|
+
resolve(value);
|
|
34
|
+
}, (error) => {
|
|
35
|
+
clearTimeout(timer);
|
|
36
|
+
reject(error);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
let asyncEntryCtor = null;
|
|
41
|
+
let nativeBindingError = null;
|
|
42
|
+
async function loadAsyncEntry() {
|
|
43
|
+
if (asyncEntryCtor)
|
|
44
|
+
return asyncEntryCtor;
|
|
45
|
+
if (nativeBindingError)
|
|
46
|
+
throw nativeBindingError;
|
|
47
|
+
try {
|
|
48
|
+
const mod = await import("@napi-rs/keyring");
|
|
49
|
+
asyncEntryCtor = mod.AsyncEntry;
|
|
50
|
+
return asyncEntryCtor;
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
nativeBindingError = error;
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
export function nativeBindingUnavailable() {
|
|
58
|
+
return nativeBindingError !== null;
|
|
59
|
+
}
|
|
60
|
+
export function _setNativeBindingErrorForTest(error) {
|
|
61
|
+
asyncEntryCtor = null;
|
|
62
|
+
nativeBindingError = error;
|
|
63
|
+
}
|
|
64
|
+
export class NapiKeyringBackend {
|
|
65
|
+
async read(service, account) {
|
|
66
|
+
const AsyncEntry = await loadAsyncEntry();
|
|
67
|
+
const entry = new AsyncEntry(service, account);
|
|
68
|
+
return withKeyringTimeout(entry.getPassword(), `read(${service})`, false);
|
|
69
|
+
}
|
|
70
|
+
async write(service, account, value) {
|
|
71
|
+
const AsyncEntry = await loadAsyncEntry();
|
|
72
|
+
const entry = new AsyncEntry(service, account);
|
|
73
|
+
await withKeyringTimeout(entry.setPassword(value), `write(${service})`, true);
|
|
74
|
+
}
|
|
75
|
+
async delete(service, account) {
|
|
76
|
+
let entry;
|
|
77
|
+
try {
|
|
78
|
+
const AsyncEntry = await loadAsyncEntry();
|
|
79
|
+
entry = new AsyncEntry(service, account);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
return await withKeyringTimeout(entry.deleteCredential(), `delete(${service})`, true);
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
if (error instanceof KeyringMutationTimeoutError)
|
|
89
|
+
throw error;
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=identity-keyring.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity-keyring.js","sourceRoot":"","sources":["../../src/pairing/identity-keyring.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare const IDENTITY_LOCK_RELEASE_AFTER: unique symbol;
|
|
2
|
+
export interface IdentityLockDeferredRelease {
|
|
3
|
+
readonly [IDENTITY_LOCK_RELEASE_AFTER]: Promise<void>;
|
|
4
|
+
}
|
|
5
|
+
export interface IdentityLockOptions {
|
|
6
|
+
readonly waitTimeoutMs?: number;
|
|
7
|
+
readonly pollIntervalMs?: number;
|
|
8
|
+
}
|
|
9
|
+
export declare class IdentityLockTimeoutError extends Error {
|
|
10
|
+
readonly lockPath: string;
|
|
11
|
+
constructor(lockPath: string, waitTimeoutMs: number);
|
|
12
|
+
}
|
|
13
|
+
export declare function ensurePrivateIdentityDirectory(directory: string): Promise<void>;
|
|
14
|
+
export declare function withIdentityLock<T>(directory: string, operation: () => Promise<T>, options?: IdentityLockOptions): Promise<T>;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { chmod, mkdir, open, readFile, unlink } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
export const IDENTITY_LOCK_RELEASE_AFTER = Symbol("identity-lock-release-after");
|
|
5
|
+
export class IdentityLockTimeoutError extends Error {
|
|
6
|
+
lockPath;
|
|
7
|
+
constructor(lockPath, waitTimeoutMs) {
|
|
8
|
+
super(`Identity initialization lock remained held for ${waitTimeoutMs}ms at ${lockPath}. ` +
|
|
9
|
+
"Another process may still be initializing the identity, or a previous process may " +
|
|
10
|
+
"have exited unexpectedly. The lock is never removed automatically. Confirm that no " +
|
|
11
|
+
"identity initialization or keyring operation is still running before removing it manually.");
|
|
12
|
+
this.name = "IdentityLockTimeoutError";
|
|
13
|
+
this.lockPath = lockPath;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function isNodeErrorWithCode(error, code) {
|
|
17
|
+
return typeof error === "object" && error !== null && "code" in error &&
|
|
18
|
+
error.code === code;
|
|
19
|
+
}
|
|
20
|
+
function sleep(ms) {
|
|
21
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
22
|
+
}
|
|
23
|
+
export async function ensurePrivateIdentityDirectory(directory) {
|
|
24
|
+
await mkdir(directory, { recursive: true, mode: 0o700 });
|
|
25
|
+
await chmod(directory, 0o700);
|
|
26
|
+
}
|
|
27
|
+
async function tryAcquire(lockPath) {
|
|
28
|
+
let handle;
|
|
29
|
+
try {
|
|
30
|
+
handle = await open(lockPath, "wx", 0o600);
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
if (isNodeErrorWithCode(error, "EEXIST"))
|
|
34
|
+
return null;
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
const token = randomUUID();
|
|
38
|
+
try {
|
|
39
|
+
await handle.writeFile(JSON.stringify({ pid: process.pid, token, createdAt: new Date().toISOString() }));
|
|
40
|
+
await handle.sync();
|
|
41
|
+
return { handle, path: lockPath, token };
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
await handle.close().catch(() => undefined);
|
|
45
|
+
await unlink(lockPath).catch(() => undefined);
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
async function acquireIdentityLock(directory, options) {
|
|
50
|
+
await ensurePrivateIdentityDirectory(directory);
|
|
51
|
+
const lockPath = join(directory, "identity.lock");
|
|
52
|
+
const waitTimeoutMs = options.waitTimeoutMs ?? 15_000;
|
|
53
|
+
const pollIntervalMs = Math.max(1, options.pollIntervalMs ?? 50);
|
|
54
|
+
const deadline = Date.now() + Math.max(0, waitTimeoutMs);
|
|
55
|
+
while (true) {
|
|
56
|
+
const held = await tryAcquire(lockPath);
|
|
57
|
+
if (held)
|
|
58
|
+
return held;
|
|
59
|
+
const remainingMs = deadline - Date.now();
|
|
60
|
+
if (remainingMs <= 0)
|
|
61
|
+
throw new IdentityLockTimeoutError(lockPath, waitTimeoutMs);
|
|
62
|
+
await sleep(Math.min(pollIntervalMs, remainingMs));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
async function releaseIdentityLock(lock) {
|
|
66
|
+
try {
|
|
67
|
+
let raw;
|
|
68
|
+
try {
|
|
69
|
+
raw = await readFile(lock.path, "utf8");
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
if (isNodeErrorWithCode(error, "ENOENT"))
|
|
73
|
+
return;
|
|
74
|
+
throw error;
|
|
75
|
+
}
|
|
76
|
+
let token;
|
|
77
|
+
try {
|
|
78
|
+
token = JSON.parse(raw).token;
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (token !== lock.token)
|
|
84
|
+
return;
|
|
85
|
+
await unlink(lock.path);
|
|
86
|
+
}
|
|
87
|
+
finally {
|
|
88
|
+
await lock.handle.close();
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function deferredRelease(error) {
|
|
92
|
+
if (typeof error !== "object" || error === null)
|
|
93
|
+
return null;
|
|
94
|
+
const settled = error[IDENTITY_LOCK_RELEASE_AFTER];
|
|
95
|
+
return settled instanceof Promise ? settled : null;
|
|
96
|
+
}
|
|
97
|
+
export async function withIdentityLock(directory, operation, options = {}) {
|
|
98
|
+
const lock = await acquireIdentityLock(directory, options);
|
|
99
|
+
let releaseWasDeferred = false;
|
|
100
|
+
try {
|
|
101
|
+
return await operation();
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
const settled = deferredRelease(error);
|
|
105
|
+
if (settled) {
|
|
106
|
+
releaseWasDeferred = true;
|
|
107
|
+
void settled.then(() => releaseIdentityLock(lock), () => releaseIdentityLock(lock)).catch(() => undefined);
|
|
108
|
+
}
|
|
109
|
+
throw error;
|
|
110
|
+
}
|
|
111
|
+
finally {
|
|
112
|
+
if (!releaseWasDeferred)
|
|
113
|
+
await releaseIdentityLock(lock);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=identity-lock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity-lock.js","sourceRoot":"","sources":["../../src/pairing/identity-lock.ts"],"names":[],"mappings":"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"}
|
package/dist/pairing/qr.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
export
|
|
3
|
-
export declare const PAIR_TTL_MAX_MS = 600000;
|
|
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 };
|
|
4
3
|
export declare const PAIRING_CODE_LENGTH = 8;
|
|
5
4
|
export declare const PAIRING_CODE_PATTERN: RegExp;
|
|
6
5
|
export declare function clampPairTtlMs(ttlMs: number): number;
|
package/dist/pairing/qr.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
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";
|
|
2
3
|
import qrTerminal from "qrcode-terminal";
|
|
3
|
-
export
|
|
4
|
-
export const PAIR_TTL_MIN_MS = 10_000;
|
|
5
|
-
export const PAIR_TTL_MAX_MS = 600_000;
|
|
4
|
+
export { PAIRING_INVITE_TTL_MS, PAIR_TTL_MAX_MS, PAIR_TTL_MIN_MS };
|
|
6
5
|
export const PAIRING_CODE_LENGTH = 8;
|
|
7
6
|
export const PAIRING_CODE_PATTERN = /^[0-9A-HJKMNP-TV-Z]{8}$/;
|
|
8
7
|
const CROCKFORD_BASE32 = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
package/dist/pairing/qr.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"qr.js","sourceRoot":"","sources":["../../src/pairing/qr.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,
|
|
1
|
+
{"version":3,"file":"qr.js","sourceRoot":"","sources":["../../src/pairing/qr.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,78 +1,36 @@
|
|
|
1
1
|
import { type Ed25519Keypair } from "./crypto.js";
|
|
2
|
+
import { type KeyStoreBackend } from "./identity-keyring.js";
|
|
2
3
|
export { addPeer, listPeers, listOwnerPubkeys, snapshotOwnerPubkeys, conditionalRemovePeer, conditionalRollbackPeer, removePeer, } from "./owner_storage.js";
|
|
3
4
|
export type { PeerRecord, OwnerStorageToken, OwnerStorageSnapshotRecord, ConditionalPeerRemoval, PeerWriteReceipt, ConditionalPeerRollback, } from "./owner_storage.js";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
* would break existing pairing — the caller surfaces this so the user can
|
|
8
|
-
* unlock the keychain and retry instead of silently re-pairing. */
|
|
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";
|
|
9
8
|
export declare class KeyringUnavailableError extends Error {
|
|
10
9
|
constructor(cause: unknown);
|
|
11
10
|
}
|
|
12
|
-
/** Raised when no identity can be resolved (keyring unreadable, no identity
|
|
13
|
-
* file) BUT `peers.json` already lists paired devices. Minting a fresh key
|
|
14
|
-
* here would make those existing pairings unusable — see issues #95 / #69. */
|
|
15
11
|
export declare class PairedIdentityMissingError extends Error {
|
|
16
12
|
constructor(pairedCount: number, cause: unknown);
|
|
17
13
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
*/
|
|
28
|
-
export interface KeyStoreBackend {
|
|
29
|
-
read(service: string, account: string): Promise<string | undefined>;
|
|
30
|
-
write(service: string, account: string, value: string): Promise<void>;
|
|
31
|
-
delete(service: string, account: string): Promise<boolean>;
|
|
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);
|
|
32
23
|
}
|
|
33
|
-
|
|
34
|
-
* the Bun/no-native-binding branch is reachable without a Bun host. */
|
|
35
|
-
export declare function _setNativeBindingErrorForTest(err: unknown): void;
|
|
36
|
-
/** Test-only: swap (or clear with `null`) the keyring backend. */
|
|
37
|
-
export declare function _setKeyStoreBackendForTest(backend: KeyStoreBackend | null): void;
|
|
38
|
-
/** Test-only: force `_keyringExpectedAvailable()` (so a darwin test host can
|
|
39
|
-
* exercise the Linux/headless branch and vice-versa). `null` restores the
|
|
40
|
-
* real platform check. */
|
|
24
|
+
export declare function _setKeyStoreBackendForTest(value: KeyStoreBackend | null): void;
|
|
41
25
|
export declare function _setKeyringExpectedForTest(value: boolean | null): void;
|
|
42
|
-
/** Test-only: shrink retry attempts/delay so the persistent-failure path is
|
|
43
|
-
* fast. `null`/omitted restores defaults. */
|
|
44
26
|
export declare function _setKeyringRetryForTest(attempts: number | null, delayMs?: number): void;
|
|
27
|
+
export declare function _setIdentityLockTimingForTest(waitTimeoutMs: number | null, pollIntervalMs?: number): void;
|
|
45
28
|
/**
|
|
46
|
-
* Returns the
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
* the keyring. A file identity is only ever written by the headless/
|
|
50
|
-
* degraded fallback (step 4) or an explicit `REMOTE_PI_ALLOW_FILE_IDENTITY`
|
|
51
|
-
* opt-in, so its mere presence means this machine established its identity
|
|
52
|
-
* as a file and the browser PWA paired against THAT pubkey. If the
|
|
53
|
-
* platform keyring later becomes readable (D-Bus/libsecret installed, a
|
|
54
|
-
* desktop session, or a stale/other entry from another install), reading
|
|
55
|
-
* it first would mask the file identity — returning a DIFFERENT key, or
|
|
56
|
-
* (when the keyring is empty) minting a fresh one and persisting it —
|
|
57
|
-
* silently breaking the existing pairing. So when both exist, file wins.
|
|
58
|
-
* 2. New keyring service `dev.remotepi.pi` (read retried — a transiently
|
|
59
|
-
* locked Keychain throws; we don't treat that as "no key")
|
|
60
|
-
* 3. Old keyring service `dev.remotepi.mac` (migrate → step 2, delete old)
|
|
61
|
-
* 4. Generate a fresh keypair, BUT only when it's safe to: either both
|
|
62
|
-
* keyring reads succeeded and returned nothing (genuine first run), or
|
|
63
|
-
* the keyring is genuinely unavailable on a platform without a core one
|
|
64
|
-
* (headless Linux → a file identity is minted here). On macOS/Windows a
|
|
65
|
-
* persistent read failure with no file identity throws
|
|
66
|
-
* `KeyringUnavailableError` instead of minting a new key — generating
|
|
67
|
-
* there silently breaks existing pairing (the "lost pairing after idle"
|
|
68
|
-
* bug). `REMOTE_PI_ALLOW_FILE_IDENTITY=1` opts back into a file identity
|
|
69
|
-
* for headless macOS/Windows hosts.
|
|
70
|
-
*
|
|
71
|
-
* Idempotent: subsequent calls return the same identity. The migration
|
|
72
|
-
* runs at most once per machine (the old entry is deleted after copy).
|
|
29
|
+
* Returns the stable Host identity. Existing file identity wins; otherwise all
|
|
30
|
+
* keyring reads, migration, generation, and persistence run under the fixed
|
|
31
|
+
* cross-process identity lock. Every source is read again after lock acquisition.
|
|
73
32
|
*/
|
|
74
33
|
export declare function getOrCreateEd25519Keypair(): Promise<Ed25519Keypair>;
|
|
75
|
-
/** Test-only: expose the identity-file path so tests can clean it. */
|
|
76
34
|
export declare const _IDENTITY_FILE_FOR_TEST: string;
|
|
77
|
-
|
|
35
|
+
export declare const _IDENTITY_LOCK_FILE_FOR_TEST: string;
|
|
78
36
|
export declare const _unlinkIdentityFileForTest: () => Promise<void>;
|