cojson 0.13.17 → 0.13.20
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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +18 -0
- package/dist/PeerState.d.ts +4 -1
- package/dist/PeerState.d.ts.map +1 -1
- package/dist/PeerState.js +16 -36
- package/dist/PeerState.js.map +1 -1
- package/dist/SyncStateManager.d.ts.map +1 -1
- package/dist/SyncStateManager.js +2 -3
- package/dist/SyncStateManager.js.map +1 -1
- package/dist/coValue.d.ts +4 -4
- package/dist/coValue.d.ts.map +1 -1
- package/dist/coValue.js +4 -4
- package/dist/coValue.js.map +1 -1
- package/dist/coValueCore/coValueCore.d.ts +143 -0
- package/dist/coValueCore/coValueCore.d.ts.map +1 -0
- package/dist/{coValueCore.js → coValueCore/coValueCore.js} +325 -253
- package/dist/coValueCore/coValueCore.js.map +1 -0
- package/dist/coValueCore/verifiedState.d.ts +65 -0
- package/dist/coValueCore/verifiedState.d.ts.map +1 -0
- package/dist/coValueCore/verifiedState.js +210 -0
- package/dist/coValueCore/verifiedState.js.map +1 -0
- package/dist/coValues/account.d.ts +8 -10
- package/dist/coValues/account.d.ts.map +1 -1
- package/dist/coValues/account.js +12 -13
- package/dist/coValues/account.js.map +1 -1
- package/dist/coValues/coList.d.ts +3 -3
- package/dist/coValues/coList.d.ts.map +1 -1
- package/dist/coValues/coList.js +6 -3
- package/dist/coValues/coList.js.map +1 -1
- package/dist/coValues/coMap.d.ts +3 -3
- package/dist/coValues/coMap.d.ts.map +1 -1
- package/dist/coValues/coMap.js +3 -3
- package/dist/coValues/coMap.js.map +1 -1
- package/dist/coValues/coPlainText.d.ts +2 -2
- package/dist/coValues/coPlainText.d.ts.map +1 -1
- package/dist/coValues/coPlainText.js +4 -4
- package/dist/coValues/coPlainText.js.map +1 -1
- package/dist/coValues/coStream.d.ts +3 -3
- package/dist/coValues/coStream.d.ts.map +1 -1
- package/dist/coValues/coStream.js +3 -3
- package/dist/coValues/coStream.js.map +1 -1
- package/dist/coValues/group.d.ts +7 -2
- package/dist/coValues/group.d.ts.map +1 -1
- package/dist/coValues/group.js +29 -26
- package/dist/coValues/group.js.map +1 -1
- package/dist/coreToCoValue.d.ts +2 -2
- package/dist/coreToCoValue.d.ts.map +1 -1
- package/dist/coreToCoValue.js +10 -14
- package/dist/coreToCoValue.js.map +1 -1
- package/dist/exports.d.ts +6 -5
- package/dist/exports.d.ts.map +1 -1
- package/dist/exports.js +3 -4
- package/dist/exports.js.map +1 -1
- package/dist/localNode.d.ts +30 -24
- package/dist/localNode.d.ts.map +1 -1
- package/dist/localNode.js +147 -173
- package/dist/localNode.js.map +1 -1
- package/dist/permissions.d.ts +2 -1
- package/dist/permissions.d.ts.map +1 -1
- package/dist/permissions.js +15 -11
- package/dist/permissions.js.map +1 -1
- package/dist/priority.d.ts +1 -1
- package/dist/priority.d.ts.map +1 -1
- package/dist/streamUtils.d.ts +5 -5
- package/dist/streamUtils.d.ts.map +1 -1
- package/dist/streamUtils.js +5 -20
- package/dist/streamUtils.js.map +1 -1
- package/dist/sync.d.ts +8 -6
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +121 -74
- package/dist/sync.js.map +1 -1
- package/dist/tests/PeerState.test.js +0 -31
- package/dist/tests/PeerState.test.js.map +1 -1
- package/dist/tests/SyncStateManager.test.js +41 -6
- package/dist/tests/SyncStateManager.test.js.map +1 -1
- package/dist/tests/account.test.js +16 -0
- package/dist/tests/account.test.js.map +1 -1
- package/dist/tests/coList.test.js +19 -16
- package/dist/tests/coList.test.js.map +1 -1
- package/dist/tests/coMap.test.js +12 -13
- package/dist/tests/coMap.test.js.map +1 -1
- package/dist/tests/coPlainText.test.js +9 -10
- package/dist/tests/coPlainText.test.js.map +1 -1
- package/dist/tests/coStream.test.js +22 -17
- package/dist/tests/coStream.test.js.map +1 -1
- package/dist/tests/coValueCore.test.js +22 -28
- package/dist/tests/coValueCore.test.js.map +1 -1
- package/dist/tests/coValueCoreLoadingState.test.d.ts +2 -0
- package/dist/tests/coValueCoreLoadingState.test.d.ts.map +1 -0
- package/dist/tests/{coValueState.test.js → coValueCoreLoadingState.test.js} +62 -46
- package/dist/tests/coValueCoreLoadingState.test.js.map +1 -0
- package/dist/tests/group.test.js +42 -43
- package/dist/tests/group.test.js.map +1 -1
- package/dist/tests/messagesTestUtils.d.ts +2 -2
- package/dist/tests/messagesTestUtils.d.ts.map +1 -1
- package/dist/tests/messagesTestUtils.js +1 -1
- package/dist/tests/messagesTestUtils.js.map +1 -1
- package/dist/tests/permissions.test.js +224 -292
- package/dist/tests/permissions.test.js.map +1 -1
- package/dist/tests/priority.test.js +13 -14
- package/dist/tests/priority.test.js.map +1 -1
- package/dist/tests/sync.auth.test.d.ts +2 -0
- package/dist/tests/sync.auth.test.d.ts.map +1 -0
- package/dist/tests/sync.auth.test.js +190 -0
- package/dist/tests/sync.auth.test.js.map +1 -0
- package/dist/tests/sync.load.test.js +6 -6
- package/dist/tests/sync.load.test.js.map +1 -1
- package/dist/tests/sync.mesh.test.js +25 -12
- package/dist/tests/sync.mesh.test.js.map +1 -1
- package/dist/tests/sync.peerReconciliation.test.js +19 -19
- package/dist/tests/sync.peerReconciliation.test.js.map +1 -1
- package/dist/tests/sync.storage.test.js +20 -13
- package/dist/tests/sync.storage.test.js.map +1 -1
- package/dist/tests/sync.test.js +32 -39
- package/dist/tests/sync.test.js.map +1 -1
- package/dist/tests/sync.upload.test.js +126 -37
- package/dist/tests/sync.upload.test.js.map +1 -1
- package/dist/tests/testUtils.d.ts +35 -17
- package/dist/tests/testUtils.d.ts.map +1 -1
- package/dist/tests/testUtils.js +103 -79
- package/dist/tests/testUtils.js.map +1 -1
- package/dist/typeUtils/expectGroup.js +1 -1
- package/dist/typeUtils/expectGroup.js.map +1 -1
- package/package.json +1 -1
- package/src/PeerState.ts +19 -40
- package/src/SyncStateManager.ts +2 -3
- package/src/coValue.ts +11 -8
- package/src/{coValueCore.ts → coValueCore/coValueCore.ts} +478 -422
- package/src/coValueCore/verifiedState.ts +376 -0
- package/src/coValues/account.ts +20 -25
- package/src/coValues/coList.ts +12 -6
- package/src/coValues/coMap.ts +9 -6
- package/src/coValues/coPlainText.ts +9 -6
- package/src/coValues/coStream.ts +9 -6
- package/src/coValues/group.ts +50 -28
- package/src/coreToCoValue.ts +14 -15
- package/src/exports.ts +9 -7
- package/src/localNode.ts +236 -275
- package/src/permissions.ts +18 -12
- package/src/priority.ts +1 -1
- package/src/streamUtils.ts +7 -34
- package/src/sync.ts +146 -84
- package/src/tests/PeerState.test.ts +0 -37
- package/src/tests/SyncStateManager.test.ts +56 -6
- package/src/tests/account.test.ts +24 -0
- package/src/tests/coList.test.ts +21 -15
- package/src/tests/coMap.test.ts +12 -13
- package/src/tests/coPlainText.test.ts +12 -9
- package/src/tests/coStream.test.ts +25 -16
- package/src/tests/coValueCore.test.ts +30 -27
- package/src/tests/{coValueState.test.ts → coValueCoreLoadingState.test.ts} +67 -57
- package/src/tests/group.test.ts +44 -69
- package/src/tests/messagesTestUtils.ts +3 -8
- package/src/tests/permissions.test.ts +283 -449
- package/src/tests/priority.test.ts +17 -13
- package/src/tests/sync.auth.test.ts +246 -0
- package/src/tests/sync.load.test.ts +7 -6
- package/src/tests/sync.mesh.test.ts +25 -12
- package/src/tests/sync.peerReconciliation.test.ts +25 -25
- package/src/tests/sync.storage.test.ts +20 -13
- package/src/tests/sync.test.ts +43 -43
- package/src/tests/sync.upload.test.ts +157 -37
- package/src/tests/testUtils.ts +143 -96
- package/src/typeUtils/expectGroup.ts +1 -1
- package/dist/CoValuesStore.d.ts +0 -14
- package/dist/CoValuesStore.d.ts.map +0 -1
- package/dist/CoValuesStore.js +0 -32
- package/dist/CoValuesStore.js.map +0 -1
- package/dist/coValueCore.d.ts +0 -142
- package/dist/coValueCore.d.ts.map +0 -1
- package/dist/coValueCore.js.map +0 -1
- package/dist/coValueState.d.ts +0 -34
- package/dist/coValueState.d.ts.map +0 -1
- package/dist/coValueState.js +0 -190
- package/dist/coValueState.js.map +0 -1
- package/dist/tests/coValueState.test.d.ts +0 -2
- package/dist/tests/coValueState.test.d.ts.map +0 -1
- package/dist/tests/coValueState.test.js.map +0 -1
- package/src/CoValuesStore.ts +0 -41
- package/src/coValueState.ts +0 -245
package/src/localNode.ts
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
|
-
import { Result,
|
|
2
|
-
import { CoValuesStore } from "./CoValuesStore.js";
|
|
1
|
+
import { Result, err, ok } from "neverthrow";
|
|
3
2
|
import { CoID } from "./coValue.js";
|
|
4
3
|
import { RawCoValue } from "./coValue.js";
|
|
5
4
|
import {
|
|
5
|
+
AvailableCoValueCore,
|
|
6
6
|
CoValueCore,
|
|
7
|
+
idforHeader,
|
|
8
|
+
} from "./coValueCore/coValueCore.js";
|
|
9
|
+
import {
|
|
7
10
|
CoValueHeader,
|
|
8
11
|
CoValueUniqueness,
|
|
9
|
-
|
|
12
|
+
VerifiedState,
|
|
13
|
+
} from "./coValueCore/verifiedState.js";
|
|
10
14
|
import {
|
|
11
15
|
AccountMeta,
|
|
16
|
+
ControlledAccount,
|
|
12
17
|
ControlledAccountOrAgent,
|
|
13
18
|
ControlledAgent,
|
|
14
19
|
InvalidAccountAgentIDError,
|
|
@@ -16,9 +21,9 @@ import {
|
|
|
16
21
|
RawAccount,
|
|
17
22
|
RawAccountID,
|
|
18
23
|
RawAccountMigration,
|
|
19
|
-
RawControlledAccount,
|
|
20
24
|
RawProfile,
|
|
21
25
|
accountHeaderForInitialAgentSecret,
|
|
26
|
+
expectAccount,
|
|
22
27
|
} from "./coValues/account.js";
|
|
23
28
|
import {
|
|
24
29
|
InviteSecret,
|
|
@@ -29,6 +34,7 @@ import { AgentSecret, CryptoProvider } from "./crypto/crypto.js";
|
|
|
29
34
|
import { AgentID, RawCoID, SessionID, isAgentID } from "./ids.js";
|
|
30
35
|
import { logger } from "./logger.js";
|
|
31
36
|
import { Peer, PeerID, SyncManager } from "./sync.js";
|
|
37
|
+
import { accountOrAgentIDfromSessionID } from "./typeUtils/accountOrAgentIDfromSessionID.js";
|
|
32
38
|
import { expectGroup } from "./typeUtils/expectGroup.js";
|
|
33
39
|
|
|
34
40
|
/** A `LocalNode` represents a local view of a set of loaded `CoValue`s, from the perspective of a particular account (or primitive cryptographic agent).
|
|
@@ -46,11 +52,12 @@ export class LocalNode {
|
|
|
46
52
|
/** @internal */
|
|
47
53
|
crypto: CryptoProvider;
|
|
48
54
|
/** @internal */
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
account: ControlledAccountOrAgent;
|
|
55
|
+
private readonly coValues = new Map<RawCoID, CoValueCore>();
|
|
56
|
+
|
|
52
57
|
/** @category 3. Low-level */
|
|
53
|
-
currentSessionID: SessionID;
|
|
58
|
+
readonly currentSessionID: SessionID;
|
|
59
|
+
readonly agentSecret: AgentSecret;
|
|
60
|
+
|
|
54
61
|
/** @category 3. Low-level */
|
|
55
62
|
syncManager = new SyncManager(this);
|
|
56
63
|
|
|
@@ -58,17 +65,129 @@ export class LocalNode {
|
|
|
58
65
|
|
|
59
66
|
/** @category 3. Low-level */
|
|
60
67
|
constructor(
|
|
61
|
-
|
|
68
|
+
agentSecret: AgentSecret,
|
|
62
69
|
currentSessionID: SessionID,
|
|
63
70
|
crypto: CryptoProvider,
|
|
64
71
|
) {
|
|
65
|
-
this.
|
|
72
|
+
this.agentSecret = agentSecret;
|
|
66
73
|
this.currentSessionID = currentSessionID;
|
|
67
74
|
this.crypto = crypto;
|
|
68
75
|
}
|
|
69
76
|
|
|
77
|
+
getCoValue(id: RawCoID) {
|
|
78
|
+
let entry = this.coValues.get(id);
|
|
79
|
+
|
|
80
|
+
if (!entry) {
|
|
81
|
+
entry = CoValueCore.fromID(id, this);
|
|
82
|
+
this.coValues.set(id, entry);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return entry;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
allCoValues() {
|
|
89
|
+
return this.coValues.values();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
private putCoValue(
|
|
93
|
+
id: RawCoID,
|
|
94
|
+
verified: VerifiedState,
|
|
95
|
+
{ forceOverwrite = false }: { forceOverwrite?: boolean } = {},
|
|
96
|
+
): AvailableCoValueCore {
|
|
97
|
+
const entry = this.getCoValue(id);
|
|
98
|
+
entry.internalMarkMagicallyAvailable(verified, { forceOverwrite });
|
|
99
|
+
return entry as AvailableCoValueCore;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
internalDeleteCoValue(id: RawCoID) {
|
|
103
|
+
this.coValues.delete(id);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
getCurrentAgent(): ControlledAccountOrAgent {
|
|
107
|
+
const accountOrAgent = accountOrAgentIDfromSessionID(this.currentSessionID);
|
|
108
|
+
if (isAgentID(accountOrAgent)) {
|
|
109
|
+
return new ControlledAgent(this.agentSecret, this.crypto);
|
|
110
|
+
}
|
|
111
|
+
return new ControlledAccount(
|
|
112
|
+
expectAccount(
|
|
113
|
+
this.expectCoValueLoaded(accountOrAgent).getCurrentContent(),
|
|
114
|
+
),
|
|
115
|
+
this.agentSecret,
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
expectCurrentAccountID(reason: string): RawAccountID {
|
|
120
|
+
const accountOrAgent = accountOrAgentIDfromSessionID(this.currentSessionID);
|
|
121
|
+
if (isAgentID(accountOrAgent)) {
|
|
122
|
+
throw new Error(
|
|
123
|
+
"Current account is an agent, but expected an account: " + reason,
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
return accountOrAgent;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
expectCurrentAccount(reason: string): RawAccount {
|
|
130
|
+
const accountID = this.expectCurrentAccountID(reason);
|
|
131
|
+
return expectAccount(
|
|
132
|
+
this.expectCoValueLoaded(accountID).getCurrentContent(),
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
static internalCreateAccount(opts: {
|
|
137
|
+
crypto: CryptoProvider;
|
|
138
|
+
initialAgentSecret?: AgentSecret;
|
|
139
|
+
peersToLoadFrom?: Peer[];
|
|
140
|
+
}): RawAccount {
|
|
141
|
+
const {
|
|
142
|
+
crypto,
|
|
143
|
+
initialAgentSecret = crypto.newRandomAgentSecret(),
|
|
144
|
+
peersToLoadFrom = [],
|
|
145
|
+
} = opts;
|
|
146
|
+
const accountHeader = accountHeaderForInitialAgentSecret(
|
|
147
|
+
initialAgentSecret,
|
|
148
|
+
crypto,
|
|
149
|
+
);
|
|
150
|
+
const accountID = idforHeader(accountHeader, crypto);
|
|
151
|
+
|
|
152
|
+
const node = new LocalNode(
|
|
153
|
+
initialAgentSecret,
|
|
154
|
+
crypto.newRandomSessionID(accountID as RawAccountID),
|
|
155
|
+
crypto,
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
for (const peer of peersToLoadFrom) {
|
|
159
|
+
node.syncManager.addPeer(peer);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const accountAgentID = crypto.getAgentID(initialAgentSecret);
|
|
163
|
+
|
|
164
|
+
const rawAccount = expectGroup(
|
|
165
|
+
node.createCoValue(accountHeader).getCurrentContent(),
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
rawAccount.set(accountAgentID, "admin", "trusting");
|
|
169
|
+
|
|
170
|
+
const readKey = crypto.newRandomKeySecret();
|
|
171
|
+
|
|
172
|
+
const sealed = crypto.seal({
|
|
173
|
+
message: readKey.secret,
|
|
174
|
+
from: crypto.getAgentSealerSecret(initialAgentSecret),
|
|
175
|
+
to: crypto.getAgentSealerID(accountAgentID),
|
|
176
|
+
nOnceMaterial: {
|
|
177
|
+
in: rawAccount.id,
|
|
178
|
+
tx: rawAccount.core.nextTransactionID(),
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
rawAccount.set(`${readKey.id}_for_${accountAgentID}`, sealed, "trusting");
|
|
183
|
+
|
|
184
|
+
rawAccount.set("readKey", readKey.id, "trusting");
|
|
185
|
+
|
|
186
|
+
return node.expectCurrentAccount("after creation");
|
|
187
|
+
}
|
|
188
|
+
|
|
70
189
|
/** @category 2. Node Creation */
|
|
71
|
-
static async withNewlyCreatedAccount
|
|
190
|
+
static async withNewlyCreatedAccount({
|
|
72
191
|
creationProps,
|
|
73
192
|
peersToLoadFrom,
|
|
74
193
|
migration,
|
|
@@ -77,7 +196,7 @@ export class LocalNode {
|
|
|
77
196
|
}: {
|
|
78
197
|
creationProps: { name: string };
|
|
79
198
|
peersToLoadFrom?: Peer[];
|
|
80
|
-
migration?: RawAccountMigration<
|
|
199
|
+
migration?: RawAccountMigration<AccountMeta>;
|
|
81
200
|
crypto: CryptoProvider;
|
|
82
201
|
initialAgentSecret?: AgentSecret;
|
|
83
202
|
}): Promise<{
|
|
@@ -86,81 +205,47 @@ export class LocalNode {
|
|
|
86
205
|
accountSecret: AgentSecret;
|
|
87
206
|
sessionID: SessionID;
|
|
88
207
|
}> {
|
|
89
|
-
const
|
|
90
|
-
const setupNode = new LocalNode(
|
|
91
|
-
new ControlledAgent(throwawayAgent, crypto),
|
|
92
|
-
crypto.newRandomSessionID(crypto.getAgentID(throwawayAgent)),
|
|
208
|
+
const account = LocalNode.internalCreateAccount({
|
|
93
209
|
crypto,
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
const nodeWithAccount = account.core.node.testWithDifferentAccount(
|
|
99
|
-
account,
|
|
100
|
-
crypto.newRandomSessionID(account.id),
|
|
101
|
-
);
|
|
102
|
-
|
|
103
|
-
const accountOnNodeWithAccount =
|
|
104
|
-
nodeWithAccount.account as RawControlledAccount<Meta>;
|
|
105
|
-
|
|
106
|
-
if (peersToLoadFrom) {
|
|
107
|
-
for (const peer of peersToLoadFrom) {
|
|
108
|
-
nodeWithAccount.syncManager.addPeer(peer);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
210
|
+
initialAgentSecret,
|
|
211
|
+
peersToLoadFrom,
|
|
212
|
+
});
|
|
213
|
+
const node = account.core.node;
|
|
111
214
|
|
|
112
215
|
if (migration) {
|
|
113
|
-
await migration(
|
|
216
|
+
await migration(account, node, creationProps);
|
|
114
217
|
} else {
|
|
115
|
-
const profileGroup =
|
|
218
|
+
const profileGroup = node.createGroup();
|
|
116
219
|
profileGroup.addMember("everyone", "reader");
|
|
117
220
|
const profile = profileGroup.createMap<Profile>({
|
|
118
221
|
name: creationProps.name,
|
|
119
222
|
});
|
|
120
|
-
|
|
223
|
+
account.set("profile", profile.id, "trusting");
|
|
121
224
|
}
|
|
122
225
|
|
|
123
|
-
const
|
|
124
|
-
accountOnNodeWithAccount.core,
|
|
125
|
-
accountOnNodeWithAccount.agentSecret,
|
|
126
|
-
);
|
|
226
|
+
const profileId = account.get("profile");
|
|
127
227
|
|
|
128
|
-
|
|
129
|
-
nodeWithAccount.coValuesStore.internalMarkMagicallyAvailable(
|
|
130
|
-
controlledAccount.id,
|
|
131
|
-
controlledAccount.core,
|
|
132
|
-
);
|
|
133
|
-
controlledAccount.core._cachedContent = undefined;
|
|
134
|
-
|
|
135
|
-
if (!controlledAccount.get("profile")) {
|
|
228
|
+
if (!profileId) {
|
|
136
229
|
throw new Error("Must set account profile in initial migration");
|
|
137
230
|
}
|
|
138
231
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
coValueEntry.core,
|
|
145
|
-
);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
232
|
+
if (node.syncManager.hasStoragePeers()) {
|
|
233
|
+
await Promise.all([
|
|
234
|
+
node.syncManager.waitForStorageSync(account.id),
|
|
235
|
+
node.syncManager.waitForStorageSync(profileId),
|
|
236
|
+
]);
|
|
148
237
|
}
|
|
149
238
|
|
|
150
|
-
syncAllCoValuesAfterCreateAccount();
|
|
151
|
-
|
|
152
|
-
setTimeout(syncAllCoValuesAfterCreateAccount, 500);
|
|
153
|
-
|
|
154
239
|
return {
|
|
155
|
-
node
|
|
156
|
-
accountID:
|
|
157
|
-
accountSecret:
|
|
158
|
-
sessionID:
|
|
240
|
+
node,
|
|
241
|
+
accountID: account.id,
|
|
242
|
+
accountSecret: initialAgentSecret,
|
|
243
|
+
sessionID: node.currentSessionID,
|
|
159
244
|
};
|
|
160
245
|
}
|
|
161
246
|
|
|
162
247
|
/** @category 2. Node Creation */
|
|
163
|
-
static async withLoadedAccount
|
|
248
|
+
static async withLoadedAccount({
|
|
164
249
|
accountID,
|
|
165
250
|
accountSecret,
|
|
166
251
|
sessionID,
|
|
@@ -173,63 +258,35 @@ export class LocalNode {
|
|
|
173
258
|
sessionID: SessionID | undefined;
|
|
174
259
|
peersToLoadFrom: Peer[];
|
|
175
260
|
crypto: CryptoProvider;
|
|
176
|
-
migration?: RawAccountMigration<
|
|
261
|
+
migration?: RawAccountMigration<AccountMeta>;
|
|
177
262
|
}): Promise<LocalNode> {
|
|
178
263
|
try {
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
crypto.newRandomSessionID(accountID),
|
|
264
|
+
const node = new LocalNode(
|
|
265
|
+
accountSecret,
|
|
266
|
+
sessionID || crypto.newRandomSessionID(accountID),
|
|
182
267
|
crypto,
|
|
183
268
|
);
|
|
184
269
|
|
|
185
270
|
for (const peer of peersToLoadFrom) {
|
|
186
|
-
|
|
271
|
+
node.syncManager.addPeer(peer);
|
|
187
272
|
}
|
|
188
273
|
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
const account = await accountPromise;
|
|
274
|
+
const account = await node.load(accountID);
|
|
192
275
|
|
|
193
276
|
if (account === "unavailable") {
|
|
194
277
|
throw new Error("Account unavailable from all peers");
|
|
195
278
|
}
|
|
196
279
|
|
|
197
|
-
const controlledAccount = new RawControlledAccount(
|
|
198
|
-
account.core,
|
|
199
|
-
accountSecret,
|
|
200
|
-
);
|
|
201
|
-
|
|
202
|
-
// since this is all synchronous, we can just swap out nodes for the SyncManager
|
|
203
|
-
const node = loadingNode.testWithDifferentAccount(
|
|
204
|
-
controlledAccount,
|
|
205
|
-
sessionID || crypto.newRandomSessionID(accountID),
|
|
206
|
-
);
|
|
207
|
-
node.syncManager = loadingNode.syncManager;
|
|
208
|
-
node.syncManager.local = node;
|
|
209
|
-
|
|
210
|
-
controlledAccount.core.node = node;
|
|
211
|
-
node.coValuesStore.internalMarkMagicallyAvailable(
|
|
212
|
-
accountID,
|
|
213
|
-
controlledAccount.core,
|
|
214
|
-
);
|
|
215
|
-
controlledAccount.core._cachedContent = undefined;
|
|
216
|
-
|
|
217
280
|
const profileID = account.get("profile");
|
|
218
281
|
if (!profileID) {
|
|
219
282
|
throw new Error("Account has no profile");
|
|
220
283
|
}
|
|
221
|
-
const profile = await node.load(profileID);
|
|
222
284
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
}
|
|
285
|
+
// Preload the profile
|
|
286
|
+
await node.load(profileID);
|
|
226
287
|
|
|
227
288
|
if (migration) {
|
|
228
|
-
await migration(
|
|
229
|
-
node.account = new RawControlledAccount(
|
|
230
|
-
controlledAccount.core,
|
|
231
|
-
controlledAccount.agentSecret,
|
|
232
|
-
);
|
|
289
|
+
await migration(account, node);
|
|
233
290
|
}
|
|
234
291
|
|
|
235
292
|
return node;
|
|
@@ -240,15 +297,19 @@ export class LocalNode {
|
|
|
240
297
|
}
|
|
241
298
|
|
|
242
299
|
/** @internal */
|
|
243
|
-
createCoValue(header: CoValueHeader):
|
|
300
|
+
createCoValue(header: CoValueHeader): AvailableCoValueCore {
|
|
244
301
|
if (this.crashed) {
|
|
245
302
|
throw new Error("Trying to create CoValue after node has crashed", {
|
|
246
303
|
cause: this.crashed,
|
|
247
304
|
});
|
|
248
305
|
}
|
|
249
306
|
|
|
250
|
-
const
|
|
251
|
-
|
|
307
|
+
const id = idforHeader(header, this.crypto);
|
|
308
|
+
|
|
309
|
+
const coValue = this.putCoValue(
|
|
310
|
+
id,
|
|
311
|
+
new VerifiedState(id, this.crypto, header, new Map()),
|
|
312
|
+
);
|
|
252
313
|
|
|
253
314
|
void this.syncManager.requestCoValueSync(coValue);
|
|
254
315
|
|
|
@@ -259,7 +320,7 @@ export class LocalNode {
|
|
|
259
320
|
async loadCoValueCore(
|
|
260
321
|
id: RawCoID,
|
|
261
322
|
skipLoadingFromPeer?: PeerID,
|
|
262
|
-
): Promise<CoValueCore
|
|
323
|
+
): Promise<CoValueCore> {
|
|
263
324
|
if (this.crashed) {
|
|
264
325
|
throw new Error("Trying to load CoValue after node has crashed", {
|
|
265
326
|
cause: this.crashed,
|
|
@@ -269,20 +330,20 @@ export class LocalNode {
|
|
|
269
330
|
let retries = 0;
|
|
270
331
|
|
|
271
332
|
while (true) {
|
|
272
|
-
const
|
|
333
|
+
const coValue = this.getCoValue(id);
|
|
273
334
|
|
|
274
335
|
if (
|
|
275
|
-
|
|
276
|
-
|
|
336
|
+
coValue.loadingState === "unknown" ||
|
|
337
|
+
coValue.loadingState === "unavailable"
|
|
277
338
|
) {
|
|
278
339
|
const peers =
|
|
279
340
|
this.syncManager.getServerAndStoragePeers(skipLoadingFromPeer);
|
|
280
341
|
|
|
281
342
|
if (peers.length === 0) {
|
|
282
|
-
return
|
|
343
|
+
return coValue;
|
|
283
344
|
}
|
|
284
345
|
|
|
285
|
-
|
|
346
|
+
coValue.loadFromPeers(peers).catch((e) => {
|
|
286
347
|
logger.error("Error loading from peers", {
|
|
287
348
|
id,
|
|
288
349
|
err: e,
|
|
@@ -290,9 +351,8 @@ export class LocalNode {
|
|
|
290
351
|
});
|
|
291
352
|
}
|
|
292
353
|
|
|
293
|
-
const result = await
|
|
294
|
-
|
|
295
|
-
if (result !== "unavailable" || retries >= 1) {
|
|
354
|
+
const result = await coValue.waitForAvailableOrUnavailable();
|
|
355
|
+
if (result.isAvailable() || retries >= 1) {
|
|
296
356
|
return result;
|
|
297
357
|
}
|
|
298
358
|
|
|
@@ -320,7 +380,7 @@ export class LocalNode {
|
|
|
320
380
|
|
|
321
381
|
const core = await this.loadCoValueCore(id);
|
|
322
382
|
|
|
323
|
-
if (core
|
|
383
|
+
if (!core.isAvailable()) {
|
|
324
384
|
return "unavailable";
|
|
325
385
|
}
|
|
326
386
|
|
|
@@ -328,10 +388,10 @@ export class LocalNode {
|
|
|
328
388
|
}
|
|
329
389
|
|
|
330
390
|
getLoaded<T extends RawCoValue>(id: CoID<T>): T | undefined {
|
|
331
|
-
const
|
|
391
|
+
const coValue = this.getCoValue(id);
|
|
332
392
|
|
|
333
|
-
if (
|
|
334
|
-
return
|
|
393
|
+
if (coValue.isAvailable()) {
|
|
394
|
+
return coValue.getCurrentContent() as T;
|
|
335
395
|
}
|
|
336
396
|
|
|
337
397
|
return undefined;
|
|
@@ -369,7 +429,6 @@ export class LocalNode {
|
|
|
369
429
|
};
|
|
370
430
|
}
|
|
371
431
|
|
|
372
|
-
/** @deprecated Use Account.acceptInvite instead */
|
|
373
432
|
async acceptInvite<T extends RawCoValue>(
|
|
374
433
|
groupOrOwnedValueID: CoID<T>,
|
|
375
434
|
inviteSecret: InviteSecret,
|
|
@@ -382,12 +441,16 @@ export class LocalNode {
|
|
|
382
441
|
);
|
|
383
442
|
}
|
|
384
443
|
|
|
385
|
-
if (
|
|
444
|
+
if (
|
|
445
|
+
groupOrOwnedValue.core.verified.header.ruleset.type === "ownedByGroup"
|
|
446
|
+
) {
|
|
386
447
|
return this.acceptInvite(
|
|
387
|
-
groupOrOwnedValue.core.header.ruleset.group as CoID<RawGroup>,
|
|
448
|
+
groupOrOwnedValue.core.verified.header.ruleset.group as CoID<RawGroup>,
|
|
388
449
|
inviteSecret,
|
|
389
450
|
);
|
|
390
|
-
} else if (
|
|
451
|
+
} else if (
|
|
452
|
+
groupOrOwnedValue.core.verified.header.ruleset.type !== "group"
|
|
453
|
+
) {
|
|
391
454
|
throw new Error("Can only accept invites to groups");
|
|
392
455
|
}
|
|
393
456
|
|
|
@@ -415,7 +478,8 @@ export class LocalNode {
|
|
|
415
478
|
throw new Error("No invite found");
|
|
416
479
|
}
|
|
417
480
|
|
|
418
|
-
const
|
|
481
|
+
const account = this.getCurrentAgent();
|
|
482
|
+
const existingRole = group.get(account.id);
|
|
419
483
|
|
|
420
484
|
if (
|
|
421
485
|
existingRole === "admin" ||
|
|
@@ -429,16 +493,13 @@ export class LocalNode {
|
|
|
429
493
|
}
|
|
430
494
|
|
|
431
495
|
const groupAsInvite = expectGroup(
|
|
432
|
-
group.core
|
|
433
|
-
.
|
|
434
|
-
|
|
435
|
-
this.crypto.newRandomSessionID(inviteAgentID),
|
|
436
|
-
)
|
|
437
|
-
.getCurrentContent(),
|
|
496
|
+
group.core.contentInClonedNodeWithDifferentAccount(
|
|
497
|
+
new ControlledAgent(inviteAgentSecret, this.crypto),
|
|
498
|
+
),
|
|
438
499
|
);
|
|
439
500
|
|
|
440
501
|
groupAsInvite.addMemberInternal(
|
|
441
|
-
|
|
502
|
+
account,
|
|
442
503
|
inviteRole === "adminInvite"
|
|
443
504
|
? "admin"
|
|
444
505
|
: inviteRole === "writerInvite"
|
|
@@ -448,24 +509,25 @@ export class LocalNode {
|
|
|
448
509
|
: "reader",
|
|
449
510
|
);
|
|
450
511
|
|
|
451
|
-
group.core.
|
|
452
|
-
|
|
512
|
+
group.core.internalShamefullyCloneVerifiedStateFrom(
|
|
513
|
+
groupAsInvite.core.verified,
|
|
514
|
+
{ forceOverwrite: true },
|
|
515
|
+
);
|
|
516
|
+
group.core.internalShamefullyResetCachedContent();
|
|
453
517
|
|
|
454
|
-
|
|
455
|
-
groupListener(group.core.getCurrentContent());
|
|
456
|
-
}
|
|
518
|
+
group.core.notifyUpdate("immediate");
|
|
457
519
|
}
|
|
458
520
|
|
|
459
521
|
/** @internal */
|
|
460
|
-
expectCoValueLoaded(id: RawCoID, expectation?: string):
|
|
461
|
-
const
|
|
522
|
+
expectCoValueLoaded(id: RawCoID, expectation?: string): AvailableCoValueCore {
|
|
523
|
+
const coValue = this.getCoValue(id);
|
|
462
524
|
|
|
463
|
-
if (!
|
|
525
|
+
if (!coValue.isAvailable()) {
|
|
464
526
|
throw new Error(
|
|
465
|
-
`${expectation ? expectation + ": " : ""}CoValue ${id} not yet loaded. Current state: ${JSON.stringify(
|
|
527
|
+
`${expectation ? expectation + ": " : ""}CoValue ${id} not yet loaded. Current state: ${JSON.stringify(coValue)}`,
|
|
466
528
|
);
|
|
467
529
|
}
|
|
468
|
-
return
|
|
530
|
+
return coValue;
|
|
469
531
|
}
|
|
470
532
|
|
|
471
533
|
/** @internal */
|
|
@@ -483,49 +545,6 @@ export class LocalNode {
|
|
|
483
545
|
).getCurrentContent() as RawProfile;
|
|
484
546
|
}
|
|
485
547
|
|
|
486
|
-
/** @internal */
|
|
487
|
-
createAccount(
|
|
488
|
-
agentSecret = this.crypto.newRandomAgentSecret(),
|
|
489
|
-
): RawControlledAccount {
|
|
490
|
-
const accountAgentID = this.crypto.getAgentID(agentSecret);
|
|
491
|
-
const account = expectGroup(
|
|
492
|
-
this.createCoValue(
|
|
493
|
-
accountHeaderForInitialAgentSecret(agentSecret, this.crypto),
|
|
494
|
-
)
|
|
495
|
-
.testWithDifferentAccount(
|
|
496
|
-
new ControlledAgent(agentSecret, this.crypto),
|
|
497
|
-
this.crypto.newRandomSessionID(accountAgentID),
|
|
498
|
-
)
|
|
499
|
-
.getCurrentContent(),
|
|
500
|
-
);
|
|
501
|
-
|
|
502
|
-
account.set(accountAgentID, "admin", "trusting");
|
|
503
|
-
|
|
504
|
-
const readKey = this.crypto.newRandomKeySecret();
|
|
505
|
-
|
|
506
|
-
const sealed = this.crypto.seal({
|
|
507
|
-
message: readKey.secret,
|
|
508
|
-
from: this.crypto.getAgentSealerSecret(agentSecret),
|
|
509
|
-
to: this.crypto.getAgentSealerID(accountAgentID),
|
|
510
|
-
nOnceMaterial: {
|
|
511
|
-
in: account.id,
|
|
512
|
-
tx: account.core.nextTransactionID(),
|
|
513
|
-
},
|
|
514
|
-
});
|
|
515
|
-
|
|
516
|
-
account.set(`${readKey.id}_for_${accountAgentID}`, sealed, "trusting");
|
|
517
|
-
|
|
518
|
-
account.set("readKey", readKey.id, "trusting");
|
|
519
|
-
|
|
520
|
-
const accountOnThisNode = this.expectCoValueLoaded(account.id);
|
|
521
|
-
|
|
522
|
-
accountOnThisNode._sessionLogs = new Map(account.core.sessionLogs);
|
|
523
|
-
|
|
524
|
-
accountOnThisNode._cachedContent = undefined;
|
|
525
|
-
|
|
526
|
-
return new RawControlledAccount(accountOnThisNode, agentSecret);
|
|
527
|
-
}
|
|
528
|
-
|
|
529
548
|
/** @internal */
|
|
530
549
|
resolveAccountAgent(
|
|
531
550
|
id: RawAccountID | AgentID,
|
|
@@ -535,7 +554,7 @@ export class LocalNode {
|
|
|
535
554
|
return ok(id);
|
|
536
555
|
}
|
|
537
556
|
|
|
538
|
-
let coValue:
|
|
557
|
+
let coValue: AvailableCoValueCore;
|
|
539
558
|
|
|
540
559
|
try {
|
|
541
560
|
coValue = this.expectCoValueLoaded(id, expectation);
|
|
@@ -549,11 +568,11 @@ export class LocalNode {
|
|
|
549
568
|
}
|
|
550
569
|
|
|
551
570
|
if (
|
|
552
|
-
coValue.header.type !== "comap" ||
|
|
553
|
-
coValue.header.ruleset.type !== "group" ||
|
|
554
|
-
!coValue.header.meta ||
|
|
555
|
-
!("type" in coValue.header.meta) ||
|
|
556
|
-
coValue.header.meta.type !== "account"
|
|
571
|
+
coValue.verified.header.type !== "comap" ||
|
|
572
|
+
coValue.verified.header.ruleset.type !== "group" ||
|
|
573
|
+
!coValue.verified.header.meta ||
|
|
574
|
+
!("type" in coValue.verified.header.meta) ||
|
|
575
|
+
coValue.verified.header.meta.type !== "account"
|
|
557
576
|
) {
|
|
558
577
|
return err({
|
|
559
578
|
type: "UnexpectedlyNotAccount",
|
|
@@ -565,75 +584,30 @@ export class LocalNode {
|
|
|
565
584
|
return ok((coValue.getCurrentContent() as RawAccount).currentAgentID());
|
|
566
585
|
}
|
|
567
586
|
|
|
568
|
-
resolveAccountAgentAsync(
|
|
569
|
-
id: RawAccountID | AgentID,
|
|
570
|
-
expectation?: string,
|
|
571
|
-
): ResultAsync<AgentID, ResolveAccountAgentError> {
|
|
572
|
-
if (isAgentID(id)) {
|
|
573
|
-
return okAsync(id);
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
return ResultAsync.fromPromise(
|
|
577
|
-
this.loadCoValueCore(id),
|
|
578
|
-
(e) =>
|
|
579
|
-
({
|
|
580
|
-
type: "ErrorLoadingCoValueCore",
|
|
581
|
-
expectation,
|
|
582
|
-
id,
|
|
583
|
-
error: e,
|
|
584
|
-
}) satisfies LoadCoValueCoreError,
|
|
585
|
-
).andThen((coValue) => {
|
|
586
|
-
if (coValue === "unavailable") {
|
|
587
|
-
return err({
|
|
588
|
-
type: "AccountUnavailableFromAllPeers" as const,
|
|
589
|
-
expectation,
|
|
590
|
-
id,
|
|
591
|
-
} satisfies AccountUnavailableFromAllPeersError);
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
if (
|
|
595
|
-
coValue.header.type !== "comap" ||
|
|
596
|
-
coValue.header.ruleset.type !== "group" ||
|
|
597
|
-
!coValue.header.meta ||
|
|
598
|
-
!("type" in coValue.header.meta) ||
|
|
599
|
-
coValue.header.meta.type !== "account"
|
|
600
|
-
) {
|
|
601
|
-
return err({
|
|
602
|
-
type: "UnexpectedlyNotAccount" as const,
|
|
603
|
-
expectation,
|
|
604
|
-
id,
|
|
605
|
-
} satisfies UnexpectedlyNotAccountError);
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
return ok((coValue.getCurrentContent() as RawAccount).currentAgentID());
|
|
609
|
-
});
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
/**
|
|
613
|
-
* @deprecated use Account.createGroup() instead
|
|
614
|
-
*/
|
|
615
587
|
createGroup(
|
|
616
588
|
uniqueness: CoValueUniqueness = this.crypto.createdNowUnique(),
|
|
617
589
|
): RawGroup {
|
|
590
|
+
const account = this.getCurrentAgent();
|
|
591
|
+
|
|
618
592
|
const groupCoValue = this.createCoValue({
|
|
619
593
|
type: "comap",
|
|
620
|
-
ruleset: { type: "group", initialAdmin:
|
|
594
|
+
ruleset: { type: "group", initialAdmin: account.id },
|
|
621
595
|
meta: null,
|
|
622
596
|
...uniqueness,
|
|
623
597
|
});
|
|
624
598
|
|
|
625
599
|
const group = expectGroup(groupCoValue.getCurrentContent());
|
|
626
600
|
|
|
627
|
-
group.set(
|
|
601
|
+
group.set(account.id, "admin", "trusting");
|
|
628
602
|
|
|
629
603
|
const readKey = this.crypto.newRandomKeySecret();
|
|
630
604
|
|
|
631
605
|
group.set(
|
|
632
|
-
`${readKey.id}_for_${
|
|
606
|
+
`${readKey.id}_for_${account.id}`,
|
|
633
607
|
this.crypto.seal({
|
|
634
608
|
message: readKey.secret,
|
|
635
|
-
from:
|
|
636
|
-
to:
|
|
609
|
+
from: account.currentSealerSecret(),
|
|
610
|
+
to: account.currentSealerID(),
|
|
637
611
|
nOnceMaterial: {
|
|
638
612
|
in: groupCoValue.id,
|
|
639
613
|
tx: groupCoValue.nextTransactionID(),
|
|
@@ -648,24 +622,34 @@ export class LocalNode {
|
|
|
648
622
|
}
|
|
649
623
|
|
|
650
624
|
/** @internal */
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
currentSessionID: SessionID,
|
|
625
|
+
cloneWithDifferentAccount(
|
|
626
|
+
controlledAccountOrAgent: ControlledAccountOrAgent,
|
|
654
627
|
): LocalNode {
|
|
655
|
-
const newNode = new LocalNode(
|
|
628
|
+
const newNode = new LocalNode(
|
|
629
|
+
controlledAccountOrAgent.agentSecret,
|
|
630
|
+
this.crypto.newRandomSessionID(controlledAccountOrAgent.id),
|
|
631
|
+
this.crypto,
|
|
632
|
+
);
|
|
633
|
+
|
|
634
|
+
newNode.cloneVerifiedStateFrom(this);
|
|
635
|
+
|
|
636
|
+
return newNode;
|
|
637
|
+
}
|
|
656
638
|
|
|
657
|
-
|
|
639
|
+
/** @internal */
|
|
640
|
+
cloneVerifiedStateFrom(otherNode: LocalNode) {
|
|
641
|
+
const coValuesToCopy = Array.from(otherNode.coValues.entries());
|
|
658
642
|
|
|
659
643
|
while (coValuesToCopy.length > 0) {
|
|
660
|
-
const [coValueID,
|
|
644
|
+
const [coValueID, coValue] = coValuesToCopy[coValuesToCopy.length - 1]!;
|
|
661
645
|
|
|
662
|
-
if (!
|
|
646
|
+
if (!coValue.isAvailable()) {
|
|
663
647
|
coValuesToCopy.pop();
|
|
664
648
|
continue;
|
|
665
649
|
} else {
|
|
666
|
-
const allDepsCopied =
|
|
650
|
+
const allDepsCopied = coValue
|
|
667
651
|
.getDependedOnCoValues()
|
|
668
|
-
.every((dep) =>
|
|
652
|
+
.every((dep) => this.coValues.get(dep)?.isAvailable());
|
|
669
653
|
|
|
670
654
|
if (!allDepsCopied) {
|
|
671
655
|
// move to end of queue
|
|
@@ -673,34 +657,11 @@ export class LocalNode {
|
|
|
673
657
|
continue;
|
|
674
658
|
}
|
|
675
659
|
|
|
676
|
-
|
|
677
|
-
entry.core.header,
|
|
678
|
-
newNode,
|
|
679
|
-
new Map(entry.core.sessionLogs),
|
|
680
|
-
);
|
|
681
|
-
|
|
682
|
-
newNode.coValuesStore.internalMarkMagicallyAvailable(
|
|
683
|
-
coValueID,
|
|
684
|
-
newCoValue,
|
|
685
|
-
);
|
|
660
|
+
this.putCoValue(coValueID, coValue.verified);
|
|
686
661
|
|
|
687
662
|
coValuesToCopy.pop();
|
|
688
663
|
}
|
|
689
664
|
}
|
|
690
|
-
|
|
691
|
-
if (account instanceof RawControlledAccount) {
|
|
692
|
-
// To make sure that when we edit the account, we're modifying the correct sessions
|
|
693
|
-
const accountInNode = new RawControlledAccount(
|
|
694
|
-
newNode.expectCoValueLoaded(account.id),
|
|
695
|
-
account.agentSecret,
|
|
696
|
-
);
|
|
697
|
-
if (accountInNode.core.node !== newNode) {
|
|
698
|
-
throw new Error("Account's node is not the new node");
|
|
699
|
-
}
|
|
700
|
-
newNode.account = accountInNode;
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
return newNode;
|
|
704
665
|
}
|
|
705
666
|
|
|
706
667
|
gracefulShutdown() {
|