@econ-v1/domain 6.7.0 → 6.10.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/dist/errors/index.d.ts +96 -25
- package/dist/errors/index.d.ts.map +1 -1
- package/dist/events/index.d.ts +127 -20
- package/dist/events/index.d.ts.map +1 -1
- package/dist/index.d.ts +250 -47
- package/dist/index.d.ts.map +1 -1
- package/dist/models/agent.d.ts +25 -4
- package/dist/models/agent.d.ts.map +1 -1
- package/dist/models/app-data.d.ts +84 -0
- package/dist/models/app-data.d.ts.map +1 -0
- package/dist/models/conversation.d.ts +12 -2
- package/dist/models/conversation.d.ts.map +1 -1
- package/dist/models/delegation.d.ts +12 -3
- package/dist/models/delegation.d.ts.map +1 -1
- package/dist/models/device-boot-state.d.ts +12 -0
- package/dist/models/device-boot-state.d.ts.map +1 -0
- package/dist/models/device-identity.d.ts +11 -3
- package/dist/models/device-identity.d.ts.map +1 -1
- package/dist/models/graph.d.ts +26 -3
- package/dist/models/graph.d.ts.map +1 -1
- package/dist/models/index.d.ts +82 -14
- package/dist/models/index.d.ts.map +1 -1
- package/dist/models/json.d.ts +5 -5
- package/dist/models/json.d.ts.map +1 -1
- package/dist/models/l402.d.ts +4 -5
- package/dist/models/l402.d.ts.map +1 -1
- package/dist/models/live-query-definition.d.ts +15 -16
- package/dist/models/live-query-definition.d.ts.map +1 -1
- package/dist/models/node-app-memory.d.ts +107 -0
- package/dist/models/node-app-memory.d.ts.map +1 -0
- package/dist/models/outbox-intent.d.ts +3 -3
- package/dist/models/outbox-intent.d.ts.map +1 -1
- package/dist/models/outbox.d.ts +67 -15
- package/dist/models/outbox.d.ts.map +1 -1
- package/dist/models/owner-views.d.ts +28 -28
- package/dist/models/owner-views.d.ts.map +1 -1
- package/dist/models/payment.d.ts +28 -9
- package/dist/models/payment.d.ts.map +1 -1
- package/dist/models/query-diff.d.ts +2 -2
- package/dist/models/query-diff.d.ts.map +1 -1
- package/dist/models/release-manifest.d.ts +32 -47
- package/dist/models/release-manifest.d.ts.map +1 -1
- package/dist/models/repository-write.d.ts +3 -3
- package/dist/models/repository-write.d.ts.map +1 -1
- package/dist/models/revisioned-commands.d.ts +66 -13
- package/dist/models/revisioned-commands.d.ts.map +1 -1
- package/dist/models/revisioned.d.ts +50 -10
- package/dist/models/revisioned.d.ts.map +1 -1
- package/dist/models/settings.d.ts +2 -2
- package/dist/models/settings.d.ts.map +1 -1
- package/dist/models/sync.d.ts +18 -18
- package/dist/models/sync.d.ts.map +1 -1
- package/dist/models/wallet.d.ts +16 -16
- package/dist/models/wallet.d.ts.map +1 -1
- package/dist/queries/certificate-trust.d.ts +13 -0
- package/dist/queries/certificate-trust.d.ts.map +1 -0
- package/dist/queries/conversations.d.ts +38 -7
- package/dist/queries/conversations.d.ts.map +1 -1
- package/dist/queries/discovery.d.ts +10 -10
- package/dist/queries/discovery.d.ts.map +1 -1
- package/dist/queries/notifications.d.ts +24 -4
- package/dist/queries/notifications.d.ts.map +1 -1
- package/dist/queries/onboarding-flow.d.ts +25 -0
- package/dist/queries/onboarding-flow.d.ts.map +1 -0
- package/dist/queries/onboarding-lcd-screen.d.ts +56 -0
- package/dist/queries/onboarding-lcd-screen.d.ts.map +1 -0
- package/dist/queries/onboarding-status.d.ts +10 -10
- package/dist/queries/onboarding-status.d.ts.map +1 -1
- package/dist/queries/revisioned.d.ts +47 -13
- package/dist/queries/revisioned.d.ts.map +1 -1
- package/dist/queries/wallet.d.ts +40 -10
- package/dist/queries/wallet.d.ts.map +1 -1
- package/dist/services/abort-signal.d.ts +54 -0
- package/dist/services/abort-signal.d.ts.map +1 -0
- package/dist/services/certificate-trust-service.d.ts +42 -0
- package/dist/services/certificate-trust-service.d.ts.map +1 -0
- package/dist/services/conversation-service.d.ts +103 -15
- package/dist/services/conversation-service.d.ts.map +1 -1
- package/dist/services/conversation-sync.d.ts +9 -5
- package/dist/services/conversation-sync.d.ts.map +1 -1
- package/dist/services/device-boot-state-service.d.ts +17 -0
- package/dist/services/device-boot-state-service.d.ts.map +1 -0
- package/dist/services/device-cleanup-service.d.ts +47 -0
- package/dist/services/device-cleanup-service.d.ts.map +1 -0
- package/dist/services/discovery-probe-service.d.ts +37 -46
- package/dist/services/discovery-probe-service.d.ts.map +1 -1
- package/dist/services/gossip-service.d.ts +28 -13
- package/dist/services/gossip-service.d.ts.map +1 -1
- package/dist/services/invite-redemption-service.d.ts +43 -0
- package/dist/services/invite-redemption-service.d.ts.map +1 -0
- package/dist/services/lan-candidate-refresh-service.d.ts +22 -7
- package/dist/services/lan-candidate-refresh-service.d.ts.map +1 -1
- package/dist/services/lcd-decision-service.d.ts +58 -0
- package/dist/services/lcd-decision-service.d.ts.map +1 -0
- package/dist/services/onboarding-checkpoint-store.d.ts +54 -0
- package/dist/services/onboarding-checkpoint-store.d.ts.map +1 -0
- package/dist/services/onboarding-flow-service.d.ts +180 -0
- package/dist/services/onboarding-flow-service.d.ts.map +1 -0
- package/dist/services/onboarding-status-service.d.ts +21 -38
- package/dist/services/onboarding-status-service.d.ts.map +1 -1
- package/dist/services/outbox-service.d.ts +105 -29
- package/dist/services/outbox-service.d.ts.map +1 -1
- package/dist/services/owner-claim-service.d.ts +187 -0
- package/dist/services/owner-claim-service.d.ts.map +1 -0
- package/dist/services/owner-view-refresh-service.d.ts +40 -0
- package/dist/services/owner-view-refresh-service.d.ts.map +1 -0
- package/dist/services/owner-view-service.d.ts +49 -4
- package/dist/services/owner-view-service.d.ts.map +1 -1
- package/dist/services/peer-directory-service.d.ts +53 -17
- package/dist/services/peer-directory-service.d.ts.map +1 -1
- package/dist/services/release-manifest-verifier.d.ts +18 -20
- package/dist/services/release-manifest-verifier.d.ts.map +1 -1
- package/dist/services/revisioned-domain-service.d.ts +50 -17
- package/dist/services/revisioned-domain-service.d.ts.map +1 -1
- package/dist/services/runtime-globals.d.ts +39 -0
- package/dist/services/runtime-globals.d.ts.map +1 -0
- package/dist/services/session-service.d.ts +23 -3
- package/dist/services/session-service.d.ts.map +1 -1
- package/dist/services/sync/authoritative-mirror.d.ts +88 -15
- package/dist/services/sync/authoritative-mirror.d.ts.map +1 -1
- package/dist/services/sync/index.d.ts +17 -5
- package/dist/services/sync/index.d.ts.map +1 -1
- package/dist/services/sync/log-sync.d.ts +42 -8
- package/dist/services/sync/log-sync.d.ts.map +1 -1
- package/dist/services/sync/revision-merge.d.ts +80 -13
- package/dist/services/sync/revision-merge.d.ts.map +1 -1
- package/dist/services/sync/strategy.d.ts +11 -1
- package/dist/services/sync/strategy.d.ts.map +1 -1
- package/dist/services/transport-manager.d.ts +84 -29
- package/dist/services/transport-manager.d.ts.map +1 -1
- package/dist/services/wallet-mirror-service.d.ts +81 -18
- package/dist/services/wallet-mirror-service.d.ts.map +1 -1
- package/dist/services/wifi-provisioning-service.d.ts +146 -0
- package/dist/services/wifi-provisioning-service.d.ts.map +1 -0
- package/package.json +18 -12
- package/src/errors/index.js +126 -0
- package/src/events/index.js +124 -0
- package/src/index.js +566 -0
- package/src/models/agent.js +43 -0
- package/src/models/app-data.js +122 -0
- package/src/models/conversation.js +36 -0
- package/{dist → src}/models/delegation.js +18 -1
- package/src/models/device-boot-state.js +3 -0
- package/{dist → src}/models/device-identity.js +18 -2
- package/{dist → src}/models/graph.js +30 -3
- package/src/models/index.js +175 -0
- package/src/models/json.js +18 -0
- package/src/models/l402.js +9 -0
- package/src/models/live-query-definition.js +30 -0
- package/src/models/node-app-memory.js +130 -0
- package/src/models/outbox-intent.js +12 -0
- package/{dist → src}/models/outbox.js +102 -3
- package/src/models/owner-views.js +59 -0
- package/src/models/payment.js +24 -0
- package/src/models/query-diff.js +7 -0
- package/src/models/release-manifest.js +160 -0
- package/src/models/repository-write.js +10 -0
- package/src/models/revisioned-commands.js +92 -0
- package/src/models/revisioned.js +41 -0
- package/src/models/settings.js +16 -0
- package/src/models/sync.js +44 -0
- package/src/models/wallet.js +40 -0
- package/src/queries/certificate-trust.js +31 -0
- package/{dist → src}/queries/conversations.js +54 -3
- package/src/queries/discovery.js +34 -0
- package/{dist → src}/queries/notifications.js +25 -2
- package/src/queries/onboarding-flow.js +48 -0
- package/src/queries/onboarding-lcd-screen.js +100 -0
- package/src/queries/onboarding-status.js +34 -0
- package/src/queries/revisioned.js +128 -0
- package/{dist → src}/queries/wallet.js +35 -1
- package/src/services/abort-signal.js +69 -0
- package/src/services/certificate-trust-service.js +119 -0
- package/{dist → src}/services/conversation-service.js +108 -1
- package/src/services/conversation-sync.js +28 -0
- package/src/services/device-boot-state-service.js +45 -0
- package/src/services/device-cleanup-service.js +112 -0
- package/src/services/discovery-probe-service.js +196 -0
- package/{dist → src}/services/gossip-service.js +54 -4
- package/src/services/invite-redemption-service.js +64 -0
- package/src/services/lan-candidate-refresh-service.js +164 -0
- package/src/services/lcd-decision-service.js +93 -0
- package/src/services/onboarding-checkpoint-store.js +155 -0
- package/src/services/onboarding-flow-service.js +1096 -0
- package/src/services/onboarding-status-service.js +64 -0
- package/{dist → src}/services/outbox-service.js +132 -1
- package/src/services/owner-claim-service.js +347 -0
- package/src/services/owner-view-refresh-service.js +208 -0
- package/src/services/owner-view-service.js +87 -0
- package/{dist → src}/services/peer-directory-service.js +72 -3
- package/src/services/release-manifest-verifier.js +108 -0
- package/{dist → src}/services/revisioned-domain-service.js +189 -9
- package/src/services/runtime-globals.js +174 -0
- package/src/services/session-service.js +50 -0
- package/{dist → src}/services/sync/authoritative-mirror.js +90 -1
- package/src/services/sync/index.js +67 -0
- package/src/services/sync/log-sync.js +60 -0
- package/src/services/sync/revision-merge.js +119 -0
- package/src/services/sync/strategy.js +44 -0
- package/src/services/transport-manager.js +478 -0
- package/{dist → src}/services/wallet-mirror-service.js +226 -6
- package/src/services/wifi-provisioning-service.js +278 -0
- package/dist/errors/index.js +0 -45
- package/dist/errors/index.js.map +0 -1
- package/dist/events/index.js +0 -17
- package/dist/events/index.js.map +0 -1
- package/dist/index.js +0 -31
- package/dist/index.js.map +0 -1
- package/dist/models/agent.js +0 -17
- package/dist/models/agent.js.map +0 -1
- package/dist/models/conversation.js +0 -2
- package/dist/models/conversation.js.map +0 -1
- package/dist/models/delegation.js.map +0 -1
- package/dist/models/device-identity.js.map +0 -1
- package/dist/models/graph.js.map +0 -1
- package/dist/models/index.js +0 -8
- package/dist/models/index.js.map +0 -1
- package/dist/models/json.js +0 -2
- package/dist/models/json.js.map +0 -1
- package/dist/models/l402.js +0 -2
- package/dist/models/l402.js.map +0 -1
- package/dist/models/live-query-definition.js +0 -2
- package/dist/models/live-query-definition.js.map +0 -1
- package/dist/models/outbox-intent.js +0 -2
- package/dist/models/outbox-intent.js.map +0 -1
- package/dist/models/outbox.js.map +0 -1
- package/dist/models/owner-views.js +0 -7
- package/dist/models/owner-views.js.map +0 -1
- package/dist/models/payment.js +0 -3
- package/dist/models/payment.js.map +0 -1
- package/dist/models/query-diff.js +0 -2
- package/dist/models/query-diff.js.map +0 -1
- package/dist/models/release-manifest.js +0 -147
- package/dist/models/release-manifest.js.map +0 -1
- package/dist/models/repository-write.js +0 -2
- package/dist/models/repository-write.js.map +0 -1
- package/dist/models/revisioned-commands.js +0 -35
- package/dist/models/revisioned-commands.js.map +0 -1
- package/dist/models/revisioned.js +0 -2
- package/dist/models/revisioned.js.map +0 -1
- package/dist/models/settings.js +0 -2
- package/dist/models/settings.js.map +0 -1
- package/dist/models/sync.js +0 -2
- package/dist/models/sync.js.map +0 -1
- package/dist/models/wallet.js +0 -2
- package/dist/models/wallet.js.map +0 -1
- package/dist/queries/conversations.js.map +0 -1
- package/dist/queries/discovery.js +0 -27
- package/dist/queries/discovery.js.map +0 -1
- package/dist/queries/notifications.js.map +0 -1
- package/dist/queries/onboarding-status.js +0 -27
- package/dist/queries/onboarding-status.js.map +0 -1
- package/dist/queries/revisioned.js +0 -58
- package/dist/queries/revisioned.js.map +0 -1
- package/dist/queries/wallet.js.map +0 -1
- package/dist/services/conversation-service.js.map +0 -1
- package/dist/services/conversation-sync.js +0 -9
- package/dist/services/conversation-sync.js.map +0 -1
- package/dist/services/discovery-probe-service.js +0 -129
- package/dist/services/discovery-probe-service.js.map +0 -1
- package/dist/services/gossip-service.js.map +0 -1
- package/dist/services/lan-candidate-refresh-service.js +0 -79
- package/dist/services/lan-candidate-refresh-service.js.map +0 -1
- package/dist/services/onboarding-status-service.js +0 -32
- package/dist/services/onboarding-status-service.js.map +0 -1
- package/dist/services/outbox-service.js.map +0 -1
- package/dist/services/owner-view-service.js +0 -21
- package/dist/services/owner-view-service.js.map +0 -1
- package/dist/services/peer-directory-service.js.map +0 -1
- package/dist/services/release-manifest-verifier.js +0 -84
- package/dist/services/release-manifest-verifier.js.map +0 -1
- package/dist/services/revisioned-domain-service.js.map +0 -1
- package/dist/services/session-service.js +0 -20
- package/dist/services/session-service.js.map +0 -1
- package/dist/services/sync/authoritative-mirror.js.map +0 -1
- package/dist/services/sync/index.js +0 -4
- package/dist/services/sync/index.js.map +0 -1
- package/dist/services/sync/log-sync.js +0 -20
- package/dist/services/sync/log-sync.js.map +0 -1
- package/dist/services/sync/revision-merge.js +0 -46
- package/dist/services/sync/revision-merge.js.map +0 -1
- package/dist/services/sync/strategy.js +0 -2
- package/dist/services/sync/strategy.js.map +0 -1
- package/dist/services/transport-manager.js +0 -234
- package/dist/services/transport-manager.js.map +0 -1
- package/dist/services/wallet-mirror-service.js.map +0 -1
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {{
|
|
5
|
+
* readonly capture?: boolean;
|
|
6
|
+
* }} AbortEventListenerOptions
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {AbortEventListenerOptions & {
|
|
11
|
+
* readonly once?: boolean;
|
|
12
|
+
* readonly passive?: boolean;
|
|
13
|
+
* readonly signal?: AbortSignalPort;
|
|
14
|
+
* }} AbortAddEventListenerOptions
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @typedef {{
|
|
19
|
+
* readonly bubbles: boolean;
|
|
20
|
+
* cancelBubble: boolean;
|
|
21
|
+
* readonly cancelable: boolean;
|
|
22
|
+
* readonly composed: boolean;
|
|
23
|
+
* readonly currentTarget: AbortEventTarget | null;
|
|
24
|
+
* readonly defaultPrevented: boolean;
|
|
25
|
+
* readonly eventPhase: number;
|
|
26
|
+
* readonly isTrusted: boolean;
|
|
27
|
+
* returnValue: boolean;
|
|
28
|
+
* readonly srcElement: AbortEventTarget | null;
|
|
29
|
+
* readonly target: AbortEventTarget | null;
|
|
30
|
+
* readonly timeStamp: number;
|
|
31
|
+
* readonly type: string;
|
|
32
|
+
* composedPath(): AbortEventTarget[];
|
|
33
|
+
* initEvent(type: string, bubbles?: boolean, cancelable?: boolean): void;
|
|
34
|
+
* preventDefault(): void;
|
|
35
|
+
* stopImmediatePropagation(): void;
|
|
36
|
+
* stopPropagation(): void;
|
|
37
|
+
* readonly NONE: 0;
|
|
38
|
+
* readonly CAPTURING_PHASE: 1;
|
|
39
|
+
* readonly AT_TARGET: 2;
|
|
40
|
+
* readonly BUBBLING_PHASE: 3;
|
|
41
|
+
* }} AbortEvent
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
/** @typedef {(event: AbortEvent) => void} AbortEventListener */
|
|
45
|
+
/** @typedef {{ handleEvent(event: AbortEvent): void }} AbortEventListenerObject */
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @typedef {{
|
|
49
|
+
* addEventListener(type: string, listener: AbortEventListener | AbortEventListenerObject | null, options?: boolean | AbortAddEventListenerOptions): void;
|
|
50
|
+
* dispatchEvent(event: AbortEvent): boolean;
|
|
51
|
+
* removeEventListener(type: string, listener: AbortEventListener | AbortEventListenerObject | null, options?: boolean | AbortEventListenerOptions): void;
|
|
52
|
+
* }} AbortEventTarget
|
|
53
|
+
*/
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Platform-neutral structural equivalent of a native AbortSignal instance.
|
|
57
|
+
*
|
|
58
|
+
* This internal contract preserves the native AbortSignal surface without
|
|
59
|
+
* expanding the public ports package.
|
|
60
|
+
*
|
|
61
|
+
* @typedef {AbortEventTarget & {
|
|
62
|
+
* readonly aborted: boolean;
|
|
63
|
+
* onabort: ((this: AbortSignalPort, event: AbortEvent) => unknown) | null;
|
|
64
|
+
* readonly reason: unknown;
|
|
65
|
+
* throwIfAborted(): void;
|
|
66
|
+
* }} AbortSignalPort
|
|
67
|
+
*/
|
|
68
|
+
|
|
69
|
+
export {};
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
/** @typedef {import('@econ-v1/ports').CertificateTrustPlatform} CertificateTrustPlatform */
|
|
3
|
+
/** @typedef {import('@econ-v1/ports').CertificateTrustProbePort} CertificateTrustProbePort */
|
|
4
|
+
export class CertificateTrustService {
|
|
5
|
+
/** @readonly @type {string} */
|
|
6
|
+
#origin;
|
|
7
|
+
/** @readonly @type {CertificateTrustPlatform} */
|
|
8
|
+
#platform;
|
|
9
|
+
/** @readonly @type {CertificateTrustProbePort} */
|
|
10
|
+
#probe;
|
|
11
|
+
/** @readonly @type {(snapshot: CertificateTrustSnapshot) => void} */
|
|
12
|
+
#onChange;
|
|
13
|
+
/** @type {CertificateTrustSnapshot} */
|
|
14
|
+
#snapshot;
|
|
15
|
+
/**
|
|
16
|
+
* @param {CertificateTrustServiceOptions} options
|
|
17
|
+
*/
|
|
18
|
+
constructor(options) {
|
|
19
|
+
this.#origin = options.origin;
|
|
20
|
+
this.#platform = options.platform;
|
|
21
|
+
this.#probe = options.probe;
|
|
22
|
+
this.#onChange = options.onChange ?? (() => undefined);
|
|
23
|
+
this.#snapshot = {
|
|
24
|
+
status: "unknown",
|
|
25
|
+
origin: options.origin,
|
|
26
|
+
platform: options.platform,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* @returns {CertificateTrustSnapshot}
|
|
31
|
+
*/
|
|
32
|
+
status() {
|
|
33
|
+
return this.#snapshot;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* @returns {CertificateTrustSnapshot}
|
|
37
|
+
*/
|
|
38
|
+
restartInstall() {
|
|
39
|
+
return this.#set({ status: "installing", lastError: undefined });
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* @returns {CertificateTrustSnapshot}
|
|
43
|
+
*/
|
|
44
|
+
skip() {
|
|
45
|
+
return this.#set({ status: "skipped", lastError: undefined });
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* @returns {Promise<CertificateTrustSnapshot>}
|
|
49
|
+
*/
|
|
50
|
+
async verify() {
|
|
51
|
+
try {
|
|
52
|
+
const result = await this.#probe.probe(this.#origin);
|
|
53
|
+
// Only an answering node reports a fingerprint; an unreachable one has
|
|
54
|
+
// nothing to report and must not clear a previously observed value.
|
|
55
|
+
const fingerprint =
|
|
56
|
+
result.status === "unreachable" ? undefined : result.fingerprint;
|
|
57
|
+
this.#snapshot = {
|
|
58
|
+
...this.#snapshot,
|
|
59
|
+
...(fingerprint === undefined ? {} : { fingerprint }),
|
|
60
|
+
};
|
|
61
|
+
if (result.status === "trusted") {
|
|
62
|
+
return this.#set({ status: "verified", lastError: undefined });
|
|
63
|
+
}
|
|
64
|
+
// A node that never answered has not refused anything. Reporting "does not
|
|
65
|
+
// trust the CA yet" here would tell someone to go re-install a certificate
|
|
66
|
+
// when the real problem is that nothing is listening — the same unmet
|
|
67
|
+
// prerequisite reported as the wrong one.
|
|
68
|
+
if (result.status === "unreachable") {
|
|
69
|
+
return this.#set({
|
|
70
|
+
status: "unavailable",
|
|
71
|
+
lastError: "The node could not be reached over HTTPS.",
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
return this.#set({
|
|
75
|
+
status: "failed",
|
|
76
|
+
lastError: "The device does not trust the node CA yet.",
|
|
77
|
+
});
|
|
78
|
+
} catch {
|
|
79
|
+
return this.#set({
|
|
80
|
+
status: "unavailable",
|
|
81
|
+
lastError: "The node could not be reached over HTTPS.",
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* @param {{ readonly status: CertificateTrustStatus; readonly lastError?: string | undefined }} next
|
|
87
|
+
* @returns {CertificateTrustSnapshot}
|
|
88
|
+
*/
|
|
89
|
+
#set(next) {
|
|
90
|
+
this.#snapshot = {
|
|
91
|
+
origin: this.#origin,
|
|
92
|
+
platform: this.#platform,
|
|
93
|
+
status: next.status,
|
|
94
|
+
...(this.#snapshot.fingerprint === undefined
|
|
95
|
+
? {}
|
|
96
|
+
: { fingerprint: this.#snapshot.fingerprint }),
|
|
97
|
+
...(next.lastError === undefined ? {} : { lastError: next.lastError }),
|
|
98
|
+
};
|
|
99
|
+
this.#onChange(this.#snapshot);
|
|
100
|
+
return this.#snapshot;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/** @typedef {"unknown" | "installing" | "verified" | "failed" | "skipped" | "unavailable"} CertificateTrustStatus */
|
|
104
|
+
/**
|
|
105
|
+
* @typedef {object} CertificateTrustSnapshot
|
|
106
|
+
* @property {CertificateTrustStatus} status
|
|
107
|
+
* @property {string} origin
|
|
108
|
+
* @property {CertificateTrustPlatform} platform
|
|
109
|
+
* @property {string} [fingerprint]
|
|
110
|
+
* @property {string} [lastError]
|
|
111
|
+
*/
|
|
112
|
+
/**
|
|
113
|
+
* @typedef {object} CertificateTrustServiceOptions
|
|
114
|
+
* @property {string} origin
|
|
115
|
+
* @property {CertificateTrustPlatform} platform
|
|
116
|
+
* @property {CertificateTrustProbePort} probe
|
|
117
|
+
* @property {(snapshot: CertificateTrustSnapshot) => void} [onChange]
|
|
118
|
+
*/
|
|
119
|
+
// @ts-check
|
|
@@ -1,9 +1,77 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {import("@econ-v1/ports").Conversation} Conversation
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* @typedef {import("@econ-v1/ports").ConversationListParams} ConversationListParams
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {import("@econ-v1/ports").ConversationLogDelta} ConversationLogDelta
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* @typedef {import("@econ-v1/ports").ConversationMessage} ConversationMessage
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* @typedef {import("@econ-v1/ports").ConversationRepository} ConversationRepository
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* @typedef {import("@econ-v1/ports").Notification} Notification
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* @typedef {import("@econ-v1/ports").NotificationListParams} NotificationListParams
|
|
23
|
+
*/
|
|
24
|
+
/**
|
|
25
|
+
* @typedef {import("@econ-v1/ports").NotificationRepository} NotificationRepository
|
|
26
|
+
*/
|
|
27
|
+
/**
|
|
28
|
+
* @typedef {import("@econ-v1/ports").OutboxRecord} OutboxRecord
|
|
29
|
+
*/
|
|
30
|
+
/**
|
|
31
|
+
* @typedef {import("@econ-v1/ports").OutboxRepository} OutboxRepository
|
|
32
|
+
*/
|
|
33
|
+
/**
|
|
34
|
+
* @typedef {import("@econ-v1/ports").RepositoryEvent} RepositoryEvent
|
|
35
|
+
*/
|
|
36
|
+
/**
|
|
37
|
+
* @typedef {{ readonly body: string; readonly conversationId: string; readonly idempotencyKey: string; readonly messageId?: string; readonly senderId: string; }} SendMessageInput
|
|
38
|
+
*/
|
|
39
|
+
/**
|
|
40
|
+
* @typedef {{ readonly id: () => string; readonly now?: () => string; }} ConversationServiceOptions
|
|
41
|
+
*/
|
|
42
|
+
/**
|
|
43
|
+
* @typedef {RepositoryEvent} ConversationRepositoryWrite
|
|
44
|
+
*/
|
|
1
45
|
export class ConversationService {
|
|
46
|
+
/**
|
|
47
|
+
* @readonly
|
|
48
|
+
* @type {ConversationRepository}
|
|
49
|
+
*/
|
|
2
50
|
#conversations;
|
|
51
|
+
/**
|
|
52
|
+
* @readonly
|
|
53
|
+
* @type {() => string}
|
|
54
|
+
*/
|
|
3
55
|
#id;
|
|
56
|
+
/**
|
|
57
|
+
* @readonly
|
|
58
|
+
* @type {NotificationRepository}
|
|
59
|
+
*/
|
|
4
60
|
#notifications;
|
|
61
|
+
/**
|
|
62
|
+
* @readonly
|
|
63
|
+
* @type {() => string}
|
|
64
|
+
*/
|
|
5
65
|
#now;
|
|
66
|
+
/**
|
|
67
|
+
* @readonly
|
|
68
|
+
* @type {OutboxRepository}
|
|
69
|
+
*/
|
|
6
70
|
#outbox;
|
|
71
|
+
/**
|
|
72
|
+
* @param {{ readonly conversations: ConversationRepository; readonly notifications: NotificationRepository; readonly outbox: OutboxRepository; }} dependencies
|
|
73
|
+
* @param {ConversationServiceOptions} options
|
|
74
|
+
*/
|
|
7
75
|
constructor(dependencies, options) {
|
|
8
76
|
this.#conversations = dependencies.conversations;
|
|
9
77
|
this.#id = options.id;
|
|
@@ -11,12 +79,17 @@ export class ConversationService {
|
|
|
11
79
|
this.#now = options.now ?? (() => new Date().toISOString());
|
|
12
80
|
this.#outbox = dependencies.outbox;
|
|
13
81
|
}
|
|
82
|
+
/**
|
|
83
|
+
* @param {SendMessageInput} input
|
|
84
|
+
* @returns {Promise<ConversationMessage>}
|
|
85
|
+
*/
|
|
14
86
|
async sendMessage(input) {
|
|
15
87
|
requireText(input.body, "message body");
|
|
16
88
|
requireText(input.conversationId, "conversation id");
|
|
17
89
|
requireText(input.idempotencyKey, "message idempotency key");
|
|
18
90
|
requireText(input.senderId, "message sender id");
|
|
19
91
|
const createdAt = this.#now();
|
|
92
|
+
/** @type {ConversationMessage} */
|
|
20
93
|
const message = {
|
|
21
94
|
body: input.body,
|
|
22
95
|
...(input.messageId === undefined ? {} : { clientMessageId: input.messageId }),
|
|
@@ -27,6 +100,7 @@ export class ConversationService {
|
|
|
27
100
|
state: "pending",
|
|
28
101
|
};
|
|
29
102
|
await this.#conversations.appendPendingMessage({ message }).commit();
|
|
103
|
+
/** @type {OutboxRecord} */
|
|
30
104
|
const outbox = {
|
|
31
105
|
createdAt,
|
|
32
106
|
domain: "conversation",
|
|
@@ -45,6 +119,10 @@ export class ConversationService {
|
|
|
45
119
|
await this.#outbox.create(outbox);
|
|
46
120
|
return message;
|
|
47
121
|
}
|
|
122
|
+
/**
|
|
123
|
+
* @param {ConversationLogDelta} delta
|
|
124
|
+
* @returns {Promise<void>}
|
|
125
|
+
*/
|
|
48
126
|
async applyDelta(delta) {
|
|
49
127
|
switch (delta.kind) {
|
|
50
128
|
case "notification.read":
|
|
@@ -59,6 +137,10 @@ export class ConversationService {
|
|
|
59
137
|
return;
|
|
60
138
|
}
|
|
61
139
|
}
|
|
140
|
+
/**
|
|
141
|
+
* @param {{ readonly acknowledgedAt?: string; readonly messageId: string; readonly serverMessageId?: string; }} input
|
|
142
|
+
* @returns {Promise<void>}
|
|
143
|
+
*/
|
|
62
144
|
async acknowledgeMessage(input) {
|
|
63
145
|
await this.#conversations.acknowledgeMessage({
|
|
64
146
|
acknowledgedAt: input.acknowledgedAt ?? this.#now(),
|
|
@@ -66,6 +148,10 @@ export class ConversationService {
|
|
|
66
148
|
...(input.serverMessageId === undefined ? {} : { serverMessageId: input.serverMessageId }),
|
|
67
149
|
});
|
|
68
150
|
}
|
|
151
|
+
/**
|
|
152
|
+
* @param {{ readonly messageId: string; readonly reason: string; readonly failedAt?: string }} input
|
|
153
|
+
* @returns {Promise<void>}
|
|
154
|
+
*/
|
|
69
155
|
async failMessage(input) {
|
|
70
156
|
requireText(input.reason, "message failure reason");
|
|
71
157
|
await this.#conversations.failMessage({
|
|
@@ -74,22 +160,43 @@ export class ConversationService {
|
|
|
74
160
|
reason: input.reason,
|
|
75
161
|
});
|
|
76
162
|
}
|
|
163
|
+
/**
|
|
164
|
+
* @param {ConversationListParams} [params]
|
|
165
|
+
* @returns {Promise<readonly Conversation[]>}
|
|
166
|
+
*/
|
|
77
167
|
async listConversations(params) {
|
|
78
168
|
return await this.#conversations.listConversations(params);
|
|
79
169
|
}
|
|
170
|
+
/**
|
|
171
|
+
* @param {string} conversationId
|
|
172
|
+
* @param {{ readonly before?: string; readonly limit?: number }} [params]
|
|
173
|
+
* @returns {Promise<readonly ConversationMessage[]>}
|
|
174
|
+
*/
|
|
80
175
|
async listMessages(conversationId, params) {
|
|
81
176
|
return await this.#conversations.listMessages(conversationId, params);
|
|
82
177
|
}
|
|
178
|
+
/**
|
|
179
|
+
* @param {NotificationListParams} [params]
|
|
180
|
+
* @returns {Promise<readonly Notification[]>}
|
|
181
|
+
*/
|
|
83
182
|
async listNotifications(params) {
|
|
84
183
|
return await this.#notifications.listNotifications(params);
|
|
85
184
|
}
|
|
185
|
+
/**
|
|
186
|
+
* @param {string} notificationId
|
|
187
|
+
* @returns {Promise<void>}
|
|
188
|
+
*/
|
|
86
189
|
async markNotificationRead(notificationId, readAt = this.#now()) {
|
|
87
190
|
requireText(notificationId, "notification id");
|
|
88
191
|
await this.#notifications.markRead({ notificationId, readAt }).commit();
|
|
89
192
|
}
|
|
90
193
|
}
|
|
194
|
+
/**
|
|
195
|
+
* @param {string} value
|
|
196
|
+
* @param {string} label
|
|
197
|
+
* @returns {void}
|
|
198
|
+
*/
|
|
91
199
|
function requireText(value, label) {
|
|
92
200
|
if (value.trim() === "")
|
|
93
201
|
throw new TypeError(`${label} must be a non-empty string`);
|
|
94
202
|
}
|
|
95
|
-
//# sourceMappingURL=conversation-service.js.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {import("../models/conversation.js").ConversationLogDelta} ConversationLogDelta
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* @typedef {import("../models/sync.js").SyncDeltaContext} SyncDeltaContext
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {import("./conversation-service.js").ConversationService} ConversationService
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* @typedef {SyncDeltaContext} ConversationSyncContext
|
|
14
|
+
*/
|
|
15
|
+
import { LogSync } from "./sync/log-sync.js";
|
|
16
|
+
/**
|
|
17
|
+
* @param {Pick<ConversationService, "applyDelta">} service
|
|
18
|
+
* @returns {LogSync<ConversationLogDelta>}
|
|
19
|
+
*/
|
|
20
|
+
export function createConversationLogSync(service) {
|
|
21
|
+
return new LogSync({
|
|
22
|
+
apply: /**
|
|
23
|
+
* @param {ConversationLogDelta} payload
|
|
24
|
+
*/ async (payload) => {
|
|
25
|
+
await service.applyDelta(payload);
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
/** @import { IdentityPort } from "@econ-v1/ports" */
|
|
3
|
+
/** @import { DeviceBootState } from "../models/device-boot-state.js" */
|
|
4
|
+
/** @typedef {{ readonly identity: Pick<IdentityPort, "loadDelegation" | "loadLifecycle">; readonly now: () => string; }} DeviceBootStateServiceOptions */
|
|
5
|
+
export class DeviceBootStateService {
|
|
6
|
+
/** @type {DeviceBootStateServiceOptions["identity"]} */
|
|
7
|
+
#identity;
|
|
8
|
+
/** @type {() => string} */
|
|
9
|
+
#now;
|
|
10
|
+
/** @param {DeviceBootStateServiceOptions} options */
|
|
11
|
+
constructor(options) {
|
|
12
|
+
this.#identity = options.identity;
|
|
13
|
+
this.#now = options.now;
|
|
14
|
+
}
|
|
15
|
+
/** @returns {Promise<DeviceBootState>} */
|
|
16
|
+
async state() {
|
|
17
|
+
/** @type {"active" | "revoked" | undefined} */
|
|
18
|
+
let lifecycle;
|
|
19
|
+
try {
|
|
20
|
+
lifecycle = await this.#identity.loadLifecycle?.();
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
// A store we cannot read must fail toward pairing, never toward a crash or
|
|
24
|
+
// toward rendering cached data on an unverified device (spec §5).
|
|
25
|
+
return { kind: "unpaired" };
|
|
26
|
+
}
|
|
27
|
+
if (lifecycle === "revoked")
|
|
28
|
+
return { kind: "revoked" };
|
|
29
|
+
/** @type {Awaited<ReturnType<IdentityPort["loadDelegation"]>>} */
|
|
30
|
+
let delegation;
|
|
31
|
+
try {
|
|
32
|
+
delegation = await this.#identity.loadDelegation();
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return { kind: "unpaired" };
|
|
36
|
+
}
|
|
37
|
+
if (delegation === undefined)
|
|
38
|
+
return { kind: "unpaired" };
|
|
39
|
+
const expiresAtMs = Date.parse(delegation.expiresAt);
|
|
40
|
+
const expired = Number.isNaN(expiresAtMs) || expiresAtMs <= Date.parse(this.#now());
|
|
41
|
+
return expired
|
|
42
|
+
? { kind: "paired-expired", delegationExpiresAt: delegation.expiresAt }
|
|
43
|
+
: { kind: "paired", delegationExpiresAt: delegation.expiresAt };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
/** @typedef {import("@econ-v1/ports").DelayPort} DelayPort */
|
|
3
|
+
/** @typedef {"pending" | "approved" | "declined" | "expired" | "no_candidate" | "not_needed" | "failed"} DeviceCleanupState */
|
|
4
|
+
/** @typedef {{ cleanupStatus: (sessionId: string) => Promise<{ readonly state: DeviceCleanupState }>; requestCleanup: () => Promise<{ readonly sessionId: string; readonly state: DeviceCleanupState }> }} DeviceCleanupPort */
|
|
5
|
+
/** @typedef {{ cleanup: DeviceCleanupPort; delay: Pick<DelayPort, "wait">; publish: (state: DeviceCleanupState | undefined) => void }} DeviceCleanupServiceOptions */
|
|
6
|
+
const POLL_BASE_DELAY_MS = 2_000;
|
|
7
|
+
const POLL_MAX_DELAY_MS = 30_000;
|
|
8
|
+
/** Unchanged from the shell's original `cleanupPollDelayMs`: 2s -> 4s -> 8s -> 16s -> 30s
|
|
9
|
+
* ceiling. The owner may take anywhere from seconds to minutes to walk to the node's screen,
|
|
10
|
+
* and the pending-op TTL server-side is 5 minutes, so polling every couple of seconds the
|
|
11
|
+
* whole time would just be noise. */
|
|
12
|
+
/** @param {number} attempt */
|
|
13
|
+
function pollDelayMs(attempt) {
|
|
14
|
+
return Math.min(POLL_BASE_DELAY_MS * (2 ** attempt), POLL_MAX_DELAY_MS);
|
|
15
|
+
}
|
|
16
|
+
export class DeviceCleanupService {
|
|
17
|
+
/** @type {DeviceCleanupPort} */
|
|
18
|
+
#cleanup;
|
|
19
|
+
/** @type {Pick<DelayPort, "wait">} */
|
|
20
|
+
#delay;
|
|
21
|
+
/** @type {(state: DeviceCleanupState | undefined) => void} */
|
|
22
|
+
#publish;
|
|
23
|
+
/** Invalidates a stale poll continuation after `cancel()` or a fresh `start()` — the same
|
|
24
|
+
* generation-counter guard `OwnerClaimService`/`OnboardingFlowService` use for their own
|
|
25
|
+
* long-running polls. */
|
|
26
|
+
#generation = 0;
|
|
27
|
+
/** @type {DeviceCleanupState | undefined} */
|
|
28
|
+
#state;
|
|
29
|
+
/** @param {DeviceCleanupServiceOptions} options */
|
|
30
|
+
constructor(options) {
|
|
31
|
+
this.#cleanup = options.cleanup;
|
|
32
|
+
this.#delay = options.delay;
|
|
33
|
+
this.#publish = options.publish;
|
|
34
|
+
}
|
|
35
|
+
/** The live-query-facing read: `undefined` while no ceremony has ever run for the current
|
|
36
|
+
* generation (nothing requested yet, or `cancel()` cleared it). */
|
|
37
|
+
state() {
|
|
38
|
+
return this.#state;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Opens/resumes the node's one live cleanup session (`client-device.cleanup-request.v1`)
|
|
42
|
+
* and, if it comes back `pending`, starts the bounded-backoff poll loop. Safe to call again
|
|
43
|
+
* — each call invalidates whatever poll was previously in flight, matching the ceremony's
|
|
44
|
+
* original "Ask the owner" re-request behaviour.
|
|
45
|
+
*
|
|
46
|
+
* A `requestCleanup()` failure is deliberately NOT caught here: it propagates to the caller
|
|
47
|
+
* exactly as it did when `pairing-gate.ts` awaited the command directly, so the shell's own
|
|
48
|
+
* catch (which renders `cleanupState: "failed"` onto the still-locally-held device-limit
|
|
49
|
+
* card) keeps working unchanged — this service has no base error state to merge it onto.
|
|
50
|
+
*/
|
|
51
|
+
async start() {
|
|
52
|
+
const generation = ++this.#generation;
|
|
53
|
+
const result = await this.#cleanup.requestCleanup();
|
|
54
|
+
if (generation !== this.#generation)
|
|
55
|
+
return result;
|
|
56
|
+
this.#state = result.state;
|
|
57
|
+
this.#publish(this.#state);
|
|
58
|
+
// The session id lives only in this closure and the one `#poll` call it seeds — nothing
|
|
59
|
+
// else ever needs to read it back, so it is not duplicated onto a field (there is no
|
|
60
|
+
// "current session id" accessor for the same reason `state()` has no "current attempt
|
|
61
|
+
// count" sibling: both are captured by the generation-guarded loop that owns them).
|
|
62
|
+
if (result.state === "pending")
|
|
63
|
+
void this.#poll(result.sessionId, generation);
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Cancels any in-flight poll and clears the ceremony state. Called from every point that
|
|
68
|
+
* used to invalidate the shell's own poll loop: a client change, suspension, `showRevoked()`,
|
|
69
|
+
* and disconnect (`pairing-gate.ts`'s `client-device.cleanup-cancel.v1` command, fired at
|
|
70
|
+
* exactly those four points).
|
|
71
|
+
*/
|
|
72
|
+
cancel() {
|
|
73
|
+
this.#generation += 1;
|
|
74
|
+
if (this.#state === undefined)
|
|
75
|
+
return;
|
|
76
|
+
this.#state = undefined;
|
|
77
|
+
this.#publish(undefined);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* The poll loop `start()` hands off to. Only ever continues while the node keeps answering
|
|
81
|
+
* `pending` — every other {@link DeviceCleanupState} (including `failed`, produced here when
|
|
82
|
+
* `cleanupStatus()` itself throws) is terminal and stops the loop, which is the rule under
|
|
83
|
+
* test in `device-cleanup-service.test.ts`'s backoff/termination coverage.
|
|
84
|
+
*/
|
|
85
|
+
/** @param {string} sessionId @param {number} generation */
|
|
86
|
+
async #poll(sessionId, generation) {
|
|
87
|
+
let attempt = 0;
|
|
88
|
+
for (;;) {
|
|
89
|
+
await this.#delay.wait(pollDelayMs(attempt));
|
|
90
|
+
if (generation !== this.#generation)
|
|
91
|
+
return;
|
|
92
|
+
let result;
|
|
93
|
+
try {
|
|
94
|
+
result = await this.#cleanup.cleanupStatus(sessionId);
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
if (generation !== this.#generation)
|
|
98
|
+
return;
|
|
99
|
+
this.#state = "failed";
|
|
100
|
+
this.#publish(this.#state);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (generation !== this.#generation)
|
|
104
|
+
return;
|
|
105
|
+
this.#state = result.state;
|
|
106
|
+
this.#publish(this.#state);
|
|
107
|
+
if (result.state !== "pending")
|
|
108
|
+
return;
|
|
109
|
+
attempt += 1;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|