@serfab/cadre-core 0.7.1 → 0.9.0
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 +11 -10
- package/dist/cadre-node.d.ts +818 -19
- package/dist/cadre-node.js +1952 -99
- package/dist/cadre-node.js.map +1 -1
- package/dist/canonical-datetime.d.ts +33 -0
- package/dist/canonical-datetime.js +38 -0
- package/dist/canonical-datetime.js.map +1 -0
- package/dist/canonical-json.d.ts +12 -0
- package/dist/canonical-json.js +33 -0
- package/dist/canonical-json.js.map +1 -0
- package/dist/control-cohort.d.ts +63 -0
- package/dist/control-cohort.js +80 -0
- package/dist/control-cohort.js.map +1 -0
- package/dist/control-database.d.ts +279 -8
- package/dist/control-database.js +563 -134
- package/dist/control-database.js.map +1 -1
- package/dist/control-formation-recorder.d.ts +89 -0
- package/dist/control-formation-recorder.js +139 -0
- package/dist/control-formation-recorder.js.map +1 -0
- package/dist/control-schema.d.ts +12 -0
- package/dist/control-schema.js +223 -0
- package/dist/control-schema.js.map +1 -0
- package/dist/control-stream.d.ts +54 -0
- package/dist/control-stream.js +104 -0
- package/dist/control-stream.js.map +1 -0
- package/dist/device-token.d.ts +50 -0
- package/dist/device-token.js +66 -0
- package/dist/device-token.js.map +1 -0
- package/dist/diagnostics/connection-path.d.ts +118 -0
- package/dist/diagnostics/connection-path.js +204 -0
- package/dist/diagnostics/connection-path.js.map +1 -0
- package/dist/diagnostics/webrtc-turn-tracker.d.ts +50 -0
- package/dist/diagnostics/webrtc-turn-tracker.js +196 -0
- package/dist/diagnostics/webrtc-turn-tracker.js.map +1 -0
- package/dist/ed25519-key.d.ts +42 -0
- package/dist/ed25519-key.js +51 -0
- package/dist/ed25519-key.js.map +1 -0
- package/dist/hibernation-manager.d.ts +79 -0
- package/dist/hibernation-manager.js +173 -23
- package/dist/hibernation-manager.js.map +1 -1
- package/dist/index.d.ts +25 -4
- package/dist/index.js +59 -4
- package/dist/index.js.map +1 -1
- package/dist/key-store-file.d.ts +52 -0
- package/dist/key-store-file.js +194 -0
- package/dist/key-store-file.js.map +1 -0
- package/dist/key-store.d.ts +75 -0
- package/dist/key-store.js +66 -0
- package/dist/key-store.js.map +1 -0
- package/dist/peer-authorization.d.ts +48 -0
- package/dist/peer-authorization.js +66 -0
- package/dist/peer-authorization.js.map +1 -0
- package/dist/peer-record.d.ts +89 -0
- package/dist/peer-record.js +117 -0
- package/dist/peer-record.js.map +1 -0
- package/dist/push-credentials.d.ts +34 -0
- package/dist/push-credentials.js +81 -0
- package/dist/push-credentials.js.map +1 -0
- package/dist/push-fanout.d.ts +137 -0
- package/dist/push-fanout.js +197 -0
- package/dist/push-fanout.js.map +1 -0
- package/dist/push-node.d.ts +38 -0
- package/dist/push-node.js +48 -0
- package/dist/push-node.js.map +1 -0
- package/dist/push-notifier-apns.d.ts +61 -0
- package/dist/push-notifier-apns.js +199 -0
- package/dist/push-notifier-apns.js.map +1 -0
- package/dist/push-notifier-fcm.d.ts +44 -0
- package/dist/push-notifier-fcm.js +182 -0
- package/dist/push-notifier-fcm.js.map +1 -0
- package/dist/push-notifier-shared.d.ts +18 -0
- package/dist/push-notifier-shared.js +25 -0
- package/dist/push-notifier-shared.js.map +1 -0
- package/dist/push-notifier.d.ts +52 -0
- package/dist/push-notifier.js +22 -0
- package/dist/push-notifier.js.map +1 -0
- package/dist/schema-verification.d.ts +11 -2
- package/dist/schema-verification.js +15 -4
- package/dist/schema-verification.js.map +1 -1
- package/dist/seed-bootstrap.d.ts +228 -21
- package/dist/seed-bootstrap.js +489 -168
- package/dist/seed-bootstrap.js.map +1 -1
- package/dist/seed-trust-policy.d.ts +63 -0
- package/dist/seed-trust-policy.js +56 -0
- package/dist/seed-trust-policy.js.map +1 -0
- package/dist/strand-addr-protocol.d.ts +132 -0
- package/dist/strand-addr-protocol.js +313 -0
- package/dist/strand-addr-protocol.js.map +1 -0
- package/dist/strand-cohort.d.ts +54 -0
- package/dist/strand-cohort.js +38 -0
- package/dist/strand-cohort.js.map +1 -0
- package/dist/strand-database.d.ts +54 -6
- package/dist/strand-database.js +88 -100
- package/dist/strand-database.js.map +1 -1
- package/dist/strand-formation-manager.d.ts +79 -19
- package/dist/strand-formation-manager.js +196 -104
- package/dist/strand-formation-manager.js.map +1 -1
- package/dist/strand-formation-protocol.d.ts +162 -0
- package/dist/strand-formation-protocol.js +279 -0
- package/dist/strand-formation-protocol.js.map +1 -0
- package/dist/strand-instance-manager.d.ts +73 -4
- package/dist/strand-instance-manager.js +167 -41
- package/dist/strand-instance-manager.js.map +1 -1
- package/dist/strand-member-key.d.ts +29 -0
- package/dist/strand-member-key.js +38 -0
- package/dist/strand-member-key.js.map +1 -0
- package/dist/strand-member-registry.d.ts +98 -0
- package/dist/strand-member-registry.js +129 -0
- package/dist/strand-member-registry.js.map +1 -0
- package/dist/strand-membership-writer.d.ts +361 -0
- package/dist/strand-membership-writer.js +449 -0
- package/dist/strand-membership-writer.js.map +1 -0
- package/dist/strand-solicitation.d.ts +77 -2
- package/dist/strand-solicitation.js +25 -4
- package/dist/strand-solicitation.js.map +1 -1
- package/dist/strand-wake-payload.d.ts +30 -0
- package/dist/strand-wake-payload.js +20 -0
- package/dist/strand-wake-payload.js.map +1 -0
- package/dist/strand-wake-protocol.d.ts +114 -0
- package/dist/strand-wake-protocol.js +257 -0
- package/dist/strand-wake-protocol.js.map +1 -0
- package/dist/strand-watcher.d.ts +6 -2
- package/dist/strand-watcher.js +57 -17
- package/dist/strand-watcher.js.map +1 -1
- package/dist/types.d.ts +436 -33
- package/dist/types.js +22 -5
- package/dist/types.js.map +1 -1
- package/package.json +18 -8
- package/src/cadre-node.ts +2981 -948
- package/src/canonical-datetime.ts +39 -0
- package/src/canonical-json.ts +30 -0
- package/src/control-cohort.ts +103 -0
- package/src/control-database.ts +948 -383
- package/src/control-formation-recorder.ts +151 -0
- package/src/control-schema.ts +222 -0
- package/src/control-stream.ts +128 -0
- package/src/device-token.ts +89 -0
- package/src/diagnostics/connection-path.ts +277 -0
- package/src/diagnostics/webrtc-turn-tracker.ts +222 -0
- package/src/ed25519-key.ts +69 -0
- package/src/hibernation-manager.ts +197 -28
- package/src/index.ts +275 -79
- package/src/key-store-file.ts +203 -0
- package/src/key-store.ts +99 -0
- package/src/peer-authorization.ts +81 -0
- package/src/peer-record.ts +146 -0
- package/src/push-credentials.ts +84 -0
- package/src/push-fanout.ts +272 -0
- package/src/push-node.ts +64 -0
- package/src/push-notifier-apns.ts +247 -0
- package/src/push-notifier-fcm.ts +220 -0
- package/src/push-notifier-shared.ts +28 -0
- package/src/push-notifier.ts +52 -0
- package/src/schema-verification.ts +18 -4
- package/src/seed-bootstrap.ts +563 -223
- package/src/seed-trust-policy.ts +107 -0
- package/src/strand-addr-protocol.ts +410 -0
- package/src/strand-cohort.ts +77 -0
- package/src/strand-database.ts +113 -141
- package/src/strand-formation-manager.ts +233 -142
- package/src/strand-formation-protocol.ts +391 -0
- package/src/strand-instance-manager.ts +207 -56
- package/src/strand-member-key.ts +39 -0
- package/src/strand-member-registry.ts +177 -0
- package/src/strand-membership-writer.ts +640 -0
- package/src/strand-solicitation.ts +93 -4
- package/src/strand-wake-payload.ts +32 -0
- package/src/strand-wake-protocol.ts +331 -0
- package/src/strand-watcher.ts +61 -17
- package/src/types.ts +1032 -582
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* webrtc-turn-tracker.ts — observe whether ICE selected a TURN relay candidate
|
|
3
|
+
* for each WebRTC session, so a TURN-relayed `/webrtc` connection can be
|
|
4
|
+
* reclassified `relayed`/`webrtc-turn` instead of silently counted `direct`.
|
|
5
|
+
*
|
|
6
|
+
* `@libp2p/webrtc` keeps the underlying `RTCPeerConnection` private and never
|
|
7
|
+
* surfaces it on the libp2p `Connection`, so the multiaddr classifier
|
|
8
|
+
* (`connection-path.ts`) cannot see the ICE candidate types. This tracker hooks
|
|
9
|
+
* `globalThis.RTCPeerConnection` at install time: every connection that reaches
|
|
10
|
+
* `connectionState === 'connected'` is inspected via `getStats()`, and the
|
|
11
|
+
* relay/not-relay verdict for the selected candidate pair is pushed onto a FIFO
|
|
12
|
+
* queue. {@link CadreNode} drains that queue on each `connection:open` to tag the
|
|
13
|
+
* matching peer (see {@link consume}).
|
|
14
|
+
*
|
|
15
|
+
* The correlation between a queued settlement and a `connection:open` is
|
|
16
|
+
* timing-based and best-effort — it degrades gracefully (unknown → treated as not
|
|
17
|
+
* relayed, a safe default that never produces a false `relayed`). It is inert on
|
|
18
|
+
* Node.js / any runtime with no `RTCPeerConnection`.
|
|
19
|
+
*/
|
|
20
|
+
import debug from 'debug';
|
|
21
|
+
const log = debug('sereus:cadre:turn-tracker');
|
|
22
|
+
/**
|
|
23
|
+
* Hard cap on the FIFO queue. In steady state every settlement is consumed by a
|
|
24
|
+
* `connection:open`, so the queue stays tiny; the cap only bounds a pathological
|
|
25
|
+
* leak (settlements that never get a matching consume).
|
|
26
|
+
*/
|
|
27
|
+
const MAX_QUEUE = 64;
|
|
28
|
+
export class TurnRelayTracker {
|
|
29
|
+
constructor() {
|
|
30
|
+
this.queue = [];
|
|
31
|
+
/** One settlement record per connection; guards against repeated `connected` events. */
|
|
32
|
+
this.observed = new WeakSet();
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Wrap `globalThis.RTCPeerConnection` with a subclass that records each
|
|
36
|
+
* connection's TURN-relay verdict when it settles. Idempotent. A NOP when no
|
|
37
|
+
* `RTCPeerConnection` exists (Node.js / no WebRTC polyfill) — the tracker stays
|
|
38
|
+
* inert and {@link consume} always returns `null`.
|
|
39
|
+
*/
|
|
40
|
+
install() {
|
|
41
|
+
if (this.OriginalRTCPeerConnection) {
|
|
42
|
+
return; // already installed
|
|
43
|
+
}
|
|
44
|
+
const Original = globalThis.RTCPeerConnection;
|
|
45
|
+
if (typeof Original !== 'function') {
|
|
46
|
+
log('install: no globalThis.RTCPeerConnection — tracker inert (Node.js / no polyfill)');
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
this.OriginalRTCPeerConnection = Original;
|
|
50
|
+
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
51
|
+
const tracker = this;
|
|
52
|
+
class TrackedRTCPeerConnection extends Original {
|
|
53
|
+
constructor(...args) {
|
|
54
|
+
super(...args);
|
|
55
|
+
try {
|
|
56
|
+
this.addEventListener('connectionstatechange', () => {
|
|
57
|
+
try {
|
|
58
|
+
if (this.connectionState === 'connected' && !tracker.observed.has(this)) {
|
|
59
|
+
tracker.observed.add(this);
|
|
60
|
+
void tracker.recordSettlement(this);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
log('connectionstatechange handler failed: %o', error);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
log('install: failed to attach connectionstatechange listener: %o', error);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
globalThis.RTCPeerConnection = TrackedRTCPeerConnection;
|
|
74
|
+
log('install: wrapped globalThis.RTCPeerConnection');
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Pop the most recent settled entry within `windowMs` ms of now, returning its
|
|
78
|
+
* relay verdict (`true`/`false`); `null` when the queue has no matching entry.
|
|
79
|
+
* Also prunes entries older than the window (they can never match a later
|
|
80
|
+
* consume). Synchronous and safe to call from a `connection:open` handler.
|
|
81
|
+
*/
|
|
82
|
+
consume(windowMs) {
|
|
83
|
+
const now = Date.now();
|
|
84
|
+
let result = null;
|
|
85
|
+
// Walk newest → oldest; the most recent in-window settlement is the best
|
|
86
|
+
// correlation for a just-opened connection.
|
|
87
|
+
for (let i = this.queue.length - 1; i >= 0; i--) {
|
|
88
|
+
if (now - this.queue[i].settledAtMs <= windowMs) {
|
|
89
|
+
result = this.queue[i].isRelay;
|
|
90
|
+
this.queue.splice(i, 1);
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
this.pruneOlderThan(now - windowMs);
|
|
95
|
+
return result;
|
|
96
|
+
}
|
|
97
|
+
/** Restore the original `RTCPeerConnection` and clear the queue. Idempotent. */
|
|
98
|
+
dispose() {
|
|
99
|
+
if (this.OriginalRTCPeerConnection) {
|
|
100
|
+
globalThis.RTCPeerConnection = this.OriginalRTCPeerConnection;
|
|
101
|
+
this.OriginalRTCPeerConnection = undefined;
|
|
102
|
+
}
|
|
103
|
+
this.queue.length = 0;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Inspect a settled connection's stats and queue its TURN-relay verdict. Any
|
|
107
|
+
* failure (stats API absent, rejected promise, malformed report) is caught and
|
|
108
|
+
* recorded as `isRelay: false` — the safe default (unknown → not relayed) keeps
|
|
109
|
+
* the connection counted rather than dropping it.
|
|
110
|
+
*/
|
|
111
|
+
async recordSettlement(pc) {
|
|
112
|
+
// Defaults to not-relayed (the safe "unknown" verdict); only the success path
|
|
113
|
+
// below overrides it, so a getStats failure leaves it false.
|
|
114
|
+
let isRelay = false;
|
|
115
|
+
try {
|
|
116
|
+
const report = await pc.getStats();
|
|
117
|
+
isRelay = selectedCandidatePairIsRelay(statsToMap(report));
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
log('recordSettlement: getStats failed, keeping not-relayed default: %o', error);
|
|
121
|
+
}
|
|
122
|
+
this.queue.push({ settledAtMs: Date.now(), isRelay });
|
|
123
|
+
if (this.queue.length > MAX_QUEUE) {
|
|
124
|
+
this.queue.shift();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/** Drop queued entries settled before `cutoffMs` (the queue is in push order). */
|
|
128
|
+
pruneOlderThan(cutoffMs) {
|
|
129
|
+
while (this.queue.length > 0 && this.queue[0].settledAtMs < cutoffMs) {
|
|
130
|
+
this.queue.shift();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/** Materialise an `RTCStatsReport` into a plain map keyed by stats id. */
|
|
135
|
+
function statsToMap(report) {
|
|
136
|
+
const map = new Map();
|
|
137
|
+
report.forEach((value, key) => {
|
|
138
|
+
map.set(key, value);
|
|
139
|
+
});
|
|
140
|
+
return map;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Whether the selected ICE candidate pair used a TURN relay candidate on either
|
|
144
|
+
* end. The pair is the transport's `selectedCandidatePairId` when present, else
|
|
145
|
+
* the nominated succeeded pair, else any succeeded pair.
|
|
146
|
+
*/
|
|
147
|
+
function selectedCandidatePairIsRelay(stats) {
|
|
148
|
+
const pair = findSelectedPair(stats);
|
|
149
|
+
if (!pair) {
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
const local = pair.localCandidateId ? stats.get(pair.localCandidateId) : undefined;
|
|
153
|
+
const remote = pair.remoteCandidateId ? stats.get(pair.remoteCandidateId) : undefined;
|
|
154
|
+
return isRelayCandidate(local) || isRelayCandidate(remote);
|
|
155
|
+
}
|
|
156
|
+
function isRelayCandidate(stat) {
|
|
157
|
+
return stat?.type === 'local-candidate' || stat?.type === 'remote-candidate'
|
|
158
|
+
? stat.candidateType === 'relay'
|
|
159
|
+
: false;
|
|
160
|
+
}
|
|
161
|
+
function findSelectedPair(stats) {
|
|
162
|
+
const selectedId = selectedPairIdFromTransport(stats);
|
|
163
|
+
if (selectedId) {
|
|
164
|
+
const stat = stats.get(selectedId);
|
|
165
|
+
if (stat?.type === 'candidate-pair') {
|
|
166
|
+
return stat;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
let succeeded;
|
|
170
|
+
for (const stat of stats.values()) {
|
|
171
|
+
if (stat.type !== 'candidate-pair') {
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
const pair = stat;
|
|
175
|
+
if (pair.state !== 'succeeded') {
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
if (pair.nominated) {
|
|
179
|
+
return pair; // nominated succeeded pair wins outright
|
|
180
|
+
}
|
|
181
|
+
succeeded ?? (succeeded = pair);
|
|
182
|
+
}
|
|
183
|
+
return succeeded;
|
|
184
|
+
}
|
|
185
|
+
function selectedPairIdFromTransport(stats) {
|
|
186
|
+
for (const stat of stats.values()) {
|
|
187
|
+
if (stat.type === 'transport') {
|
|
188
|
+
const id = stat.selectedCandidatePairId;
|
|
189
|
+
if (typeof id === 'string') {
|
|
190
|
+
return id;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return undefined;
|
|
195
|
+
}
|
|
196
|
+
//# sourceMappingURL=webrtc-turn-tracker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webrtc-turn-tracker.js","sourceRoot":"","sources":["../../src/diagnostics/webrtc-turn-tracker.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { PrivateKey } from '@libp2p/interface';
|
|
2
|
+
/**
|
|
3
|
+
* An Ed25519 keypair expressed in the base64url form that
|
|
4
|
+
* `@optimystic/quereus-plugin-crypto` (`sign`/`verify`/`getPublicKey`) consumes.
|
|
5
|
+
* Generic bridge type — reused for owner keys, member keys, and any other
|
|
6
|
+
* seed→public keypair the control/strand databases sign with.
|
|
7
|
+
*/
|
|
8
|
+
export interface Ed25519KeyPair {
|
|
9
|
+
/** 32-byte Ed25519 seed, base64url-encoded — the crypto-plugin private key. */
|
|
10
|
+
privateKeyB64: string;
|
|
11
|
+
/** 32-byte Ed25519 public key, base64url-encoded. */
|
|
12
|
+
publicKeyB64: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Bridge a libp2p Ed25519 private key into the base64url keypair used by the
|
|
16
|
+
* control- and strand-database signing constraints.
|
|
17
|
+
*
|
|
18
|
+
* libp2p stores an Ed25519 private key as 64 raw bytes: the first 32 are the
|
|
19
|
+
* seed (the actual scalar source), the last 32 are the public key — see
|
|
20
|
+
* `@libp2p/crypto`'s `Ed25519PrivateKey`. `@optimystic/quereus-plugin-crypto`
|
|
21
|
+
* (via `@noble/curves`) treats the 32-byte seed *as* the private key and
|
|
22
|
+
* derives the public key from it with standard Ed25519. The two derivations
|
|
23
|
+
* agree, so the node's peer identity and its signing key are one keypair:
|
|
24
|
+
* `getPublicKey(privateKeyB64)` === `publicKeyB64`.
|
|
25
|
+
*
|
|
26
|
+
* @param privateKey - The node's libp2p Ed25519 private key.
|
|
27
|
+
* @returns The base64url seed/public-key pair for signing operations.
|
|
28
|
+
* @throws If the key is not Ed25519 or the raw bytes aren't the expected length.
|
|
29
|
+
*/
|
|
30
|
+
export declare function ed25519KeyPairFromLibp2p(privateKey: PrivateKey): Ed25519KeyPair;
|
|
31
|
+
/**
|
|
32
|
+
* Derive the base64url Ed25519 public key from a base64url 32-byte private seed
|
|
33
|
+
* — the same derivation the seed-bootstrap signer uses internally
|
|
34
|
+
* (`SeedBootstrapService` constructor). Use this to enroll a standalone
|
|
35
|
+
* (non-libp2p) key into the control DB before minting an invite, when the
|
|
36
|
+
* key is *not* the node's peer identity (so `ed25519KeyPairFromLibp2p`,
|
|
37
|
+
* which needs a libp2p key object, does not apply).
|
|
38
|
+
*
|
|
39
|
+
* @param privateKeyB64 - The base64url-encoded 32-byte Ed25519 seed.
|
|
40
|
+
* @returns The base64url-encoded Ed25519 public key.
|
|
41
|
+
*/
|
|
42
|
+
export declare function ed25519PublicKeyFromPrivate(privateKeyB64: string): string;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { toString as uint8ArrayToString } from 'uint8arrays';
|
|
2
|
+
import { getPublicKey } from '@optimystic/quereus-plugin-crypto';
|
|
3
|
+
/**
|
|
4
|
+
* Bridge a libp2p Ed25519 private key into the base64url keypair used by the
|
|
5
|
+
* control- and strand-database signing constraints.
|
|
6
|
+
*
|
|
7
|
+
* libp2p stores an Ed25519 private key as 64 raw bytes: the first 32 are the
|
|
8
|
+
* seed (the actual scalar source), the last 32 are the public key — see
|
|
9
|
+
* `@libp2p/crypto`'s `Ed25519PrivateKey`. `@optimystic/quereus-plugin-crypto`
|
|
10
|
+
* (via `@noble/curves`) treats the 32-byte seed *as* the private key and
|
|
11
|
+
* derives the public key from it with standard Ed25519. The two derivations
|
|
12
|
+
* agree, so the node's peer identity and its signing key are one keypair:
|
|
13
|
+
* `getPublicKey(privateKeyB64)` === `publicKeyB64`.
|
|
14
|
+
*
|
|
15
|
+
* @param privateKey - The node's libp2p Ed25519 private key.
|
|
16
|
+
* @returns The base64url seed/public-key pair for signing operations.
|
|
17
|
+
* @throws If the key is not Ed25519 or the raw bytes aren't the expected length.
|
|
18
|
+
*/
|
|
19
|
+
export function ed25519KeyPairFromLibp2p(privateKey) {
|
|
20
|
+
if (privateKey.type !== 'Ed25519') {
|
|
21
|
+
throw new Error(`ed25519KeyPairFromLibp2p requires an Ed25519 key, got ${privateKey.type}`);
|
|
22
|
+
}
|
|
23
|
+
const raw = privateKey.raw;
|
|
24
|
+
if (raw.length !== 64) {
|
|
25
|
+
throw new Error(`Expected a 64-byte Ed25519 raw private key (seed||public), got ${raw.length} bytes`);
|
|
26
|
+
}
|
|
27
|
+
const seed = raw.subarray(0, 32);
|
|
28
|
+
const publicKeyRaw = privateKey.publicKey.raw;
|
|
29
|
+
if (publicKeyRaw.length !== 32) {
|
|
30
|
+
throw new Error(`Expected a 32-byte Ed25519 public key, got ${publicKeyRaw.length} bytes`);
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
privateKeyB64: uint8ArrayToString(seed, 'base64url'),
|
|
34
|
+
publicKeyB64: uint8ArrayToString(publicKeyRaw, 'base64url'),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Derive the base64url Ed25519 public key from a base64url 32-byte private seed
|
|
39
|
+
* — the same derivation the seed-bootstrap signer uses internally
|
|
40
|
+
* (`SeedBootstrapService` constructor). Use this to enroll a standalone
|
|
41
|
+
* (non-libp2p) key into the control DB before minting an invite, when the
|
|
42
|
+
* key is *not* the node's peer identity (so `ed25519KeyPairFromLibp2p`,
|
|
43
|
+
* which needs a libp2p key object, does not apply).
|
|
44
|
+
*
|
|
45
|
+
* @param privateKeyB64 - The base64url-encoded 32-byte Ed25519 seed.
|
|
46
|
+
* @returns The base64url-encoded Ed25519 public key.
|
|
47
|
+
*/
|
|
48
|
+
export function ed25519PublicKeyFromPrivate(privateKeyB64) {
|
|
49
|
+
return getPublicKey(privateKeyB64, 'ed25519', 'base64url', 'base64url');
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=ed25519-key.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ed25519-key.js","sourceRoot":"","sources":["../src/ed25519-key.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAC7D,OAAO,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AAgBjE;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,wBAAwB,CAAC,UAAsB;IAC7D,IAAI,UAAU,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAClC,MAAM,IAAI,KAAK,CAAC,yDAAyD,UAAU,CAAC,IAAI,EAAE,CAAC,CAAC;IAC9F,CAAC;IAED,MAAM,GAAG,GAAG,UAAU,CAAC,GAAG,CAAC;IAC3B,IAAI,GAAG,CAAC,MAAM,KAAK,EAAE,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CAAC,kEAAkE,GAAG,CAAC,MAAM,QAAQ,CAAC,CAAC;IACxG,CAAC;IAED,MAAM,IAAI,GAAG,GAAG,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACjC,MAAM,YAAY,GAAG,UAAU,CAAC,SAAS,CAAC,GAAG,CAAC;IAC9C,IAAI,YAAY,CAAC,MAAM,KAAK,EAAE,EAAE,CAAC;QAC/B,MAAM,IAAI,KAAK,CAAC,8CAA8C,YAAY,CAAC,MAAM,QAAQ,CAAC,CAAC;IAC7F,CAAC;IAED,OAAO;QACL,aAAa,EAAE,kBAAkB,CAAC,IAAI,EAAE,WAAW,CAAC;QACpD,YAAY,EAAE,kBAAkB,CAAC,YAAY,EAAE,WAAW,CAAC;KAC5D,CAAC;AACJ,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,2BAA2B,CAAC,aAAqB;IAC/D,OAAO,YAAY,CAAC,aAAa,EAAE,SAAS,EAAE,WAAW,EAAE,WAAW,CAAW,CAAC;AACpF,CAAC"}
|
|
@@ -6,6 +6,17 @@ export interface HibernationCallbacks {
|
|
|
6
6
|
onIdle: (strandId: string) => Promise<void>;
|
|
7
7
|
onHibernate: (strandId: string) => Promise<void>;
|
|
8
8
|
onWake: (strandId: string) => Promise<void>;
|
|
9
|
+
/**
|
|
10
|
+
* Perform a real cohort check-in for a hibernating strand. The implementation
|
|
11
|
+
* (`CadreNode.handleStrandCheckIn`) resumes the strand, gives it a bounded
|
|
12
|
+
* window to connect to reachable cohort peers and surface pending activity,
|
|
13
|
+
* then re-hibernates if still idle. The manager AWAITS this before scheduling
|
|
14
|
+
* the next (longer-delayed) check-in, so a slow check-in never overlaps the
|
|
15
|
+
* next tick. After it resolves the manager inspects `instance.status`: a
|
|
16
|
+
* strand left non-`hibernating` is treated as woken (backoff resets on the
|
|
17
|
+
* next hibernation); a strand left `hibernating` escalates the backoff.
|
|
18
|
+
*/
|
|
19
|
+
onCheckIn: (strandId: string) => Promise<void>;
|
|
9
20
|
}
|
|
10
21
|
/**
|
|
11
22
|
* Manages strand hibernation state transitions based on activity.
|
|
@@ -20,7 +31,20 @@ export declare class HibernationManager {
|
|
|
20
31
|
private readonly config;
|
|
21
32
|
private readonly callbacks;
|
|
22
33
|
private readonly timers;
|
|
34
|
+
/**
|
|
35
|
+
* Pending check-in timers, one per hibernating strand. Unlike the old fixed
|
|
36
|
+
* `setInterval`, these are single-shot `setTimeout`s rescheduled by
|
|
37
|
+
* {@link runCheckIn} with an escalating delay — so a long-running `onCheckIn`
|
|
38
|
+
* can never overlap the next tick, and the period adapts to the backoff.
|
|
39
|
+
*/
|
|
23
40
|
private readonly checkInTimers;
|
|
41
|
+
/**
|
|
42
|
+
* In-flight wake promises keyed by strandId. Coalesces overlapping wake
|
|
43
|
+
* triggers (two near-simultaneous activities, or activity racing a force wake)
|
|
44
|
+
* so `onWake` — and the libp2p-node rebuild it drives — runs at most once per
|
|
45
|
+
* concurrent wake.
|
|
46
|
+
*/
|
|
47
|
+
private readonly wakePromises;
|
|
24
48
|
private running;
|
|
25
49
|
constructor(config: HibernationConfig, callbacks: HibernationCallbacks);
|
|
26
50
|
/**
|
|
@@ -35,6 +59,31 @@ export declare class HibernationManager {
|
|
|
35
59
|
* Stop managing hibernation
|
|
36
60
|
*/
|
|
37
61
|
stop(): void;
|
|
62
|
+
/**
|
|
63
|
+
* Whether a strand with this instance's latency hint ever hibernates — `false`
|
|
64
|
+
* for realtime (Infinity idle timeout, see {@link HIBERNATION_TIMEOUTS}), also
|
|
65
|
+
* honouring any per-hint `customTimeouts` override. Imperative callers
|
|
66
|
+
* (`CadreNode.hibernateStrand` / `hibernateAll`) use this as the single source
|
|
67
|
+
* of truth for "skip realtime", consistent with {@link trackStrand} declining
|
|
68
|
+
* to track Infinity-timeout strands.
|
|
69
|
+
*/
|
|
70
|
+
hibernates(instance: StrandInstance): boolean;
|
|
71
|
+
/**
|
|
72
|
+
* Imperatively hibernate a tracked strand now, bypassing the idle/hibernate
|
|
73
|
+
* timers — the mobile background-entry path. Cancels the strand's pending
|
|
74
|
+
* idle/hibernate AND check-in timers so none can later re-fire `onHibernate`
|
|
75
|
+
* on the already-quiesced strand or resurrect one the caller means to keep
|
|
76
|
+
* down, then invokes `onHibernate` (quiesce + mark hibernating).
|
|
77
|
+
*
|
|
78
|
+
* Unlike the timer path ({@link handleHibernateTimeout}) it deliberately does
|
|
79
|
+
* NOT re-arm the check-in chain: a force-hibernate keeps the strand down until
|
|
80
|
+
* the caller drives the next wake on demand (push-delivered on mobile), so a
|
|
81
|
+
* stray check-in timer must not bring it back up.
|
|
82
|
+
*
|
|
83
|
+
* @returns `true` if the strand was hibernated, `false` (no-op) for a realtime
|
|
84
|
+
* strand that never hibernates.
|
|
85
|
+
*/
|
|
86
|
+
forceHibernate(instance: StrandInstance): Promise<boolean>;
|
|
38
87
|
/**
|
|
39
88
|
* Register a strand for hibernation management
|
|
40
89
|
*/
|
|
@@ -51,11 +100,41 @@ export declare class HibernationManager {
|
|
|
51
100
|
* Force wake a hibernating strand
|
|
52
101
|
*/
|
|
53
102
|
wakeStrand(strandId: string): Promise<void>;
|
|
103
|
+
/**
|
|
104
|
+
* Begin a wake for a strand, or coalesce with one already in flight. Ensures
|
|
105
|
+
* `onWake` runs at most once per concurrent wake — the returned promise is
|
|
106
|
+
* shared by all overlapping callers and cleared once it settles. Force-wake
|
|
107
|
+
* callers await it; activity-driven callers fire-and-forget.
|
|
108
|
+
*/
|
|
109
|
+
private beginWake;
|
|
54
110
|
private scheduleIdleTransition;
|
|
55
111
|
private handleIdleTimeout;
|
|
56
112
|
private scheduleHibernateTransition;
|
|
57
113
|
private handleHibernateTimeout;
|
|
114
|
+
/**
|
|
115
|
+
* Arm the next check-in for a hibernating strand. Each call is a single-shot
|
|
116
|
+
* `setTimeout` (not a fixed `setInterval`) so the period escalates per
|
|
117
|
+
* {@link runCheckIn} and a slow `onCheckIn` never overlaps the next tick.
|
|
118
|
+
*
|
|
119
|
+
* `delay` is omitted by the chain start ({@link handleHibernateTimeout}),
|
|
120
|
+
* defaulting to the base `checkInInterval` — which is why backoff naturally
|
|
121
|
+
* resets to base each fresh hibernation cycle, with no per-strand counter to
|
|
122
|
+
* clear on wake. Subsequent ticks pass the escalated, capped delay.
|
|
123
|
+
*/
|
|
58
124
|
private scheduleCheckIn;
|
|
125
|
+
/**
|
|
126
|
+
* Run a single check-in tick: invoke `onCheckIn` (a real resume → bounded
|
|
127
|
+
* sync window → re-hibernate-if-idle cycle in `CadreNode`) and AWAIT it before
|
|
128
|
+
* deciding the next step.
|
|
129
|
+
*
|
|
130
|
+
* - If the strand woke during the check-in (`onCheckIn` left it non-
|
|
131
|
+
* `hibernating`), stop the chain — the activity path has re-armed the
|
|
132
|
+
* idle/hibernate timers, and the next hibernation restarts the chain at the
|
|
133
|
+
* base delay (backoff reset).
|
|
134
|
+
* - Otherwise escalate the delay by `checkInBackoffFactor`, capped at
|
|
135
|
+
* `checkInMaxInterval`, and reschedule.
|
|
136
|
+
*/
|
|
137
|
+
private runCheckIn;
|
|
59
138
|
private clearTimer;
|
|
60
139
|
private clearTimers;
|
|
61
140
|
/**
|