cojson 0.1.12 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/coValue.d.ts +1 -1
- package/dist/coValueCore.d.ts +10 -4
- package/dist/coValueCore.js +91 -46
- package/dist/coValueCore.js.map +1 -1
- package/dist/coValues/coList.js +2 -1
- package/dist/coValues/coList.js.map +1 -1
- package/dist/coValues/coMap.d.ts +7 -12
- package/dist/coValues/coMap.js +2 -1
- package/dist/coValues/coMap.js.map +1 -1
- package/dist/coValues/coStream.d.ts +11 -2
- package/dist/coValues/coStream.js +61 -14
- package/dist/coValues/coStream.js.map +1 -1
- package/dist/crypto.d.ts +8 -0
- package/dist/crypto.js +10 -3
- package/dist/crypto.js.map +1 -1
- package/dist/group.d.ts +1 -1
- package/dist/index.d.ts +6 -3
- package/dist/index.js +5 -3
- package/dist/index.js.map +1 -1
- package/dist/jsonStringify.d.ts +6 -0
- package/dist/{fastJsonStableStringify.js → jsonStringify.js} +10 -6
- package/dist/jsonStringify.js.map +1 -0
- package/dist/jsonValue.d.ts +1 -1
- package/dist/media.d.ts +8 -0
- package/dist/media.js +2 -0
- package/dist/media.js.map +1 -0
- package/dist/node.js +1 -1
- package/dist/permissions.js +4 -2
- package/dist/permissions.js.map +1 -1
- package/dist/streamUtils.js +14 -5
- package/dist/streamUtils.js.map +1 -1
- package/dist/sync.js +35 -15
- package/dist/sync.js.map +1 -1
- package/package.json +2 -2
- package/src/coValue.test.ts +113 -4
- package/src/coValue.ts +1 -1
- package/src/coValueCore.test.ts +11 -10
- package/src/coValueCore.ts +162 -75
- package/src/coValues/coList.ts +2 -1
- package/src/coValues/coMap.ts +11 -12
- package/src/coValues/coStream.ts +73 -21
- package/src/crypto.ts +22 -4
- package/src/group.ts +1 -1
- package/src/index.ts +7 -2
- package/src/{fastJsonStableStringify.ts → jsonStringify.ts} +23 -11
- package/src/jsonValue.ts +1 -1
- package/src/media.ts +9 -0
- package/src/node.ts +1 -1
- package/src/permissions.ts +5 -2
- package/src/streamUtils.ts +26 -6
- package/src/sync.test.ts +19 -20
- package/src/sync.ts +47 -26
- package/dist/fastJsonStableStringify.d.ts +0 -1
- package/dist/fastJsonStableStringify.js.map +0 -1
package/src/crypto.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { AgentID, RawCoID, TransactionID } from "./ids.js";
|
|
|
7
7
|
import { base64URLtoBytes, bytesToBase64url } from "./base64url.js";
|
|
8
8
|
|
|
9
9
|
import { createBLAKE3 } from 'hash-wasm';
|
|
10
|
-
import { stableStringify } from "./
|
|
10
|
+
import { Stringified, parseJSON, stableStringify } from "./jsonStringify.js";
|
|
11
11
|
|
|
12
12
|
let blake3Instance: Awaited<ReturnType<typeof createBLAKE3>>;
|
|
13
13
|
let blake3HashOnce: (data: Uint8Array) => Uint8Array;
|
|
@@ -316,11 +316,11 @@ export function encryptKeySecret(keys: {
|
|
|
316
316
|
};
|
|
317
317
|
}
|
|
318
318
|
|
|
319
|
-
function
|
|
319
|
+
function decryptRaw<T extends JsonValue, N extends JsonValue>(
|
|
320
320
|
encrypted: Encrypted<T, N>,
|
|
321
321
|
keySecret: KeySecret,
|
|
322
322
|
nOnceMaterial: N
|
|
323
|
-
): T
|
|
323
|
+
): Stringified<T> {
|
|
324
324
|
const keySecretBytes = base58.decode(
|
|
325
325
|
keySecret.substring("keySecret_z".length)
|
|
326
326
|
);
|
|
@@ -333,13 +333,31 @@ function decrypt<T extends JsonValue, N extends JsonValue>(
|
|
|
333
333
|
);
|
|
334
334
|
const plaintext = xsalsa20(keySecretBytes, nOnce, ciphertext);
|
|
335
335
|
|
|
336
|
+
return textDecoder.decode(plaintext) as Stringified<T>;
|
|
337
|
+
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function decrypt<T extends JsonValue, N extends JsonValue>(
|
|
341
|
+
encrypted: Encrypted<T, N>,
|
|
342
|
+
keySecret: KeySecret,
|
|
343
|
+
nOnceMaterial: N
|
|
344
|
+
): T | undefined {
|
|
336
345
|
try {
|
|
337
|
-
return
|
|
346
|
+
return parseJSON(decryptRaw(encrypted, keySecret, nOnceMaterial));
|
|
338
347
|
} catch (e) {
|
|
348
|
+
console.error("Decryption error", e)
|
|
339
349
|
return undefined;
|
|
340
350
|
}
|
|
341
351
|
}
|
|
342
352
|
|
|
353
|
+
export function decryptRawForTransaction<T extends JsonValue>(
|
|
354
|
+
encrypted: Encrypted<T, { in: RawCoID; tx: TransactionID }>,
|
|
355
|
+
keySecret: KeySecret,
|
|
356
|
+
nOnceMaterial: { in: RawCoID; tx: TransactionID }
|
|
357
|
+
): Stringified<T> | undefined {
|
|
358
|
+
return decryptRaw(encrypted, keySecret, nOnceMaterial);
|
|
359
|
+
}
|
|
360
|
+
|
|
343
361
|
export function decryptForTransaction<T extends JsonValue>(
|
|
344
362
|
encrypted: Encrypted<T, { in: RawCoID; tx: TransactionID }>,
|
|
345
363
|
keySecret: KeySecret,
|
package/src/group.ts
CHANGED
|
@@ -238,7 +238,7 @@ export class Group {
|
|
|
238
238
|
|
|
239
239
|
/** Creates a new `CoMap` within this group, with the specified specialized
|
|
240
240
|
* `CoMap` type `M` and optional static metadata. */
|
|
241
|
-
createMap<M extends CoMap<{ [key: string]: JsonValue }, JsonObject | null>>(
|
|
241
|
+
createMap<M extends CoMap<{ [key: string]: JsonValue | undefined; }, JsonObject | null>>(
|
|
242
242
|
meta?: M["meta"]
|
|
243
243
|
): M {
|
|
244
244
|
return this.node
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CoValueCore, newRandomSessionID } from "./coValueCore.js";
|
|
1
|
+
import { CoValueCore, newRandomSessionID, MAX_RECOMMENDED_TX_SIZE } from "./coValueCore.js";
|
|
2
2
|
import { LocalNode } from "./node.js";
|
|
3
3
|
import type { CoValue, ReadableCoValue } from "./coValue.js";
|
|
4
4
|
import { CoMap, WriteableCoMap } from "./coValues/coMap.js";
|
|
@@ -25,6 +25,7 @@ import { AnonymousControlledAccount, ControlledAccount } from "./account.js";
|
|
|
25
25
|
import { rawCoIDtoBytes, rawCoIDfromBytes } from "./ids.js";
|
|
26
26
|
import { Group, expectGroupContent } from "./group.js";
|
|
27
27
|
import { base64URLtoBytes, bytesToBase64url } from "./base64url.js";
|
|
28
|
+
import { parseJSON } from "./jsonStringify.js";
|
|
28
29
|
|
|
29
30
|
import type { SessionID, AgentID } from "./ids.js";
|
|
30
31
|
import type { CoID, CoValueImpl } from "./coValue.js";
|
|
@@ -34,6 +35,7 @@ import type { SyncMessage, Peer } from "./sync.js";
|
|
|
34
35
|
import type { AgentSecret } from "./crypto.js";
|
|
35
36
|
import type { AccountID, Profile } from "./account.js";
|
|
36
37
|
import type { InviteSecret } from "./group.js";
|
|
38
|
+
import type * as Media from "./media.js";
|
|
37
39
|
|
|
38
40
|
type Value = JsonValue | CoValueImpl;
|
|
39
41
|
|
|
@@ -53,7 +55,8 @@ export const cojsonInternals = {
|
|
|
53
55
|
shortHashLength,
|
|
54
56
|
expectGroupContent,
|
|
55
57
|
base64URLtoBytes,
|
|
56
|
-
bytesToBase64url
|
|
58
|
+
bytesToBase64url,
|
|
59
|
+
parseJSON
|
|
57
60
|
};
|
|
58
61
|
|
|
59
62
|
export {
|
|
@@ -71,6 +74,7 @@ export {
|
|
|
71
74
|
AnonymousControlledAccount,
|
|
72
75
|
ControlledAccount,
|
|
73
76
|
cryptoReady as cojsonReady,
|
|
77
|
+
MAX_RECOMMENDED_TX_SIZE
|
|
74
78
|
};
|
|
75
79
|
|
|
76
80
|
export type {
|
|
@@ -90,6 +94,7 @@ export type {
|
|
|
90
94
|
AgentSecret,
|
|
91
95
|
InviteSecret,
|
|
92
96
|
SyncMessage,
|
|
97
|
+
Media
|
|
93
98
|
};
|
|
94
99
|
|
|
95
100
|
// eslint-disable-next-line @typescript-eslint/no-namespace
|
|
@@ -1,24 +1,32 @@
|
|
|
1
1
|
// adapted from fast-json-stable-stringify (https://github.com/epoberezkin/fast-json-stable-stringify)
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
export type Stringified<T> = string & { __type: T };
|
|
4
|
+
|
|
5
|
+
export function stableStringify<T>(data: T): Stringified<T>
|
|
6
|
+
export function stableStringify(data: undefined): undefined
|
|
7
|
+
export function stableStringify<T>(data: T | undefined): Stringified<T> | undefined {
|
|
5
8
|
const cycles = false;
|
|
6
9
|
|
|
7
10
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
8
11
|
const seen: any[] = [];
|
|
9
|
-
|
|
12
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
13
|
+
let node = data as any;
|
|
10
14
|
|
|
11
15
|
if (node && node.toJSON && typeof node.toJSON === "function") {
|
|
12
16
|
node = node.toJSON();
|
|
13
17
|
}
|
|
14
18
|
|
|
15
19
|
if (node === undefined) return;
|
|
16
|
-
if (typeof node == "number")
|
|
20
|
+
if (typeof node == "number")
|
|
21
|
+
return (isFinite(node) ? "" + node : "null") as Stringified<T>;
|
|
17
22
|
if (typeof node !== "object") {
|
|
18
|
-
if (
|
|
19
|
-
|
|
23
|
+
if (
|
|
24
|
+
typeof node === "string" &&
|
|
25
|
+
(node.startsWith("encrypted_U") || node.startsWith("binary_U"))
|
|
26
|
+
) {
|
|
27
|
+
return `"${node}"` as Stringified<T>;
|
|
20
28
|
}
|
|
21
|
-
return JSON.stringify(node)
|
|
29
|
+
return JSON.stringify(node) as Stringified<T>;
|
|
22
30
|
}
|
|
23
31
|
|
|
24
32
|
let i, out;
|
|
@@ -28,13 +36,13 @@ export function stableStringify(data: any): string | undefined {
|
|
|
28
36
|
if (i) out += ",";
|
|
29
37
|
out += stableStringify(node[i]) || "null";
|
|
30
38
|
}
|
|
31
|
-
return out + "]"
|
|
39
|
+
return (out + "]") as Stringified<T>;
|
|
32
40
|
}
|
|
33
41
|
|
|
34
|
-
if (node === null) return "null"
|
|
42
|
+
if (node === null) return "null" as Stringified<T>;
|
|
35
43
|
|
|
36
44
|
if (seen.indexOf(node) !== -1) {
|
|
37
|
-
if (cycles) return JSON.stringify("__cycle__")
|
|
45
|
+
if (cycles) return JSON.stringify("__cycle__") as Stringified<T>;
|
|
38
46
|
throw new TypeError("Converting circular structure to JSON");
|
|
39
47
|
}
|
|
40
48
|
|
|
@@ -50,5 +58,9 @@ export function stableStringify(data: any): string | undefined {
|
|
|
50
58
|
out += JSON.stringify(key) + ":" + value;
|
|
51
59
|
}
|
|
52
60
|
seen.splice(seenIndex, 1);
|
|
53
|
-
return "{" + out + "}"
|
|
61
|
+
return ("{" + out + "}") as Stringified<T>;
|
|
54
62
|
}
|
|
63
|
+
|
|
64
|
+
export function parseJSON<T>(json: Stringified<T>): T {
|
|
65
|
+
return JSON.parse(json);
|
|
66
|
+
}
|
package/src/jsonValue.ts
CHANGED
|
@@ -3,4 +3,4 @@ import { RawCoID } from './ids.js';
|
|
|
3
3
|
export type JsonAtom = string | number | boolean | null;
|
|
4
4
|
export type JsonValue = JsonAtom | JsonArray | JsonObject | RawCoID;
|
|
5
5
|
export type JsonArray = JsonValue[];
|
|
6
|
-
export type JsonObject = { [key: string]: JsonValue; };
|
|
6
|
+
export type JsonObject = { [key: string]: JsonValue | undefined; };
|
package/src/media.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { CoMap } from './coValues/coMap.js'
|
|
2
|
+
import { CoID } from './coValue.js'
|
|
3
|
+
import { BinaryCoStream } from './coValues/coStream.js'
|
|
4
|
+
|
|
5
|
+
export type ImageDefinition = CoMap<{
|
|
6
|
+
originalSize: [number, number];
|
|
7
|
+
placeholderDataURL?: string;
|
|
8
|
+
[res: `${number}x${number}`]: CoID<BinaryCoStream>;
|
|
9
|
+
}>;
|
package/src/node.ts
CHANGED
package/src/permissions.ts
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
AccountID,
|
|
16
16
|
Profile,
|
|
17
17
|
} from "./account.js";
|
|
18
|
+
import { parseJSON } from "./jsonStringify.js";
|
|
18
19
|
|
|
19
20
|
export type PermissionsDef =
|
|
20
21
|
| { type: "group"; initialAdmin: AccountID | AgentID }
|
|
@@ -76,11 +77,13 @@ export function determineValidTransactions(
|
|
|
76
77
|
// console.log("before", { memberState, validTransactions });
|
|
77
78
|
const transactor = accountOrAgentIDfromSessionID(sessionID);
|
|
78
79
|
|
|
79
|
-
const
|
|
80
|
+
const changes = parseJSON(tx.changes)
|
|
81
|
+
|
|
82
|
+
const change = changes[0] as
|
|
80
83
|
| MapOpPayload<AccountID | AgentID, Role>
|
|
81
84
|
| MapOpPayload<"readKey", JsonValue>
|
|
82
85
|
| MapOpPayload<"profile", CoID<Profile>>;
|
|
83
|
-
if (
|
|
86
|
+
if (changes.length !== 1) {
|
|
84
87
|
console.warn("Group transaction must have exactly one change");
|
|
85
88
|
continue;
|
|
86
89
|
}
|
package/src/streamUtils.ts
CHANGED
|
@@ -34,7 +34,14 @@ export function connectedPeers(
|
|
|
34
34
|
trace &&
|
|
35
35
|
console.debug(
|
|
36
36
|
`${peer2id} -> ${peer1id}`,
|
|
37
|
-
JSON.stringify(
|
|
37
|
+
JSON.stringify(
|
|
38
|
+
chunk,
|
|
39
|
+
(k, v) =>
|
|
40
|
+
(k === "changes" || k === "encryptedChanges")
|
|
41
|
+
? v.slice(0, 20) + "..."
|
|
42
|
+
: v,
|
|
43
|
+
2
|
|
44
|
+
)
|
|
38
45
|
);
|
|
39
46
|
controller.enqueue(chunk);
|
|
40
47
|
},
|
|
@@ -52,7 +59,14 @@ export function connectedPeers(
|
|
|
52
59
|
trace &&
|
|
53
60
|
console.debug(
|
|
54
61
|
`${peer1id} -> ${peer2id}`,
|
|
55
|
-
JSON.stringify(
|
|
62
|
+
JSON.stringify(
|
|
63
|
+
chunk,
|
|
64
|
+
(k, v) =>
|
|
65
|
+
(k === "changes" || k === "encryptedChanges")
|
|
66
|
+
? v.slice(0, 20) + "..."
|
|
67
|
+
: v,
|
|
68
|
+
2
|
|
69
|
+
)
|
|
56
70
|
);
|
|
57
71
|
controller.enqueue(chunk);
|
|
58
72
|
},
|
|
@@ -102,16 +116,22 @@ export function newStreamPair<T>(): [ReadableStream<T>, WritableStream<T>] {
|
|
|
102
116
|
},
|
|
103
117
|
});
|
|
104
118
|
|
|
119
|
+
let lastWritePromise = Promise.resolve();
|
|
120
|
+
|
|
105
121
|
const writable = new WritableStream<T>({
|
|
106
122
|
async write(chunk) {
|
|
107
123
|
const enqueue = await enqueuePromise;
|
|
108
124
|
if (readerClosed) {
|
|
109
125
|
throw new Error("Reader closed");
|
|
110
126
|
} else {
|
|
111
|
-
// make sure write resolves before corresponding read
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
127
|
+
// make sure write resolves before corresponding read, but make sure writes are still in order
|
|
128
|
+
await lastWritePromise;
|
|
129
|
+
lastWritePromise = new Promise((resolve) => {
|
|
130
|
+
setTimeout(() => {
|
|
131
|
+
enqueue(chunk);
|
|
132
|
+
resolve();
|
|
133
|
+
});
|
|
134
|
+
});
|
|
115
135
|
}
|
|
116
136
|
},
|
|
117
137
|
async abort(reason) {
|
package/src/sync.test.ts
CHANGED
|
@@ -8,12 +8,10 @@ import {
|
|
|
8
8
|
randomAnonymousAccountAndSessionID,
|
|
9
9
|
shouldNotResolve,
|
|
10
10
|
} from "./testUtils.js";
|
|
11
|
-
import {
|
|
12
|
-
connectedPeers,
|
|
13
|
-
newStreamPair
|
|
14
|
-
} from "./streamUtils.js";
|
|
11
|
+
import { connectedPeers, newStreamPair } from "./streamUtils.js";
|
|
15
12
|
import { AccountID } from "./account.js";
|
|
16
13
|
import { cojsonReady } from "./index.js";
|
|
14
|
+
import { stableStringify } from "./jsonStringify.js";
|
|
17
15
|
|
|
18
16
|
beforeEach(async () => {
|
|
19
17
|
await cojsonReady;
|
|
@@ -84,13 +82,13 @@ test("Node replies with initial tx and header to empty subscribe", async () => {
|
|
|
84
82
|
privacy: "trusting" as const,
|
|
85
83
|
madeAt: map.core.sessions[node.currentSessionID]!
|
|
86
84
|
.transactions[0]!.madeAt,
|
|
87
|
-
changes: [
|
|
85
|
+
changes: stableStringify([
|
|
88
86
|
{
|
|
89
87
|
op: "set",
|
|
90
88
|
key: "hello",
|
|
91
89
|
value: "world",
|
|
92
90
|
} satisfies MapOpPayload<string, string>,
|
|
93
|
-
],
|
|
91
|
+
]),
|
|
94
92
|
},
|
|
95
93
|
],
|
|
96
94
|
lastSignature:
|
|
@@ -162,13 +160,13 @@ test("Node replies with only new tx to subscribe with some known state", async (
|
|
|
162
160
|
privacy: "trusting" as const,
|
|
163
161
|
madeAt: map.core.sessions[node.currentSessionID]!
|
|
164
162
|
.transactions[1]!.madeAt,
|
|
165
|
-
changes: [
|
|
163
|
+
changes: stableStringify([
|
|
166
164
|
{
|
|
167
165
|
op: "set",
|
|
168
166
|
key: "goodbye",
|
|
169
167
|
value: "world",
|
|
170
168
|
} satisfies MapOpPayload<string, string>,
|
|
171
|
-
],
|
|
169
|
+
]),
|
|
172
170
|
},
|
|
173
171
|
],
|
|
174
172
|
lastSignature:
|
|
@@ -251,13 +249,13 @@ test("After subscribing, node sends own known state and new txs to peer", async
|
|
|
251
249
|
privacy: "trusting" as const,
|
|
252
250
|
madeAt: map.core.sessions[node.currentSessionID]!
|
|
253
251
|
.transactions[0]!.madeAt,
|
|
254
|
-
changes: [
|
|
252
|
+
changes: stableStringify([
|
|
255
253
|
{
|
|
256
254
|
op: "set",
|
|
257
255
|
key: "hello",
|
|
258
256
|
value: "world",
|
|
259
257
|
} satisfies MapOpPayload<string, string>,
|
|
260
|
-
],
|
|
258
|
+
]),
|
|
261
259
|
},
|
|
262
260
|
],
|
|
263
261
|
lastSignature:
|
|
@@ -283,13 +281,13 @@ test("After subscribing, node sends own known state and new txs to peer", async
|
|
|
283
281
|
privacy: "trusting" as const,
|
|
284
282
|
madeAt: map.core.sessions[node.currentSessionID]!
|
|
285
283
|
.transactions[1]!.madeAt,
|
|
286
|
-
changes: [
|
|
284
|
+
changes: stableStringify([
|
|
287
285
|
{
|
|
288
286
|
op: "set",
|
|
289
287
|
key: "goodbye",
|
|
290
288
|
value: "world",
|
|
291
289
|
} satisfies MapOpPayload<string, string>,
|
|
292
|
-
],
|
|
290
|
+
]),
|
|
293
291
|
},
|
|
294
292
|
],
|
|
295
293
|
lastSignature:
|
|
@@ -362,13 +360,13 @@ test("Client replies with known new content to tellKnownState from server", asyn
|
|
|
362
360
|
privacy: "trusting" as const,
|
|
363
361
|
madeAt: map.core.sessions[node.currentSessionID]!
|
|
364
362
|
.transactions[0]!.madeAt,
|
|
365
|
-
changes: [
|
|
363
|
+
changes: stableStringify([
|
|
366
364
|
{
|
|
367
365
|
op: "set",
|
|
368
366
|
key: "hello",
|
|
369
367
|
value: "world",
|
|
370
368
|
} satisfies MapOpPayload<string, string>,
|
|
371
|
-
],
|
|
369
|
+
]),
|
|
372
370
|
},
|
|
373
371
|
],
|
|
374
372
|
lastSignature:
|
|
@@ -438,8 +436,9 @@ test("No matter the optimistic known state, node respects invalid known state me
|
|
|
438
436
|
editable.set("goodbye", "world", "trusting");
|
|
439
437
|
});
|
|
440
438
|
|
|
441
|
-
const
|
|
442
|
-
|
|
439
|
+
const _mapEditMsgs = await reader.read();
|
|
440
|
+
|
|
441
|
+
console.log("Sending correction");
|
|
443
442
|
|
|
444
443
|
await writer.write({
|
|
445
444
|
action: "known",
|
|
@@ -465,13 +464,13 @@ test("No matter the optimistic known state, node respects invalid known state me
|
|
|
465
464
|
privacy: "trusting" as const,
|
|
466
465
|
madeAt: map.core.sessions[node.currentSessionID]!
|
|
467
466
|
.transactions[1]!.madeAt,
|
|
468
|
-
changes: [
|
|
467
|
+
changes: stableStringify([
|
|
469
468
|
{
|
|
470
469
|
op: "set",
|
|
471
470
|
key: "goodbye",
|
|
472
471
|
value: "world",
|
|
473
472
|
} satisfies MapOpPayload<string, string>,
|
|
474
|
-
],
|
|
473
|
+
]),
|
|
475
474
|
},
|
|
476
475
|
],
|
|
477
476
|
lastSignature:
|
|
@@ -568,13 +567,13 @@ test("If we add a server peer, all updates to all coValues are sent to it, even
|
|
|
568
567
|
privacy: "trusting" as const,
|
|
569
568
|
madeAt: map.core.sessions[node.currentSessionID]!
|
|
570
569
|
.transactions[0]!.madeAt,
|
|
571
|
-
changes: [
|
|
570
|
+
changes: stableStringify([
|
|
572
571
|
{
|
|
573
572
|
op: "set",
|
|
574
573
|
key: "hello",
|
|
575
574
|
value: "world",
|
|
576
575
|
} satisfies MapOpPayload<string, string>,
|
|
577
|
-
],
|
|
576
|
+
]),
|
|
578
577
|
},
|
|
579
578
|
],
|
|
580
579
|
lastSignature:
|
package/src/sync.ts
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
WritableStreamDefaultWriter,
|
|
10
10
|
} from "isomorphic-streams";
|
|
11
11
|
import { RawCoID, SessionID } from "./ids.js";
|
|
12
|
-
import { stableStringify } from "./
|
|
12
|
+
import { stableStringify } from "./jsonStringify.js";
|
|
13
13
|
|
|
14
14
|
export type CoValueKnownState = {
|
|
15
15
|
id: RawCoID;
|
|
@@ -215,14 +215,32 @@ export class SyncManager {
|
|
|
215
215
|
await this.sendNewContentIncludingDependencies(id, peer);
|
|
216
216
|
}
|
|
217
217
|
|
|
218
|
-
const
|
|
218
|
+
const newContentPieces = coValue.newContentSince(
|
|
219
219
|
peer.optimisticKnownStates[id]
|
|
220
220
|
);
|
|
221
221
|
|
|
222
|
-
if (
|
|
223
|
-
|
|
222
|
+
if (newContentPieces) {
|
|
223
|
+
const optimisticKnownStateBefore =
|
|
224
|
+
peer.optimisticKnownStates[id] || emptyKnownState(id);
|
|
225
|
+
|
|
226
|
+
const sendPieces = async () => {
|
|
227
|
+
for (const [i, piece] of newContentPieces.entries()) {
|
|
228
|
+
// console.log(
|
|
229
|
+
// `${id} -> ${peer.id}: Sending content piece ${i + 1}/${newContentPieces.length} header: ${!!piece.header}`,
|
|
230
|
+
// // Object.values(piece.new).map((s) => s.newTransactions)
|
|
231
|
+
// );
|
|
232
|
+
await this.trySendToPeer(peer, piece);
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
sendPieces().catch((e) => {
|
|
237
|
+
console.error("Error sending new content piece, retrying", e);
|
|
238
|
+
peer.optimisticKnownStates[id] = optimisticKnownStateBefore;
|
|
239
|
+
return this.sendNewContentIncludingDependencies(id, peer);
|
|
240
|
+
});
|
|
241
|
+
|
|
224
242
|
peer.optimisticKnownStates[id] = combinedKnownStates(
|
|
225
|
-
|
|
243
|
+
optimisticKnownStateBefore,
|
|
226
244
|
coValue.knownState()
|
|
227
245
|
);
|
|
228
246
|
}
|
|
@@ -261,6 +279,9 @@ export class SyncManager {
|
|
|
261
279
|
for await (const msg of peerState.incoming) {
|
|
262
280
|
try {
|
|
263
281
|
await this.handleSyncMessage(msg, peerState);
|
|
282
|
+
await new Promise<void>((resolve) => {
|
|
283
|
+
setTimeout(resolve, 0);
|
|
284
|
+
});
|
|
264
285
|
} catch (e) {
|
|
265
286
|
console.error(
|
|
266
287
|
`Error reading from peer ${peer.id}, handling msg`,
|
|
@@ -269,7 +290,6 @@ export class SyncManager {
|
|
|
269
290
|
);
|
|
270
291
|
}
|
|
271
292
|
}
|
|
272
|
-
console.log("DONE!!!");
|
|
273
293
|
} catch (e) {
|
|
274
294
|
console.error(`Error reading from peer ${peer.id}`, e);
|
|
275
295
|
}
|
|
@@ -446,6 +466,10 @@ export class SyncManager {
|
|
|
446
466
|
const newTransactions =
|
|
447
467
|
newContentForSession.newTransactions.slice(alreadyKnownOffset);
|
|
448
468
|
|
|
469
|
+
if (newTransactions.length === 0) {
|
|
470
|
+
continue;
|
|
471
|
+
}
|
|
472
|
+
|
|
449
473
|
const before = performance.now();
|
|
450
474
|
const success = await coValue.tryAddTransactionsAsync(
|
|
451
475
|
sessionID,
|
|
@@ -455,20 +479,26 @@ export class SyncManager {
|
|
|
455
479
|
);
|
|
456
480
|
const after = performance.now();
|
|
457
481
|
if (after - before > 10) {
|
|
458
|
-
const totalTxLength = newTransactions
|
|
482
|
+
const totalTxLength = newTransactions
|
|
483
|
+
.map((t) =>
|
|
484
|
+
t.privacy === "private"
|
|
485
|
+
? t.encryptedChanges.length
|
|
486
|
+
: t.changes.length
|
|
487
|
+
)
|
|
488
|
+
.reduce((a, b) => a + b, 0);
|
|
459
489
|
console.log(
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
(
|
|
490
|
+
`Adding incoming transactions took ${(
|
|
491
|
+
after - before
|
|
492
|
+
).toFixed(2)}ms for ${totalTxLength} bytes = bandwidth: ${(
|
|
493
|
+
(1000 * totalTxLength) /
|
|
494
|
+
(after - before) /
|
|
495
|
+
(1024 * 1024)
|
|
496
|
+
).toFixed(2)} MB/s`
|
|
467
497
|
);
|
|
468
498
|
}
|
|
469
499
|
|
|
470
500
|
if (!success) {
|
|
471
|
-
console.error("Failed to add transactions", newTransactions);
|
|
501
|
+
console.error("Failed to add transactions", msg.id, newTransactions);
|
|
472
502
|
continue;
|
|
473
503
|
}
|
|
474
504
|
|
|
@@ -493,18 +523,9 @@ export class SyncManager {
|
|
|
493
523
|
}
|
|
494
524
|
|
|
495
525
|
async handleCorrection(msg: KnownStateMessage, peer: PeerState) {
|
|
496
|
-
|
|
526
|
+
peer.optimisticKnownStates[msg.id] = msg;
|
|
497
527
|
|
|
498
|
-
|
|
499
|
-
msg,
|
|
500
|
-
coValue.knownState()
|
|
501
|
-
);
|
|
502
|
-
|
|
503
|
-
const newContent = coValue.newContentSince(msg);
|
|
504
|
-
|
|
505
|
-
if (newContent) {
|
|
506
|
-
await this.trySendToPeer(peer, newContent);
|
|
507
|
-
}
|
|
528
|
+
return this.sendNewContentIncludingDependencies(msg.id, peer);
|
|
508
529
|
}
|
|
509
530
|
|
|
510
531
|
handleUnsubscribe(_msg: DoneMessage) {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function stableStringify(data: any): string | undefined;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"fastJsonStableStringify.js","sourceRoot":"","sources":["../src/fastJsonStableStringify.ts"],"names":[],"mappings":"AAAA,sGAAsG;AAEtG,8DAA8D;AAC9D,MAAM,UAAU,eAAe,CAAC,IAAS;IACrC,MAAM,MAAM,GAAG,KAAK,CAAC;IAErB,8DAA8D;IAC9D,MAAM,IAAI,GAAU,EAAE,CAAC;IACvB,IAAI,IAAI,GAAG,IAAI,CAAC;IAEhB,IAAI,IAAI,IAAI,IAAI,CAAC,MAAM,IAAI,OAAO,IAAI,CAAC,MAAM,KAAK,UAAU,EAAE;QAC1D,IAAI,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;KACxB;IAED,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO;IAC/B,IAAI,OAAO,IAAI,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC;IACxE,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE;QAC1B,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,EAAE;YAC7F,OAAO,IAAI,IAAI,GAAG,CAAC;SACtB;QACD,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;KAC/B;IAED,IAAI,CAAC,EAAE,GAAG,CAAC;IACX,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE;QACrB,GAAG,GAAG,GAAG,CAAC;QACV,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;YAC9B,IAAI,CAAC;gBAAE,GAAG,IAAI,GAAG,CAAC;YAClB,GAAG,IAAI,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC;SAC7C;QACD,OAAO,GAAG,GAAG,GAAG,CAAC;KACpB;IAED,IAAI,IAAI,KAAK,IAAI;QAAE,OAAO,MAAM,CAAC;IAEjC,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE;QAC3B,IAAI,MAAM;YAAE,OAAO,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;QAC/C,MAAM,IAAI,SAAS,CAAC,uCAAuC,CAAC,CAAC;KAChE;IAED,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACtC,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC;IACtC,GAAG,GAAG,EAAE,CAAC;IACT,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;QAC9B,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAE,CAAC;QACrB,MAAM,KAAK,GAAG,eAAe,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;QAEzC,IAAI,CAAC,KAAK;YAAE,SAAS;QACrB,IAAI,GAAG;YAAE,GAAG,IAAI,GAAG,CAAC;QACpB,GAAG,IAAI,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,GAAG,GAAG,KAAK,CAAC;KAC5C;IACD,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC;IAC1B,OAAO,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;AAC3B,CAAC"}
|