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
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { getGroupDependentKeyList, getParentGroupId, isParentGroupReference, } from "
|
|
5
|
-
import { parseJSON, stableStringify } from "
|
|
6
|
-
import { logger } from "
|
|
7
|
-
import { determineValidTransactions, isKeyForKeyField, } from "
|
|
8
|
-
import {
|
|
9
|
-
import { accountOrAgentIDfromSessionID } from "
|
|
10
|
-
import { expectGroup } from "
|
|
11
|
-
import { isAccountID } from "
|
|
1
|
+
import { ValueType, metrics } from "@opentelemetry/api";
|
|
2
|
+
import { err } from "neverthrow";
|
|
3
|
+
import { coreToCoValue } from "../coreToCoValue.js";
|
|
4
|
+
import { getGroupDependentKeyList, getParentGroupId, isParentGroupReference, } from "../ids.js";
|
|
5
|
+
import { parseJSON, stableStringify } from "../jsonStringify.js";
|
|
6
|
+
import { logger } from "../logger.js";
|
|
7
|
+
import { determineValidTransactions, isKeyForKeyField, } from "../permissions.js";
|
|
8
|
+
import { emptyKnownState } from "../sync.js";
|
|
9
|
+
import { accountOrAgentIDfromSessionID } from "../typeUtils/accountOrAgentIDfromSessionID.js";
|
|
10
|
+
import { expectGroup } from "../typeUtils/expectGroup.js";
|
|
11
|
+
import { isAccountID } from "../typeUtils/isAccountID.js";
|
|
12
|
+
import { VerifiedState } from "./verifiedState.js";
|
|
12
13
|
/**
|
|
13
14
|
In order to not block other concurrently syncing CoValues we introduce a maximum size of transactions,
|
|
14
15
|
since they are the smallest unit of progress that can be synced within a CoValue.
|
|
@@ -22,27 +23,164 @@ export function idforHeader(header, crypto) {
|
|
|
22
23
|
return `co_z${hash.slice("shortHash_z".length)}`;
|
|
23
24
|
}
|
|
24
25
|
const readKeyCache = new WeakMap();
|
|
26
|
+
export const CO_VALUE_LOADING_CONFIG = {
|
|
27
|
+
MAX_RETRIES: 2,
|
|
28
|
+
TIMEOUT: 30000,
|
|
29
|
+
};
|
|
25
30
|
export class CoValueCore {
|
|
26
|
-
|
|
31
|
+
/** Holds the fundamental syncable content of a CoValue,
|
|
32
|
+
* consisting of the header (verified by hash -> RawCoID)
|
|
33
|
+
* and the sessions (verified by signature).
|
|
34
|
+
*
|
|
35
|
+
* It does not do any *validation* or *decryption* and as such doesn't
|
|
36
|
+
* depend on other CoValues or the LocalNode.
|
|
37
|
+
*
|
|
38
|
+
* `CoValueCore.verified` may be null when a CoValue is requested to be
|
|
39
|
+
* loaded but no content has been received from storage or peers yet.
|
|
40
|
+
* In this case, it acts as a centralised entry to keep track of peer loading
|
|
41
|
+
* state and to subscribe to its content when it does become available. */
|
|
42
|
+
get verified() {
|
|
43
|
+
return this._verified;
|
|
44
|
+
}
|
|
45
|
+
constructor(init, node) {
|
|
46
|
+
this.peers = new Map();
|
|
27
47
|
this.listeners = new Set();
|
|
28
48
|
this._decryptionCache = {};
|
|
29
49
|
this.deferredUpdates = 0;
|
|
30
50
|
this.crypto = node.crypto;
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
51
|
+
if ("header" in init) {
|
|
52
|
+
this.id = idforHeader(init.header, node.crypto);
|
|
53
|
+
this._verified = new VerifiedState(this.id, node.crypto, init.header, new Map());
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
this.id = init.id;
|
|
57
|
+
this._verified = null;
|
|
58
|
+
}
|
|
34
59
|
this.node = node;
|
|
60
|
+
this.counter = metrics
|
|
61
|
+
.getMeter("cojson")
|
|
62
|
+
.createUpDownCounter("jazz.covalues.loaded", {
|
|
63
|
+
description: "The number of covalues in the system",
|
|
64
|
+
unit: "covalue",
|
|
65
|
+
valueType: ValueType.INT,
|
|
66
|
+
});
|
|
67
|
+
this.updateCounter(null);
|
|
68
|
+
}
|
|
69
|
+
static fromID(id, node) {
|
|
70
|
+
return new CoValueCore({ id }, node);
|
|
71
|
+
}
|
|
72
|
+
static fromHeader(header, node) {
|
|
73
|
+
return new CoValueCore({ header }, node);
|
|
74
|
+
}
|
|
75
|
+
get loadingState() {
|
|
76
|
+
if (this.verified) {
|
|
77
|
+
return "available";
|
|
78
|
+
}
|
|
79
|
+
else if (this.peers.size === 0) {
|
|
80
|
+
return "unknown";
|
|
81
|
+
}
|
|
82
|
+
for (const peer of this.peers.values()) {
|
|
83
|
+
if (peer.type === "pending") {
|
|
84
|
+
return "loading";
|
|
85
|
+
}
|
|
86
|
+
else if (peer.type === "unknown") {
|
|
87
|
+
return "unknown";
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return "unavailable";
|
|
91
|
+
}
|
|
92
|
+
isAvailable() {
|
|
93
|
+
return !!this.verified;
|
|
94
|
+
}
|
|
95
|
+
isErroredInPeer(peerId) {
|
|
96
|
+
return this.peers.get(peerId)?.type === "errored";
|
|
97
|
+
}
|
|
98
|
+
waitForAvailableOrUnavailable() {
|
|
99
|
+
return new Promise((resolve) => {
|
|
100
|
+
const listener = (core) => {
|
|
101
|
+
if (core.isAvailable() || core.loadingState === "unavailable") {
|
|
102
|
+
resolve(core);
|
|
103
|
+
this.listeners.delete(listener);
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
this.listeners.add(listener);
|
|
107
|
+
listener(this);
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
getStateForPeer(peerId) {
|
|
111
|
+
return this.peers.get(peerId);
|
|
112
|
+
}
|
|
113
|
+
updateCounter(previousState) {
|
|
114
|
+
const newState = this.loadingState;
|
|
115
|
+
if (previousState !== newState) {
|
|
116
|
+
if (previousState) {
|
|
117
|
+
this.counter.add(-1, { state: previousState });
|
|
118
|
+
}
|
|
119
|
+
this.counter.add(1, { state: newState });
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
markNotFoundInPeer(peerId) {
|
|
123
|
+
const previousState = this.loadingState;
|
|
124
|
+
this.peers.set(peerId, { type: "unavailable" });
|
|
125
|
+
this.updateCounter(previousState);
|
|
126
|
+
this.notifyUpdate("immediate");
|
|
127
|
+
}
|
|
128
|
+
// TODO: rename to "provided"
|
|
129
|
+
markAvailable(header, fromPeerId) {
|
|
130
|
+
const previousState = this.loadingState;
|
|
131
|
+
if (this._verified?.sessions.size) {
|
|
132
|
+
throw new Error("CoValueCore: markAvailable called on coValue with verified sessions present!");
|
|
133
|
+
}
|
|
134
|
+
this._verified = new VerifiedState(this.id, this.node.crypto, header, new Map());
|
|
135
|
+
this.peers.set(fromPeerId, { type: "available" });
|
|
136
|
+
this.updateCounter(previousState);
|
|
137
|
+
this.notifyUpdate("immediate");
|
|
138
|
+
}
|
|
139
|
+
internalMarkMagicallyAvailable(verified, { forceOverwrite = false } = {}) {
|
|
140
|
+
const previousState = this.loadingState;
|
|
141
|
+
this.internalShamefullyCloneVerifiedStateFrom(verified, {
|
|
142
|
+
forceOverwrite,
|
|
143
|
+
});
|
|
144
|
+
this.updateCounter(previousState);
|
|
145
|
+
this.notifyUpdate("immediate");
|
|
146
|
+
}
|
|
147
|
+
markErrored(peerId, error) {
|
|
148
|
+
const previousState = this.loadingState;
|
|
149
|
+
this.peers.set(peerId, { type: "errored", error });
|
|
150
|
+
this.updateCounter(previousState);
|
|
151
|
+
this.notifyUpdate("immediate");
|
|
152
|
+
}
|
|
153
|
+
markPending(peerId) {
|
|
154
|
+
const previousState = this.loadingState;
|
|
155
|
+
this.peers.set(peerId, { type: "pending" });
|
|
156
|
+
this.updateCounter(previousState);
|
|
157
|
+
this.notifyUpdate("immediate");
|
|
158
|
+
}
|
|
159
|
+
internalShamefullyCloneVerifiedStateFrom(state, { forceOverwrite = false } = {}) {
|
|
160
|
+
if (!forceOverwrite && this._verified?.sessions.size) {
|
|
161
|
+
throw new Error("CoValueCore: internalShamefullyCloneVerifiedStateFrom called on coValue with verified sessions present!");
|
|
162
|
+
}
|
|
163
|
+
this._verified = state.clone();
|
|
164
|
+
this._cachedContent = undefined;
|
|
165
|
+
this._cachedDependentOn = undefined;
|
|
166
|
+
}
|
|
167
|
+
internalShamefullyResetCachedContent() {
|
|
168
|
+
this._cachedContent = undefined;
|
|
169
|
+
this._cachedDependentOn = undefined;
|
|
35
170
|
}
|
|
36
171
|
subscribeToGroupInvalidation() {
|
|
172
|
+
if (!this.verified) {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
37
175
|
if (this.groupInvalidationSubscription) {
|
|
38
176
|
return;
|
|
39
177
|
}
|
|
40
|
-
const header = this.header;
|
|
178
|
+
const header = this.verified.header;
|
|
41
179
|
if (header.ruleset.type == "ownedByGroup") {
|
|
42
180
|
const groupId = header.ruleset.group;
|
|
43
|
-
const entry = this.node.
|
|
181
|
+
const entry = this.node.getCoValue(groupId);
|
|
44
182
|
if (entry.isAvailable()) {
|
|
45
|
-
this.groupInvalidationSubscription = entry.
|
|
183
|
+
this.groupInvalidationSubscription = entry.subscribe((_groupUpdate) => {
|
|
46
184
|
this._cachedContent = undefined;
|
|
47
185
|
this.notifyUpdate("immediate");
|
|
48
186
|
}, false);
|
|
@@ -55,131 +193,71 @@ export class CoValueCore {
|
|
|
55
193
|
}
|
|
56
194
|
}
|
|
57
195
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
testWithDifferentAccount(account, currentSessionID) {
|
|
62
|
-
const newNode = this.node.testWithDifferentAccount(account, currentSessionID);
|
|
63
|
-
return newNode.expectCoValueLoaded(this.id);
|
|
196
|
+
contentInClonedNodeWithDifferentAccount(controlledAccountOrAgent) {
|
|
197
|
+
const newNode = this.node.cloneWithDifferentAccount(controlledAccountOrAgent);
|
|
198
|
+
return newNode.expectCoValueLoaded(this.id).getCurrentContent();
|
|
64
199
|
}
|
|
65
200
|
knownState() {
|
|
66
|
-
if (this.
|
|
67
|
-
return this.
|
|
201
|
+
if (this.isAvailable()) {
|
|
202
|
+
return this.verified.knownState();
|
|
68
203
|
}
|
|
69
204
|
else {
|
|
70
|
-
|
|
71
|
-
this._cachedKnownState = knownState;
|
|
72
|
-
return knownState;
|
|
205
|
+
return emptyKnownState(this.id);
|
|
73
206
|
}
|
|
74
207
|
}
|
|
75
|
-
/** @internal */
|
|
76
|
-
knownStateUncached() {
|
|
77
|
-
const sessions = {};
|
|
78
|
-
for (const [sessionID, sessionLog] of this.sessionLogs.entries()) {
|
|
79
|
-
sessions[sessionID] = sessionLog.transactions.length;
|
|
80
|
-
}
|
|
81
|
-
return {
|
|
82
|
-
id: this.id,
|
|
83
|
-
header: true,
|
|
84
|
-
sessions,
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
208
|
get meta() {
|
|
88
|
-
return this.header
|
|
209
|
+
return this.verified?.header.meta ?? null;
|
|
89
210
|
}
|
|
90
211
|
nextTransactionID() {
|
|
212
|
+
if (!this.verified) {
|
|
213
|
+
throw new Error("CoValueCore: nextTransactionID called on coValue without verified state");
|
|
214
|
+
}
|
|
91
215
|
// This is an ugly hack to get a unique but stable session ID for editing the current account
|
|
92
|
-
const sessionID = this.header.meta?.type === "account"
|
|
93
|
-
? this.node.currentSessionID.replace(this.node.
|
|
216
|
+
const sessionID = this.verified.header.meta?.type === "account"
|
|
217
|
+
? this.node.currentSessionID.replace(this.node.getCurrentAgent().id, this.node.getCurrentAgent().currentAgentID())
|
|
94
218
|
: this.node.currentSessionID;
|
|
95
219
|
return {
|
|
96
220
|
sessionID,
|
|
97
|
-
txIndex: this.
|
|
221
|
+
txIndex: this.verified.sessions.get(sessionID)?.transactions.length || 0,
|
|
98
222
|
};
|
|
99
223
|
}
|
|
100
|
-
tryAddTransactions(sessionID, newTransactions, givenExpectedNewHash, newSignature, skipVerify = false, givenNewStreamingHash) {
|
|
224
|
+
tryAddTransactions(sessionID, newTransactions, givenExpectedNewHash, newSignature, notifyMode, skipVerify = false, givenNewStreamingHash) {
|
|
101
225
|
return this.node
|
|
102
226
|
.resolveAccountAgent(accountOrAgentIDfromSessionID(sessionID), "Expected to know signer of transaction")
|
|
103
227
|
.andThen((agent) => {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
228
|
+
if (!this.verified) {
|
|
229
|
+
return err({
|
|
230
|
+
type: "TriedToAddTransactionsWithoutVerifiedState",
|
|
231
|
+
id: this.id,
|
|
232
|
+
});
|
|
109
233
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
expectedNewHash,
|
|
118
|
-
givenExpectedNewHash,
|
|
119
|
-
});
|
|
234
|
+
const signerID = this.crypto.getAgentSignerID(agent);
|
|
235
|
+
const result = this.verified.tryAddTransactions(sessionID, signerID, newTransactions, givenExpectedNewHash, newSignature, skipVerify, givenNewStreamingHash);
|
|
236
|
+
if (result.isOk()) {
|
|
237
|
+
if (this._cachedContent &&
|
|
238
|
+
"processNewTransactions" in this._cachedContent &&
|
|
239
|
+
typeof this._cachedContent.processNewTransactions === "function") {
|
|
240
|
+
this._cachedContent.processNewTransactions();
|
|
120
241
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
type: "InvalidSignature",
|
|
124
|
-
id: this.id,
|
|
125
|
-
newSignature,
|
|
126
|
-
sessionID,
|
|
127
|
-
signerID,
|
|
128
|
-
});
|
|
242
|
+
else {
|
|
243
|
+
this._cachedContent = undefined;
|
|
129
244
|
}
|
|
130
|
-
this.
|
|
245
|
+
this._cachedDependentOn = undefined;
|
|
246
|
+
this.notifyUpdate(notifyMode);
|
|
131
247
|
}
|
|
132
|
-
return
|
|
248
|
+
return result;
|
|
133
249
|
});
|
|
134
250
|
}
|
|
135
|
-
doAddTransactions(sessionID, newTransactions, newSignature, expectedNewHash, newStreamingHash, notifyMode) {
|
|
136
|
-
if (this.node.crashed) {
|
|
137
|
-
throw new Error("Trying to add transactions after node is crashed");
|
|
138
|
-
}
|
|
139
|
-
const transactions = this.sessionLogs.get(sessionID)?.transactions ?? [];
|
|
140
|
-
for (const tx of newTransactions) {
|
|
141
|
-
transactions.push(tx);
|
|
142
|
-
}
|
|
143
|
-
const signatureAfter = this.sessionLogs.get(sessionID)?.signatureAfter ?? {};
|
|
144
|
-
const lastInbetweenSignatureIdx = Object.keys(signatureAfter).reduce((max, idx) => (parseInt(idx) > max ? parseInt(idx) : max), -1);
|
|
145
|
-
const sizeOfTxsSinceLastInbetweenSignature = transactions
|
|
146
|
-
.slice(lastInbetweenSignatureIdx + 1)
|
|
147
|
-
.reduce((sum, tx) => sum +
|
|
148
|
-
(tx.privacy === "private"
|
|
149
|
-
? tx.encryptedChanges.length
|
|
150
|
-
: tx.changes.length), 0);
|
|
151
|
-
if (sizeOfTxsSinceLastInbetweenSignature > MAX_RECOMMENDED_TX_SIZE) {
|
|
152
|
-
signatureAfter[transactions.length - 1] = newSignature;
|
|
153
|
-
}
|
|
154
|
-
this._sessionLogs.set(sessionID, {
|
|
155
|
-
transactions,
|
|
156
|
-
lastHash: expectedNewHash,
|
|
157
|
-
streamingHash: newStreamingHash,
|
|
158
|
-
lastSignature: newSignature,
|
|
159
|
-
signatureAfter: signatureAfter,
|
|
160
|
-
});
|
|
161
|
-
if (this._cachedContent &&
|
|
162
|
-
"processNewTransactions" in this._cachedContent &&
|
|
163
|
-
typeof this._cachedContent.processNewTransactions === "function") {
|
|
164
|
-
this._cachedContent.processNewTransactions();
|
|
165
|
-
}
|
|
166
|
-
else {
|
|
167
|
-
this._cachedContent = undefined;
|
|
168
|
-
}
|
|
169
|
-
this._cachedKnownState = undefined;
|
|
170
|
-
this._cachedDependentOn = undefined;
|
|
171
|
-
this._cachedNewContentSinceEmpty = undefined;
|
|
172
|
-
this.notifyUpdate(notifyMode);
|
|
173
|
-
}
|
|
174
251
|
notifyUpdate(notifyMode) {
|
|
175
252
|
if (this.listeners.size === 0) {
|
|
176
253
|
return;
|
|
177
254
|
}
|
|
178
255
|
if (notifyMode === "immediate") {
|
|
179
|
-
const content = this.getCurrentContent();
|
|
180
256
|
for (const listener of this.listeners) {
|
|
181
257
|
try {
|
|
182
|
-
listener(
|
|
258
|
+
listener(this, () => {
|
|
259
|
+
this.listeners.delete(listener);
|
|
260
|
+
});
|
|
183
261
|
}
|
|
184
262
|
catch (e) {
|
|
185
263
|
logger.error("Error in listener for coValue " + this.id, { err: e });
|
|
@@ -192,10 +270,11 @@ export class CoValueCore {
|
|
|
192
270
|
setTimeout(() => {
|
|
193
271
|
this.nextDeferredNotify = undefined;
|
|
194
272
|
this.deferredUpdates = 0;
|
|
195
|
-
const content = this.getCurrentContent();
|
|
196
273
|
for (const listener of this.listeners) {
|
|
197
274
|
try {
|
|
198
|
-
listener(
|
|
275
|
+
listener(this, () => {
|
|
276
|
+
this.listeners.delete(listener);
|
|
277
|
+
});
|
|
199
278
|
}
|
|
200
279
|
catch (e) {
|
|
201
280
|
logger.error("Error in listener for coValue " + this.id, {
|
|
@@ -213,24 +292,18 @@ export class CoValueCore {
|
|
|
213
292
|
subscribe(listener, immediateInvoke = true) {
|
|
214
293
|
this.listeners.add(listener);
|
|
215
294
|
if (immediateInvoke) {
|
|
216
|
-
listener(this
|
|
295
|
+
listener(this, () => {
|
|
296
|
+
this.listeners.delete(listener);
|
|
297
|
+
});
|
|
217
298
|
}
|
|
218
299
|
return () => {
|
|
219
300
|
this.listeners.delete(listener);
|
|
220
301
|
};
|
|
221
302
|
}
|
|
222
|
-
expectedNewHashAfter(sessionID, newTransactions) {
|
|
223
|
-
const streamingHash = this.sessionLogs.get(sessionID)?.streamingHash.clone() ??
|
|
224
|
-
new StreamingHash(this.crypto);
|
|
225
|
-
for (const transaction of newTransactions) {
|
|
226
|
-
streamingHash.update(transaction);
|
|
227
|
-
}
|
|
228
|
-
return {
|
|
229
|
-
expectedNewHash: streamingHash.digest(),
|
|
230
|
-
newStreamingHash: streamingHash,
|
|
231
|
-
};
|
|
232
|
-
}
|
|
233
303
|
makeTransaction(changes, privacy) {
|
|
304
|
+
if (!this.verified) {
|
|
305
|
+
throw new Error("CoValueCore: makeTransaction called on coValue without verified state");
|
|
306
|
+
}
|
|
234
307
|
const madeAt = Date.now();
|
|
235
308
|
let transaction;
|
|
236
309
|
if (privacy === "private") {
|
|
@@ -258,12 +331,12 @@ export class CoValueCore {
|
|
|
258
331
|
};
|
|
259
332
|
}
|
|
260
333
|
// This is an ugly hack to get a unique but stable session ID for editing the current account
|
|
261
|
-
const sessionID = this.header.meta?.type === "account"
|
|
262
|
-
? this.node.currentSessionID.replace(this.node.
|
|
334
|
+
const sessionID = this.verified.header.meta?.type === "account"
|
|
335
|
+
? this.node.currentSessionID.replace(this.node.getCurrentAgent().id, this.node.getCurrentAgent().currentAgentID())
|
|
263
336
|
: this.node.currentSessionID;
|
|
264
|
-
const { expectedNewHash, newStreamingHash } = this.expectedNewHashAfter(sessionID, [transaction]);
|
|
265
|
-
const signature = this.crypto.sign(this.node.
|
|
266
|
-
const success = this.tryAddTransactions(sessionID, [transaction], expectedNewHash, signature, true, newStreamingHash)._unsafeUnwrap({ withStackTrace: true });
|
|
337
|
+
const { expectedNewHash, newStreamingHash } = this.verified.expectedNewHashAfter(sessionID, [transaction]);
|
|
338
|
+
const signature = this.crypto.sign(this.node.getCurrentAgent().currentSignerSecret(), expectedNewHash);
|
|
339
|
+
const success = this.tryAddTransactions(sessionID, [transaction], expectedNewHash, signature, "immediate", true, newStreamingHash)._unsafeUnwrap({ withStackTrace: true });
|
|
267
340
|
if (success) {
|
|
268
341
|
this.node.syncManager.recordTransactionsSize([transaction], "local");
|
|
269
342
|
void this.node.syncManager.requestCoValueSync(this);
|
|
@@ -271,6 +344,9 @@ export class CoValueCore {
|
|
|
271
344
|
return success;
|
|
272
345
|
}
|
|
273
346
|
getCurrentContent(options) {
|
|
347
|
+
if (!this.verified) {
|
|
348
|
+
throw new Error("CoValueCore: getCurrentContent called on coValue without verified state");
|
|
349
|
+
}
|
|
274
350
|
if (!options?.ignorePrivateTransactions && this._cachedContent) {
|
|
275
351
|
return this._cachedContent;
|
|
276
352
|
}
|
|
@@ -332,16 +408,19 @@ export class CoValueCore {
|
|
|
332
408
|
return allTransactions;
|
|
333
409
|
}
|
|
334
410
|
compareTransactions(a, b) {
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
411
|
+
if (a.madeAt !== b.madeAt) {
|
|
412
|
+
return a.madeAt - b.madeAt;
|
|
413
|
+
}
|
|
414
|
+
if (a.txID.sessionID === b.txID.sessionID) {
|
|
415
|
+
return a.txID.txIndex - b.txID.txIndex;
|
|
416
|
+
}
|
|
417
|
+
return 0;
|
|
342
418
|
}
|
|
343
419
|
getCurrentReadKey() {
|
|
344
|
-
if (this.
|
|
420
|
+
if (!this.verified) {
|
|
421
|
+
throw new Error("CoValueCore: getCurrentReadKey called on coValue without verified state");
|
|
422
|
+
}
|
|
423
|
+
if (this.verified.header.ruleset.type === "group") {
|
|
345
424
|
const content = expectGroup(this.getCurrentContent());
|
|
346
425
|
const currentKeyId = content.getCurrentReadKeyId();
|
|
347
426
|
if (!currentKeyId) {
|
|
@@ -353,9 +432,9 @@ export class CoValueCore {
|
|
|
353
432
|
id: currentKeyId,
|
|
354
433
|
};
|
|
355
434
|
}
|
|
356
|
-
else if (this.header.ruleset.type === "ownedByGroup") {
|
|
435
|
+
else if (this.verified.header.ruleset.type === "ownedByGroup") {
|
|
357
436
|
return this.node
|
|
358
|
-
.expectCoValueLoaded(this.header.ruleset.group)
|
|
437
|
+
.expectCoValueLoaded(this.verified.header.ruleset.group)
|
|
359
438
|
.getCurrentReadKey();
|
|
360
439
|
}
|
|
361
440
|
else {
|
|
@@ -378,22 +457,31 @@ export class CoValueCore {
|
|
|
378
457
|
return key;
|
|
379
458
|
}
|
|
380
459
|
getUncachedReadKey(keyID) {
|
|
381
|
-
if (this.
|
|
460
|
+
if (!this.verified) {
|
|
461
|
+
throw new Error("CoValueCore: getUncachedReadKey called on coValue without verified state");
|
|
462
|
+
}
|
|
463
|
+
if (this.verified.header.ruleset.type === "group") {
|
|
382
464
|
const content = expectGroup(this.getCurrentContent({ ignorePrivateTransactions: true }));
|
|
383
465
|
const keyForEveryone = content.get(`${keyID}_for_everyone`);
|
|
384
|
-
if (keyForEveryone)
|
|
466
|
+
if (keyForEveryone) {
|
|
385
467
|
return keyForEveryone;
|
|
468
|
+
}
|
|
386
469
|
// Try to find key revelation for us
|
|
387
|
-
const
|
|
388
|
-
|
|
389
|
-
|
|
470
|
+
const currentAgentOrAccountID = accountOrAgentIDfromSessionID(this.node.currentSessionID);
|
|
471
|
+
// being careful here to avoid recursion
|
|
472
|
+
const lookupAccountOrAgentID = isAccountID(currentAgentOrAccountID)
|
|
473
|
+
? this.id === currentAgentOrAccountID
|
|
474
|
+
? this.crypto.getAgentID(this.node.agentSecret) // in accounts, the read key is revealed for the primitive agent
|
|
475
|
+
: currentAgentOrAccountID // current account ID
|
|
476
|
+
: currentAgentOrAccountID; // current agent ID
|
|
390
477
|
const lastReadyKeyEdit = content.lastEditAt(`${keyID}_for_${lookupAccountOrAgentID}`);
|
|
391
478
|
if (lastReadyKeyEdit?.value) {
|
|
392
479
|
const revealer = lastReadyKeyEdit.by;
|
|
393
480
|
const revealerAgent = this.node
|
|
394
481
|
.resolveAccountAgent(revealer, "Expected to know revealer")
|
|
395
482
|
._unsafeUnwrap({ withStackTrace: true });
|
|
396
|
-
const secret = this.crypto.unseal(lastReadyKeyEdit.value, this.
|
|
483
|
+
const secret = this.crypto.unseal(lastReadyKeyEdit.value, this.crypto.getAgentSealerSecret(this.node.agentSecret), // being careful here to avoid recursion
|
|
484
|
+
this.crypto.getAgentSealerID(revealerAgent), {
|
|
397
485
|
in: this.id,
|
|
398
486
|
tx: lastReadyKeyEdit.tx,
|
|
399
487
|
});
|
|
@@ -449,9 +537,9 @@ export class CoValueCore {
|
|
|
449
537
|
}
|
|
450
538
|
return undefined;
|
|
451
539
|
}
|
|
452
|
-
else if (this.header.ruleset.type === "ownedByGroup") {
|
|
540
|
+
else if (this.verified.header.ruleset.type === "ownedByGroup") {
|
|
453
541
|
return this.node
|
|
454
|
-
.expectCoValueLoaded(this.header.ruleset.group)
|
|
542
|
+
.expectCoValueLoaded(this.verified.header.ruleset.group)
|
|
455
543
|
.getReadKey(keyID);
|
|
456
544
|
}
|
|
457
545
|
else {
|
|
@@ -476,106 +564,18 @@ export class CoValueCore {
|
|
|
476
564
|
return validParentKeys;
|
|
477
565
|
}
|
|
478
566
|
getGroup() {
|
|
479
|
-
if (this.
|
|
567
|
+
if (!this.verified) {
|
|
568
|
+
throw new Error("CoValueCore: getGroup called on coValue without verified state");
|
|
569
|
+
}
|
|
570
|
+
if (this.verified.header.ruleset.type !== "ownedByGroup") {
|
|
480
571
|
throw new Error("Only values owned by groups have groups");
|
|
481
572
|
}
|
|
482
573
|
return expectGroup(this.node
|
|
483
|
-
.expectCoValueLoaded(this.header.ruleset.group)
|
|
574
|
+
.expectCoValueLoaded(this.verified.header.ruleset.group)
|
|
484
575
|
.getCurrentContent());
|
|
485
576
|
}
|
|
486
577
|
getTx(txID) {
|
|
487
|
-
return this.
|
|
488
|
-
}
|
|
489
|
-
newContentSince(knownState) {
|
|
490
|
-
const isKnownStateEmpty = !knownState?.header && !knownState?.sessions;
|
|
491
|
-
if (isKnownStateEmpty && this._cachedNewContentSinceEmpty) {
|
|
492
|
-
return this._cachedNewContentSinceEmpty;
|
|
493
|
-
}
|
|
494
|
-
let currentPiece = {
|
|
495
|
-
action: "content",
|
|
496
|
-
id: this.id,
|
|
497
|
-
header: knownState?.header ? undefined : this.header,
|
|
498
|
-
priority: getPriorityFromHeader(this.header),
|
|
499
|
-
new: {},
|
|
500
|
-
};
|
|
501
|
-
const pieces = [currentPiece];
|
|
502
|
-
const sentState = {};
|
|
503
|
-
let pieceSize = 0;
|
|
504
|
-
let sessionsTodoAgain = "first";
|
|
505
|
-
while (sessionsTodoAgain === "first" || sessionsTodoAgain?.size || 0 > 0) {
|
|
506
|
-
if (sessionsTodoAgain === "first") {
|
|
507
|
-
sessionsTodoAgain = undefined;
|
|
508
|
-
}
|
|
509
|
-
const sessionsTodo = sessionsTodoAgain ?? this.sessionLogs.keys();
|
|
510
|
-
for (const sessionIDKey of sessionsTodo) {
|
|
511
|
-
const sessionID = sessionIDKey;
|
|
512
|
-
const log = this.sessionLogs.get(sessionID);
|
|
513
|
-
const knownStateForSessionID = knownState?.sessions[sessionID];
|
|
514
|
-
const sentStateForSessionID = sentState[sessionID];
|
|
515
|
-
const nextKnownSignatureIdx = getNextKnownSignatureIdx(log, knownStateForSessionID, sentStateForSessionID);
|
|
516
|
-
const firstNewTxIdx = sentStateForSessionID ?? knownStateForSessionID ?? 0;
|
|
517
|
-
const afterLastNewTxIdx = nextKnownSignatureIdx === undefined
|
|
518
|
-
? log.transactions.length
|
|
519
|
-
: nextKnownSignatureIdx + 1;
|
|
520
|
-
const nNewTx = Math.max(0, afterLastNewTxIdx - firstNewTxIdx);
|
|
521
|
-
if (nNewTx === 0) {
|
|
522
|
-
sessionsTodoAgain?.delete(sessionID);
|
|
523
|
-
continue;
|
|
524
|
-
}
|
|
525
|
-
if (afterLastNewTxIdx < log.transactions.length) {
|
|
526
|
-
if (!sessionsTodoAgain) {
|
|
527
|
-
sessionsTodoAgain = new Set();
|
|
528
|
-
}
|
|
529
|
-
sessionsTodoAgain.add(sessionID);
|
|
530
|
-
}
|
|
531
|
-
const oldPieceSize = pieceSize;
|
|
532
|
-
for (let txIdx = firstNewTxIdx; txIdx < afterLastNewTxIdx; txIdx++) {
|
|
533
|
-
const tx = log.transactions[txIdx];
|
|
534
|
-
pieceSize +=
|
|
535
|
-
tx.privacy === "private"
|
|
536
|
-
? tx.encryptedChanges.length
|
|
537
|
-
: tx.changes.length;
|
|
538
|
-
}
|
|
539
|
-
if (pieceSize >= MAX_RECOMMENDED_TX_SIZE) {
|
|
540
|
-
currentPiece = {
|
|
541
|
-
action: "content",
|
|
542
|
-
id: this.id,
|
|
543
|
-
header: undefined,
|
|
544
|
-
new: {},
|
|
545
|
-
priority: getPriorityFromHeader(this.header),
|
|
546
|
-
};
|
|
547
|
-
pieces.push(currentPiece);
|
|
548
|
-
pieceSize = pieceSize - oldPieceSize;
|
|
549
|
-
}
|
|
550
|
-
let sessionEntry = currentPiece.new[sessionID];
|
|
551
|
-
if (!sessionEntry) {
|
|
552
|
-
sessionEntry = {
|
|
553
|
-
after: sentStateForSessionID ?? knownStateForSessionID ?? 0,
|
|
554
|
-
newTransactions: [],
|
|
555
|
-
lastSignature: "WILL_BE_REPLACED",
|
|
556
|
-
};
|
|
557
|
-
currentPiece.new[sessionID] = sessionEntry;
|
|
558
|
-
}
|
|
559
|
-
for (let txIdx = firstNewTxIdx; txIdx < afterLastNewTxIdx; txIdx++) {
|
|
560
|
-
const tx = log.transactions[txIdx];
|
|
561
|
-
sessionEntry.newTransactions.push(tx);
|
|
562
|
-
}
|
|
563
|
-
sessionEntry.lastSignature =
|
|
564
|
-
nextKnownSignatureIdx === undefined
|
|
565
|
-
? log.lastSignature
|
|
566
|
-
: log.signatureAfter[nextKnownSignatureIdx];
|
|
567
|
-
sentState[sessionID] =
|
|
568
|
-
(sentStateForSessionID ?? knownStateForSessionID ?? 0) + nNewTx;
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
const piecesWithContent = pieces.filter((piece) => Object.keys(piece.new).length > 0 || piece.header);
|
|
572
|
-
if (piecesWithContent.length === 0) {
|
|
573
|
-
return undefined;
|
|
574
|
-
}
|
|
575
|
-
if (isKnownStateEmpty) {
|
|
576
|
-
this._cachedNewContentSinceEmpty = piecesWithContent;
|
|
577
|
-
}
|
|
578
|
-
return piecesWithContent;
|
|
578
|
+
return this.verified?.sessions.get(txID.sessionID)?.transactions[txID.txIndex];
|
|
579
579
|
}
|
|
580
580
|
getDependedOnCoValues() {
|
|
581
581
|
if (this._cachedDependentOn) {
|
|
@@ -589,12 +589,15 @@ export class CoValueCore {
|
|
|
589
589
|
}
|
|
590
590
|
/** @internal */
|
|
591
591
|
getDependedOnCoValuesUncached() {
|
|
592
|
-
|
|
592
|
+
if (!this.verified) {
|
|
593
|
+
return [];
|
|
594
|
+
}
|
|
595
|
+
return this.verified.header.ruleset.type === "group"
|
|
593
596
|
? getGroupDependentKeyList(expectGroup(this.getCurrentContent()).keys())
|
|
594
|
-
: this.header.ruleset.type === "ownedByGroup"
|
|
597
|
+
: this.verified.header.ruleset.type === "ownedByGroup"
|
|
595
598
|
? [
|
|
596
|
-
this.header.ruleset.group,
|
|
597
|
-
...new Set([...this.
|
|
599
|
+
this.verified.header.ruleset.group,
|
|
600
|
+
...new Set([...this.verified.sessions.keys()]
|
|
598
601
|
.map((sessionID) => accountOrAgentIDfromSessionID(sessionID))
|
|
599
602
|
.filter((session) => isAccountID(session) && session !== this.id)),
|
|
600
603
|
]
|
|
@@ -603,11 +606,80 @@ export class CoValueCore {
|
|
|
603
606
|
waitForSync(options) {
|
|
604
607
|
return this.node.syncManager.waitForSync(this.id, options?.timeout);
|
|
605
608
|
}
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
609
|
+
async loadFromPeers(peers) {
|
|
610
|
+
if (peers.length === 0) {
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
const peersToActuallyLoadFrom = [];
|
|
614
|
+
for (const peer of peers) {
|
|
615
|
+
const currentState = this.peers.get(peer.id);
|
|
616
|
+
if (currentState?.type === "available" ||
|
|
617
|
+
currentState?.type === "pending") {
|
|
618
|
+
continue;
|
|
619
|
+
}
|
|
620
|
+
if (currentState?.type === "errored") {
|
|
621
|
+
continue;
|
|
622
|
+
}
|
|
623
|
+
if (currentState?.type === "unavailable") {
|
|
624
|
+
if (peer.shouldRetryUnavailableCoValues()) {
|
|
625
|
+
this.markPending(peer.id);
|
|
626
|
+
peersToActuallyLoadFrom.push(peer);
|
|
627
|
+
}
|
|
628
|
+
continue;
|
|
629
|
+
}
|
|
630
|
+
if (!currentState || currentState?.type === "unknown") {
|
|
631
|
+
this.markPending(peer.id);
|
|
632
|
+
peersToActuallyLoadFrom.push(peer);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
for (const peer of peersToActuallyLoadFrom) {
|
|
636
|
+
if (peer.closed) {
|
|
637
|
+
this.markNotFoundInPeer(peer.id);
|
|
638
|
+
continue;
|
|
639
|
+
}
|
|
640
|
+
peer.pushOutgoingMessage({
|
|
641
|
+
action: "load",
|
|
642
|
+
...this.knownState(),
|
|
643
|
+
});
|
|
644
|
+
peer.trackLoadRequestSent(this.id);
|
|
645
|
+
/**
|
|
646
|
+
* Use a very long timeout for storage peers, because under pressure
|
|
647
|
+
* they may take a long time to consume the messages queue
|
|
648
|
+
*
|
|
649
|
+
* TODO: Track errors on storage and do not rely on timeout
|
|
650
|
+
*/
|
|
651
|
+
const timeoutDuration = peer.role === "storage"
|
|
652
|
+
? CO_VALUE_LOADING_CONFIG.TIMEOUT * 10
|
|
653
|
+
: CO_VALUE_LOADING_CONFIG.TIMEOUT;
|
|
654
|
+
const waitingForPeer = new Promise((resolve) => {
|
|
655
|
+
const markNotFound = () => {
|
|
656
|
+
if (this.peers.get(peer.id)?.type === "pending") {
|
|
657
|
+
logger.warn("Timeout waiting for peer to load coValue", {
|
|
658
|
+
id: this.id,
|
|
659
|
+
peerID: peer.id,
|
|
660
|
+
});
|
|
661
|
+
this.markNotFoundInPeer(peer.id);
|
|
662
|
+
}
|
|
663
|
+
};
|
|
664
|
+
const timeout = setTimeout(markNotFound, timeoutDuration);
|
|
665
|
+
const removeCloseListener = peer.addCloseListener(markNotFound);
|
|
666
|
+
const listener = (state) => {
|
|
667
|
+
const peerState = state.peers.get(peer.id);
|
|
668
|
+
if (state.isAvailable() || // might have become available from another peer e.g. through handleNewContent
|
|
669
|
+
peerState?.type === "available" ||
|
|
670
|
+
peerState?.type === "errored" ||
|
|
671
|
+
peerState?.type === "unavailable") {
|
|
672
|
+
this.listeners.delete(listener);
|
|
673
|
+
removeCloseListener();
|
|
674
|
+
clearTimeout(timeout);
|
|
675
|
+
resolve();
|
|
676
|
+
}
|
|
677
|
+
};
|
|
678
|
+
this.listeners.add(listener);
|
|
679
|
+
listener(this);
|
|
680
|
+
});
|
|
681
|
+
await waitingForPeer;
|
|
682
|
+
}
|
|
683
|
+
}
|
|
612
684
|
}
|
|
613
685
|
//# sourceMappingURL=coValueCore.js.map
|