cojson 0.8.12 → 0.8.17
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/CHANGELOG.md +95 -83
- package/dist/native/PeerKnownStates.js +6 -1
- package/dist/native/PeerKnownStates.js.map +1 -1
- package/dist/native/PeerState.js +4 -3
- package/dist/native/PeerState.js.map +1 -1
- package/dist/native/PriorityBasedMessageQueue.js +1 -10
- package/dist/native/PriorityBasedMessageQueue.js.map +1 -1
- package/dist/native/SyncStateSubscriptionManager.js +70 -0
- package/dist/native/SyncStateSubscriptionManager.js.map +1 -0
- package/dist/native/base64url.js.map +1 -1
- package/dist/native/base64url.test.js +1 -1
- package/dist/native/base64url.test.js.map +1 -1
- package/dist/native/coValue.js.map +1 -1
- package/dist/native/coValueCore.js +141 -149
- package/dist/native/coValueCore.js.map +1 -1
- package/dist/native/coValueState.js.map +1 -1
- package/dist/native/coValues/account.js +6 -6
- package/dist/native/coValues/account.js.map +1 -1
- package/dist/native/coValues/coList.js +2 -3
- package/dist/native/coValues/coList.js.map +1 -1
- package/dist/native/coValues/coMap.js +1 -1
- package/dist/native/coValues/coMap.js.map +1 -1
- package/dist/native/coValues/coStream.js +3 -5
- package/dist/native/coValues/coStream.js.map +1 -1
- package/dist/native/coValues/group.js +11 -11
- package/dist/native/coValues/group.js.map +1 -1
- package/dist/native/coreToCoValue.js +2 -2
- package/dist/native/coreToCoValue.js.map +1 -1
- package/dist/native/crypto/PureJSCrypto.js +4 -4
- package/dist/native/crypto/PureJSCrypto.js.map +1 -1
- package/dist/native/crypto/crypto.js.map +1 -1
- package/dist/native/exports.js +12 -12
- package/dist/native/exports.js.map +1 -1
- package/dist/native/ids.js.map +1 -1
- package/dist/native/jsonStringify.js.map +1 -1
- package/dist/native/localNode.js +5 -7
- package/dist/native/localNode.js.map +1 -1
- package/dist/native/permissions.js +4 -7
- package/dist/native/permissions.js.map +1 -1
- package/dist/native/priority.js.map +1 -1
- package/dist/native/storage/FileSystem.js.map +1 -1
- package/dist/native/storage/chunksAndKnownStates.js +2 -4
- package/dist/native/storage/chunksAndKnownStates.js.map +1 -1
- package/dist/native/storage/index.js +6 -15
- package/dist/native/storage/index.js.map +1 -1
- package/dist/native/streamUtils.js.map +1 -1
- package/dist/native/sync.js +57 -7
- package/dist/native/sync.js.map +1 -1
- package/dist/native/typeUtils/accountOrAgentIDfromSessionID.js.map +1 -1
- package/dist/native/typeUtils/expectGroup.js.map +1 -1
- package/dist/native/typeUtils/isAccountID.js.map +1 -1
- package/dist/native/typeUtils/isCoValue.js +1 -1
- package/dist/native/typeUtils/isCoValue.js.map +1 -1
- package/dist/web/PeerKnownStates.js +6 -1
- package/dist/web/PeerKnownStates.js.map +1 -1
- package/dist/web/PeerState.js +4 -3
- package/dist/web/PeerState.js.map +1 -1
- package/dist/web/PriorityBasedMessageQueue.js +1 -10
- package/dist/web/PriorityBasedMessageQueue.js.map +1 -1
- package/dist/web/SyncStateSubscriptionManager.js +70 -0
- package/dist/web/SyncStateSubscriptionManager.js.map +1 -0
- package/dist/web/base64url.js.map +1 -1
- package/dist/web/base64url.test.js +1 -1
- package/dist/web/base64url.test.js.map +1 -1
- package/dist/web/coValue.js.map +1 -1
- package/dist/web/coValueCore.js +141 -149
- package/dist/web/coValueCore.js.map +1 -1
- package/dist/web/coValueState.js.map +1 -1
- package/dist/web/coValues/account.js +6 -6
- package/dist/web/coValues/account.js.map +1 -1
- package/dist/web/coValues/coList.js +2 -3
- package/dist/web/coValues/coList.js.map +1 -1
- package/dist/web/coValues/coMap.js +1 -1
- package/dist/web/coValues/coMap.js.map +1 -1
- package/dist/web/coValues/coStream.js +3 -5
- package/dist/web/coValues/coStream.js.map +1 -1
- package/dist/web/coValues/group.js +11 -11
- package/dist/web/coValues/group.js.map +1 -1
- package/dist/web/coreToCoValue.js +2 -2
- package/dist/web/coreToCoValue.js.map +1 -1
- package/dist/web/crypto/PureJSCrypto.js +4 -4
- package/dist/web/crypto/PureJSCrypto.js.map +1 -1
- package/dist/web/crypto/WasmCrypto.js +5 -5
- package/dist/web/crypto/WasmCrypto.js.map +1 -1
- package/dist/web/crypto/crypto.js.map +1 -1
- package/dist/web/exports.js +12 -12
- package/dist/web/exports.js.map +1 -1
- package/dist/web/ids.js.map +1 -1
- package/dist/web/jsonStringify.js.map +1 -1
- package/dist/web/localNode.js +5 -7
- package/dist/web/localNode.js.map +1 -1
- package/dist/web/permissions.js +4 -7
- package/dist/web/permissions.js.map +1 -1
- package/dist/web/priority.js.map +1 -1
- package/dist/web/storage/FileSystem.js.map +1 -1
- package/dist/web/storage/chunksAndKnownStates.js +2 -4
- package/dist/web/storage/chunksAndKnownStates.js.map +1 -1
- package/dist/web/storage/index.js +6 -15
- package/dist/web/storage/index.js.map +1 -1
- package/dist/web/streamUtils.js.map +1 -1
- package/dist/web/sync.js +57 -7
- package/dist/web/sync.js.map +1 -1
- package/dist/web/typeUtils/accountOrAgentIDfromSessionID.js.map +1 -1
- package/dist/web/typeUtils/expectGroup.js.map +1 -1
- package/dist/web/typeUtils/isAccountID.js.map +1 -1
- package/dist/web/typeUtils/isCoValue.js +1 -1
- package/dist/web/typeUtils/isCoValue.js.map +1 -1
- package/package.json +4 -14
- package/src/PeerKnownStates.ts +98 -90
- package/src/PeerState.ts +92 -73
- package/src/PriorityBasedMessageQueue.ts +42 -49
- package/src/SyncStateSubscriptionManager.ts +124 -0
- package/src/base64url.test.ts +24 -24
- package/src/base64url.ts +44 -45
- package/src/coValue.ts +45 -45
- package/src/coValueCore.ts +746 -785
- package/src/coValueState.ts +82 -72
- package/src/coValues/account.ts +143 -150
- package/src/coValues/coList.ts +520 -522
- package/src/coValues/coMap.ts +283 -285
- package/src/coValues/coStream.ts +320 -324
- package/src/coValues/group.ts +306 -305
- package/src/coreToCoValue.ts +28 -31
- package/src/crypto/PureJSCrypto.ts +188 -194
- package/src/crypto/WasmCrypto.ts +236 -254
- package/src/crypto/crypto.ts +302 -309
- package/src/exports.ts +116 -116
- package/src/ids.ts +9 -9
- package/src/jsonStringify.ts +46 -46
- package/src/jsonValue.ts +24 -10
- package/src/localNode.ts +635 -660
- package/src/media.ts +3 -3
- package/src/permissions.ts +272 -278
- package/src/priority.ts +21 -19
- package/src/storage/FileSystem.ts +91 -99
- package/src/storage/chunksAndKnownStates.ts +110 -115
- package/src/storage/index.ts +466 -497
- package/src/streamUtils.ts +60 -60
- package/src/sync.ts +656 -608
- package/src/tests/PeerKnownStates.test.ts +38 -34
- package/src/tests/PeerState.test.ts +101 -64
- package/src/tests/PriorityBasedMessageQueue.test.ts +91 -91
- package/src/tests/SyncStateSubscriptionManager.test.ts +232 -0
- package/src/tests/account.test.ts +59 -59
- package/src/tests/coList.test.ts +65 -65
- package/src/tests/coMap.test.ts +137 -137
- package/src/tests/coStream.test.ts +254 -257
- package/src/tests/coValueCore.test.ts +153 -156
- package/src/tests/crypto.test.ts +136 -144
- package/src/tests/cryptoImpl.test.ts +205 -197
- package/src/tests/group.test.ts +24 -24
- package/src/tests/permissions.test.ts +1306 -1371
- package/src/tests/priority.test.ts +65 -82
- package/src/tests/sync.test.ts +1573 -1263
- package/src/tests/testUtils.ts +85 -53
- package/src/typeUtils/accountOrAgentIDfromSessionID.ts +4 -4
- package/src/typeUtils/expectGroup.ts +9 -9
- package/src/typeUtils/isAccountID.ts +1 -1
- package/src/typeUtils/isCoValue.ts +9 -9
- package/tsconfig.json +4 -6
- package/tsconfig.native.json +9 -11
- package/tsconfig.web.json +4 -10
- package/.eslintrc.cjs +0 -25
- package/.prettierrc.js +0 -9
package/src/localNode.ts
CHANGED
|
@@ -1,34 +1,34 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Result, ResultAsync, err, ok, okAsync } from "neverthrow";
|
|
2
|
+
import { CoID } from "./coValue.js";
|
|
3
|
+
import { RawCoValue } from "./coValue.js";
|
|
2
4
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
5
|
+
CoValueCore,
|
|
6
|
+
CoValueHeader,
|
|
7
|
+
CoValueUniqueness,
|
|
6
8
|
} from "./coValueCore.js";
|
|
9
|
+
import { CoValueState } from "./coValueState.js";
|
|
10
|
+
import {
|
|
11
|
+
AccountMeta,
|
|
12
|
+
ControlledAccountOrAgent,
|
|
13
|
+
ControlledAgent,
|
|
14
|
+
InvalidAccountAgentIDError,
|
|
15
|
+
RawProfile as Profile,
|
|
16
|
+
RawAccount,
|
|
17
|
+
RawAccountID,
|
|
18
|
+
RawAccountMigration,
|
|
19
|
+
RawControlledAccount,
|
|
20
|
+
RawProfile,
|
|
21
|
+
accountHeaderForInitialAgentSecret,
|
|
22
|
+
} from "./coValues/account.js";
|
|
7
23
|
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
24
|
+
InviteSecret,
|
|
25
|
+
RawGroup,
|
|
26
|
+
secretSeedFromInviteSecret,
|
|
11
27
|
} from "./coValues/group.js";
|
|
12
|
-
import {
|
|
28
|
+
import { AgentSecret, CryptoProvider } from "./crypto/crypto.js";
|
|
13
29
|
import { AgentID, RawCoID, SessionID, isAgentID } from "./ids.js";
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
RawAccount,
|
|
17
|
-
AccountMeta,
|
|
18
|
-
accountHeaderForInitialAgentSecret,
|
|
19
|
-
ControlledAccountOrAgent,
|
|
20
|
-
RawControlledAccount,
|
|
21
|
-
ControlledAgent,
|
|
22
|
-
RawAccountID,
|
|
23
|
-
RawProfile,
|
|
24
|
-
RawProfile as Profile,
|
|
25
|
-
RawAccountMigration,
|
|
26
|
-
InvalidAccountAgentIDError,
|
|
27
|
-
} from "./coValues/account.js";
|
|
28
|
-
import { RawCoValue } from "./coValue.js";
|
|
30
|
+
import { Peer, PeerID, SyncManager } from "./sync.js";
|
|
29
31
|
import { expectGroup } from "./typeUtils/expectGroup.js";
|
|
30
|
-
import { err, ok, okAsync, Result, ResultAsync } from "neverthrow";
|
|
31
|
-
import { CoValueState } from "./coValueState.js";
|
|
32
32
|
|
|
33
33
|
/** A `LocalNode` represents a local view of a set of loaded `CoValue`s, from the perspective of a particular account (or primitive cryptographic agent).
|
|
34
34
|
|
|
@@ -42,712 +42,687 @@ const { localNode } = useJazz();
|
|
|
42
42
|
```
|
|
43
43
|
*/
|
|
44
44
|
export class LocalNode {
|
|
45
|
-
|
|
45
|
+
/** @internal */
|
|
46
|
+
crypto: CryptoProvider;
|
|
47
|
+
/** @internal */
|
|
48
|
+
coValues: { [key: RawCoID]: CoValueState } = {};
|
|
49
|
+
/** @category 3. Low-level */
|
|
50
|
+
account: ControlledAccountOrAgent;
|
|
51
|
+
/** @category 3. Low-level */
|
|
52
|
+
currentSessionID: SessionID;
|
|
53
|
+
/** @category 3. Low-level */
|
|
54
|
+
syncManager = new SyncManager(this);
|
|
55
|
+
|
|
56
|
+
crashed: Error | undefined = undefined;
|
|
57
|
+
|
|
58
|
+
/** @category 3. Low-level */
|
|
59
|
+
constructor(
|
|
60
|
+
account: ControlledAccountOrAgent,
|
|
61
|
+
currentSessionID: SessionID,
|
|
62
|
+
crypto: CryptoProvider,
|
|
63
|
+
) {
|
|
64
|
+
this.account = account;
|
|
65
|
+
this.currentSessionID = currentSessionID;
|
|
66
|
+
this.crypto = crypto;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** @category 2. Node Creation */
|
|
70
|
+
static async withNewlyCreatedAccount<Meta extends AccountMeta = AccountMeta>({
|
|
71
|
+
creationProps,
|
|
72
|
+
peersToLoadFrom,
|
|
73
|
+
migration,
|
|
74
|
+
crypto,
|
|
75
|
+
initialAgentSecret = crypto.newRandomAgentSecret(),
|
|
76
|
+
}: {
|
|
77
|
+
creationProps: { name: string };
|
|
78
|
+
peersToLoadFrom?: Peer[];
|
|
79
|
+
migration?: RawAccountMigration<Meta>;
|
|
46
80
|
crypto: CryptoProvider;
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
81
|
+
initialAgentSecret?: AgentSecret;
|
|
82
|
+
}): Promise<{
|
|
83
|
+
node: LocalNode;
|
|
84
|
+
accountID: RawAccountID;
|
|
85
|
+
accountSecret: AgentSecret;
|
|
86
|
+
sessionID: SessionID;
|
|
87
|
+
}> {
|
|
88
|
+
const throwawayAgent = crypto.newRandomAgentSecret();
|
|
89
|
+
const setupNode = new LocalNode(
|
|
90
|
+
new ControlledAgent(throwawayAgent, crypto),
|
|
91
|
+
crypto.newRandomSessionID(crypto.getAgentID(throwawayAgent)),
|
|
92
|
+
crypto,
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
const account = setupNode.createAccount(initialAgentSecret);
|
|
96
|
+
|
|
97
|
+
const nodeWithAccount = account.core.node.testWithDifferentAccount(
|
|
98
|
+
account,
|
|
99
|
+
crypto.newRandomSessionID(account.id),
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
const accountOnNodeWithAccount =
|
|
103
|
+
nodeWithAccount.account as RawControlledAccount<Meta>;
|
|
104
|
+
|
|
105
|
+
if (peersToLoadFrom) {
|
|
106
|
+
for (const peer of peersToLoadFrom) {
|
|
107
|
+
nodeWithAccount.syncManager.addPeer(peer);
|
|
108
|
+
}
|
|
67
109
|
}
|
|
68
110
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
79
|
-
creationProps: { name: string };
|
|
80
|
-
peersToLoadFrom?: Peer[];
|
|
81
|
-
migration?: RawAccountMigration<Meta>;
|
|
82
|
-
crypto: CryptoProvider;
|
|
83
|
-
initialAgentSecret?: AgentSecret;
|
|
84
|
-
}): Promise<{
|
|
85
|
-
node: LocalNode;
|
|
86
|
-
accountID: RawAccountID;
|
|
87
|
-
accountSecret: AgentSecret;
|
|
88
|
-
sessionID: SessionID;
|
|
89
|
-
}> {
|
|
90
|
-
const throwawayAgent = crypto.newRandomAgentSecret();
|
|
91
|
-
const setupNode = new LocalNode(
|
|
92
|
-
new ControlledAgent(throwawayAgent, crypto),
|
|
93
|
-
crypto.newRandomSessionID(crypto.getAgentID(throwawayAgent)),
|
|
94
|
-
crypto,
|
|
95
|
-
);
|
|
96
|
-
|
|
97
|
-
const account = setupNode.createAccount(initialAgentSecret);
|
|
111
|
+
if (migration) {
|
|
112
|
+
await migration(accountOnNodeWithAccount, nodeWithAccount, creationProps);
|
|
113
|
+
} else {
|
|
114
|
+
const profileGroup = accountOnNodeWithAccount.createGroup();
|
|
115
|
+
profileGroup.addMember("everyone", "reader");
|
|
116
|
+
const profile = profileGroup.createMap<Profile>({
|
|
117
|
+
name: creationProps.name,
|
|
118
|
+
});
|
|
119
|
+
accountOnNodeWithAccount.set("profile", profile.id, "trusting");
|
|
120
|
+
}
|
|
98
121
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
122
|
+
const controlledAccount = new RawControlledAccount(
|
|
123
|
+
accountOnNodeWithAccount.core,
|
|
124
|
+
accountOnNodeWithAccount.agentSecret,
|
|
125
|
+
);
|
|
103
126
|
|
|
104
|
-
|
|
105
|
-
|
|
127
|
+
nodeWithAccount.account = controlledAccount;
|
|
128
|
+
nodeWithAccount.coValues[controlledAccount.id] = CoValueState.Available(
|
|
129
|
+
controlledAccount.core,
|
|
130
|
+
);
|
|
131
|
+
controlledAccount.core._cachedContent = undefined;
|
|
106
132
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
111
|
-
}
|
|
133
|
+
if (!controlledAccount.get("profile")) {
|
|
134
|
+
throw new Error("Must set account profile in initial migration");
|
|
135
|
+
}
|
|
112
136
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
const profileGroup = accountOnNodeWithAccount.createGroup();
|
|
121
|
-
profileGroup.addMember("everyone", "reader");
|
|
122
|
-
const profile = profileGroup.createMap<Profile>({
|
|
123
|
-
name: creationProps.name,
|
|
124
|
-
});
|
|
125
|
-
accountOnNodeWithAccount.set("profile", profile.id, "trusting");
|
|
137
|
+
// we shouldn't need this, but it fixes account data not syncing for new accounts
|
|
138
|
+
function syncAllCoValuesAfterCreateAccount() {
|
|
139
|
+
for (const coValueEntry of Object.values(nodeWithAccount.coValues)) {
|
|
140
|
+
if (coValueEntry.state.type === "available") {
|
|
141
|
+
void nodeWithAccount.syncManager.syncCoValue(
|
|
142
|
+
coValueEntry.state.coValue,
|
|
143
|
+
);
|
|
126
144
|
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
127
147
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
148
|
+
syncAllCoValuesAfterCreateAccount();
|
|
149
|
+
|
|
150
|
+
setTimeout(syncAllCoValuesAfterCreateAccount, 500);
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
node: nodeWithAccount,
|
|
154
|
+
accountID: accountOnNodeWithAccount.id,
|
|
155
|
+
accountSecret: accountOnNodeWithAccount.agentSecret,
|
|
156
|
+
sessionID: nodeWithAccount.currentSessionID,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** @category 2. Node Creation */
|
|
161
|
+
static async withLoadedAccount<Meta extends AccountMeta = AccountMeta>({
|
|
162
|
+
accountID,
|
|
163
|
+
accountSecret,
|
|
164
|
+
sessionID,
|
|
165
|
+
peersToLoadFrom,
|
|
166
|
+
crypto,
|
|
167
|
+
migration,
|
|
168
|
+
}: {
|
|
169
|
+
accountID: RawAccountID;
|
|
170
|
+
accountSecret: AgentSecret;
|
|
171
|
+
sessionID: SessionID | undefined;
|
|
172
|
+
peersToLoadFrom: Peer[];
|
|
173
|
+
crypto: CryptoProvider;
|
|
174
|
+
migration?: RawAccountMigration<Meta>;
|
|
175
|
+
}): Promise<LocalNode> {
|
|
176
|
+
try {
|
|
177
|
+
const loadingNode = new LocalNode(
|
|
178
|
+
new ControlledAgent(accountSecret, crypto),
|
|
179
|
+
crypto.newRandomSessionID(accountID),
|
|
180
|
+
crypto,
|
|
181
|
+
);
|
|
132
182
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
);
|
|
137
|
-
controlledAccount.core._cachedContent = undefined;
|
|
183
|
+
for (const peer of peersToLoadFrom) {
|
|
184
|
+
loadingNode.syncManager.addPeer(peer);
|
|
185
|
+
}
|
|
138
186
|
|
|
139
|
-
|
|
140
|
-
throw new Error("Must set account profile in initial migration");
|
|
141
|
-
}
|
|
187
|
+
const accountPromise = loadingNode.load(accountID);
|
|
142
188
|
|
|
143
|
-
|
|
144
|
-
function syncAllCoValuesAfterCreateAccount() {
|
|
145
|
-
for (const coValueEntry of Object.values(
|
|
146
|
-
nodeWithAccount.coValues,
|
|
147
|
-
)) {
|
|
148
|
-
if (coValueEntry.state.type === "available") {
|
|
149
|
-
void nodeWithAccount.syncManager.syncCoValue(
|
|
150
|
-
coValueEntry.state.coValue,
|
|
151
|
-
);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
189
|
+
const account = await accountPromise;
|
|
155
190
|
|
|
156
|
-
|
|
191
|
+
if (account === "unavailable") {
|
|
192
|
+
throw new Error("Account unavailable from all peers");
|
|
193
|
+
}
|
|
157
194
|
|
|
158
|
-
|
|
195
|
+
const controlledAccount = new RawControlledAccount(
|
|
196
|
+
account.core,
|
|
197
|
+
accountSecret,
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
// since this is all synchronous, we can just swap out nodes for the SyncManager
|
|
201
|
+
const node = loadingNode.testWithDifferentAccount(
|
|
202
|
+
controlledAccount,
|
|
203
|
+
sessionID || crypto.newRandomSessionID(accountID),
|
|
204
|
+
);
|
|
205
|
+
node.syncManager = loadingNode.syncManager;
|
|
206
|
+
node.syncManager.local = node;
|
|
207
|
+
|
|
208
|
+
controlledAccount.core.node = node;
|
|
209
|
+
node.coValues[accountID] = CoValueState.Available(controlledAccount.core);
|
|
210
|
+
controlledAccount.core._cachedContent = undefined;
|
|
211
|
+
|
|
212
|
+
const profileID = account.get("profile");
|
|
213
|
+
if (!profileID) {
|
|
214
|
+
throw new Error("Account has no profile");
|
|
215
|
+
}
|
|
216
|
+
const profile = await node.load(profileID);
|
|
217
|
+
|
|
218
|
+
if (profile === "unavailable") {
|
|
219
|
+
throw new Error("Profile unavailable from all peers");
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (migration) {
|
|
223
|
+
await migration(controlledAccount as RawControlledAccount<Meta>, node);
|
|
224
|
+
node.account = new RawControlledAccount(
|
|
225
|
+
controlledAccount.core,
|
|
226
|
+
controlledAccount.agentSecret,
|
|
227
|
+
);
|
|
228
|
+
}
|
|
159
229
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
230
|
+
return node;
|
|
231
|
+
} catch (e) {
|
|
232
|
+
console.error("Error withLoadedAccount", e);
|
|
233
|
+
throw e;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/** @internal */
|
|
238
|
+
createCoValue(header: CoValueHeader): CoValueCore {
|
|
239
|
+
if (this.crashed) {
|
|
240
|
+
throw new Error("Trying to create CoValue after node has crashed", {
|
|
241
|
+
cause: this.crashed,
|
|
242
|
+
});
|
|
166
243
|
}
|
|
167
244
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
crypto,
|
|
189
|
-
);
|
|
190
|
-
|
|
191
|
-
for (const peer of peersToLoadFrom) {
|
|
192
|
-
loadingNode.syncManager.addPeer(peer);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
const accountPromise = loadingNode.load(accountID);
|
|
196
|
-
|
|
197
|
-
const account = await accountPromise;
|
|
198
|
-
|
|
199
|
-
if (account === "unavailable") {
|
|
200
|
-
throw new Error("Account unavailable from all peers");
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
const controlledAccount = new RawControlledAccount(
|
|
204
|
-
account.core,
|
|
205
|
-
accountSecret,
|
|
206
|
-
);
|
|
207
|
-
|
|
208
|
-
// since this is all synchronous, we can just swap out nodes for the SyncManager
|
|
209
|
-
const node = loadingNode.testWithDifferentAccount(
|
|
210
|
-
controlledAccount,
|
|
211
|
-
sessionID || crypto.newRandomSessionID(accountID),
|
|
212
|
-
);
|
|
213
|
-
node.syncManager = loadingNode.syncManager;
|
|
214
|
-
node.syncManager.local = node;
|
|
215
|
-
|
|
216
|
-
controlledAccount.core.node = node;
|
|
217
|
-
node.coValues[accountID] = CoValueState.Available(
|
|
218
|
-
controlledAccount.core,
|
|
219
|
-
);
|
|
220
|
-
controlledAccount.core._cachedContent = undefined;
|
|
221
|
-
|
|
222
|
-
const profileID = account.get("profile");
|
|
223
|
-
if (!profileID) {
|
|
224
|
-
throw new Error("Account has no profile");
|
|
225
|
-
}
|
|
226
|
-
const profile = await node.load(profileID);
|
|
227
|
-
|
|
228
|
-
if (profile === "unavailable") {
|
|
229
|
-
throw new Error("Profile unavailable from all peers");
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
if (migration) {
|
|
233
|
-
await migration(
|
|
234
|
-
controlledAccount as RawControlledAccount<Meta>,
|
|
235
|
-
node,
|
|
236
|
-
);
|
|
237
|
-
node.account = new RawControlledAccount(
|
|
238
|
-
controlledAccount.core,
|
|
239
|
-
controlledAccount.agentSecret,
|
|
240
|
-
);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
return node;
|
|
244
|
-
} catch (e) {
|
|
245
|
-
console.error("Error withLoadedAccount", e);
|
|
246
|
-
throw e;
|
|
247
|
-
}
|
|
245
|
+
const coValue = new CoValueCore(header, this);
|
|
246
|
+
this.coValues[coValue.id] = CoValueState.Available(coValue);
|
|
247
|
+
|
|
248
|
+
void this.syncManager.syncCoValue(coValue);
|
|
249
|
+
|
|
250
|
+
return coValue;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/** @internal */
|
|
254
|
+
async loadCoValueCore(
|
|
255
|
+
id: RawCoID,
|
|
256
|
+
options: {
|
|
257
|
+
dontLoadFrom?: PeerID;
|
|
258
|
+
dontWaitFor?: PeerID;
|
|
259
|
+
} = {},
|
|
260
|
+
): Promise<CoValueCore | "unavailable"> {
|
|
261
|
+
if (this.crashed) {
|
|
262
|
+
throw new Error("Trying to load CoValue after node has crashed", {
|
|
263
|
+
cause: this.crashed,
|
|
264
|
+
});
|
|
248
265
|
}
|
|
249
266
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
267
|
+
let entry = this.coValues[id];
|
|
268
|
+
if (!entry) {
|
|
269
|
+
const peersToWaitFor = new Set(
|
|
270
|
+
Object.values(this.syncManager.peers)
|
|
271
|
+
.filter((peer) => peer.isServerOrStoragePeer())
|
|
272
|
+
.map((peer) => peer.id),
|
|
273
|
+
);
|
|
274
|
+
if (options.dontWaitFor) peersToWaitFor.delete(options.dontWaitFor);
|
|
275
|
+
entry = CoValueState.Unknown(peersToWaitFor);
|
|
257
276
|
|
|
258
|
-
|
|
259
|
-
this.coValues[coValue.id] = CoValueState.Available(coValue);
|
|
277
|
+
this.coValues[id] = entry;
|
|
260
278
|
|
|
261
|
-
|
|
279
|
+
this.syncManager.loadFromPeers(id, options.dontLoadFrom).catch((e) => {
|
|
280
|
+
console.error(
|
|
281
|
+
"Error loading from peers",
|
|
282
|
+
id,
|
|
262
283
|
|
|
263
|
-
|
|
284
|
+
e,
|
|
285
|
+
);
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
if (entry.state.type === "available") {
|
|
289
|
+
return Promise.resolve(entry.state.coValue);
|
|
264
290
|
}
|
|
265
291
|
|
|
266
|
-
|
|
267
|
-
async loadCoValueCore(
|
|
268
|
-
id: RawCoID,
|
|
269
|
-
options: {
|
|
270
|
-
dontLoadFrom?: PeerID;
|
|
271
|
-
dontWaitFor?: PeerID;
|
|
272
|
-
} = {},
|
|
273
|
-
): Promise<CoValueCore | "unavailable"> {
|
|
274
|
-
if (this.crashed) {
|
|
275
|
-
throw new Error("Trying to load CoValue after node has crashed", {
|
|
276
|
-
cause: this.crashed,
|
|
277
|
-
});
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
let entry = this.coValues[id];
|
|
281
|
-
if (!entry) {
|
|
282
|
-
const peersToWaitFor = new Set(
|
|
283
|
-
Object.values(this.syncManager.peers)
|
|
284
|
-
.filter((peer) => peer.isServerOrStoragePeer())
|
|
285
|
-
.map((peer) => peer.id),
|
|
286
|
-
);
|
|
287
|
-
if (options.dontWaitFor) peersToWaitFor.delete(options.dontWaitFor);
|
|
288
|
-
entry = CoValueState.Unknown(peersToWaitFor);
|
|
289
|
-
|
|
290
|
-
this.coValues[id] = entry;
|
|
291
|
-
|
|
292
|
-
this.syncManager
|
|
293
|
-
.loadFromPeers(id, options.dontLoadFrom)
|
|
294
|
-
.catch((e) => {
|
|
295
|
-
console.error(
|
|
296
|
-
"Error loading from peers",
|
|
297
|
-
id,
|
|
298
|
-
|
|
299
|
-
e,
|
|
300
|
-
);
|
|
301
|
-
});
|
|
302
|
-
}
|
|
303
|
-
if (entry.state.type === "available") {
|
|
304
|
-
return Promise.resolve(entry.state.coValue);
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
await entry.state.ready;
|
|
292
|
+
await entry.state.ready;
|
|
308
293
|
|
|
309
|
-
|
|
294
|
+
const updatedEntry = this.coValues[id];
|
|
310
295
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
296
|
+
if (updatedEntry?.state.type === "available") {
|
|
297
|
+
return Promise.resolve(updatedEntry.state.coValue);
|
|
298
|
+
}
|
|
314
299
|
|
|
315
|
-
|
|
300
|
+
return "unavailable";
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Loads a CoValue's content, syncing from peers as necessary and resolving the returned
|
|
305
|
+
* promise once a first version has been loaded. See `coValue.subscribe()` and `node.useTelepathicData()`
|
|
306
|
+
* for listening to subsequent updates to the CoValue.
|
|
307
|
+
*
|
|
308
|
+
* @category 3. Low-level
|
|
309
|
+
*/
|
|
310
|
+
async load<T extends RawCoValue>(id: CoID<T>): Promise<T | "unavailable"> {
|
|
311
|
+
const core = await this.loadCoValueCore(id);
|
|
312
|
+
|
|
313
|
+
if (core === "unavailable") {
|
|
314
|
+
return "unavailable";
|
|
316
315
|
}
|
|
317
316
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
* promise once a first version has been loaded. See `coValue.subscribe()` and `node.useTelepathicData()`
|
|
321
|
-
* for listening to subsequent updates to the CoValue.
|
|
322
|
-
*
|
|
323
|
-
* @category 3. Low-level
|
|
324
|
-
*/
|
|
325
|
-
async load<T extends RawCoValue>(id: CoID<T>): Promise<T | "unavailable"> {
|
|
326
|
-
const core = await this.loadCoValueCore(id);
|
|
327
|
-
|
|
328
|
-
if (core === "unavailable") {
|
|
329
|
-
return "unavailable";
|
|
330
|
-
}
|
|
317
|
+
return core.getCurrentContent() as T;
|
|
318
|
+
}
|
|
331
319
|
|
|
332
|
-
|
|
320
|
+
getLoaded<T extends RawCoValue>(id: CoID<T>): T | undefined {
|
|
321
|
+
const entry = this.coValues[id];
|
|
322
|
+
if (!entry) {
|
|
323
|
+
return undefined;
|
|
333
324
|
}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
325
|
+
if (entry.state.type === "available") {
|
|
326
|
+
return entry.state.coValue.getCurrentContent() as T;
|
|
327
|
+
}
|
|
328
|
+
return undefined;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/** @category 3. Low-level */
|
|
332
|
+
subscribe<T extends RawCoValue>(
|
|
333
|
+
id: CoID<T>,
|
|
334
|
+
callback: (update: T | "unavailable") => void,
|
|
335
|
+
): () => void {
|
|
336
|
+
let stopped = false;
|
|
337
|
+
let unsubscribe!: () => void;
|
|
338
|
+
|
|
339
|
+
// console.log("Subscribing to " + id);
|
|
340
|
+
|
|
341
|
+
this.load(id)
|
|
342
|
+
.then((coValue) => {
|
|
343
|
+
if (stopped) {
|
|
344
|
+
return;
|
|
339
345
|
}
|
|
340
|
-
if (
|
|
341
|
-
|
|
346
|
+
if (coValue === "unavailable") {
|
|
347
|
+
callback("unavailable");
|
|
348
|
+
return;
|
|
342
349
|
}
|
|
343
|
-
|
|
350
|
+
unsubscribe = coValue.subscribe(callback);
|
|
351
|
+
})
|
|
352
|
+
.catch((e) => {
|
|
353
|
+
console.error("Error subscribing to ", id, e);
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
return () => {
|
|
357
|
+
console.log("Unsubscribing from " + id);
|
|
358
|
+
stopped = true;
|
|
359
|
+
unsubscribe?.();
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/** @deprecated Use Account.acceptInvite instead */
|
|
364
|
+
async acceptInvite<T extends RawCoValue>(
|
|
365
|
+
groupOrOwnedValueID: CoID<T>,
|
|
366
|
+
inviteSecret: InviteSecret,
|
|
367
|
+
): Promise<void> {
|
|
368
|
+
const groupOrOwnedValue = await this.load(groupOrOwnedValueID);
|
|
369
|
+
|
|
370
|
+
if (groupOrOwnedValue === "unavailable") {
|
|
371
|
+
throw new Error(
|
|
372
|
+
"Trying to accept invite: Group/owned value unavailable from all peers",
|
|
373
|
+
);
|
|
344
374
|
}
|
|
345
375
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
// console.log("Subscribing to " + id);
|
|
355
|
-
|
|
356
|
-
this.load(id)
|
|
357
|
-
.then((coValue) => {
|
|
358
|
-
if (stopped) {
|
|
359
|
-
return;
|
|
360
|
-
}
|
|
361
|
-
if (coValue === "unavailable") {
|
|
362
|
-
callback("unavailable");
|
|
363
|
-
return;
|
|
364
|
-
}
|
|
365
|
-
unsubscribe = coValue.subscribe(callback);
|
|
366
|
-
})
|
|
367
|
-
.catch((e) => {
|
|
368
|
-
console.error("Error subscribing to ", id, e);
|
|
369
|
-
});
|
|
370
|
-
|
|
371
|
-
return () => {
|
|
372
|
-
console.log("Unsubscribing from " + id);
|
|
373
|
-
stopped = true;
|
|
374
|
-
unsubscribe?.();
|
|
375
|
-
};
|
|
376
|
+
if (groupOrOwnedValue.core.header.ruleset.type === "ownedByGroup") {
|
|
377
|
+
return this.acceptInvite(
|
|
378
|
+
groupOrOwnedValue.core.header.ruleset.group as CoID<RawGroup>,
|
|
379
|
+
inviteSecret,
|
|
380
|
+
);
|
|
381
|
+
} else if (groupOrOwnedValue.core.header.ruleset.type !== "group") {
|
|
382
|
+
throw new Error("Can only accept invites to groups");
|
|
376
383
|
}
|
|
377
384
|
|
|
378
|
-
|
|
379
|
-
async acceptInvite<T extends RawCoValue>(
|
|
380
|
-
groupOrOwnedValueID: CoID<T>,
|
|
381
|
-
inviteSecret: InviteSecret,
|
|
382
|
-
): Promise<void> {
|
|
383
|
-
const groupOrOwnedValue = await this.load(groupOrOwnedValueID);
|
|
384
|
-
|
|
385
|
-
if (groupOrOwnedValue === "unavailable") {
|
|
386
|
-
throw new Error(
|
|
387
|
-
"Trying to accept invite: Group/owned value unavailable from all peers",
|
|
388
|
-
);
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
if (groupOrOwnedValue.core.header.ruleset.type === "ownedByGroup") {
|
|
392
|
-
return this.acceptInvite(
|
|
393
|
-
groupOrOwnedValue.core.header.ruleset.group as CoID<RawGroup>,
|
|
394
|
-
inviteSecret,
|
|
395
|
-
);
|
|
396
|
-
} else if (groupOrOwnedValue.core.header.ruleset.type !== "group") {
|
|
397
|
-
throw new Error("Can only accept invites to groups");
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
const group = expectGroup(groupOrOwnedValue);
|
|
385
|
+
const group = expectGroup(groupOrOwnedValue);
|
|
401
386
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
const inviteRole = await new Promise((resolve, reject) => {
|
|
408
|
-
group.subscribe((groupUpdate) => {
|
|
409
|
-
const role = groupUpdate.get(inviteAgentID);
|
|
410
|
-
if (role) {
|
|
411
|
-
resolve(role);
|
|
412
|
-
}
|
|
413
|
-
});
|
|
414
|
-
setTimeout(
|
|
415
|
-
() => reject(new Error("Couldn't find invite before timeout")),
|
|
416
|
-
2000,
|
|
417
|
-
);
|
|
418
|
-
});
|
|
419
|
-
|
|
420
|
-
if (!inviteRole) {
|
|
421
|
-
throw new Error("No invite found");
|
|
422
|
-
}
|
|
387
|
+
const inviteAgentSecret = this.crypto.agentSecretFromSecretSeed(
|
|
388
|
+
secretSeedFromInviteSecret(inviteSecret),
|
|
389
|
+
);
|
|
390
|
+
const inviteAgentID = this.crypto.getAgentID(inviteAgentSecret);
|
|
423
391
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
(existingRole === "writer" && inviteRole === "reader") ||
|
|
430
|
-
(existingRole === "reader" && inviteRole === "readerInvite")
|
|
431
|
-
) {
|
|
432
|
-
console.debug(
|
|
433
|
-
"Not accepting invite that would replace or downgrade role",
|
|
434
|
-
);
|
|
435
|
-
return;
|
|
392
|
+
const inviteRole = await new Promise((resolve, reject) => {
|
|
393
|
+
group.subscribe((groupUpdate) => {
|
|
394
|
+
const role = groupUpdate.get(inviteAgentID);
|
|
395
|
+
if (role) {
|
|
396
|
+
resolve(role);
|
|
436
397
|
}
|
|
398
|
+
});
|
|
399
|
+
setTimeout(
|
|
400
|
+
() => reject(new Error("Couldn't find invite before timeout")),
|
|
401
|
+
2000,
|
|
402
|
+
);
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
if (!inviteRole) {
|
|
406
|
+
throw new Error("No invite found");
|
|
407
|
+
}
|
|
437
408
|
|
|
438
|
-
|
|
439
|
-
group.core
|
|
440
|
-
.testWithDifferentAccount(
|
|
441
|
-
new ControlledAgent(inviteAgentSecret, this.crypto),
|
|
442
|
-
this.crypto.newRandomSessionID(inviteAgentID),
|
|
443
|
-
)
|
|
444
|
-
.getCurrentContent(),
|
|
445
|
-
);
|
|
446
|
-
|
|
447
|
-
groupAsInvite.addMemberInternal(
|
|
448
|
-
this.account,
|
|
449
|
-
inviteRole === "adminInvite"
|
|
450
|
-
? "admin"
|
|
451
|
-
: inviteRole === "writerInvite"
|
|
452
|
-
? "writer"
|
|
453
|
-
: "reader",
|
|
454
|
-
);
|
|
455
|
-
|
|
456
|
-
group.core._sessionLogs = groupAsInvite.core.sessionLogs;
|
|
457
|
-
group.core._cachedContent = undefined;
|
|
409
|
+
const existingRole = group.get(this.account.id);
|
|
458
410
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
411
|
+
if (
|
|
412
|
+
existingRole === "admin" ||
|
|
413
|
+
(existingRole === "writer" && inviteRole === "writerInvite") ||
|
|
414
|
+
(existingRole === "writer" && inviteRole === "reader") ||
|
|
415
|
+
(existingRole === "reader" && inviteRole === "readerInvite")
|
|
416
|
+
) {
|
|
417
|
+
console.debug(
|
|
418
|
+
"Not accepting invite that would replace or downgrade role",
|
|
419
|
+
);
|
|
420
|
+
return;
|
|
462
421
|
}
|
|
463
422
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
423
|
+
const groupAsInvite = expectGroup(
|
|
424
|
+
group.core
|
|
425
|
+
.testWithDifferentAccount(
|
|
426
|
+
new ControlledAgent(inviteAgentSecret, this.crypto),
|
|
427
|
+
this.crypto.newRandomSessionID(inviteAgentID),
|
|
428
|
+
)
|
|
429
|
+
.getCurrentContent(),
|
|
430
|
+
);
|
|
431
|
+
|
|
432
|
+
groupAsInvite.addMemberInternal(
|
|
433
|
+
this.account,
|
|
434
|
+
inviteRole === "adminInvite"
|
|
435
|
+
? "admin"
|
|
436
|
+
: inviteRole === "writerInvite"
|
|
437
|
+
? "writer"
|
|
438
|
+
: "reader",
|
|
439
|
+
);
|
|
440
|
+
|
|
441
|
+
group.core._sessionLogs = groupAsInvite.core.sessionLogs;
|
|
442
|
+
group.core._cachedContent = undefined;
|
|
443
|
+
|
|
444
|
+
for (const groupListener of group.core.listeners) {
|
|
445
|
+
groupListener(group.core.getCurrentContent());
|
|
480
446
|
}
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
`${
|
|
491
|
-
expectation ? expectation + ": " : ""
|
|
492
|
-
}Account ${id} has no profile`,
|
|
493
|
-
);
|
|
494
|
-
}
|
|
495
|
-
return this.expectCoValueLoaded(
|
|
496
|
-
profileID,
|
|
497
|
-
expectation,
|
|
498
|
-
).getCurrentContent() as RawProfile;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/** @internal */
|
|
450
|
+
expectCoValueLoaded(id: RawCoID, expectation?: string): CoValueCore {
|
|
451
|
+
const entry = this.coValues[id];
|
|
452
|
+
if (!entry) {
|
|
453
|
+
throw new Error(
|
|
454
|
+
`${expectation ? expectation + ": " : ""}Unknown CoValue ${id}`,
|
|
455
|
+
);
|
|
499
456
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
457
|
+
if (entry.state.type === "unknown") {
|
|
458
|
+
throw new Error(
|
|
459
|
+
`${expectation ? expectation + ": " : ""}CoValue ${id} not yet loaded`,
|
|
460
|
+
);
|
|
461
|
+
}
|
|
462
|
+
return entry.state.coValue;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/** @internal */
|
|
466
|
+
expectProfileLoaded(id: RawAccountID, expectation?: string): RawProfile {
|
|
467
|
+
const account = this.expectCoValueLoaded(id, expectation);
|
|
468
|
+
const profileID = expectGroup(account.getCurrentContent()).get("profile");
|
|
469
|
+
if (!profileID) {
|
|
470
|
+
throw new Error(
|
|
471
|
+
`${expectation ? expectation + ": " : ""}Account ${id} has no profile`,
|
|
472
|
+
);
|
|
473
|
+
}
|
|
474
|
+
return this.expectCoValueLoaded(
|
|
475
|
+
profileID,
|
|
476
|
+
expectation,
|
|
477
|
+
).getCurrentContent() as RawProfile;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/** @internal */
|
|
481
|
+
createAccount(
|
|
482
|
+
agentSecret = this.crypto.newRandomAgentSecret(),
|
|
483
|
+
): RawControlledAccount {
|
|
484
|
+
const accountAgentID = this.crypto.getAgentID(agentSecret);
|
|
485
|
+
const account = expectGroup(
|
|
486
|
+
this.createCoValue(
|
|
487
|
+
accountHeaderForInitialAgentSecret(agentSecret, this.crypto),
|
|
488
|
+
)
|
|
489
|
+
.testWithDifferentAccount(
|
|
490
|
+
new ControlledAgent(agentSecret, this.crypto),
|
|
491
|
+
this.crypto.newRandomSessionID(accountAgentID),
|
|
492
|
+
)
|
|
493
|
+
.getCurrentContent(),
|
|
494
|
+
);
|
|
495
|
+
|
|
496
|
+
account.set(accountAgentID, "admin", "trusting");
|
|
497
|
+
|
|
498
|
+
const readKey = this.crypto.newRandomKeySecret();
|
|
499
|
+
|
|
500
|
+
const sealed = this.crypto.seal({
|
|
501
|
+
message: readKey.secret,
|
|
502
|
+
from: this.crypto.getAgentSealerSecret(agentSecret),
|
|
503
|
+
to: this.crypto.getAgentSealerID(accountAgentID),
|
|
504
|
+
nOnceMaterial: {
|
|
505
|
+
in: account.id,
|
|
506
|
+
tx: account.core.nextTransactionID(),
|
|
507
|
+
},
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
account.set(`${readKey.id}_for_${accountAgentID}`, sealed, "trusting");
|
|
511
|
+
|
|
512
|
+
account.set("readKey", readKey.id, "trusting");
|
|
513
|
+
|
|
514
|
+
const accountOnThisNode = this.expectCoValueLoaded(account.id);
|
|
515
|
+
|
|
516
|
+
accountOnThisNode._sessionLogs = new Map(account.core.sessionLogs);
|
|
517
|
+
|
|
518
|
+
accountOnThisNode._cachedContent = undefined;
|
|
519
|
+
|
|
520
|
+
return new RawControlledAccount(accountOnThisNode, agentSecret);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/** @internal */
|
|
524
|
+
resolveAccountAgent(
|
|
525
|
+
id: RawAccountID | AgentID,
|
|
526
|
+
expectation?: string,
|
|
527
|
+
): Result<AgentID, ResolveAccountAgentError> {
|
|
528
|
+
if (isAgentID(id)) {
|
|
529
|
+
return ok(id);
|
|
542
530
|
}
|
|
543
531
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
532
|
+
let coValue: CoValueCore;
|
|
533
|
+
|
|
534
|
+
try {
|
|
535
|
+
coValue = this.expectCoValueLoaded(id, expectation);
|
|
536
|
+
} catch (e) {
|
|
537
|
+
return err({
|
|
538
|
+
type: "ErrorLoadingCoValueCore",
|
|
539
|
+
expectation,
|
|
540
|
+
id,
|
|
541
|
+
error: e,
|
|
542
|
+
} satisfies LoadCoValueCoreError);
|
|
543
|
+
}
|
|
552
544
|
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
545
|
+
if (
|
|
546
|
+
coValue.header.type !== "comap" ||
|
|
547
|
+
coValue.header.ruleset.type !== "group" ||
|
|
548
|
+
!coValue.header.meta ||
|
|
549
|
+
!("type" in coValue.header.meta) ||
|
|
550
|
+
coValue.header.meta.type !== "account"
|
|
551
|
+
) {
|
|
552
|
+
return err({
|
|
553
|
+
type: "UnexpectedlyNotAccount",
|
|
554
|
+
expectation,
|
|
555
|
+
id,
|
|
556
|
+
} satisfies UnexpectedlyNotAccountError);
|
|
557
|
+
}
|
|
565
558
|
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
coValue.header.ruleset.type !== "group" ||
|
|
569
|
-
!coValue.header.meta ||
|
|
570
|
-
!("type" in coValue.header.meta) ||
|
|
571
|
-
coValue.header.meta.type !== "account"
|
|
572
|
-
) {
|
|
573
|
-
return err({
|
|
574
|
-
type: "UnexpectedlyNotAccount",
|
|
575
|
-
expectation,
|
|
576
|
-
id,
|
|
577
|
-
} satisfies UnexpectedlyNotAccountError);
|
|
578
|
-
}
|
|
559
|
+
return (coValue.getCurrentContent() as RawAccount).currentAgentID();
|
|
560
|
+
}
|
|
579
561
|
|
|
580
|
-
|
|
562
|
+
resolveAccountAgentAsync(
|
|
563
|
+
id: RawAccountID | AgentID,
|
|
564
|
+
expectation?: string,
|
|
565
|
+
): ResultAsync<AgentID, ResolveAccountAgentError> {
|
|
566
|
+
if (isAgentID(id)) {
|
|
567
|
+
return okAsync(id);
|
|
581
568
|
}
|
|
582
569
|
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
570
|
+
return ResultAsync.fromPromise(
|
|
571
|
+
this.loadCoValueCore(id),
|
|
572
|
+
(e) =>
|
|
573
|
+
({
|
|
574
|
+
type: "ErrorLoadingCoValueCore",
|
|
575
|
+
expectation,
|
|
576
|
+
id,
|
|
577
|
+
error: e,
|
|
578
|
+
}) satisfies LoadCoValueCoreError,
|
|
579
|
+
).andThen((coValue) => {
|
|
580
|
+
if (coValue === "unavailable") {
|
|
581
|
+
return err({
|
|
582
|
+
type: "AccountUnavailableFromAllPeers" as const,
|
|
583
|
+
expectation,
|
|
584
|
+
id,
|
|
585
|
+
} satisfies AccountUnavailableFromAllPeersError);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
if (
|
|
589
|
+
coValue.header.type !== "comap" ||
|
|
590
|
+
coValue.header.ruleset.type !== "group" ||
|
|
591
|
+
!coValue.header.meta ||
|
|
592
|
+
!("type" in coValue.header.meta) ||
|
|
593
|
+
coValue.header.meta.type !== "account"
|
|
594
|
+
) {
|
|
595
|
+
return err({
|
|
596
|
+
type: "UnexpectedlyNotAccount" as const,
|
|
597
|
+
expectation,
|
|
598
|
+
id,
|
|
599
|
+
} satisfies UnexpectedlyNotAccountError);
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
return (coValue.getCurrentContent() as RawAccount).currentAgentID();
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
/**
|
|
607
|
+
* @deprecated use Account.createGroup() instead
|
|
608
|
+
*/
|
|
609
|
+
createGroup(
|
|
610
|
+
uniqueness: CoValueUniqueness = this.crypto.createdNowUnique(),
|
|
611
|
+
): RawGroup {
|
|
612
|
+
const groupCoValue = this.createCoValue({
|
|
613
|
+
type: "comap",
|
|
614
|
+
ruleset: { type: "group", initialAdmin: this.account.id },
|
|
615
|
+
meta: null,
|
|
616
|
+
...uniqueness,
|
|
617
|
+
});
|
|
618
|
+
|
|
619
|
+
const group = expectGroup(groupCoValue.getCurrentContent());
|
|
620
|
+
|
|
621
|
+
group.set(this.account.id, "admin", "trusting");
|
|
622
|
+
|
|
623
|
+
const readKey = this.crypto.newRandomKeySecret();
|
|
624
|
+
|
|
625
|
+
group.set(
|
|
626
|
+
`${readKey.id}_for_${this.account.id}`,
|
|
627
|
+
this.crypto.seal({
|
|
628
|
+
message: readKey.secret,
|
|
629
|
+
from: this.account.currentSealerSecret(),
|
|
630
|
+
to: this.account
|
|
631
|
+
.currentSealerID()
|
|
632
|
+
._unsafeUnwrap({ withStackTrace: true }),
|
|
633
|
+
nOnceMaterial: {
|
|
634
|
+
in: groupCoValue.id,
|
|
635
|
+
tx: groupCoValue.nextTransactionID(),
|
|
636
|
+
},
|
|
637
|
+
}),
|
|
638
|
+
"trusting",
|
|
639
|
+
);
|
|
640
|
+
|
|
641
|
+
group.set("readKey", readKey.id, "trusting");
|
|
642
|
+
|
|
643
|
+
return group;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
/** @internal */
|
|
647
|
+
testWithDifferentAccount(
|
|
648
|
+
account: ControlledAccountOrAgent,
|
|
649
|
+
currentSessionID: SessionID,
|
|
650
|
+
): LocalNode {
|
|
651
|
+
const newNode = new LocalNode(account, currentSessionID, this.crypto);
|
|
652
|
+
|
|
653
|
+
const coValuesToCopy = Object.entries(this.coValues);
|
|
654
|
+
|
|
655
|
+
while (coValuesToCopy.length > 0) {
|
|
656
|
+
const [coValueID, entry] = coValuesToCopy[coValuesToCopy.length - 1]!;
|
|
657
|
+
|
|
658
|
+
if (entry.state.type === "unknown") {
|
|
659
|
+
coValuesToCopy.pop();
|
|
660
|
+
continue;
|
|
661
|
+
} else {
|
|
662
|
+
const allDepsCopied = entry.state.coValue
|
|
663
|
+
.getDependedOnCoValues()
|
|
664
|
+
.every((dep) => newNode.coValues[dep]?.state.type === "available");
|
|
665
|
+
|
|
666
|
+
if (!allDepsCopied) {
|
|
667
|
+
// move to end of queue
|
|
668
|
+
coValuesToCopy.unshift(coValuesToCopy.pop()!);
|
|
669
|
+
continue;
|
|
589
670
|
}
|
|
590
671
|
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
type: "ErrorLoadingCoValueCore",
|
|
596
|
-
expectation,
|
|
597
|
-
id,
|
|
598
|
-
error: e,
|
|
599
|
-
}) satisfies LoadCoValueCoreError,
|
|
600
|
-
).andThen((coValue) => {
|
|
601
|
-
if (coValue === "unavailable") {
|
|
602
|
-
return err({
|
|
603
|
-
type: "AccountUnavailableFromAllPeers" as const,
|
|
604
|
-
expectation,
|
|
605
|
-
id,
|
|
606
|
-
} satisfies AccountUnavailableFromAllPeersError);
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
if (
|
|
610
|
-
coValue.header.type !== "comap" ||
|
|
611
|
-
coValue.header.ruleset.type !== "group" ||
|
|
612
|
-
!coValue.header.meta ||
|
|
613
|
-
!("type" in coValue.header.meta) ||
|
|
614
|
-
coValue.header.meta.type !== "account"
|
|
615
|
-
) {
|
|
616
|
-
return err({
|
|
617
|
-
type: "UnexpectedlyNotAccount" as const,
|
|
618
|
-
expectation,
|
|
619
|
-
id,
|
|
620
|
-
} satisfies UnexpectedlyNotAccountError);
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
return (coValue.getCurrentContent() as RawAccount).currentAgentID();
|
|
624
|
-
});
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
/**
|
|
628
|
-
* @deprecated use Account.createGroup() instead
|
|
629
|
-
*/
|
|
630
|
-
createGroup(
|
|
631
|
-
uniqueness: CoValueUniqueness = this.crypto.createdNowUnique(),
|
|
632
|
-
): RawGroup {
|
|
633
|
-
const groupCoValue = this.createCoValue({
|
|
634
|
-
type: "comap",
|
|
635
|
-
ruleset: { type: "group", initialAdmin: this.account.id },
|
|
636
|
-
meta: null,
|
|
637
|
-
...uniqueness,
|
|
638
|
-
});
|
|
639
|
-
|
|
640
|
-
const group = expectGroup(groupCoValue.getCurrentContent());
|
|
641
|
-
|
|
642
|
-
group.set(this.account.id, "admin", "trusting");
|
|
643
|
-
|
|
644
|
-
const readKey = this.crypto.newRandomKeySecret();
|
|
645
|
-
|
|
646
|
-
group.set(
|
|
647
|
-
`${readKey.id}_for_${this.account.id}`,
|
|
648
|
-
this.crypto.seal({
|
|
649
|
-
message: readKey.secret,
|
|
650
|
-
from: this.account.currentSealerSecret(),
|
|
651
|
-
to: this.account
|
|
652
|
-
.currentSealerID()
|
|
653
|
-
._unsafeUnwrap({ withStackTrace: true }),
|
|
654
|
-
nOnceMaterial: {
|
|
655
|
-
in: groupCoValue.id,
|
|
656
|
-
tx: groupCoValue.nextTransactionID(),
|
|
657
|
-
},
|
|
658
|
-
}),
|
|
659
|
-
"trusting",
|
|
672
|
+
const newCoValue = new CoValueCore(
|
|
673
|
+
entry.state.coValue.header,
|
|
674
|
+
newNode,
|
|
675
|
+
new Map(entry.state.coValue.sessionLogs),
|
|
660
676
|
);
|
|
661
677
|
|
|
662
|
-
|
|
678
|
+
newNode.coValues[coValueID as RawCoID] =
|
|
679
|
+
CoValueState.Available(newCoValue);
|
|
663
680
|
|
|
664
|
-
|
|
681
|
+
coValuesToCopy.pop();
|
|
682
|
+
}
|
|
665
683
|
}
|
|
666
684
|
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
const [coValueID, entry] =
|
|
678
|
-
coValuesToCopy[coValuesToCopy.length - 1]!;
|
|
679
|
-
|
|
680
|
-
if (entry.state.type === "unknown") {
|
|
681
|
-
coValuesToCopy.pop();
|
|
682
|
-
continue;
|
|
683
|
-
} else {
|
|
684
|
-
const allDepsCopied = entry.state.coValue
|
|
685
|
-
.getDependedOnCoValues()
|
|
686
|
-
.every(
|
|
687
|
-
(dep) =>
|
|
688
|
-
newNode.coValues[dep]?.state.type === "available",
|
|
689
|
-
);
|
|
690
|
-
|
|
691
|
-
if (!allDepsCopied) {
|
|
692
|
-
// move to end of queue
|
|
693
|
-
coValuesToCopy.unshift(coValuesToCopy.pop()!);
|
|
694
|
-
continue;
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
const newCoValue = new CoValueCore(
|
|
698
|
-
entry.state.coValue.header,
|
|
699
|
-
newNode,
|
|
700
|
-
new Map(entry.state.coValue.sessionLogs),
|
|
701
|
-
);
|
|
702
|
-
|
|
703
|
-
newNode.coValues[coValueID as RawCoID] =
|
|
704
|
-
CoValueState.Available(newCoValue);
|
|
705
|
-
|
|
706
|
-
coValuesToCopy.pop();
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
if (account instanceof RawControlledAccount) {
|
|
711
|
-
// To make sure that when we edit the account, we're modifying the correct sessions
|
|
712
|
-
const accountInNode = new RawControlledAccount(
|
|
713
|
-
newNode.expectCoValueLoaded(account.id),
|
|
714
|
-
account.agentSecret,
|
|
715
|
-
);
|
|
716
|
-
if (accountInNode.core.node !== newNode) {
|
|
717
|
-
throw new Error("Account's node is not the new node");
|
|
718
|
-
}
|
|
719
|
-
newNode.account = accountInNode;
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
return newNode;
|
|
685
|
+
if (account instanceof RawControlledAccount) {
|
|
686
|
+
// To make sure that when we edit the account, we're modifying the correct sessions
|
|
687
|
+
const accountInNode = new RawControlledAccount(
|
|
688
|
+
newNode.expectCoValueLoaded(account.id),
|
|
689
|
+
account.agentSecret,
|
|
690
|
+
);
|
|
691
|
+
if (accountInNode.core.node !== newNode) {
|
|
692
|
+
throw new Error("Account's node is not the new node");
|
|
693
|
+
}
|
|
694
|
+
newNode.account = accountInNode;
|
|
723
695
|
}
|
|
724
696
|
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
697
|
+
return newNode;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
gracefulShutdown() {
|
|
701
|
+
this.syncManager.gracefulShutdown();
|
|
702
|
+
}
|
|
728
703
|
}
|
|
729
704
|
|
|
730
705
|
export type LoadCoValueCoreError = {
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
706
|
+
type: "ErrorLoadingCoValueCore";
|
|
707
|
+
error: unknown;
|
|
708
|
+
expectation?: string;
|
|
709
|
+
id: RawAccountID;
|
|
735
710
|
};
|
|
736
711
|
|
|
737
712
|
export type AccountUnavailableFromAllPeersError = {
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
713
|
+
type: "AccountUnavailableFromAllPeers";
|
|
714
|
+
expectation?: string;
|
|
715
|
+
id: RawAccountID;
|
|
741
716
|
};
|
|
742
717
|
|
|
743
718
|
export type UnexpectedlyNotAccountError = {
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
719
|
+
type: "UnexpectedlyNotAccount";
|
|
720
|
+
expectation?: string;
|
|
721
|
+
id: RawAccountID;
|
|
747
722
|
};
|
|
748
723
|
|
|
749
724
|
export type ResolveAccountAgentError =
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
725
|
+
| InvalidAccountAgentIDError
|
|
726
|
+
| LoadCoValueCoreError
|
|
727
|
+
| AccountUnavailableFromAllPeersError
|
|
728
|
+
| UnexpectedlyNotAccountError;
|