@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
|
@@ -1,19 +1,95 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {import("@econ-v1/ports").HomeNodeClient} HomeNodeClient
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* @typedef {import("@econ-v1/ports").OutboxRecord} OutboxRecord
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {import("@econ-v1/ports").OutboxRepository} OutboxRepository
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* @template TPayload
|
|
14
|
+
* @typedef {import("../models/sync.js").AuthoritativeCommand<TPayload>} AuthoritativeCommand
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* @template TPayload
|
|
18
|
+
* @typedef {import("../models/sync.js").SyncDelta<TPayload>} SyncDelta
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* @typedef {import("../models/sync.js").SyncApplyResult} SyncApplyResult
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* @typedef {import("../models/wallet.js").CreateInvoiceInput} CreateInvoiceInput
|
|
25
|
+
*/
|
|
26
|
+
/**
|
|
27
|
+
* @typedef {import("../models/wallet.js").PaymentHistoryView} PaymentHistoryView
|
|
28
|
+
*/
|
|
29
|
+
/**
|
|
30
|
+
* @typedef {import("../models/wallet.js").PayInvoiceInput} PayInvoiceInput
|
|
31
|
+
*/
|
|
32
|
+
/**
|
|
33
|
+
* @typedef {import("../models/wallet.js").WalletAuthoritativeSnapshot} WalletAuthoritativeSnapshot
|
|
34
|
+
*/
|
|
35
|
+
/**
|
|
36
|
+
* @typedef {import("../models/wallet.js").WalletBalanceView} WalletBalanceView
|
|
37
|
+
*/
|
|
38
|
+
/**
|
|
39
|
+
* @typedef {import("../models/wallet.js").WalletCommand} WalletCommand
|
|
40
|
+
*/
|
|
41
|
+
/**
|
|
42
|
+
* @typedef {import("../models/wallet.js").WalletMirrorRepository} WalletMirrorRepository
|
|
43
|
+
*/
|
|
44
|
+
/**
|
|
45
|
+
* @typedef {import("../models/wallet.js").WalletSnapshotPayload} WalletSnapshotPayload
|
|
46
|
+
*/
|
|
47
|
+
/**
|
|
48
|
+
* @typedef {import("../models/payment.js").WalletCommandResult} WalletCommandResult
|
|
49
|
+
*/
|
|
50
|
+
/**
|
|
51
|
+
* @typedef {import("../models/l402.js").L402HistoryView} L402HistoryView
|
|
52
|
+
*/
|
|
53
|
+
/**
|
|
54
|
+
* @typedef {{ readonly id?: () => string; readonly now?: () => string; readonly staleAfterMs: number; }} WalletMirrorServiceOptions
|
|
55
|
+
*/
|
|
1
56
|
import { AuthoritativeMirror, AuthoritativeRevalidationRequiredError, } from "./sync/index.js";
|
|
57
|
+
/**
|
|
58
|
+
* @extends {Error}
|
|
59
|
+
*/
|
|
2
60
|
export class OfflineWalletCommandError extends Error {
|
|
3
61
|
constructor() {
|
|
4
62
|
super("Wallet commands require an online home node");
|
|
5
63
|
this.name = "OfflineWalletCommandError";
|
|
6
64
|
}
|
|
7
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* @extends {Error}
|
|
68
|
+
*/
|
|
8
69
|
export class WalletFreshStateRequiredError extends Error {
|
|
9
70
|
constructor() {
|
|
10
71
|
super("Wallet state could not be freshly revalidated; confirm again when connected");
|
|
11
72
|
this.name = "WalletFreshStateRequiredError";
|
|
12
73
|
}
|
|
13
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* @extends {Error}
|
|
77
|
+
*/
|
|
14
78
|
export class InsufficientWalletBalanceError extends Error {
|
|
79
|
+
/**
|
|
80
|
+
* @readonly
|
|
81
|
+
* @type {number}
|
|
82
|
+
*/
|
|
15
83
|
amountMsat;
|
|
84
|
+
/**
|
|
85
|
+
* @readonly
|
|
86
|
+
* @type {number}
|
|
87
|
+
*/
|
|
16
88
|
spendableMsat;
|
|
89
|
+
/**
|
|
90
|
+
* @param {number} amountMsat
|
|
91
|
+
* @param {number} spendableMsat
|
|
92
|
+
*/
|
|
17
93
|
constructor(amountMsat, spendableMsat) {
|
|
18
94
|
super("The authoritative wallet balance is insufficient for this payment");
|
|
19
95
|
this.name = "InsufficientWalletBalanceError";
|
|
@@ -21,12 +97,18 @@ export class InsufficientWalletBalanceError extends Error {
|
|
|
21
97
|
this.spendableMsat = spendableMsat;
|
|
22
98
|
}
|
|
23
99
|
}
|
|
100
|
+
/**
|
|
101
|
+
* @extends {Error}
|
|
102
|
+
*/
|
|
24
103
|
export class ExpiredInvoiceError extends Error {
|
|
25
104
|
constructor() {
|
|
26
105
|
super("The invoice has expired");
|
|
27
106
|
this.name = "ExpiredInvoiceError";
|
|
28
107
|
}
|
|
29
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* @extends {Error}
|
|
111
|
+
*/
|
|
30
112
|
export class WalletCommandRejectedError extends Error {
|
|
31
113
|
constructor() {
|
|
32
114
|
super("The authoritative wallet command was rejected");
|
|
@@ -34,12 +116,40 @@ export class WalletCommandRejectedError extends Error {
|
|
|
34
116
|
}
|
|
35
117
|
}
|
|
36
118
|
export class WalletMirrorService {
|
|
119
|
+
/**
|
|
120
|
+
* @readonly
|
|
121
|
+
* @type {() => string}
|
|
122
|
+
*/
|
|
37
123
|
#id;
|
|
124
|
+
/**
|
|
125
|
+
* @readonly
|
|
126
|
+
* @type {WalletMirrorRepository}
|
|
127
|
+
*/
|
|
38
128
|
#mirror;
|
|
129
|
+
/**
|
|
130
|
+
* @readonly
|
|
131
|
+
* @type {() => string}
|
|
132
|
+
*/
|
|
39
133
|
#now;
|
|
134
|
+
/**
|
|
135
|
+
* @readonly
|
|
136
|
+
* @type {OutboxRepository}
|
|
137
|
+
*/
|
|
40
138
|
#outbox;
|
|
139
|
+
/**
|
|
140
|
+
* @readonly
|
|
141
|
+
* @type {AuthoritativeMirror<WalletSnapshotPayload, WalletCommand, WalletCommandResult>}
|
|
142
|
+
*/
|
|
41
143
|
#strategy;
|
|
144
|
+
/**
|
|
145
|
+
* @readonly
|
|
146
|
+
* @type {HomeNodeClient}
|
|
147
|
+
*/
|
|
42
148
|
#homeNode;
|
|
149
|
+
/**
|
|
150
|
+
* @param {{ readonly homeNode: HomeNodeClient; readonly mirror: WalletMirrorRepository; readonly outbox: OutboxRepository; }} dependencies
|
|
151
|
+
* @param {WalletMirrorServiceOptions} options
|
|
152
|
+
*/
|
|
43
153
|
constructor(dependencies, options) {
|
|
44
154
|
if (!Number.isInteger(options.staleAfterMs) || options.staleAfterMs <= 0) {
|
|
45
155
|
throw new RangeError("Wallet mirror staleAfterMs must be a positive integer");
|
|
@@ -55,9 +165,16 @@ export class WalletMirrorService {
|
|
|
55
165
|
staleAfterMs: options.staleAfterMs,
|
|
56
166
|
});
|
|
57
167
|
}
|
|
168
|
+
/**
|
|
169
|
+
* @param {SyncDelta<WalletSnapshotPayload>} delta
|
|
170
|
+
* @returns {Promise<SyncApplyResult>}
|
|
171
|
+
*/
|
|
58
172
|
async applyDelta(delta) {
|
|
59
173
|
return await this.#strategy.applyDelta(delta, { domain: "wallet", now: this.#now() });
|
|
60
174
|
}
|
|
175
|
+
/**
|
|
176
|
+
* @returns {Promise<WalletBalanceView>}
|
|
177
|
+
*/
|
|
61
178
|
async readBalance() {
|
|
62
179
|
const snapshot = await this.#mirror.readSnapshot();
|
|
63
180
|
if (snapshot === undefined)
|
|
@@ -70,6 +187,9 @@ export class WalletMirrorService {
|
|
|
70
187
|
staleAt: snapshot.staleAt,
|
|
71
188
|
};
|
|
72
189
|
}
|
|
190
|
+
/**
|
|
191
|
+
* @returns {Promise<PaymentHistoryView>}
|
|
192
|
+
*/
|
|
73
193
|
async listPayments() {
|
|
74
194
|
const snapshot = await this.#mirror.readSnapshot();
|
|
75
195
|
if (snapshot === undefined)
|
|
@@ -81,6 +201,9 @@ export class WalletMirrorService {
|
|
|
81
201
|
staleAt: snapshot.staleAt,
|
|
82
202
|
};
|
|
83
203
|
}
|
|
204
|
+
/**
|
|
205
|
+
* @returns {Promise<L402HistoryView>}
|
|
206
|
+
*/
|
|
84
207
|
async listL402Receipts() {
|
|
85
208
|
const snapshot = await this.#mirror.readSnapshot();
|
|
86
209
|
if (snapshot === undefined)
|
|
@@ -92,25 +215,33 @@ export class WalletMirrorService {
|
|
|
92
215
|
staleAt: snapshot.staleAt,
|
|
93
216
|
};
|
|
94
217
|
}
|
|
218
|
+
/**
|
|
219
|
+
* @param {CreateInvoiceInput} input
|
|
220
|
+
* @returns {Promise<Extract<WalletCommandResult, { readonly kind: "invoice-created" }>>}
|
|
221
|
+
*/
|
|
95
222
|
async createInvoice(input) {
|
|
96
223
|
requirePositiveMsat(input.amountMsat, "invoice amount");
|
|
97
224
|
requireText(input.idempotencyKey, "invoice idempotency key");
|
|
98
|
-
return await this.#runCommand({
|
|
225
|
+
return /** @type {Extract<WalletCommandResult, { readonly kind: "invoice-created" }>} */ (await this.#runCommand({
|
|
99
226
|
amountMsat: input.amountMsat,
|
|
100
227
|
...(input.description === undefined ? {} : { description: input.description }),
|
|
101
228
|
kind: "create-invoice",
|
|
102
229
|
}, input.idempotencyKey, (snapshot) => {
|
|
103
230
|
if (snapshot.value.balanceMsat < 0)
|
|
104
231
|
throw new WalletFreshStateRequiredError();
|
|
105
|
-
});
|
|
232
|
+
}));
|
|
106
233
|
}
|
|
234
|
+
/**
|
|
235
|
+
* @param {PayInvoiceInput} input
|
|
236
|
+
* @returns {Promise<Extract<WalletCommandResult, { readonly kind: "payment-submitted" }>>}
|
|
237
|
+
*/
|
|
107
238
|
async payInvoice(input) {
|
|
108
239
|
requirePositiveMsat(input.amountMsat, "payment amount");
|
|
109
240
|
requireText(input.idempotencyKey, "payment idempotency key");
|
|
110
241
|
requireText(input.invoiceId, "invoice id");
|
|
111
242
|
if (input.expiresAt !== undefined && isExpired(input.expiresAt, this.#now()))
|
|
112
243
|
throw new ExpiredInvoiceError();
|
|
113
|
-
return await this.#runCommand({
|
|
244
|
+
return /** @type {Extract<WalletCommandResult, { readonly kind: "payment-submitted" }>} */ (await this.#runCommand({
|
|
114
245
|
amountMsat: input.amountMsat,
|
|
115
246
|
...(input.expiresAt === undefined ? {} : { expiresAt: input.expiresAt }),
|
|
116
247
|
invoiceId: input.invoiceId,
|
|
@@ -119,8 +250,14 @@ export class WalletMirrorService {
|
|
|
119
250
|
if (input.amountMsat > snapshot.value.spendableMsat) {
|
|
120
251
|
throw new InsufficientWalletBalanceError(input.amountMsat, snapshot.value.spendableMsat);
|
|
121
252
|
}
|
|
122
|
-
});
|
|
253
|
+
}));
|
|
123
254
|
}
|
|
255
|
+
/**
|
|
256
|
+
* @param {WalletCommand} command
|
|
257
|
+
* @param {string} idempotencyKey
|
|
258
|
+
* @param {(snapshot: WalletAuthoritativeSnapshot) => void} validate
|
|
259
|
+
* @returns {Promise<WalletCommandResult>}
|
|
260
|
+
*/
|
|
124
261
|
async #runCommand(command, idempotencyKey, validate) {
|
|
125
262
|
const cached = await this.#mirror.readCommandResult(idempotencyKey);
|
|
126
263
|
if (cached !== undefined)
|
|
@@ -161,6 +298,9 @@ export class WalletMirrorService {
|
|
|
161
298
|
throw error instanceof WalletFreshStateRequiredError ? error : new WalletCommandRejectedError();
|
|
162
299
|
}
|
|
163
300
|
}
|
|
301
|
+
/**
|
|
302
|
+
* @returns {Promise<WalletAuthoritativeSnapshot>}
|
|
303
|
+
*/
|
|
164
304
|
async #ensureFresh() {
|
|
165
305
|
const current = await this.#mirror.readSnapshot();
|
|
166
306
|
if (current !== undefined && this.#isFresh(current))
|
|
@@ -181,6 +321,10 @@ export class WalletMirrorService {
|
|
|
181
321
|
throw new WalletFreshStateRequiredError();
|
|
182
322
|
return refreshed;
|
|
183
323
|
}
|
|
324
|
+
/**
|
|
325
|
+
* @param {AuthoritativeCommand<WalletCommand>} command
|
|
326
|
+
* @returns {Promise<WalletCommandResult>}
|
|
327
|
+
*/
|
|
184
328
|
async #execute(command) {
|
|
185
329
|
this.#assertOnline();
|
|
186
330
|
try {
|
|
@@ -198,6 +342,11 @@ export class WalletMirrorService {
|
|
|
198
342
|
throw new WalletCommandRejectedError();
|
|
199
343
|
}
|
|
200
344
|
}
|
|
345
|
+
/**
|
|
346
|
+
* @param {WalletCommand} command
|
|
347
|
+
* @param {string} idempotencyKey
|
|
348
|
+
* @returns {OutboxRecord}
|
|
349
|
+
*/
|
|
201
350
|
#outboxRecord(command, idempotencyKey) {
|
|
202
351
|
const createdAt = this.#now();
|
|
203
352
|
return {
|
|
@@ -210,13 +359,27 @@ export class WalletMirrorService {
|
|
|
210
359
|
updatedAt: createdAt,
|
|
211
360
|
};
|
|
212
361
|
}
|
|
362
|
+
/**
|
|
363
|
+
* @returns {void}
|
|
364
|
+
*/
|
|
213
365
|
#assertOnline() {
|
|
214
366
|
if (!this.#isOnline())
|
|
215
367
|
throw new OfflineWalletCommandError();
|
|
216
368
|
}
|
|
369
|
+
/**
|
|
370
|
+
* @returns {boolean}
|
|
371
|
+
*/
|
|
217
372
|
#isOnline() { return this.#homeNode.state.status === "connected"; }
|
|
373
|
+
/**
|
|
374
|
+
* @param {WalletAuthoritativeSnapshot} snapshot
|
|
375
|
+
* @returns {boolean}
|
|
376
|
+
*/
|
|
218
377
|
#isFresh(snapshot) { return Date.parse(this.#now()) < Date.parse(snapshot.staleAt); }
|
|
219
378
|
}
|
|
379
|
+
/**
|
|
380
|
+
* @param {unknown} value
|
|
381
|
+
* @returns {WalletSnapshotPayload}
|
|
382
|
+
*/
|
|
220
383
|
function parseSnapshotPayload(value) {
|
|
221
384
|
const record = asRecord(value, "wallet snapshot");
|
|
222
385
|
return {
|
|
@@ -226,6 +389,11 @@ function parseSnapshotPayload(value) {
|
|
|
226
389
|
spendableMsat: positiveOrZero(record.spendableMsat, "wallet spendable balance"),
|
|
227
390
|
};
|
|
228
391
|
}
|
|
392
|
+
/**
|
|
393
|
+
* @param {unknown} value
|
|
394
|
+
* @param {number} amountMsat
|
|
395
|
+
* @returns {Extract<WalletCommandResult, { readonly kind: "invoice-created" }>}
|
|
396
|
+
*/
|
|
229
397
|
function parseInvoiceResult(value, amountMsat) {
|
|
230
398
|
const record = asRecord(value, "invoice result");
|
|
231
399
|
const invoiceId = text(record.invoiceId, "invoice id");
|
|
@@ -239,6 +407,11 @@ function parseInvoiceResult(value, amountMsat) {
|
|
|
239
407
|
status: "created",
|
|
240
408
|
};
|
|
241
409
|
}
|
|
410
|
+
/**
|
|
411
|
+
* @param {unknown} value
|
|
412
|
+
* @param {number} amountMsat
|
|
413
|
+
* @returns {Extract<WalletCommandResult, { readonly kind: "payment-submitted" }>}
|
|
414
|
+
*/
|
|
242
415
|
function parsePaymentResult(value, amountMsat) {
|
|
243
416
|
const record = asRecord(value, "payment result");
|
|
244
417
|
const status = record.status ?? "pending";
|
|
@@ -252,6 +425,10 @@ function parsePaymentResult(value, amountMsat) {
|
|
|
252
425
|
status,
|
|
253
426
|
};
|
|
254
427
|
}
|
|
428
|
+
/**
|
|
429
|
+
* @param {unknown} value
|
|
430
|
+
* @returns {WalletSnapshotPayload["payments"]}
|
|
431
|
+
*/
|
|
255
432
|
function parsePayments(value) {
|
|
256
433
|
if (value === undefined)
|
|
257
434
|
return [];
|
|
@@ -277,6 +454,10 @@ function parsePayments(value) {
|
|
|
277
454
|
};
|
|
278
455
|
});
|
|
279
456
|
}
|
|
457
|
+
/**
|
|
458
|
+
* @param {unknown} value
|
|
459
|
+
* @returns {WalletSnapshotPayload["l402"]}
|
|
460
|
+
*/
|
|
280
461
|
function parseL402(value) {
|
|
281
462
|
if (value === undefined)
|
|
282
463
|
return [];
|
|
@@ -293,42 +474,81 @@ function parseL402(value) {
|
|
|
293
474
|
};
|
|
294
475
|
});
|
|
295
476
|
}
|
|
477
|
+
/**
|
|
478
|
+
* @param {unknown} value
|
|
479
|
+
* @param {string} _label
|
|
480
|
+
* @returns {Record<string, unknown>}
|
|
481
|
+
*/
|
|
296
482
|
function asRecord(value, _label) {
|
|
297
483
|
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
298
484
|
throw new WalletFreshStateRequiredError();
|
|
299
|
-
return value;
|
|
485
|
+
return /** @type {Record<string, unknown>} */ (value);
|
|
300
486
|
}
|
|
487
|
+
/**
|
|
488
|
+
* @param {unknown} value
|
|
489
|
+
* @param {string} _label
|
|
490
|
+
* @returns {string}
|
|
491
|
+
*/
|
|
301
492
|
function text(value, _label) {
|
|
302
493
|
if (typeof value !== "string" || value.trim() === "")
|
|
303
494
|
throw new WalletCommandRejectedError();
|
|
304
495
|
return value;
|
|
305
496
|
}
|
|
497
|
+
/**
|
|
498
|
+
* @param {unknown} value
|
|
499
|
+
* @param {string} label
|
|
500
|
+
* @returns {string}
|
|
501
|
+
*/
|
|
306
502
|
function timestamp(value, label) {
|
|
307
503
|
const parsed = text(value, label);
|
|
308
504
|
if (!Number.isFinite(Date.parse(parsed)))
|
|
309
505
|
throw new WalletFreshStateRequiredError();
|
|
310
506
|
return parsed;
|
|
311
507
|
}
|
|
508
|
+
/**
|
|
509
|
+
* @param {unknown} value
|
|
510
|
+
* @param {string} _label
|
|
511
|
+
* @returns {number}
|
|
512
|
+
*/
|
|
312
513
|
function positiveOrZero(value, _label) {
|
|
313
514
|
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0)
|
|
314
515
|
throw new WalletFreshStateRequiredError();
|
|
315
516
|
return value;
|
|
316
517
|
}
|
|
518
|
+
/**
|
|
519
|
+
* @param {unknown} value
|
|
520
|
+
* @param {number} fallback
|
|
521
|
+
* @returns {number}
|
|
522
|
+
*/
|
|
317
523
|
function numberOrDefault(value, fallback) {
|
|
318
524
|
return value === undefined ? fallback : positiveOrZero(value, "amount");
|
|
319
525
|
}
|
|
526
|
+
/**
|
|
527
|
+
* @param {number} value
|
|
528
|
+
* @param {string} label
|
|
529
|
+
* @returns {void}
|
|
530
|
+
*/
|
|
320
531
|
function requirePositiveMsat(value, label) {
|
|
321
532
|
if (!Number.isSafeInteger(value) || value <= 0)
|
|
322
533
|
throw new TypeError(`${label} must be a positive integer`);
|
|
323
534
|
}
|
|
535
|
+
/**
|
|
536
|
+
* @param {string} value
|
|
537
|
+
* @param {string} label
|
|
538
|
+
* @returns {void}
|
|
539
|
+
*/
|
|
324
540
|
function requireText(value, label) {
|
|
325
541
|
if (value.trim() === "")
|
|
326
542
|
throw new TypeError(`${label} must be a non-empty string`);
|
|
327
543
|
}
|
|
544
|
+
/**
|
|
545
|
+
* @param {string} expiresAt
|
|
546
|
+
* @param {string} now
|
|
547
|
+
* @returns {boolean}
|
|
548
|
+
*/
|
|
328
549
|
function isExpired(expiresAt, now) {
|
|
329
550
|
const expiry = Date.parse(expiresAt);
|
|
330
551
|
if (!Number.isFinite(expiry))
|
|
331
552
|
throw new TypeError("invoice expiry must be an ISO timestamp");
|
|
332
553
|
return expiry <= Date.parse(now);
|
|
333
554
|
}
|
|
334
|
-
//# sourceMappingURL=wallet-mirror-service.js.map
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
/** @typedef {import('@econ-v1/ports').DelayPort} DelayPort */
|
|
3
|
+
/** @typedef {import('@econ-v1/ports').HomeNodeClient} HomeNodeClient */
|
|
4
|
+
import { DomainError } from "../errors/index.js";
|
|
5
|
+
const HANDOFF_POLL_INTERVAL_MS = 3000;
|
|
6
|
+
const HANDOFF_TRANSPORT_LOST_POLLS = 2;
|
|
7
|
+
const RECONNECT_POLL_INTERVAL_MS = 4000;
|
|
8
|
+
export class WifiProvisioningService {
|
|
9
|
+
/**
|
|
10
|
+
* @param {() => boolean} [shouldContinue]
|
|
11
|
+
* @returns {Promise<WifiReconnectOutcome>}
|
|
12
|
+
*/
|
|
13
|
+
async awaitReconnect(shouldContinue = () => true) {
|
|
14
|
+
for (;;) {
|
|
15
|
+
if (!shouldContinue()) return "superseded";
|
|
16
|
+
const outcome = await this.status();
|
|
17
|
+
if (outcome.kind === "connected") return "connected";
|
|
18
|
+
await this.#delay.wait(RECONNECT_POLL_INTERVAL_MS);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* @readonly
|
|
23
|
+
*/
|
|
24
|
+
#delay;
|
|
25
|
+
/**
|
|
26
|
+
* @readonly
|
|
27
|
+
*/
|
|
28
|
+
#node;
|
|
29
|
+
/**
|
|
30
|
+
* @readonly
|
|
31
|
+
*/
|
|
32
|
+
#now;
|
|
33
|
+
/**
|
|
34
|
+
* @param {WifiProvisioningServiceOptions} options
|
|
35
|
+
*/
|
|
36
|
+
constructor(options) {
|
|
37
|
+
this.#delay = options.delay;
|
|
38
|
+
this.#node = options.node;
|
|
39
|
+
this.#now = options.now;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Whether the node is ALREADY joined to a home network. Asked once at flow entry so a person
|
|
43
|
+
* who configured Wi-Fi on the node's own captive portal is never marched through the
|
|
44
|
+
* credential steps a second time — resubmitting working credentials is not idempotent on the
|
|
45
|
+
* node side (the join tears down and re-adds the connection) and a handshake flake in that
|
|
46
|
+
* window strands the node back in setup AP mode. The wire values are node-server's
|
|
47
|
+
* `http_status` PascalCase strings; only an explicit `"Connected"` counts.
|
|
48
|
+
* @returns {Promise<WifiStatusOutcome>}
|
|
49
|
+
*/
|
|
50
|
+
async status() {
|
|
51
|
+
try {
|
|
52
|
+
const wire = await this.#node.invoke({
|
|
53
|
+
operation: "GET /api/v2/wifi/status",
|
|
54
|
+
payload: {},
|
|
55
|
+
});
|
|
56
|
+
if (wire.connection_status !== "Connected")
|
|
57
|
+
return { kind: "not-connected" };
|
|
58
|
+
return {
|
|
59
|
+
kind: "connected",
|
|
60
|
+
ssid: typeof wire.current_ssid === "string" ? wire.current_ssid : "",
|
|
61
|
+
};
|
|
62
|
+
} catch {
|
|
63
|
+
return { kind: "unreachable" };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/** Lists what the node's radio can currently see. A failure is a first-class outcome, not a
|
|
67
|
+
* throw: step 4 stays on screen and offers "Scan again".
|
|
68
|
+
* @returns {Promise<WifiScanOutcome>}
|
|
69
|
+
*/
|
|
70
|
+
async scan() {
|
|
71
|
+
try {
|
|
72
|
+
const wire = await this.#node.invoke({
|
|
73
|
+
operation: "GET /api/v2/wifi/scan",
|
|
74
|
+
payload: {},
|
|
75
|
+
});
|
|
76
|
+
const rows = Array.isArray(wire.networks) ? wire.networks : [];
|
|
77
|
+
/** @type {WifiProvisioningNetwork[]} */
|
|
78
|
+
const networks = [];
|
|
79
|
+
for (const row of rows) {
|
|
80
|
+
const ssid = typeof row.ssid === "string" ? row.ssid : "";
|
|
81
|
+
if (ssid.length === 0) continue;
|
|
82
|
+
networks.push({
|
|
83
|
+
security: normaliseSecurity(row.security),
|
|
84
|
+
signal: clampSignal(row.signal_quality_percent),
|
|
85
|
+
ssid,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
return { kind: "scanned", networks };
|
|
89
|
+
} catch {
|
|
90
|
+
return { kind: "scan-failed" };
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Hands the credentials to the node exactly once. `secret === undefined` means an open
|
|
95
|
+
* network, in which case the `password` key is omitted rather than sent as an empty string —
|
|
96
|
+
* the node validates length 8-63 whenever the field is present.
|
|
97
|
+
* @param {string} ssid
|
|
98
|
+
* @param {string | undefined} secret
|
|
99
|
+
* @returns {Promise<WifiConfigureOutcome>}
|
|
100
|
+
*/
|
|
101
|
+
async configure(ssid, secret) {
|
|
102
|
+
/** @type {{ auto_connect: boolean, hidden: boolean, password?: string, priority: number, ssid: string }} */
|
|
103
|
+
/** @type {Record<string, unknown>} */
|
|
104
|
+
const payload = {
|
|
105
|
+
auto_connect: true,
|
|
106
|
+
hidden: false,
|
|
107
|
+
priority: 100,
|
|
108
|
+
ssid,
|
|
109
|
+
};
|
|
110
|
+
if (secret !== undefined) payload.password = secret;
|
|
111
|
+
try {
|
|
112
|
+
const wire = await this.#node.invoke({
|
|
113
|
+
operation: "POST /api/v2/wifi/configure",
|
|
114
|
+
payload,
|
|
115
|
+
});
|
|
116
|
+
const operationId =
|
|
117
|
+
typeof wire.operation_id === "string" ? wire.operation_id : "";
|
|
118
|
+
// No handle means nothing to watch. Reporting "accepted" here would strand step 6 forever.
|
|
119
|
+
if (operationId.length === 0) return { kind: "unreachable" };
|
|
120
|
+
return { kind: "accepted", operationId };
|
|
121
|
+
} catch (error) {
|
|
122
|
+
// The node validates ssid/password shape before it ever touches the radio, and
|
|
123
|
+
// `PrePairingHomeNodeClient` maps that envelope onto `invalid_request`. Everything else —
|
|
124
|
+
// transport, 5xx, unknown envelope — is "we could not tell", never a credential verdict.
|
|
125
|
+
if (error instanceof DomainError && error.code === "invalid_request") {
|
|
126
|
+
return { kind: "credentials-rejected" };
|
|
127
|
+
}
|
|
128
|
+
return { kind: "unreachable" };
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Watches one configure operation across the network handoff. See the class comment: a
|
|
133
|
+
* thrown poll is EXPECTED and means "keep waiting".
|
|
134
|
+
*
|
|
135
|
+
* `deadline` is an absolute epoch-millisecond value from the same clock as `now`, supplied by
|
|
136
|
+
* the caller so a resumed flow can keep the ORIGINAL deadline rather than restarting it.
|
|
137
|
+
* @param {string} operationId
|
|
138
|
+
* @param {number} deadline
|
|
139
|
+
* @param {() => void} [onTransportLost]
|
|
140
|
+
* @returns {Promise<WifiHandoffOutcome>}
|
|
141
|
+
*/
|
|
142
|
+
async awaitHandoff(operationId, deadline, onTransportLost) {
|
|
143
|
+
const operation = `GET /api/v2/wifi/operations/${encodeURIComponent(operationId)}`;
|
|
144
|
+
let consecutiveFailures = 0;
|
|
145
|
+
let reportedTransportLost = false;
|
|
146
|
+
for (;;) {
|
|
147
|
+
try {
|
|
148
|
+
const wire = await this.#node.invoke({ operation, payload: {} });
|
|
149
|
+
consecutiveFailures = 0;
|
|
150
|
+
const status = typeof wire.status === "string" ? wire.status : "";
|
|
151
|
+
if (status === "Connected") return "connected";
|
|
152
|
+
if (status === "Failed") return "credentials-rejected";
|
|
153
|
+
} catch {
|
|
154
|
+
consecutiveFailures += 1;
|
|
155
|
+
if (consecutiveFailures >= HANDOFF_TRANSPORT_LOST_POLLS && !reportedTransportLost) {
|
|
156
|
+
reportedTransportLost = true;
|
|
157
|
+
try {
|
|
158
|
+
onTransportLost?.();
|
|
159
|
+
} catch {
|
|
160
|
+
// The caller's problem, never this loop's.
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (this.#now() >= deadline) return "unreachable";
|
|
165
|
+
await this.#delay.wait(HANDOFF_POLL_INTERVAL_MS);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/** The node reports whatever its supplicant said. Anything not recognisably open/WEP/WPA3 is
|
|
170
|
+
* treated as WPA2 — the conservative choice, because it demands a password rather than
|
|
171
|
+
* silently offering to join without one.
|
|
172
|
+
* @param {unknown} value
|
|
173
|
+
* @returns {WifiProvisioningSecurity}
|
|
174
|
+
*/
|
|
175
|
+
function normaliseSecurity(value) {
|
|
176
|
+
if (typeof value !== "string") return "wpa2";
|
|
177
|
+
const lowered = value.toLowerCase();
|
|
178
|
+
if (lowered.includes("wpa3")) return "wpa3";
|
|
179
|
+
if (lowered.includes("wep")) return "wep";
|
|
180
|
+
if (lowered === "open" || lowered === "none" || lowered === "") return "open";
|
|
181
|
+
return "wpa2";
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* @param {unknown} value
|
|
185
|
+
* @returns {number}
|
|
186
|
+
*/
|
|
187
|
+
function clampSignal(value) {
|
|
188
|
+
if (typeof value !== "number" || Number.isNaN(value)) return 0;
|
|
189
|
+
if (value < 0) return 0;
|
|
190
|
+
if (value > 100) return 100;
|
|
191
|
+
return Math.round(value);
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* The node's Wi-Fi provisioning surface, expressed as three operations with CLOSED outcome
|
|
195
|
+
* unions (design §10). Nothing here throws at its caller and nothing here returns node text:
|
|
196
|
+
* `OnboardingFlowService` turns these outcomes into `OnboardingReasonCode`s, and the UI turns
|
|
197
|
+
* those into copy. A raw `DomainError.message` reaching a subscriber would be both a leak and
|
|
198
|
+
* an unreviewable string in the product.
|
|
199
|
+
*
|
|
200
|
+
* THE CENTRAL DIFFICULTY is `awaitHandoff()`. Between `configure()` and the node reporting
|
|
201
|
+
* `Connected`, the node LEAVES its own access point and joins the home network — the browser
|
|
202
|
+
* that issued the request is on that access point, so the poll *is expected to fail*, possibly
|
|
203
|
+
* for the entire operation. A thrown request here therefore means "keep waiting", not "it
|
|
204
|
+
* broke"; only three things end the wait:
|
|
205
|
+
*
|
|
206
|
+
* - the node answering `Connected` -> "connected" (it came back, on the LAN)
|
|
207
|
+
* - the node answering `Failed` -> "credentials-rejected" (it came back to say no)
|
|
208
|
+
* - the deadline passing -> "unreachable" (step 7's manual way back)
|
|
209
|
+
*
|
|
210
|
+
* `Failed` is the ONLY signal that distinguishes a wrong password from a normal handoff, which
|
|
211
|
+
* is why it must not be collapsed into the generic failure branch: collapsing it would send a
|
|
212
|
+
* person who mistyped their password to "walk back to your node" instead of to the keyboard.
|
|
213
|
+
*
|
|
214
|
+
* The status strings are the node's, verified against
|
|
215
|
+
* `core/adapters-network/src/http/server/wifi.rs::http_operation_status`, which maps its
|
|
216
|
+
* internal states onto exactly `"Connected"` / `"Failed"` / `"Connecting"` / `"Pending"`.
|
|
217
|
+
* (The design doc's "Completed" is wrong; the wire value is "Connected".)
|
|
218
|
+
*
|
|
219
|
+
* SECURITY: `configure()` takes the secret as an argument and forwards it in a single request.
|
|
220
|
+
* It is never assigned to a field, never returned, never logged, and never included in an
|
|
221
|
+
* outcome — `WifiConfigureOutcome` has no field it could occupy.
|
|
222
|
+
* @typedef {"open" | "wep" | "wpa2" | "wpa3"} WifiProvisioningSecurity
|
|
223
|
+
*/
|
|
224
|
+
/**
|
|
225
|
+
* @typedef {| { readonly kind: "scanned"; readonly networks: readonly WifiProvisioningNetwork[] }
|
|
226
|
+
* | { readonly kind: "scan-failed" }} WifiScanOutcome
|
|
227
|
+
*/
|
|
228
|
+
/**
|
|
229
|
+
* @typedef {| { readonly kind: "accepted"; readonly operationId: string }
|
|
230
|
+
* | { readonly kind: "credentials-rejected" }
|
|
231
|
+
* | { readonly kind: "unreachable" }} WifiConfigureOutcome
|
|
232
|
+
*/
|
|
233
|
+
/** @typedef {"connected" | "credentials-rejected" | "unreachable"} WifiHandoffOutcome */
|
|
234
|
+
/** @typedef {"connected" | "superseded"} WifiReconnectOutcome */
|
|
235
|
+
/** What the node's radio is doing RIGHT NOW, asked once at flow entry. `connected` carries the
|
|
236
|
+
* joined SSID so the flow can show which network needs no credentials. `unreachable` collapses
|
|
237
|
+
* every failure — a flow that cannot ask simply walks the ordinary Wi-Fi steps.
|
|
238
|
+
* @typedef {| { readonly kind: "connected"; readonly ssid: string }
|
|
239
|
+
* | { readonly kind: "not-connected" }
|
|
240
|
+
* | { readonly kind: "unreachable" }} WifiStatusOutcome
|
|
241
|
+
*/
|
|
242
|
+
/**
|
|
243
|
+
* @typedef {object} WifiProvisioningNetwork
|
|
244
|
+
* @property {WifiProvisioningSecurity} security
|
|
245
|
+
* @property {number} signal 0-100 signal quality. dBm never crosses this boundary.
|
|
246
|
+
* @property {string} ssid
|
|
247
|
+
*/
|
|
248
|
+
/**
|
|
249
|
+
* @typedef {object} WifiProvisioningServiceOptions
|
|
250
|
+
* @property {Pick<DelayPort, "wait">} delay
|
|
251
|
+
* @property {Pick<HomeNodeClient, "invoke">} node
|
|
252
|
+
* @property {() => number} now Epoch milliseconds. Injected so the deadline is testable and domain owns no clock.
|
|
253
|
+
*/
|
|
254
|
+
/**
|
|
255
|
+
* @typedef {object} WifiScanRowWire
|
|
256
|
+
* @property {unknown} [security]
|
|
257
|
+
* @property {unknown} [signal_quality_percent]
|
|
258
|
+
* @property {unknown} [ssid]
|
|
259
|
+
*/
|
|
260
|
+
/**
|
|
261
|
+
* @typedef {object} WifiScanWire
|
|
262
|
+
* @property {readonly WifiScanRowWire[]} [networks]
|
|
263
|
+
*/
|
|
264
|
+
/**
|
|
265
|
+
* @typedef {object} WifiConfigureWire
|
|
266
|
+
* @property {unknown} [operation_id]
|
|
267
|
+
* @property {unknown} [success]
|
|
268
|
+
*/
|
|
269
|
+
/**
|
|
270
|
+
* @typedef {object} WifiOperationStatusWire
|
|
271
|
+
* @property {unknown} [status]
|
|
272
|
+
*/
|
|
273
|
+
/**
|
|
274
|
+
* @typedef {object} WifiStatusWire
|
|
275
|
+
* @property {unknown} [connection_status]
|
|
276
|
+
* @property {unknown} [current_ssid]
|
|
277
|
+
*/
|
|
278
|
+
// @ts-check
|