@calimero-network/mero-js 24.1.0 → 24.1.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/dist/device-cert/device-cert.d.ts +4 -3
- package/dist/device-cert/device-cert.js +6 -4
- package/dist/events/sse.d.ts +4 -0
- package/dist/events/sse.js +35 -11
- package/dist/index.browser.mjs +2 -2
- package/dist/index.browser.mjs.map +3 -3
- package/dist/index.cjs +45 -26
- package/dist/index.cjs.map +2 -2
- package/dist/index.mjs +45 -26
- package/dist/index.mjs.map +2 -2
- package/package.json +1 -1
|
@@ -46,11 +46,12 @@ export interface DeviceCertInput {
|
|
|
46
46
|
deviceEpoch: number;
|
|
47
47
|
}
|
|
48
48
|
/**
|
|
49
|
-
* Mint a device id: `H(DEVICE_ID_DOMAIN, account ‖ nonce)`.
|
|
49
|
+
* Mint a device id: `nonce ‖ H(DEVICE_ID_DOMAIN, account ‖ nonce)[..16]`.
|
|
50
50
|
*
|
|
51
51
|
* Derived from the account and a fresh nonce rather than from the device's keys,
|
|
52
|
-
* so rotating a keypair keeps the replica identity
|
|
53
|
-
* slots and HLC lineage
|
|
52
|
+
* so rotating a keypair keeps the replica identity - and with it the counter
|
|
53
|
+
* slots and HLC lineage - intact. The hash half binds the id to its account:
|
|
54
|
+
* core refuses a certificate naming an id minted for another account.
|
|
54
55
|
*/
|
|
55
56
|
export declare function mintDeviceId(account: string, nonce: Uint8Array): Promise<string>;
|
|
56
57
|
/**
|
|
@@ -34,18 +34,20 @@ export const ACCOUNT_GENESIS_VERSION = 2;
|
|
|
34
34
|
/** Byte length of an `AccountProof<DeviceCert>` with an empty handoff chain. */
|
|
35
35
|
const ACCOUNT_PROOF_BYTES = 237;
|
|
36
36
|
/**
|
|
37
|
-
* Mint a device id: `H(DEVICE_ID_DOMAIN, account ‖ nonce)`.
|
|
37
|
+
* Mint a device id: `nonce ‖ H(DEVICE_ID_DOMAIN, account ‖ nonce)[..16]`.
|
|
38
38
|
*
|
|
39
39
|
* Derived from the account and a fresh nonce rather than from the device's keys,
|
|
40
|
-
* so rotating a keypair keeps the replica identity
|
|
41
|
-
* slots and HLC lineage
|
|
40
|
+
* so rotating a keypair keeps the replica identity - and with it the counter
|
|
41
|
+
* slots and HLC lineage - intact. The hash half binds the id to its account:
|
|
42
|
+
* core refuses a certificate naming an id minted for another account.
|
|
42
43
|
*/
|
|
43
44
|
export async function mintDeviceId(account, nonce) {
|
|
44
45
|
if (nonce.length !== 16) {
|
|
45
46
|
throw new Error(`nonce must be 16 bytes, got ${nonce.length}`);
|
|
46
47
|
}
|
|
47
48
|
const accountBytes = fromHex(account, 'account', 32);
|
|
48
|
-
|
|
49
|
+
const binding = await domainHash(DEVICE_ID_DOMAIN, [accountBytes, nonce]);
|
|
50
|
+
return hex(concat(nonce, binding.subarray(0, 16)));
|
|
49
51
|
}
|
|
50
52
|
/**
|
|
51
53
|
* The 32 bytes a root signs to certify a device.
|
package/dist/events/sse.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { GroupMembershipEventData, GroupMigrationEventData } from './group.js';
|
|
2
|
+
/** How long a subscription POST may take before it counts as failed. */
|
|
3
|
+
export declare const SUBSCRIPTION_TIMEOUT_MS = 10000;
|
|
2
4
|
export type { GroupMembershipEventData, GroupMigrationEventData };
|
|
3
5
|
export interface SseEventData {
|
|
4
6
|
contextId: string;
|
|
@@ -109,6 +111,8 @@ export declare class SseClient {
|
|
|
109
111
|
groupIds?: string[];
|
|
110
112
|
}): Promise<void>;
|
|
111
113
|
private sendSubscription;
|
|
114
|
+
/** Sends one subscription change and returns why it failed, or `null`. */
|
|
115
|
+
private postSubscription;
|
|
112
116
|
private forceReconnect;
|
|
113
117
|
private scheduleReconnect;
|
|
114
118
|
close(): void;
|
package/dist/events/sse.js
CHANGED
|
@@ -17,6 +17,8 @@ const TERMINAL_AUTH_ERRORS = new Set(['token_reuse', 'token_revoked']);
|
|
|
17
17
|
* `HTTPError(403)` and reconnected on a timer forever.
|
|
18
18
|
*/
|
|
19
19
|
const TERMINAL_AUTH_STATUSES = new Set([401, 403]);
|
|
20
|
+
/** How long a subscription POST may take before it counts as failed. */
|
|
21
|
+
export const SUBSCRIPTION_TIMEOUT_MS = 10000;
|
|
20
22
|
/**
|
|
21
23
|
* Whether a failed response means the whole token family is gone, so the caller
|
|
22
24
|
* must re-authenticate and the client must stop reconnecting.
|
|
@@ -233,18 +235,27 @@ export class SseClient {
|
|
|
233
235
|
const msg = JSON.parse(jsonStr);
|
|
234
236
|
// Connection message
|
|
235
237
|
if (msg.type === 'connect' && msg.session_id) {
|
|
236
|
-
|
|
237
|
-
this.
|
|
238
|
+
const sessionId = msg.session_id;
|
|
239
|
+
this.sessionId = sessionId;
|
|
238
240
|
// A new connection gets a new replay when it re-subscribes; what the
|
|
239
241
|
// old one said may name authors who have since gone.
|
|
240
242
|
this.presence.clear();
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
contextIds: [...this.subscribedContextIds],
|
|
245
|
-
groupIds: [...this.subscribedGroupIds],
|
|
246
|
-
});
|
|
243
|
+
if (this.subscribedContextIds.size === 0 && this.subscribedGroupIds.size === 0) {
|
|
244
|
+
this.emit('connect', sessionId);
|
|
245
|
+
return;
|
|
247
246
|
}
|
|
247
|
+
// A new session starts subscribed to nothing, so announce it once the re-subscribe
|
|
248
|
+
// lands; a failed one still announces, then reports the error, as consumers saw before.
|
|
249
|
+
void this.postSubscription('subscribe', {
|
|
250
|
+
contextIds: [...this.subscribedContextIds],
|
|
251
|
+
groupIds: [...this.subscribedGroupIds],
|
|
252
|
+
}).then((failure) => {
|
|
253
|
+
if (this.closed || this.sessionId !== sessionId)
|
|
254
|
+
return;
|
|
255
|
+
this.emit('connect', sessionId);
|
|
256
|
+
if (failure)
|
|
257
|
+
this.emit('error', failure);
|
|
258
|
+
});
|
|
248
259
|
return;
|
|
249
260
|
}
|
|
250
261
|
if (!msg.result)
|
|
@@ -348,6 +359,14 @@ export class SseClient {
|
|
|
348
359
|
}
|
|
349
360
|
}
|
|
350
361
|
async sendSubscription(method, ids) {
|
|
362
|
+
const failure = await this.postSubscription(method, ids);
|
|
363
|
+
if (failure)
|
|
364
|
+
this.emit('error', failure);
|
|
365
|
+
}
|
|
366
|
+
/** Sends one subscription change and returns why it failed, or `null`. */
|
|
367
|
+
async postSubscription(method, ids) {
|
|
368
|
+
const timeout = new AbortController();
|
|
369
|
+
const timer = setTimeout(() => timeout.abort(new DOMException(`SSE ${method} timed out`, 'TimeoutError')), SUBSCRIPTION_TIMEOUT_MS);
|
|
351
370
|
try {
|
|
352
371
|
const params = {};
|
|
353
372
|
if (ids.contextIds && ids.contextIds.length > 0)
|
|
@@ -367,6 +386,7 @@ export class SseClient {
|
|
|
367
386
|
'Content-Type': 'application/json',
|
|
368
387
|
},
|
|
369
388
|
body,
|
|
389
|
+
signal: timeout.signal,
|
|
370
390
|
});
|
|
371
391
|
if (!response.ok) {
|
|
372
392
|
// The subscribe path used to report only the status. On the one failure
|
|
@@ -377,13 +397,17 @@ export class SseClient {
|
|
|
377
397
|
// does carry a body.
|
|
378
398
|
const terminal = isTerminalAuthFailure(response);
|
|
379
399
|
const bodyText = await response.text().catch(() => undefined);
|
|
380
|
-
|
|
400
|
+
return terminal
|
|
381
401
|
? new AuthRevokedError(terminal, response.status, response.statusText, `${this.baseUrl}/sse/subscription`, response.headers, bodyText)
|
|
382
|
-
: new HTTPError(response.status, response.statusText, `${this.baseUrl}/sse/subscription`, response.headers, bodyText)
|
|
402
|
+
: new HTTPError(response.status, response.statusText, `${this.baseUrl}/sse/subscription`, response.headers, bodyText);
|
|
383
403
|
}
|
|
404
|
+
return null;
|
|
384
405
|
}
|
|
385
406
|
catch (err) {
|
|
386
|
-
|
|
407
|
+
return err instanceof Error ? err : new Error(`SSE ${method} failed`);
|
|
408
|
+
}
|
|
409
|
+
finally {
|
|
410
|
+
clearTimeout(timer);
|
|
387
411
|
}
|
|
388
412
|
}
|
|
389
413
|
forceReconnect() {
|