@abraca/dabra 0.9.0 → 1.0.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/index.d.ts CHANGED
@@ -131,6 +131,63 @@ declare class EventEmitter {
131
131
  removeAllListeners(): void;
132
132
  }
133
133
  //#endregion
134
+ //#region packages/provider/src/OfflineStore.d.ts
135
+ /**
136
+ * IndexedDB-backed offline store for a single Abracadabra document.
137
+ *
138
+ * Responsibilities:
139
+ * - Persist CRDT updates locally so they survive page refreshes / network loss.
140
+ * - Store the last-known state vector for fast reconnect diffs.
141
+ * - Store a permission snapshot so the UI can gate writes without a network round-trip.
142
+ * - Queue subdoc registration events created while offline.
143
+ * - Store a full document snapshot so the app can load content without a server connection.
144
+ *
145
+ * Falls back to a silent no-op when IndexedDB is unavailable (e.g. SSR / Node.js).
146
+ *
147
+ * Database key is scoped by server origin to prevent cross-server data contamination
148
+ * when the same docId is used on multiple servers.
149
+ */
150
+ interface PendingSubdoc {
151
+ childId: string;
152
+ parentId: string;
153
+ createdAt: number;
154
+ }
155
+ declare class OfflineStore {
156
+ private storeKey;
157
+ private db;
158
+ /**
159
+ * @param docId The document UUID.
160
+ * @param serverOrigin Hostname of the server (e.g. "abra.cou.sh").
161
+ * When provided the IndexedDB database is namespaced
162
+ * per-server, preventing cross-server data contamination.
163
+ */
164
+ constructor(docId: string, serverOrigin?: string);
165
+ private dbPromise;
166
+ private getDb;
167
+ persistUpdate(update: Uint8Array): Promise<void>;
168
+ getPendingUpdates(): Promise<Uint8Array[]>;
169
+ clearPendingUpdates(): Promise<void>;
170
+ /**
171
+ * Persist a full Y.js state snapshot (Y.encodeStateAsUpdate output).
172
+ * Replaces any previously stored snapshot.
173
+ */
174
+ saveDocSnapshot(snapshot: Uint8Array): Promise<void>;
175
+ /**
176
+ * Retrieve the stored full document snapshot, or null if none exists.
177
+ */
178
+ getDocSnapshot(): Promise<Uint8Array | null>;
179
+ getStateVector(): Promise<Uint8Array | null>;
180
+ saveStateVector(sv: Uint8Array): Promise<void>;
181
+ getPermissionSnapshot(): Promise<string | null>;
182
+ savePermissionSnapshot(role: string): Promise<void>;
183
+ queueSubdoc(entry: PendingSubdoc): Promise<void>;
184
+ getPendingSubdocs(): Promise<PendingSubdoc[]>;
185
+ removeSubdocFromQueue(childId: string): Promise<void>;
186
+ getMeta(key: string): Promise<string | null>;
187
+ setMeta(key: string, value: string): Promise<void>;
188
+ destroy(): void;
189
+ }
190
+ //#endregion
134
191
  //#region packages/provider/src/DocumentCache.d.ts
135
192
  interface DocumentCacheOptions {
136
193
  /** How long cached entries remain valid. Default: 5 minutes. */
@@ -245,11 +302,42 @@ declare class AbracadabraClient {
245
302
  addKey(opts: {
246
303
  publicKey: string;
247
304
  deviceName?: string;
305
+ x25519Key?: string;
248
306
  }): Promise<void>;
249
307
  /** List all registered public keys for the current user. */
250
308
  listKeys(): Promise<PublicKeyInfo[]>;
251
309
  /** Revoke a public key by its ID. */
252
310
  revokeKey(keyId: string): Promise<void>;
311
+ /** Get encryption info for a document. */
312
+ getDocEncryption(docId: string): Promise<DocEncryptionInfo>;
313
+ /** Set the encryption mode for a document (no downgrade). */
314
+ setDocEncryption(docId: string, mode: "none" | "cse" | "e2e"): Promise<void>;
315
+ /** Get the caller's key envelope for a document (for decrypting the DocKey). */
316
+ getMyKeyEnvelope(docId: string): Promise<{
317
+ encrypted_key: string;
318
+ key_epoch: number;
319
+ } | null>;
320
+ /** Upload key envelopes for a document (Owner only). */
321
+ uploadKeyEnvelopes(docId: string, opts: {
322
+ key_epoch: number;
323
+ envelopes: {
324
+ recipient_key_id: string;
325
+ encrypted_key: string;
326
+ }[];
327
+ }): Promise<void>;
328
+ /** Get the X25519 public key for a user. */
329
+ getUserX25519Key(userId: string): Promise<string | null>;
330
+ /** List all non-revoked keys for a user (Owner/Admin or self). */
331
+ listUserKeys(userId: string): Promise<{
332
+ id: string;
333
+ publicKey: string;
334
+ x25519Key: string | null;
335
+ }[]>;
336
+ /** Fetch encrypted E2E update blobs since a given sequence number. */
337
+ getE2EUpdatesSince(docId: string, sinceSeq: number): Promise<{
338
+ seq: number;
339
+ data: Uint8Array;
340
+ }[]>;
253
341
  /** Clear token from memory and storage. */
254
342
  logout(): void;
255
343
  /** Get the current user's profile. */
@@ -317,6 +405,98 @@ declare class AbracadabraClient {
317
405
  private clearPersistedToken;
318
406
  }
319
407
  //#endregion
408
+ //#region packages/provider/src/CryptoIdentityKeystore.d.ts
409
+ /**
410
+ * CryptoIdentityKeystore
411
+ *
412
+ * Per-device Ed25519 keypair, private key protected by WebAuthn PRF + AES-256-GCM.
413
+ * Stored in IndexedDB under "abracadabra:identity" / "identity" / key "current".
414
+ *
415
+ * No private key is ever shared between devices. Each device generates its own
416
+ * keypair, encrypts the private key with the PRF output from its own WebAuthn
417
+ * credential, and stores the ciphertext in IndexedDB.
418
+ *
419
+ * Dependencies: @noble/ed25519, @noble/hashes (for HKDF)
420
+ */
421
+ declare class CryptoIdentityKeystore {
422
+ /**
423
+ * One-time setup for a device: generates an Ed25519 keypair, creates a
424
+ * WebAuthn credential with PRF extension, encrypts the private key, and
425
+ * stores everything in IndexedDB.
426
+ *
427
+ * Returns the base64url-encoded public key. The caller must register this
428
+ * key with the server via POST /auth/register (first device) or
429
+ * POST /auth/keys (additional device).
430
+ *
431
+ * @param username - The user's account name.
432
+ * @param rpId - WebAuthn relying party ID (e.g. "example.com").
433
+ * @param rpName - Human-readable relying party name.
434
+ */
435
+ register(username: string, rpId: string, rpName: string): Promise<{
436
+ publicKey: string;
437
+ x25519PublicKey: string;
438
+ }>;
439
+ /**
440
+ * Sign a base64url-encoded challenge using the stored Ed25519 private key.
441
+ *
442
+ * This triggers a WebAuthn assertion (biometric / PIN prompt) to unlock the
443
+ * private key via PRF → HKDF → AES-GCM decryption. The private key is
444
+ * wiped from memory after signing.
445
+ *
446
+ * @param challengeB64 - base64url-encoded challenge bytes from the server.
447
+ * @returns base64url-encoded Ed25519 signature (64 bytes).
448
+ */
449
+ sign(challengeB64: string): Promise<string>;
450
+ /** Returns the stored base64url public key, or null if no identity exists. */
451
+ getPublicKey(): Promise<string | null>;
452
+ /**
453
+ * Returns the locally-stored internal username label, or null if no identity exists.
454
+ *
455
+ * This is NOT the auth identifier (the public key is). It can be used as a
456
+ * hint when calling POST /auth/register, or displayed before the user sets
457
+ * a real display name via PATCH /users/me.
458
+ */
459
+ getUsername(): Promise<string | null>;
460
+ /** Returns true if an identity is stored in IndexedDB. */
461
+ hasIdentity(): Promise<boolean>;
462
+ /** Remove the stored identity from IndexedDB. */
463
+ clear(): Promise<void>;
464
+ /**
465
+ * Returns the X25519 public key derived from the stored Ed25519 private key.
466
+ * Does NOT require WebAuthn — computed from the stored encrypted key... actually
467
+ * we derive from the Ed25519 public key directly (Montgomery form), no decryption needed
468
+ * since nobleEd25519Curves.utils.toMontgomery only needs the public key.
469
+ * Returns null if no identity is stored.
470
+ */
471
+ getX25519PublicKey(): Promise<Uint8Array | null>;
472
+ /**
473
+ * Returns the X25519 private key derived from the stored Ed25519 private key.
474
+ * Requires WebAuthn assertion to decrypt the private key.
475
+ * The caller MUST wipe the returned Uint8Array after use.
476
+ */
477
+ getX25519PrivateKey(): Promise<Uint8Array>;
478
+ }
479
+ //#endregion
480
+ //#region packages/provider/src/DocKeyManager.d.ts
481
+ declare class DocKeyManager {
482
+ private cache;
483
+ /** Generate a new random AES-256-GCM document key. */
484
+ static generateDocKey(): Promise<CryptoKey>;
485
+ /**
486
+ * Get (or fetch) the DocKey for a document.
487
+ * Returns null if no envelope exists (user not provisioned).
488
+ */
489
+ getDocKey(docId: string, client: AbracadabraClient, keystore: CryptoIdentityKeystore): Promise<CryptoKey | null>;
490
+ /**
491
+ * Wrap a DocKey for a recipient.
492
+ * Output: [ephemeralPub(32) || nonce(12) || ciphertext(~48)] bytes
493
+ */
494
+ wrapKeyForRecipient(docKey: CryptoKey, recipientX25519PubKey: Uint8Array, docId: string): Promise<Uint8Array>;
495
+ private _unwrapKey;
496
+ /** Clear the cached key for a document (or all if docId omitted). */
497
+ clearCache(docId?: string): void;
498
+ }
499
+ //#endregion
320
500
  //#region packages/provider/src/AbracadabraProvider.d.ts
321
501
  interface AbracadabraProviderConfiguration extends Omit<AbracadabraBaseProviderConfiguration, "url" | "websocketProvider"> {
322
502
  /**
@@ -352,6 +532,10 @@ interface AbracadabraProviderConfiguration extends Omit<AbracadabraBaseProviderC
352
532
  client?: AbracadabraClient;
353
533
  /** WebSocket URL. Derived from client.wsUrl if client is provided. */
354
534
  url?: string;
535
+ /** DocKeyManager for E2E/CSE encryption key management. */
536
+ docKeyManager?: DocKeyManager;
537
+ /** Keystore for X25519 key derivation. */
538
+ keystore?: CryptoIdentityKeystore;
355
539
  /** Shared WebSocket connection (use when multiplexing multiple root documents). */
356
540
  websocketProvider?: AbracadabraWS;
357
541
  }
@@ -398,7 +582,7 @@ declare class AbracadabraProvider extends AbracadabraBaseProvider {
398
582
  * Used to namespace the IndexedDB key so docs from different servers
399
583
  * never share the same database.
400
584
  */
401
- private static deriveServerOrigin;
585
+ protected static deriveServerOrigin(config: AbracadabraProviderConfiguration, client: AbracadabraClient | null): string | undefined;
402
586
  /**
403
587
  * Load the stored document snapshot (and any pending local edits) from
404
588
  * IndexedDB and apply them to the Y.Doc so the UI can render without a
@@ -425,6 +609,8 @@ declare class AbracadabraProvider extends AbracadabraBaseProvider {
425
609
  get canWrite(): boolean;
426
610
  /** The AbracadabraClient instance for REST API access, if configured. */
427
611
  get client(): AbracadabraClient | null;
612
+ /** The OfflineStore instance, or null if offline storage is disabled. */
613
+ get store(): OfflineStore | null;
428
614
  /**
429
615
  * Called when a MSG_STATELESS frame arrives from the server.
430
616
  * Abracadabra uses stateless frames to deliver subdoc confirmations
@@ -475,7 +661,7 @@ declare class AbracadabraProvider extends AbracadabraBaseProvider {
475
661
  destroy(): void;
476
662
  }
477
663
  //#endregion
478
- //#region node_modules/lib0/encoding.d.ts
664
+ //#region packages/provider/node_modules/lib0/encoding.d.ts
479
665
  /**
480
666
  * A BinaryEncoder handles the encoding to an Uint8Array.
481
667
  */
@@ -519,7 +705,7 @@ declare const Forbidden: CloseEvent;
519
705
  */
520
706
  declare const ConnectionTimeout: CloseEvent;
521
707
  //#endregion
522
- //#region node_modules/lib0/decoding.d.ts
708
+ //#region packages/provider/node_modules/lib0/decoding.d.ts
523
709
  /**
524
710
  * A Decoder handles the decoding of an Uint8Array.
525
711
  * @template {ArrayBufferLike} [Buf=ArrayBufferLike]
@@ -801,6 +987,11 @@ interface UploadQueueEntry {
801
987
  createdAt: number;
802
988
  error?: string;
803
989
  }
990
+ interface DocEncryptionInfo {
991
+ mode: "none" | "cse" | "e2e";
992
+ effective_mode: "none" | "cse" | "e2e";
993
+ inherited_from?: string;
994
+ }
804
995
  //#endregion
805
996
  //#region packages/provider/src/AbracadabraWS.d.ts
806
997
  type AbracadabraWebSocketConn = WebSocket & {
@@ -1061,61 +1252,6 @@ declare const HocuspocusProvider: typeof AbracadabraBaseProvider;
1061
1252
  /** @deprecated Use AbracadabraBaseProvider */
1062
1253
  type HocuspocusProvider = AbracadabraBaseProvider;
1063
1254
  //#endregion
1064
- //#region packages/provider/src/OfflineStore.d.ts
1065
- /**
1066
- * IndexedDB-backed offline store for a single Abracadabra document.
1067
- *
1068
- * Responsibilities:
1069
- * - Persist CRDT updates locally so they survive page refreshes / network loss.
1070
- * - Store the last-known state vector for fast reconnect diffs.
1071
- * - Store a permission snapshot so the UI can gate writes without a network round-trip.
1072
- * - Queue subdoc registration events created while offline.
1073
- * - Store a full document snapshot so the app can load content without a server connection.
1074
- *
1075
- * Falls back to a silent no-op when IndexedDB is unavailable (e.g. SSR / Node.js).
1076
- *
1077
- * Database key is scoped by server origin to prevent cross-server data contamination
1078
- * when the same docId is used on multiple servers.
1079
- */
1080
- interface PendingSubdoc {
1081
- childId: string;
1082
- parentId: string;
1083
- createdAt: number;
1084
- }
1085
- declare class OfflineStore {
1086
- private storeKey;
1087
- private db;
1088
- /**
1089
- * @param docId The document UUID.
1090
- * @param serverOrigin Hostname of the server (e.g. "abra.cou.sh").
1091
- * When provided the IndexedDB database is namespaced
1092
- * per-server, preventing cross-server data contamination.
1093
- */
1094
- constructor(docId: string, serverOrigin?: string);
1095
- private dbPromise;
1096
- private getDb;
1097
- persistUpdate(update: Uint8Array): Promise<void>;
1098
- getPendingUpdates(): Promise<Uint8Array[]>;
1099
- clearPendingUpdates(): Promise<void>;
1100
- /**
1101
- * Persist a full Y.js state snapshot (Y.encodeStateAsUpdate output).
1102
- * Replaces any previously stored snapshot.
1103
- */
1104
- saveDocSnapshot(snapshot: Uint8Array): Promise<void>;
1105
- /**
1106
- * Retrieve the stored full document snapshot, or null if none exists.
1107
- */
1108
- getDocSnapshot(): Promise<Uint8Array | null>;
1109
- getStateVector(): Promise<Uint8Array | null>;
1110
- saveStateVector(sv: Uint8Array): Promise<void>;
1111
- getPermissionSnapshot(): Promise<string | null>;
1112
- savePermissionSnapshot(role: string): Promise<void>;
1113
- queueSubdoc(entry: PendingSubdoc): Promise<void>;
1114
- getPendingSubdocs(): Promise<PendingSubdoc[]>;
1115
- removeSubdocFromQueue(childId: string): Promise<void>;
1116
- destroy(): void;
1117
- }
1118
- //#endregion
1119
1255
  //#region packages/provider/src/auth.d.ts
1120
1256
  declare enum AuthMessageType {
1121
1257
  Token = 0,
@@ -1150,61 +1286,6 @@ declare class SubdocMessage extends OutgoingMessage {
1150
1286
  get(args: Partial<OutgoingMessageArguments>): Encoder;
1151
1287
  }
1152
1288
  //#endregion
1153
- //#region packages/provider/src/CryptoIdentityKeystore.d.ts
1154
- /**
1155
- * CryptoIdentityKeystore
1156
- *
1157
- * Per-device Ed25519 keypair, private key protected by WebAuthn PRF + AES-256-GCM.
1158
- * Stored in IndexedDB under "abracadabra:identity" / "identity" / key "current".
1159
- *
1160
- * No private key is ever shared between devices. Each device generates its own
1161
- * keypair, encrypts the private key with the PRF output from its own WebAuthn
1162
- * credential, and stores the ciphertext in IndexedDB.
1163
- *
1164
- * Dependencies: @noble/ed25519, @noble/hashes (for HKDF)
1165
- */
1166
- declare class CryptoIdentityKeystore {
1167
- /**
1168
- * One-time setup for a device: generates an Ed25519 keypair, creates a
1169
- * WebAuthn credential with PRF extension, encrypts the private key, and
1170
- * stores everything in IndexedDB.
1171
- *
1172
- * Returns the base64url-encoded public key. The caller must register this
1173
- * key with the server via POST /auth/register (first device) or
1174
- * POST /auth/keys (additional device).
1175
- *
1176
- * @param username - The user's account name.
1177
- * @param rpId - WebAuthn relying party ID (e.g. "example.com").
1178
- * @param rpName - Human-readable relying party name.
1179
- */
1180
- register(username: string, rpId: string, rpName: string): Promise<string>;
1181
- /**
1182
- * Sign a base64url-encoded challenge using the stored Ed25519 private key.
1183
- *
1184
- * This triggers a WebAuthn assertion (biometric / PIN prompt) to unlock the
1185
- * private key via PRF → HKDF → AES-GCM decryption. The private key is
1186
- * wiped from memory after signing.
1187
- *
1188
- * @param challengeB64 - base64url-encoded challenge bytes from the server.
1189
- * @returns base64url-encoded Ed25519 signature (64 bytes).
1190
- */
1191
- sign(challengeB64: string): Promise<string>;
1192
- /** Returns the stored base64url public key, or null if no identity exists. */
1193
- getPublicKey(): Promise<string | null>;
1194
- /**
1195
- * Returns the locally-stored internal username label, or null if no identity exists.
1196
- *
1197
- * This is NOT the auth identifier (the public key is). It can be used as a
1198
- * hint when calling POST /auth/register, or displayed before the user sets
1199
- * a real display name via PATCH /users/me.
1200
- */
1201
- getUsername(): Promise<string | null>;
1202
- /** Returns true if an identity is stored in IndexedDB. */
1203
- hasIdentity(): Promise<boolean>;
1204
- /** Remove the stored identity from IndexedDB. */
1205
- clear(): Promise<void>;
1206
- }
1207
- //#endregion
1208
1289
  //#region packages/provider/src/SearchIndex.d.ts
1209
1290
  declare class SearchIndex {
1210
1291
  private readonly origin;
@@ -1248,6 +1329,12 @@ declare class FileBlobStore extends EventEmitter {
1248
1329
  * URL.createObjectURL is unavailable (e.g. Node.js / SSR).
1249
1330
  */
1250
1331
  getBlobUrl(docId: string, uploadId: string): Promise<string | null>;
1332
+ /**
1333
+ * Store a blob directly into the cache under the given (docId, uploadId) key
1334
+ * and return its object URL. Use this to pre-populate the cache for files
1335
+ * that haven't been uploaded to the server yet (e.g. offline upload queue).
1336
+ */
1337
+ putBlob(docId: string, uploadId: string, blob: Blob, filename: string): Promise<string>;
1251
1338
  /** Revoke the object URL and remove the blob from cache. */
1252
1339
  evictBlob(docId: string, uploadId: string): Promise<void>;
1253
1340
  /**
@@ -1268,4 +1355,212 @@ declare class FileBlobStore extends EventEmitter {
1268
1355
  destroy(): void;
1269
1356
  }
1270
1357
  //#endregion
1271
- export { AbracadabraBaseProvider, AbracadabraBaseProviderConfiguration, AbracadabraClient, AbracadabraClientConfig, AbracadabraOutgoingMessageArguments, AbracadabraProvider, AbracadabraProviderConfiguration, AbracadabraWS, AbracadabraWSConfiguration, AbracadabraWebSocketConn, AuthMessageType, AuthorizedScope, AwarenessError, CloseEvent, CompleteAbracadabraBaseProviderConfiguration, CompleteAbracadabraWSConfiguration, CompleteHocuspocusProviderConfiguration, CompleteHocuspocusProviderWebsocketConfiguration, ConnectionTimeout, Constructable, ConstructableOutgoingMessage, CryptoIdentity, CryptoIdentityKeystore, DocumentCache, type DocumentCacheOptions, DocumentMeta, EffectiveRole, FileBlobStore, Forbidden, HealthStatus, HocusPocusWebSocket, HocuspocusProvider, HocuspocusProviderConfiguration, HocuspocusProviderWebsocket, HocuspocusProviderWebsocketConfiguration, HocuspocusWebSocket, InviteRow, MessageTooBig, MessageType, OfflineStore, OutgoingMessageArguments, OutgoingMessageInterface, PendingSubdoc, PermissionEntry, PublicKeyInfo, ResetConnection, SearchIndex, SearchResult, ServerInfo, StatesArray, SubdocMessage, SubdocRegisteredEvent, Unauthorized, UploadInfo, UploadMeta, UploadQueueEntry, UploadQueueStatus, UserProfile, WebSocketStatus, WsReadyStates, awarenessStatesToArray, onAuthenticatedParameters, onAuthenticationFailedParameters, onAwarenessChangeParameters, onAwarenessUpdateParameters, onCloseParameters, onDisconnectParameters, onMessageParameters, onOpenParameters, onOutgoingMessageParameters, onStatelessParameters, onStatusParameters, onSubdocLoadedParameters, onSubdocRegisteredParameters, onSyncedParameters, onUnsyncedChangesParameters, readAuthMessage, writeAuthenticated, writeAuthentication, writePermissionDenied, writeTokenSyncRequest };
1358
+ //#region packages/provider/src/E2EAbracadabraProvider.d.ts
1359
+ interface E2EAbracadabraProviderConfiguration extends AbracadabraProviderConfiguration {
1360
+ docKeyManager: DocKeyManager;
1361
+ keystore: CryptoIdentityKeystore;
1362
+ client: AbracadabraClient;
1363
+ }
1364
+ declare class E2EAbracadabraProvider extends AbracadabraProvider {
1365
+ private readonly docKeyManager;
1366
+ private readonly keystore;
1367
+ private readonly e2eClient;
1368
+ private docKey;
1369
+ private lastSeq;
1370
+ /** E2E-encrypted offline store; created after the doc key is available. */
1371
+ private e2eStore;
1372
+ private readonly e2eServerOrigin;
1373
+ constructor(configuration: E2EAbracadabraProviderConfiguration);
1374
+ /** Fetch the doc key from the server (requires WebAuthn if not cached). */
1375
+ private ensureDocKey;
1376
+ /** Handle stateless messages including e2e_ready and e2e_update. */
1377
+ receiveStateless(payload: string): void;
1378
+ private _fetchAndApplyAllBlobs;
1379
+ private _decryptAndApply;
1380
+ /** Encrypt local updates before sending over WebSocket. */
1381
+ documentUpdateHandler(update: Uint8Array, origin: unknown): void;
1382
+ private _encryptAndSend;
1383
+ destroy(): void;
1384
+ }
1385
+ //#endregion
1386
+ //#region packages/provider/src/E2EOfflineStore.d.ts
1387
+ declare class E2EOfflineStore extends OfflineStore {
1388
+ private readonly docKey;
1389
+ /**
1390
+ * @param docId The document UUID.
1391
+ * @param serverOrigin Hostname of the server (namespaces the IDB key).
1392
+ * @param docKey AES-GCM CryptoKey used to encrypt/decrypt the snapshot.
1393
+ */
1394
+ constructor(docId: string, serverOrigin: string | undefined, docKey: CryptoKey);
1395
+ /**
1396
+ * Encrypt the snapshot before storing it.
1397
+ */
1398
+ saveDocSnapshot(snapshot: Uint8Array): Promise<void>;
1399
+ /**
1400
+ * Decrypt the snapshot after loading it.
1401
+ * Returns null if decryption fails (e.g. key mismatch or corrupt data).
1402
+ */
1403
+ getDocSnapshot(): Promise<Uint8Array | null>;
1404
+ }
1405
+ //#endregion
1406
+ //#region packages/provider/src/EncryptedY.d.ts
1407
+ /** Encrypt a field value with AES-256-GCM. Returns [nonce(12) || ciphertext]. */
1408
+ declare function encryptField(value: Uint8Array, docKey: CryptoKey): Promise<Uint8Array>;
1409
+ /** Decrypt a field value from [nonce(12) || ciphertext] format. */
1410
+ declare function decryptField(ciphertext: Uint8Array, docKey: CryptoKey): Promise<Uint8Array>;
1411
+ /**
1412
+ * A proxy wrapper around a Y.Map<Uint8Array> that transparently encrypts
1413
+ * values on .set() and decrypts on .get().
1414
+ *
1415
+ * NOTE: Concurrent writes to the same key produce last-write-wins (LWW)
1416
+ * because encrypted values are opaque blobs from Yrs's perspective.
1417
+ */
1418
+ declare class EncryptedYMap {
1419
+ private readonly ymap;
1420
+ private readonly docKey;
1421
+ constructor(ymap: Y.Map<Uint8Array>, docKey: CryptoKey);
1422
+ set(key: string, value: Uint8Array): Promise<void>;
1423
+ get(key: string): Promise<Uint8Array | null>;
1424
+ has(key: string): boolean;
1425
+ delete(key: string): void;
1426
+ get size(): number;
1427
+ /** Get all keys (encrypted values remain opaque until .get() is called). */
1428
+ keys(): string[];
1429
+ /** Observe changes on the underlying Y.Map. */
1430
+ observe(f: (event: Y.YMapEvent<Uint8Array>) => void): void;
1431
+ unobserve(f: (event: Y.YMapEvent<Uint8Array>) => void): void;
1432
+ }
1433
+ /** Create an EncryptedYMap wrapping a Y.Map<Uint8Array>. */
1434
+ declare function makeEncryptedYMap(ymap: Y.Map<Uint8Array>, docKey: CryptoKey): EncryptedYMap;
1435
+ /**
1436
+ * Stores full text as a single encrypted blob in a Y.Map<Uint8Array> under a
1437
+ * fixed field name. This is last-write-wins for the entire text field.
1438
+ *
1439
+ * NOTE: This does NOT preserve character-level CRDT merge for concurrent edits.
1440
+ * It trades CRDT merge for confidentiality.
1441
+ */
1442
+ declare class EncryptedYText {
1443
+ private readonly docKey;
1444
+ private readonly ymap;
1445
+ constructor(ydoc: Y.Doc, fieldName: string, docKey: CryptoKey);
1446
+ set(text: string): Promise<void>;
1447
+ get(): Promise<string | null>;
1448
+ observe(f: (event: Y.YMapEvent<Uint8Array>) => void): void;
1449
+ unobserve(f: (event: Y.YMapEvent<Uint8Array>) => void): void;
1450
+ }
1451
+ /** Create an EncryptedYText for a field in a Y.Doc. */
1452
+ declare function makeEncryptedYText(ydoc: Y.Doc, fieldName: string, docKey: CryptoKey): EncryptedYText;
1453
+ //#endregion
1454
+ //#region packages/provider/src/TreeTimestamps.d.ts
1455
+ /**
1456
+ * Attach an observer that writes `updatedAt: Date.now()` to the root
1457
+ * doc-tree entry for `childDocId` whenever the child doc receives a
1458
+ * non-offline update.
1459
+ *
1460
+ * @param treeMap The root doc's "doc-tree" Y.Map.
1461
+ * @param childDocId The child document's UUID (key in treeMap).
1462
+ * @param childDoc The child Y.Doc to observe.
1463
+ * @param offlineStore The child provider's OfflineStore (used to detect
1464
+ * offline-replay origins and skip them). Pass null when
1465
+ * the offline store is disabled.
1466
+ * @returns Cleanup function — call on provider destroy.
1467
+ */
1468
+ declare function attachUpdatedAtObserver(treeMap: Y.Map<any>, childDocId: string, childDoc: Y.Doc, offlineStore: OfflineStore | null): () => void;
1469
+ //#endregion
1470
+ //#region packages/provider/src/BackgroundSyncPersistence.d.ts
1471
+ /**
1472
+ * BackgroundSyncPersistence
1473
+ *
1474
+ * Minimal IndexedDB store (`abracadabra:bgsync:{serverOrigin}`) that persists
1475
+ * per-document sync state across page reloads. A single object store,
1476
+ * `sync_state`, is keyed by `docId`.
1477
+ *
1478
+ * Falls back to a no-op when IndexedDB is unavailable (SSR / Node.js).
1479
+ */
1480
+ interface DocSyncState {
1481
+ docId: string;
1482
+ /** Current lifecycle status of this document's background sync. */
1483
+ status: "pending" | "syncing" | "synced" | "error" | "skipped";
1484
+ /** Unix ms of the last successful sync, or null if never synced. */
1485
+ lastSynced: number | null;
1486
+ /** Human-readable error message if status === "error". */
1487
+ error?: string;
1488
+ /** Whether the document uses E2E encryption. */
1489
+ isE2E: boolean;
1490
+ }
1491
+ declare class BackgroundSyncPersistence {
1492
+ private dbPromise;
1493
+ private readonly serverOrigin;
1494
+ constructor(serverOrigin: string);
1495
+ private getDb;
1496
+ getState(docId: string): Promise<DocSyncState | null>;
1497
+ getAllStates(): Promise<DocSyncState[]>;
1498
+ setState(state: DocSyncState): Promise<void>;
1499
+ deleteState(docId: string): Promise<void>;
1500
+ destroy(): void;
1501
+ }
1502
+ //#endregion
1503
+ //#region packages/provider/src/BackgroundSyncManager.d.ts
1504
+ interface BackgroundSyncManagerOptions {
1505
+ /** Max parallel WS connections for background sync. Default: 2. */
1506
+ concurrency?: number;
1507
+ /** Timeout (ms) waiting for a provider to sync. Default: 15 000. */
1508
+ syncTimeout?: number;
1509
+ /** Pre-cache file blobs after syncing a doc. Default: true. */
1510
+ prefetchFiles?: boolean;
1511
+ }
1512
+ declare class BackgroundSyncManager extends EventEmitter {
1513
+ private readonly rootProvider;
1514
+ private readonly client;
1515
+ private readonly fileBlobStore;
1516
+ private readonly opts;
1517
+ private readonly persistence;
1518
+ private readonly semaphore;
1519
+ private readonly syncStates;
1520
+ private _destroyed;
1521
+ constructor(rootProvider: AbracadabraProvider, client: AbracadabraClient, fileBlobStore?: FileBlobStore | null, opts?: BackgroundSyncManagerOptions);
1522
+ /** Sync all documents in the root tree. */
1523
+ syncAll(): Promise<void>;
1524
+ /** Sync a single document by ID. */
1525
+ syncDoc(docId: string): Promise<DocSyncState>;
1526
+ /** Return a snapshot of all known sync states. */
1527
+ getSyncStatus(): Map<string, DocSyncState>;
1528
+ /**
1529
+ * Start periodic background sync.
1530
+ * @param intervalMs Interval between sync runs. Default: 5 minutes.
1531
+ * @returns Cleanup function to stop the periodic sync.
1532
+ */
1533
+ startPeriodicSync(intervalMs?: number): () => void;
1534
+ destroy(): void;
1535
+ /**
1536
+ * Build a priority-sorted list of doc IDs:
1537
+ * 1. Never-synced docs first (lastSynced === null, status !== "error")
1538
+ * 2. Synced docs sorted by updatedAt desc (most-recently-edited first)
1539
+ * 3. Errored docs last
1540
+ */
1541
+ private _buildQueue;
1542
+ private _syncWithSemaphore;
1543
+ private _doSyncDoc;
1544
+ private _syncNonE2EDoc;
1545
+ private _syncE2EDoc;
1546
+ /**
1547
+ * Wait for a provider to emit the "synced" event (with state=true),
1548
+ * timing out after opts.syncTimeout ms.
1549
+ */
1550
+ private _waitForSynced;
1551
+ /**
1552
+ * Pre-cache all cover images referenced in the root tree map.
1553
+ */
1554
+ private _prefetchCovers;
1555
+ /**
1556
+ * Pre-cache file blobs referenced in a synced Y.Doc.
1557
+ * Walks:
1558
+ * - ydoc.getXmlFragment('default') for `fileBlock` nodes (TipTap)
1559
+ * - ydoc.getMap('doc-tree') for meta.coverUploadId entries
1560
+ */
1561
+ private _prefetchDocFiles;
1562
+ private _extractFileRefs;
1563
+ private _walkXml;
1564
+ }
1565
+ //#endregion
1566
+ export { AbracadabraBaseProvider, AbracadabraBaseProviderConfiguration, AbracadabraClient, AbracadabraClientConfig, AbracadabraOutgoingMessageArguments, AbracadabraProvider, AbracadabraProviderConfiguration, AbracadabraWS, AbracadabraWSConfiguration, AbracadabraWebSocketConn, AuthMessageType, AuthorizedScope, AwarenessError, BackgroundSyncManager, type BackgroundSyncManagerOptions, BackgroundSyncPersistence, CloseEvent, CompleteAbracadabraBaseProviderConfiguration, CompleteAbracadabraWSConfiguration, CompleteHocuspocusProviderConfiguration, CompleteHocuspocusProviderWebsocketConfiguration, ConnectionTimeout, Constructable, ConstructableOutgoingMessage, CryptoIdentity, CryptoIdentityKeystore, type DocEncryptionInfo, DocKeyManager, type DocSyncState, DocumentCache, type DocumentCacheOptions, DocumentMeta, E2EAbracadabraProvider, E2EOfflineStore, EffectiveRole, EncryptedYMap, EncryptedYText, FileBlobStore, Forbidden, HealthStatus, HocusPocusWebSocket, HocuspocusProvider, HocuspocusProviderConfiguration, HocuspocusProviderWebsocket, HocuspocusProviderWebsocketConfiguration, HocuspocusWebSocket, InviteRow, MessageTooBig, MessageType, OfflineStore, OutgoingMessageArguments, OutgoingMessageInterface, PendingSubdoc, PermissionEntry, PublicKeyInfo, ResetConnection, SearchIndex, SearchResult, ServerInfo, StatesArray, SubdocMessage, SubdocRegisteredEvent, Unauthorized, UploadInfo, UploadMeta, UploadQueueEntry, UploadQueueStatus, UserProfile, WebSocketStatus, WsReadyStates, attachUpdatedAtObserver, awarenessStatesToArray, decryptField, encryptField, makeEncryptedYMap, makeEncryptedYText, onAuthenticatedParameters, onAuthenticationFailedParameters, onAwarenessChangeParameters, onAwarenessUpdateParameters, onCloseParameters, onDisconnectParameters, onMessageParameters, onOpenParameters, onOutgoingMessageParameters, onStatelessParameters, onStatusParameters, onSubdocLoadedParameters, onSubdocRegisteredParameters, onSyncedParameters, onUnsyncedChangesParameters, readAuthMessage, writeAuthenticated, writeAuthentication, writePermissionDenied, writeTokenSyncRequest };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abraca/dabra",
3
- "version": "0.9.0",
3
+ "version": "1.0.1",
4
4
  "description": "abracadabra provider",
5
5
  "keywords": [
6
6
  "abracadabra",
@@ -29,6 +29,7 @@
29
29
  ],
30
30
  "dependencies": {
31
31
  "@lifeomic/attempt": "^3.1.0",
32
+ "@noble/curves": "^2.0.1",
32
33
  "@noble/ed25519": "^2.3.0",
33
34
  "@noble/hashes": "^1.8.0",
34
35
  "lib0": "^0.2.117",