cojson 0.19.22 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +54 -0
- package/dist/coValueContentMessage.d.ts +0 -2
- package/dist/coValueContentMessage.d.ts.map +1 -1
- package/dist/coValueContentMessage.js +0 -8
- package/dist/coValueContentMessage.js.map +1 -1
- package/dist/coValueCore/SessionMap.d.ts +4 -2
- package/dist/coValueCore/SessionMap.d.ts.map +1 -1
- package/dist/coValueCore/SessionMap.js +30 -0
- package/dist/coValueCore/SessionMap.js.map +1 -1
- package/dist/coValueCore/coValueCore.d.ts +67 -3
- package/dist/coValueCore/coValueCore.d.ts.map +1 -1
- package/dist/coValueCore/coValueCore.js +289 -12
- package/dist/coValueCore/coValueCore.js.map +1 -1
- package/dist/coValueCore/verifiedState.d.ts +6 -1
- package/dist/coValueCore/verifiedState.d.ts.map +1 -1
- package/dist/coValueCore/verifiedState.js +9 -0
- package/dist/coValueCore/verifiedState.js.map +1 -1
- package/dist/coValues/coList.d.ts +3 -2
- package/dist/coValues/coList.d.ts.map +1 -1
- package/dist/coValues/coList.js.map +1 -1
- package/dist/coValues/group.d.ts.map +1 -1
- package/dist/coValues/group.js +3 -6
- package/dist/coValues/group.js.map +1 -1
- package/dist/config.d.ts +0 -6
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +0 -8
- package/dist/config.js.map +1 -1
- package/dist/crypto/NapiCrypto.d.ts +1 -2
- package/dist/crypto/NapiCrypto.d.ts.map +1 -1
- package/dist/crypto/NapiCrypto.js +19 -4
- package/dist/crypto/NapiCrypto.js.map +1 -1
- package/dist/crypto/RNCrypto.d.ts.map +1 -1
- package/dist/crypto/RNCrypto.js +19 -4
- package/dist/crypto/RNCrypto.js.map +1 -1
- package/dist/crypto/WasmCrypto.d.ts +11 -4
- package/dist/crypto/WasmCrypto.d.ts.map +1 -1
- package/dist/crypto/WasmCrypto.js +52 -10
- package/dist/crypto/WasmCrypto.js.map +1 -1
- package/dist/crypto/WasmCryptoEdge.d.ts +1 -0
- package/dist/crypto/WasmCryptoEdge.d.ts.map +1 -1
- package/dist/crypto/WasmCryptoEdge.js +4 -1
- package/dist/crypto/WasmCryptoEdge.js.map +1 -1
- package/dist/crypto/crypto.d.ts +3 -3
- package/dist/crypto/crypto.d.ts.map +1 -1
- package/dist/crypto/crypto.js +6 -1
- package/dist/crypto/crypto.js.map +1 -1
- package/dist/exports.d.ts +2 -2
- package/dist/exports.d.ts.map +1 -1
- package/dist/exports.js +2 -1
- package/dist/exports.js.map +1 -1
- package/dist/ids.d.ts +4 -1
- package/dist/ids.d.ts.map +1 -1
- package/dist/ids.js +4 -0
- package/dist/ids.js.map +1 -1
- package/dist/knownState.d.ts +2 -0
- package/dist/knownState.d.ts.map +1 -1
- package/dist/localNode.d.ts +12 -0
- package/dist/localNode.d.ts.map +1 -1
- package/dist/localNode.js +14 -0
- package/dist/localNode.js.map +1 -1
- package/dist/platformUtils.d.ts +3 -0
- package/dist/platformUtils.d.ts.map +1 -0
- package/dist/platformUtils.js +24 -0
- package/dist/platformUtils.js.map +1 -0
- package/dist/storage/DeletedCoValuesEraserScheduler.d.ts +30 -0
- package/dist/storage/DeletedCoValuesEraserScheduler.d.ts.map +1 -0
- package/dist/storage/DeletedCoValuesEraserScheduler.js +84 -0
- package/dist/storage/DeletedCoValuesEraserScheduler.js.map +1 -0
- package/dist/storage/sqlite/client.d.ts +3 -0
- package/dist/storage/sqlite/client.d.ts.map +1 -1
- package/dist/storage/sqlite/client.js +44 -0
- package/dist/storage/sqlite/client.js.map +1 -1
- package/dist/storage/sqlite/sqliteMigrations.d.ts.map +1 -1
- package/dist/storage/sqlite/sqliteMigrations.js +7 -0
- package/dist/storage/sqlite/sqliteMigrations.js.map +1 -1
- package/dist/storage/sqliteAsync/client.d.ts +3 -0
- package/dist/storage/sqliteAsync/client.d.ts.map +1 -1
- package/dist/storage/sqliteAsync/client.js +42 -0
- package/dist/storage/sqliteAsync/client.js.map +1 -1
- package/dist/storage/storageAsync.d.ts +7 -0
- package/dist/storage/storageAsync.d.ts.map +1 -1
- package/dist/storage/storageAsync.js +48 -0
- package/dist/storage/storageAsync.js.map +1 -1
- package/dist/storage/storageSync.d.ts +6 -0
- package/dist/storage/storageSync.d.ts.map +1 -1
- package/dist/storage/storageSync.js +42 -0
- package/dist/storage/storageSync.js.map +1 -1
- package/dist/storage/types.d.ts +59 -0
- package/dist/storage/types.d.ts.map +1 -1
- package/dist/storage/types.js +12 -1
- package/dist/storage/types.js.map +1 -1
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +44 -11
- package/dist/sync.js.map +1 -1
- package/dist/tests/DeletedCoValuesEraserScheduler.test.d.ts +2 -0
- package/dist/tests/DeletedCoValuesEraserScheduler.test.d.ts.map +1 -0
- package/dist/tests/DeletedCoValuesEraserScheduler.test.js +149 -0
- package/dist/tests/DeletedCoValuesEraserScheduler.test.js.map +1 -0
- package/dist/tests/GarbageCollector.test.js +5 -6
- package/dist/tests/GarbageCollector.test.js.map +1 -1
- package/dist/tests/StorageApiAsync.test.js +484 -152
- package/dist/tests/StorageApiAsync.test.js.map +1 -1
- package/dist/tests/StorageApiSync.test.js +505 -136
- package/dist/tests/StorageApiSync.test.js.map +1 -1
- package/dist/tests/WasmCrypto.test.js +6 -3
- package/dist/tests/WasmCrypto.test.js.map +1 -1
- package/dist/tests/coValueCore.loadFromStorage.test.js +3 -0
- package/dist/tests/coValueCore.loadFromStorage.test.js.map +1 -1
- package/dist/tests/coValueCore.test.js +34 -13
- package/dist/tests/coValueCore.test.js.map +1 -1
- package/dist/tests/coreWasm.test.js +127 -4
- package/dist/tests/coreWasm.test.js.map +1 -1
- package/dist/tests/crypto.test.js +89 -93
- package/dist/tests/crypto.test.js.map +1 -1
- package/dist/tests/deleteCoValue.test.d.ts +2 -0
- package/dist/tests/deleteCoValue.test.d.ts.map +1 -0
- package/dist/tests/deleteCoValue.test.js +313 -0
- package/dist/tests/deleteCoValue.test.js.map +1 -0
- package/dist/tests/group.removeMember.test.js +18 -30
- package/dist/tests/group.removeMember.test.js.map +1 -1
- package/dist/tests/knownState.lazyLoading.test.js +3 -0
- package/dist/tests/knownState.lazyLoading.test.js.map +1 -1
- package/dist/tests/sync.deleted.test.d.ts +2 -0
- package/dist/tests/sync.deleted.test.d.ts.map +1 -0
- package/dist/tests/sync.deleted.test.js +214 -0
- package/dist/tests/sync.deleted.test.js.map +1 -0
- package/dist/tests/sync.mesh.test.js +3 -2
- package/dist/tests/sync.mesh.test.js.map +1 -1
- package/dist/tests/sync.storage.test.js +3 -2
- package/dist/tests/sync.storage.test.js.map +1 -1
- package/dist/tests/sync.test.js +3 -2
- package/dist/tests/sync.test.js.map +1 -1
- package/dist/tests/testStorage.d.ts +3 -0
- package/dist/tests/testStorage.d.ts.map +1 -1
- package/dist/tests/testStorage.js +14 -0
- package/dist/tests/testStorage.js.map +1 -1
- package/dist/tests/testUtils.d.ts +6 -3
- package/dist/tests/testUtils.d.ts.map +1 -1
- package/dist/tests/testUtils.js +17 -3
- package/dist/tests/testUtils.js.map +1 -1
- package/package.json +6 -16
- package/src/coValueContentMessage.ts +0 -14
- package/src/coValueCore/SessionMap.ts +43 -1
- package/src/coValueCore/coValueCore.ts +400 -8
- package/src/coValueCore/verifiedState.ts +26 -3
- package/src/coValues/coList.ts +5 -3
- package/src/coValues/group.ts +5 -6
- package/src/config.ts +0 -9
- package/src/crypto/NapiCrypto.ts +29 -13
- package/src/crypto/RNCrypto.ts +29 -11
- package/src/crypto/WasmCrypto.ts +67 -20
- package/src/crypto/WasmCryptoEdge.ts +5 -1
- package/src/crypto/crypto.ts +16 -4
- package/src/exports.ts +2 -0
- package/src/ids.ts +11 -1
- package/src/localNode.ts +15 -0
- package/src/platformUtils.ts +26 -0
- package/src/storage/DeletedCoValuesEraserScheduler.ts +124 -0
- package/src/storage/sqlite/client.ts +77 -0
- package/src/storage/sqlite/sqliteMigrations.ts +7 -0
- package/src/storage/sqliteAsync/client.ts +75 -0
- package/src/storage/storageAsync.ts +62 -0
- package/src/storage/storageSync.ts +58 -0
- package/src/storage/types.ts +69 -0
- package/src/sync.ts +51 -11
- package/src/tests/DeletedCoValuesEraserScheduler.test.ts +185 -0
- package/src/tests/GarbageCollector.test.ts +6 -10
- package/src/tests/StorageApiAsync.test.ts +572 -162
- package/src/tests/StorageApiSync.test.ts +580 -143
- package/src/tests/WasmCrypto.test.ts +8 -3
- package/src/tests/coValueCore.loadFromStorage.test.ts +6 -0
- package/src/tests/coValueCore.test.ts +49 -14
- package/src/tests/coreWasm.test.ts +319 -10
- package/src/tests/crypto.test.ts +141 -150
- package/src/tests/deleteCoValue.test.ts +528 -0
- package/src/tests/group.removeMember.test.ts +35 -35
- package/src/tests/knownState.lazyLoading.test.ts +6 -0
- package/src/tests/sync.deleted.test.ts +294 -0
- package/src/tests/sync.mesh.test.ts +5 -2
- package/src/tests/sync.storage.test.ts +5 -2
- package/src/tests/sync.test.ts +5 -2
- package/src/tests/testStorage.ts +28 -1
- package/src/tests/testUtils.ts +28 -9
- package/dist/crypto/PureJSCrypto.d.ts +0 -77
- package/dist/crypto/PureJSCrypto.d.ts.map +0 -1
- package/dist/crypto/PureJSCrypto.js +0 -236
- package/dist/crypto/PureJSCrypto.js.map +0 -1
- package/dist/tests/PureJSCrypto.test.d.ts +0 -2
- package/dist/tests/PureJSCrypto.test.d.ts.map +0 -1
- package/dist/tests/PureJSCrypto.test.js +0 -145
- package/dist/tests/PureJSCrypto.test.js.map +0 -1
- package/src/crypto/PureJSCrypto.ts +0 -429
- package/src/tests/PureJSCrypto.test.ts +0 -217
package/src/crypto/NapiCrypto.ts
CHANGED
|
@@ -19,7 +19,6 @@ import { RawCoID, SessionID, TransactionID } from "../ids.js";
|
|
|
19
19
|
import { Stringified, stableStringify } from "../jsonStringify.js";
|
|
20
20
|
import { JsonObject, JsonValue } from "../jsonValue.js";
|
|
21
21
|
import { logger } from "../logger.js";
|
|
22
|
-
import { PureJSCrypto } from "./PureJSCrypto.js";
|
|
23
22
|
import {
|
|
24
23
|
CryptoProvider,
|
|
25
24
|
Encrypted,
|
|
@@ -58,7 +57,7 @@ export class NapiCrypto extends CryptoProvider<Blake3State> {
|
|
|
58
57
|
super();
|
|
59
58
|
}
|
|
60
59
|
|
|
61
|
-
static async create(): Promise<NapiCrypto |
|
|
60
|
+
static async create(): Promise<NapiCrypto | WasmCrypto> {
|
|
62
61
|
return new NapiCrypto();
|
|
63
62
|
}
|
|
64
63
|
|
|
@@ -190,11 +189,24 @@ class SessionLogAdapter {
|
|
|
190
189
|
newSignature: Signature,
|
|
191
190
|
skipVerify: boolean,
|
|
192
191
|
): void {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
192
|
+
// Use direct calls instead of JSON.stringify for better performance
|
|
193
|
+
for (const tx of transactions) {
|
|
194
|
+
if (tx.privacy === "private") {
|
|
195
|
+
this.sessionLog.addExistingPrivateTransaction(
|
|
196
|
+
tx.encryptedChanges,
|
|
197
|
+
tx.keyUsed,
|
|
198
|
+
tx.madeAt,
|
|
199
|
+
tx.meta,
|
|
200
|
+
);
|
|
201
|
+
} else {
|
|
202
|
+
this.sessionLog.addExistingTrustingTransaction(
|
|
203
|
+
tx.changes,
|
|
204
|
+
tx.madeAt,
|
|
205
|
+
tx.meta,
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
this.sessionLog.commitTransactions(newSignature, skipVerify);
|
|
198
210
|
}
|
|
199
211
|
|
|
200
212
|
addNewPrivateTransaction(
|
|
@@ -206,12 +218,14 @@ class SessionLogAdapter {
|
|
|
206
218
|
meta: JsonObject | undefined,
|
|
207
219
|
): { signature: Signature; transaction: PrivateTransaction } {
|
|
208
220
|
const output = this.sessionLog.addNewPrivateTransaction(
|
|
209
|
-
stableStringify
|
|
221
|
+
// We can avoid stableStringify because it will be encrypted.
|
|
222
|
+
JSON.stringify(changes),
|
|
210
223
|
signerAgent.currentSignerSecret(),
|
|
211
224
|
keySecret,
|
|
212
225
|
keyID,
|
|
213
226
|
madeAt,
|
|
214
|
-
|
|
227
|
+
// We can avoid stableStringify because it will be encrypted.
|
|
228
|
+
meta ? JSON.stringify(meta) : undefined,
|
|
215
229
|
);
|
|
216
230
|
const parsedOutput = JSON.parse(output);
|
|
217
231
|
const transaction: PrivateTransaction = {
|
|
@@ -230,8 +244,10 @@ class SessionLogAdapter {
|
|
|
230
244
|
madeAt: number,
|
|
231
245
|
meta: JsonObject | undefined,
|
|
232
246
|
): { signature: Signature; transaction: TrustingTransaction } {
|
|
233
|
-
|
|
234
|
-
const
|
|
247
|
+
// We can avoid stableStringify because the changes will be in a string format already.
|
|
248
|
+
const stringifiedChanges = JSON.stringify(changes);
|
|
249
|
+
// We can avoid stableStringify because the meta will be in a string format already.
|
|
250
|
+
const stringifiedMeta = meta ? JSON.stringify(meta) : undefined;
|
|
235
251
|
const output = this.sessionLog.addNewTrustingTransaction(
|
|
236
252
|
stringifiedChanges,
|
|
237
253
|
signerAgent.currentSignerSecret(),
|
|
@@ -241,8 +257,8 @@ class SessionLogAdapter {
|
|
|
241
257
|
const transaction: TrustingTransaction = {
|
|
242
258
|
privacy: "trusting",
|
|
243
259
|
madeAt,
|
|
244
|
-
changes: stringifiedChanges
|
|
245
|
-
meta: stringifiedMeta,
|
|
260
|
+
changes: stringifiedChanges as Stringified<JsonValue[]>,
|
|
261
|
+
meta: stringifiedMeta as Stringified<JsonObject> | undefined,
|
|
246
262
|
};
|
|
247
263
|
return { signature: output as Signature, transaction };
|
|
248
264
|
}
|
package/src/crypto/RNCrypto.ts
CHANGED
|
@@ -47,6 +47,7 @@ import {
|
|
|
47
47
|
Blake3Hasher,
|
|
48
48
|
SessionLog,
|
|
49
49
|
} from "cojson-core-rn";
|
|
50
|
+
import { WasmCrypto } from "./WasmCrypto.js";
|
|
50
51
|
|
|
51
52
|
type Blake3State = Blake3Hasher;
|
|
52
53
|
|
|
@@ -221,11 +222,24 @@ class SessionLogAdapter implements SessionLogImpl {
|
|
|
221
222
|
newSignature: Signature,
|
|
222
223
|
skipVerify: boolean,
|
|
223
224
|
): void {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
225
|
+
// Use direct calls instead of JSON.stringify for better performance
|
|
226
|
+
for (const tx of transactions) {
|
|
227
|
+
if (tx.privacy === "private") {
|
|
228
|
+
this.sessionLog.addExistingPrivateTransaction(
|
|
229
|
+
tx.encryptedChanges,
|
|
230
|
+
tx.keyUsed,
|
|
231
|
+
tx.madeAt,
|
|
232
|
+
tx.meta,
|
|
233
|
+
);
|
|
234
|
+
} else {
|
|
235
|
+
this.sessionLog.addExistingTrustingTransaction(
|
|
236
|
+
tx.changes,
|
|
237
|
+
tx.madeAt,
|
|
238
|
+
tx.meta,
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
this.sessionLog.commitTransactions(newSignature, skipVerify);
|
|
229
243
|
}
|
|
230
244
|
|
|
231
245
|
addNewPrivateTransaction(
|
|
@@ -237,12 +251,14 @@ class SessionLogAdapter implements SessionLogImpl {
|
|
|
237
251
|
meta: JsonObject | undefined,
|
|
238
252
|
) {
|
|
239
253
|
const output = this.sessionLog.addNewPrivateTransaction(
|
|
240
|
-
stableStringify
|
|
254
|
+
// We can avoid stableStringify because it will be encrypted.
|
|
255
|
+
JSON.stringify(changes),
|
|
241
256
|
signerAgent.currentSignerSecret(),
|
|
242
257
|
keySecret,
|
|
243
258
|
keyID,
|
|
244
259
|
madeAt,
|
|
245
|
-
|
|
260
|
+
// We can avoid stableStringify because it will be encrypted.
|
|
261
|
+
meta ? JSON.stringify(meta) : undefined,
|
|
246
262
|
);
|
|
247
263
|
const parsedOutput = JSON.parse(output);
|
|
248
264
|
const transaction: PrivateTransaction = {
|
|
@@ -261,8 +277,10 @@ class SessionLogAdapter implements SessionLogImpl {
|
|
|
261
277
|
madeAt: number,
|
|
262
278
|
meta: JsonObject | undefined,
|
|
263
279
|
) {
|
|
264
|
-
|
|
265
|
-
const
|
|
280
|
+
// We can avoid stableStringify because the changes will be in a string format already.
|
|
281
|
+
const stringifiedChanges = JSON.stringify(changes);
|
|
282
|
+
// We can avoid stableStringify because the meta will be in a string format already.
|
|
283
|
+
const stringifiedMeta = meta ? JSON.stringify(meta) : undefined;
|
|
266
284
|
const output = this.sessionLog.addNewTrustingTransaction(
|
|
267
285
|
stringifiedChanges,
|
|
268
286
|
signerAgent.currentSignerSecret(),
|
|
@@ -272,8 +290,8 @@ class SessionLogAdapter implements SessionLogImpl {
|
|
|
272
290
|
const transaction: TrustingTransaction = {
|
|
273
291
|
privacy: "trusting",
|
|
274
292
|
madeAt,
|
|
275
|
-
changes: stringifiedChanges
|
|
276
|
-
meta: stringifiedMeta,
|
|
293
|
+
changes: stringifiedChanges as Stringified<JsonValue[]>,
|
|
294
|
+
meta: stringifiedMeta as Stringified<JsonObject> | undefined,
|
|
277
295
|
};
|
|
278
296
|
return { signature: output as Signature, transaction };
|
|
279
297
|
}
|
package/src/crypto/WasmCrypto.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
SessionLog,
|
|
3
3
|
initialize,
|
|
4
|
+
initializeSync,
|
|
4
5
|
Blake3Hasher,
|
|
5
6
|
blake3HashOnce,
|
|
6
7
|
blake3HashOnceWithContext,
|
|
@@ -20,7 +21,6 @@ import { RawCoID, SessionID, TransactionID } from "../ids.js";
|
|
|
20
21
|
import { Stringified, stableStringify } from "../jsonStringify.js";
|
|
21
22
|
import { JsonObject, JsonValue } from "../jsonValue.js";
|
|
22
23
|
import { logger } from "../logger.js";
|
|
23
|
-
import { PureJSCrypto } from "./PureJSCrypto.js";
|
|
24
24
|
import {
|
|
25
25
|
CryptoProvider,
|
|
26
26
|
Encrypted,
|
|
@@ -41,10 +41,34 @@ import {
|
|
|
41
41
|
Transaction,
|
|
42
42
|
TrustingTransaction,
|
|
43
43
|
} from "../coValueCore/verifiedState.js";
|
|
44
|
+
import { isCloudflare, isEvalAllowed } from "../platformUtils.js";
|
|
44
45
|
|
|
45
46
|
type Blake3State = Blake3Hasher;
|
|
46
47
|
|
|
47
48
|
let wasmInit = initialize;
|
|
49
|
+
let wasmInitSync = initializeSync;
|
|
50
|
+
|
|
51
|
+
const wasmCryptoErrorMessage = (
|
|
52
|
+
e: unknown,
|
|
53
|
+
) => `Critical Error: Failed to load WASM module
|
|
54
|
+
|
|
55
|
+
${!isEvalAllowed() ? `You need to add \`import "jazz-tools/load-edge-wasm";\` on top of your entry module to make Jazz work with ${isCloudflare() ? "Cloudflare workers" : "this runtime"}` : (e as Error).message}
|
|
56
|
+
|
|
57
|
+
A native crypto module is required for Jazz to work. See https://jazz.tools/docs/react/reference/performance#use-the-best-crypto-implementation-for-your-platform for possible alternatives.`;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Initializes the WasmCrypto module. This function can be used to initialize the WasmCrypto module in a worker or a browser.
|
|
61
|
+
* if you are using SSR and you want to initialize WASM crypto asynchronously you can use this function.
|
|
62
|
+
* @returns A promise that resolves when the WasmCrypto module is successfully initialized.
|
|
63
|
+
*/
|
|
64
|
+
export async function initWasmCrypto() {
|
|
65
|
+
try {
|
|
66
|
+
await wasmInit();
|
|
67
|
+
} catch (e) {
|
|
68
|
+
throw new Error(wasmCryptoErrorMessage(e), { cause: e });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
48
72
|
/**
|
|
49
73
|
* WebAssembly implementation of the CryptoProvider interface using cojson-core-wasm.
|
|
50
74
|
* This provides the primary implementation using WebAssembly for optimal performance, offering:
|
|
@@ -54,7 +78,7 @@ let wasmInit = initialize;
|
|
|
54
78
|
* - Hashing (BLAKE3)
|
|
55
79
|
*/
|
|
56
80
|
export class WasmCrypto extends CryptoProvider<Blake3State> {
|
|
57
|
-
|
|
81
|
+
protected constructor() {
|
|
58
82
|
super();
|
|
59
83
|
}
|
|
60
84
|
|
|
@@ -62,17 +86,23 @@ export class WasmCrypto extends CryptoProvider<Blake3State> {
|
|
|
62
86
|
wasmInit = value;
|
|
63
87
|
}
|
|
64
88
|
|
|
65
|
-
static
|
|
89
|
+
static setInitSync(value: typeof initializeSync) {
|
|
90
|
+
wasmInitSync = value;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
static createSync(): WasmCrypto {
|
|
66
94
|
try {
|
|
67
|
-
|
|
95
|
+
wasmInitSync();
|
|
68
96
|
} catch (e) {
|
|
69
|
-
|
|
70
|
-
"Failed to initialize WasmCrypto, falling back to PureJSCrypto",
|
|
71
|
-
{ err: e },
|
|
72
|
-
);
|
|
73
|
-
return new PureJSCrypto();
|
|
97
|
+
throw new Error(wasmCryptoErrorMessage(e), { cause: e });
|
|
74
98
|
}
|
|
99
|
+
return new WasmCrypto();
|
|
100
|
+
}
|
|
75
101
|
|
|
102
|
+
// TODO: Remove this method and use createSync instead, this is not necessary since we can use createSync in the browser and in the worker.
|
|
103
|
+
// @deprecated
|
|
104
|
+
static async create(): Promise<WasmCrypto> {
|
|
105
|
+
await initWasmCrypto();
|
|
76
106
|
return new WasmCrypto();
|
|
77
107
|
}
|
|
78
108
|
|
|
@@ -204,11 +234,24 @@ class SessionLogAdapter {
|
|
|
204
234
|
newSignature: Signature,
|
|
205
235
|
skipVerify: boolean,
|
|
206
236
|
): void {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
237
|
+
// Use direct calls instead of JSON.stringify for better performance
|
|
238
|
+
for (const tx of transactions) {
|
|
239
|
+
if (tx.privacy === "private") {
|
|
240
|
+
this.sessionLog.addExistingPrivateTransaction(
|
|
241
|
+
tx.encryptedChanges,
|
|
242
|
+
tx.keyUsed,
|
|
243
|
+
tx.madeAt,
|
|
244
|
+
tx.meta,
|
|
245
|
+
);
|
|
246
|
+
} else {
|
|
247
|
+
this.sessionLog.addExistingTrustingTransaction(
|
|
248
|
+
tx.changes,
|
|
249
|
+
tx.madeAt,
|
|
250
|
+
tx.meta,
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
this.sessionLog.commitTransactions(newSignature, skipVerify);
|
|
212
255
|
}
|
|
213
256
|
|
|
214
257
|
addNewPrivateTransaction(
|
|
@@ -220,12 +263,14 @@ class SessionLogAdapter {
|
|
|
220
263
|
meta: JsonObject | undefined,
|
|
221
264
|
): { signature: Signature; transaction: PrivateTransaction } {
|
|
222
265
|
const output = this.sessionLog.addNewPrivateTransaction(
|
|
223
|
-
stableStringify
|
|
266
|
+
// We can avoid stableStringify because it will be encrypted.
|
|
267
|
+
JSON.stringify(changes),
|
|
224
268
|
signerAgent.currentSignerSecret(),
|
|
225
269
|
keySecret,
|
|
226
270
|
keyID,
|
|
227
271
|
madeAt,
|
|
228
|
-
|
|
272
|
+
// We can avoid stableStringify because it will be encrypted.
|
|
273
|
+
meta ? JSON.stringify(meta) : undefined,
|
|
229
274
|
);
|
|
230
275
|
const parsedOutput = JSON.parse(output);
|
|
231
276
|
const transaction: PrivateTransaction = {
|
|
@@ -244,8 +289,10 @@ class SessionLogAdapter {
|
|
|
244
289
|
madeAt: number,
|
|
245
290
|
meta: JsonObject | undefined,
|
|
246
291
|
): { signature: Signature; transaction: TrustingTransaction } {
|
|
247
|
-
|
|
248
|
-
const
|
|
292
|
+
// We can avoid stableStringify because the changes will be in a string format already.
|
|
293
|
+
const stringifiedChanges = JSON.stringify(changes);
|
|
294
|
+
// We can avoid stableStringify because the meta will be in a string format already.
|
|
295
|
+
const stringifiedMeta = meta ? JSON.stringify(meta) : undefined;
|
|
249
296
|
const output = this.sessionLog.addNewTrustingTransaction(
|
|
250
297
|
stringifiedChanges,
|
|
251
298
|
signerAgent.currentSignerSecret(),
|
|
@@ -255,8 +302,8 @@ class SessionLogAdapter {
|
|
|
255
302
|
const transaction: TrustingTransaction = {
|
|
256
303
|
privacy: "trusting",
|
|
257
304
|
madeAt,
|
|
258
|
-
changes: stringifiedChanges
|
|
259
|
-
meta: stringifiedMeta,
|
|
305
|
+
changes: stringifiedChanges as Stringified<JsonValue[]>,
|
|
306
|
+
meta: stringifiedMeta as Stringified<JsonObject> | undefined,
|
|
260
307
|
};
|
|
261
308
|
return { signature: output as Signature, transaction };
|
|
262
309
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { initialize } from "cojson-core-wasm/edge-lite";
|
|
1
|
+
import { initialize, initializeSync } from "cojson-core-wasm/edge-lite";
|
|
2
2
|
import { WasmCrypto } from "./WasmCrypto.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -12,3 +12,7 @@ import { WasmCrypto } from "./WasmCrypto.js";
|
|
|
12
12
|
export const init = async () => {
|
|
13
13
|
return initialize();
|
|
14
14
|
};
|
|
15
|
+
|
|
16
|
+
export const initSync = () => {
|
|
17
|
+
return initializeSync();
|
|
18
|
+
};
|
package/src/crypto/crypto.ts
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { base58 } from "@scure/base";
|
|
2
2
|
import { ControlledAccountOrAgent, RawAccountID } from "../coValues/account.js";
|
|
3
|
-
import {
|
|
4
|
-
|
|
3
|
+
import {
|
|
4
|
+
AgentID,
|
|
5
|
+
RawCoID,
|
|
6
|
+
TransactionID,
|
|
7
|
+
SessionID,
|
|
8
|
+
ActiveSessionID,
|
|
9
|
+
DeleteSessionID,
|
|
10
|
+
} from "../ids.js";
|
|
5
11
|
import { Stringified, parseJSON, stableStringify } from "../jsonStringify.js";
|
|
6
12
|
import { JsonObject, JsonValue } from "../jsonValue.js";
|
|
7
13
|
import { logger } from "../logger.js";
|
|
@@ -246,8 +252,14 @@ export abstract class CryptoProvider<Blake3State = any> {
|
|
|
246
252
|
)}`;
|
|
247
253
|
}
|
|
248
254
|
|
|
249
|
-
newRandomSessionID(accountID: RawAccountID | AgentID):
|
|
250
|
-
|
|
255
|
+
newRandomSessionID(accountID: RawAccountID | AgentID): ActiveSessionID {
|
|
256
|
+
const randomPart = base58.encode(this.randomBytes(8));
|
|
257
|
+
return `${accountID}_session_z${randomPart}`;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
newDeleteSessionID(accountID: RawAccountID | AgentID): DeleteSessionID {
|
|
261
|
+
const randomPart = base58.encode(this.randomBytes(7));
|
|
262
|
+
return `${accountID}_session_d${randomPart}$`;
|
|
251
263
|
}
|
|
252
264
|
|
|
253
265
|
abstract createSessionLog(
|
package/src/exports.ts
CHANGED
|
@@ -77,6 +77,7 @@ import { emptyKnownState } from "./knownState.js";
|
|
|
77
77
|
import {
|
|
78
78
|
getContentMessageSize,
|
|
79
79
|
getTransactionSize,
|
|
80
|
+
knownStateFromContent,
|
|
80
81
|
} from "./coValueContentMessage.js";
|
|
81
82
|
import { getDependedOnCoValuesFromRawData } from "./coValueCore/utils.js";
|
|
82
83
|
import {
|
|
@@ -102,6 +103,7 @@ type Value = JsonValue | AnyRawCoValue;
|
|
|
102
103
|
export { PriorityBasedMessageQueue } from "./queue/PriorityBasedMessageQueue.js";
|
|
103
104
|
/** @hidden */
|
|
104
105
|
export const cojsonInternals = {
|
|
106
|
+
knownStateFromContent,
|
|
105
107
|
connectedPeers,
|
|
106
108
|
rawCoIDtoBytes,
|
|
107
109
|
rawCoIDfromBytes,
|
package/src/ids.ts
CHANGED
|
@@ -36,7 +36,17 @@ export function isAgentID(id: unknown): id is AgentID {
|
|
|
36
36
|
);
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
export type
|
|
39
|
+
export type ActiveSessionID = `${RawAccountID | AgentID}_session_z${string}`;
|
|
40
|
+
export type DeleteSessionID = `${RawAccountID | AgentID}_session_d${string}$`;
|
|
41
|
+
export type SessionID = ActiveSessionID | DeleteSessionID;
|
|
42
|
+
|
|
43
|
+
const CHAR_DOLLAR = "$".charCodeAt(0);
|
|
44
|
+
|
|
45
|
+
export function isDeleteSessionID(
|
|
46
|
+
sessionID: SessionID,
|
|
47
|
+
): sessionID is DeleteSessionID {
|
|
48
|
+
return sessionID.charCodeAt(sessionID.length - 1) === CHAR_DOLLAR;
|
|
49
|
+
}
|
|
40
50
|
|
|
41
51
|
export function isParentGroupReference(
|
|
42
52
|
key: string,
|
package/src/localNode.ts
CHANGED
|
@@ -101,6 +101,21 @@ export class LocalNode {
|
|
|
101
101
|
this.syncManager.removeStorage();
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
+
/**
|
|
105
|
+
* Enable background erasure of deleted coValues (space reclamation).
|
|
106
|
+
*
|
|
107
|
+
* Deleted coValues are immediately blocked from syncing via tombstones; this feature
|
|
108
|
+
* only reclaims local storage space by deleting historical content while preserving
|
|
109
|
+
* the tombstone (header + delete session).
|
|
110
|
+
*
|
|
111
|
+
* This is opt-in and affects only the currently configured storage (if any)
|
|
112
|
+
*
|
|
113
|
+
* @category 3. Low-level
|
|
114
|
+
*/
|
|
115
|
+
enableDeletedCoValuesErasure() {
|
|
116
|
+
this.storage?.enableDeletedCoValuesErasure();
|
|
117
|
+
}
|
|
118
|
+
|
|
104
119
|
hasCoValue(id: RawCoID) {
|
|
105
120
|
const coValue = this.coValues.get(id);
|
|
106
121
|
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export function isCloudflare() {
|
|
2
|
+
if (
|
|
3
|
+
// @ts-ignore
|
|
4
|
+
typeof navigator !== "undefined" &&
|
|
5
|
+
// @ts-ignore
|
|
6
|
+
navigator?.userAgent?.includes("Cloudflare")
|
|
7
|
+
) {
|
|
8
|
+
return true;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const isEvalAllowed = () => {
|
|
15
|
+
if (isCloudflare()) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
const F = Function;
|
|
21
|
+
new F("");
|
|
22
|
+
return true;
|
|
23
|
+
} catch (_) {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { logger } from "../logger.js";
|
|
2
|
+
|
|
3
|
+
export type DeletedCoValuesEraserSchedulerRunResult = {
|
|
4
|
+
hasMore: boolean;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export type DeletedCoValuesEraserSchedulerOpts = {
|
|
8
|
+
throttleMs: number;
|
|
9
|
+
startupDelayMs: number;
|
|
10
|
+
followUpDelayMs: number;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
type SchedulerState =
|
|
14
|
+
| "idle"
|
|
15
|
+
| "startup_scheduled"
|
|
16
|
+
| "throttle_scheduled"
|
|
17
|
+
| "followup_scheduled"
|
|
18
|
+
| "running"
|
|
19
|
+
| "disposed";
|
|
20
|
+
|
|
21
|
+
export const DEFAULT_DELETE_SCHEDULE_OPTS = {
|
|
22
|
+
throttleMs: 60_000,
|
|
23
|
+
startupDelayMs: 1_000,
|
|
24
|
+
followUpDelayMs: 1_000,
|
|
25
|
+
} satisfies DeletedCoValuesEraserSchedulerOpts;
|
|
26
|
+
|
|
27
|
+
export class DeletedCoValuesEraserScheduler {
|
|
28
|
+
private readonly runCallback: () => Promise<DeletedCoValuesEraserSchedulerRunResult>;
|
|
29
|
+
private readonly opts: DeletedCoValuesEraserSchedulerOpts;
|
|
30
|
+
|
|
31
|
+
private state: SchedulerState = "idle";
|
|
32
|
+
|
|
33
|
+
private isDisposed(): boolean {
|
|
34
|
+
return this.state === "disposed";
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
private scheduledTimeout: ReturnType<typeof setTimeout> | undefined;
|
|
38
|
+
|
|
39
|
+
constructor({
|
|
40
|
+
run,
|
|
41
|
+
opts,
|
|
42
|
+
}: {
|
|
43
|
+
run: () => Promise<DeletedCoValuesEraserSchedulerRunResult>;
|
|
44
|
+
opts?: DeletedCoValuesEraserSchedulerOpts;
|
|
45
|
+
}) {
|
|
46
|
+
this.runCallback = run;
|
|
47
|
+
this.opts = opts || DEFAULT_DELETE_SCHEDULE_OPTS;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
scheduleStartupDrain() {
|
|
51
|
+
if (this.isDisposed()) return;
|
|
52
|
+
|
|
53
|
+
// Only schedule startup drain if nothing is already scheduled/running.
|
|
54
|
+
if (this.state !== "idle") return;
|
|
55
|
+
|
|
56
|
+
this.scheduleTimer("startup_scheduled", this.opts.startupDelayMs);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
onEnqueueDeletedCoValue() {
|
|
60
|
+
if (this.isDisposed()) return;
|
|
61
|
+
|
|
62
|
+
// While we're already draining (or have a follow-up scheduled), ignore enqueue
|
|
63
|
+
// to avoid overlapping phases. The active drain loop will pick up new work.
|
|
64
|
+
if (this.state !== "idle") return;
|
|
65
|
+
|
|
66
|
+
// Only idle reaches here.
|
|
67
|
+
this.scheduleTimer("throttle_scheduled", this.opts.throttleMs);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
dispose() {
|
|
71
|
+
if (this.isDisposed()) return;
|
|
72
|
+
this.state = "disposed";
|
|
73
|
+
|
|
74
|
+
if (this.scheduledTimeout) clearTimeout(this.scheduledTimeout);
|
|
75
|
+
this.scheduledTimeout = undefined;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
private scheduleTimer(
|
|
79
|
+
state: Exclude<SchedulerState, "idle" | "running" | "disposed">,
|
|
80
|
+
delayMs: number,
|
|
81
|
+
) {
|
|
82
|
+
if (this.isDisposed()) return;
|
|
83
|
+
if (this.scheduledTimeout) return;
|
|
84
|
+
|
|
85
|
+
this.state = state;
|
|
86
|
+
this.scheduledTimeout = setTimeout(() => {
|
|
87
|
+
this.scheduledTimeout = undefined;
|
|
88
|
+
void this.run();
|
|
89
|
+
}, delayMs);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
private async run() {
|
|
93
|
+
if (this.isDisposed()) return;
|
|
94
|
+
|
|
95
|
+
// Clear any pre-run scheduled state and enter running state.
|
|
96
|
+
this.state = "running";
|
|
97
|
+
|
|
98
|
+
let result: DeletedCoValuesEraserSchedulerRunResult;
|
|
99
|
+
try {
|
|
100
|
+
result = await this.runCallback();
|
|
101
|
+
} catch (error) {
|
|
102
|
+
logger.error("Error running deleted co values eraser scheduler", {
|
|
103
|
+
err: error,
|
|
104
|
+
});
|
|
105
|
+
// If the run callback fails, recover to idle so future enqueues/startup drains
|
|
106
|
+
// can retry instead of getting stuck in "running".
|
|
107
|
+
if (!this.isDisposed()) {
|
|
108
|
+
this.state = "idle";
|
|
109
|
+
}
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (this.isDisposed()) return;
|
|
114
|
+
|
|
115
|
+
if (result.hasMore) {
|
|
116
|
+
// One follow-up phase at a time. Further enqueues while follow-up is scheduled
|
|
117
|
+
// are ignored.
|
|
118
|
+
this.scheduleTimer("followup_scheduled", this.opts.followUpDelayMs);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
this.state = "idle";
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -16,6 +16,7 @@ import type {
|
|
|
16
16
|
StoredSessionRow,
|
|
17
17
|
TransactionRow,
|
|
18
18
|
} from "../types.js";
|
|
19
|
+
import { DeletedCoValueDeletionStatus } from "../types.js";
|
|
19
20
|
import type { SQLiteDatabaseDriver } from "./types.js";
|
|
20
21
|
|
|
21
22
|
export type RawCoValueRow = {
|
|
@@ -29,6 +30,10 @@ export type RawTransactionRow = {
|
|
|
29
30
|
tx: string;
|
|
30
31
|
};
|
|
31
32
|
|
|
33
|
+
type DeletedCoValueQueueRow = {
|
|
34
|
+
id: RawCoID;
|
|
35
|
+
};
|
|
36
|
+
|
|
32
37
|
export function getErrorMessage(error: unknown) {
|
|
33
38
|
return error instanceof Error ? error.message : "Unknown error";
|
|
34
39
|
}
|
|
@@ -143,6 +148,78 @@ export class SQLiteClient
|
|
|
143
148
|
return result.rowID;
|
|
144
149
|
}
|
|
145
150
|
|
|
151
|
+
markCoValueAsDeleted(id: RawCoID) {
|
|
152
|
+
// Work queue entry. Table only stores the coValueID.
|
|
153
|
+
// Idempotent by design.
|
|
154
|
+
this.db.run(
|
|
155
|
+
`INSERT INTO deletedCoValues (coValueID) VALUES (?) ON CONFLICT(coValueID) DO NOTHING`,
|
|
156
|
+
[id],
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
eraseCoValueButKeepTombstone(coValueId: RawCoID) {
|
|
161
|
+
const coValueRow = this.db.get<{ rowID: number }>(
|
|
162
|
+
"SELECT rowID FROM coValues WHERE id = ?",
|
|
163
|
+
[coValueId],
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
if (!coValueRow) {
|
|
167
|
+
logger.warn(`CoValue ${coValueId} not found, skipping deletion`);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
this.transaction(() => {
|
|
172
|
+
this.db.run(
|
|
173
|
+
`DELETE FROM transactions
|
|
174
|
+
WHERE ses IN (
|
|
175
|
+
SELECT rowID FROM sessions
|
|
176
|
+
WHERE coValue = ?
|
|
177
|
+
AND sessionID NOT LIKE '%$'
|
|
178
|
+
)`,
|
|
179
|
+
[coValueRow.rowID],
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
this.db.run(
|
|
183
|
+
`DELETE FROM signatureAfter
|
|
184
|
+
WHERE ses IN (
|
|
185
|
+
SELECT rowID FROM sessions
|
|
186
|
+
WHERE coValue = ?
|
|
187
|
+
AND sessionID NOT LIKE '%$'
|
|
188
|
+
)`,
|
|
189
|
+
[coValueRow.rowID],
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
this.db.run(
|
|
193
|
+
`DELETE FROM sessions
|
|
194
|
+
WHERE coValue = ?
|
|
195
|
+
AND sessionID NOT LIKE '%$'`,
|
|
196
|
+
[coValueRow.rowID],
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
// Mark the delete as done
|
|
200
|
+
this.db.run(
|
|
201
|
+
`INSERT INTO deletedCoValues (coValueID, status) VALUES (?, ?)
|
|
202
|
+
ON CONFLICT(coValueID) DO UPDATE SET status=?`,
|
|
203
|
+
[
|
|
204
|
+
coValueId,
|
|
205
|
+
DeletedCoValueDeletionStatus.Done,
|
|
206
|
+
DeletedCoValueDeletionStatus.Done,
|
|
207
|
+
],
|
|
208
|
+
);
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
getAllCoValuesWaitingForDelete(): RawCoID[] {
|
|
213
|
+
return this.db
|
|
214
|
+
.query<DeletedCoValueQueueRow>(
|
|
215
|
+
`SELECT coValueID as id
|
|
216
|
+
FROM deletedCoValues
|
|
217
|
+
WHERE status = ?`,
|
|
218
|
+
[DeletedCoValueDeletionStatus.Pending],
|
|
219
|
+
)
|
|
220
|
+
.map((r) => r.id);
|
|
221
|
+
}
|
|
222
|
+
|
|
146
223
|
addSessionUpdate({ sessionUpdate }: { sessionUpdate: SessionRow }): number {
|
|
147
224
|
const result = this.db.get<{ rowID: number }>(
|
|
148
225
|
`INSERT INTO sessions (coValue, sessionID, lastIdx, lastSignature, bytesSinceLastSignature) VALUES (?, ?, ?, ?, ?)
|