@abraca/dabra 1.0.15 → 1.0.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/abracadabra-provider.cjs +72 -2
- package/dist/abracadabra-provider.cjs.map +1 -1
- package/dist/abracadabra-provider.esm.js +72 -2
- package/dist/abracadabra-provider.esm.js.map +1 -1
- package/dist/index.d.ts +54 -4
- package/package.json +1 -1
- package/src/AbracadabraClient.ts +32 -0
- package/src/AbracadabraProvider.ts +7 -2
- package/src/BackgroundSyncManager.ts +43 -0
- package/src/OfflineStore.ts +22 -0
- package/src/types.ts +16 -4
package/dist/index.d.ts
CHANGED
|
@@ -187,6 +187,11 @@ declare class OfflineStore {
|
|
|
187
187
|
removeSubdocFromQueue(childId: string): Promise<void>;
|
|
188
188
|
getMeta(key: string): Promise<string | null>;
|
|
189
189
|
setMeta(key: string, value: string): Promise<void>;
|
|
190
|
+
/**
|
|
191
|
+
* Clear all stored data (updates, snapshots, state vectors, subdoc queue).
|
|
192
|
+
* The database itself is kept but emptied.
|
|
193
|
+
*/
|
|
194
|
+
clearAll(): Promise<void>;
|
|
190
195
|
destroy(): void;
|
|
191
196
|
}
|
|
192
197
|
//#endregion
|
|
@@ -366,6 +371,8 @@ declare class AbracadabraClient {
|
|
|
366
371
|
doc_type?: string;
|
|
367
372
|
label?: string;
|
|
368
373
|
}): Promise<DocumentMeta>;
|
|
374
|
+
/** Broadcast a stateless message to all connected clients on a document (requires manage permission). */
|
|
375
|
+
broadcast(docId: string, payload: string): Promise<void>;
|
|
369
376
|
/** List all permissions for a document (requires read access). */
|
|
370
377
|
listPermissions(docId: string): Promise<PermissionEntry[]>;
|
|
371
378
|
/** List effective permissions including inherited ones from ancestor documents. */
|
|
@@ -416,6 +423,26 @@ declare class AbracadabraClient {
|
|
|
416
423
|
updateSpace(spaceId: string, opts: Partial<Pick<SpaceMeta, "name" | "description" | "visibility" | "is_hub">>): Promise<SpaceMeta>;
|
|
417
424
|
/** Delete a space and its root document (Owner or admin required). */
|
|
418
425
|
deleteSpace(spaceId: string): Promise<void>;
|
|
426
|
+
/** List all users (requires elevated role: admin or service). */
|
|
427
|
+
adminListUsers(): Promise<{
|
|
428
|
+
users: (UserProfile & {
|
|
429
|
+
revoked: boolean;
|
|
430
|
+
deviceKeys: string[];
|
|
431
|
+
})[];
|
|
432
|
+
}>;
|
|
433
|
+
/** Promote a user to admin (requires service role). */
|
|
434
|
+
adminPromote(userId: string): Promise<void>;
|
|
435
|
+
/** Demote an admin user back to regular (requires service role). */
|
|
436
|
+
adminDemote(userId: string): Promise<void>;
|
|
437
|
+
/** Sweep orphaned file blobs from storage (requires elevated role). */
|
|
438
|
+
adminStorageSweep(): Promise<{
|
|
439
|
+
blobsDeleted: number;
|
|
440
|
+
}>;
|
|
441
|
+
/** Repair blob ref-counts and sweep orphans (requires elevated role). */
|
|
442
|
+
adminStorageRepair(): Promise<{
|
|
443
|
+
refCountsRepaired: number;
|
|
444
|
+
blobsSwept: number;
|
|
445
|
+
}>;
|
|
419
446
|
/** Health check — no auth required. */
|
|
420
447
|
health(): Promise<HealthStatus>;
|
|
421
448
|
/**
|
|
@@ -640,6 +667,7 @@ declare class AbracadabraProvider extends AbracadabraBaseProvider {
|
|
|
640
667
|
private handleAuthChallenge;
|
|
641
668
|
private restorePermissionSnapshot;
|
|
642
669
|
get canWrite(): boolean;
|
|
670
|
+
get canAwareness(): boolean;
|
|
643
671
|
/** The AbracadabraClient instance for REST API access, if configured. */
|
|
644
672
|
get client(): AbracadabraClient | null;
|
|
645
673
|
/** The OfflineStore instance, or null if offline storage is disabled. */
|
|
@@ -859,7 +887,7 @@ declare enum WebSocketStatus {
|
|
|
859
887
|
Connected = "connected",
|
|
860
888
|
Disconnected = "disconnected"
|
|
861
889
|
}
|
|
862
|
-
type AuthorizedScope = "service" | "admin" | "owner" | "editor" | "viewer" | "read-write" | "readonly";
|
|
890
|
+
type AuthorizedScope = "service" | "admin" | "owner" | "editor" | "viewer" | "observer" | "read-write" | "readonly";
|
|
863
891
|
interface OutgoingMessageInterface {
|
|
864
892
|
encoder: Encoder;
|
|
865
893
|
type?: MessageType;
|
|
@@ -925,7 +953,7 @@ type StatesArray = {
|
|
|
925
953
|
clientId: number;
|
|
926
954
|
[key: string | number]: any;
|
|
927
955
|
}[];
|
|
928
|
-
type EffectiveRole = "service" | "admin" | "owner" | "editor" | "viewer" | null;
|
|
956
|
+
type EffectiveRole = "service" | "admin" | "owner" | "editor" | "viewer" | "observer" | null;
|
|
929
957
|
/**
|
|
930
958
|
* Ed25519 identity for passwordless crypto auth.
|
|
931
959
|
*
|
|
@@ -961,6 +989,8 @@ interface UserProfile {
|
|
|
961
989
|
interface DocumentMeta {
|
|
962
990
|
id: string;
|
|
963
991
|
parent_id: string | null;
|
|
992
|
+
doc_type?: string | null;
|
|
993
|
+
label?: string | null;
|
|
964
994
|
}
|
|
965
995
|
interface UploadMeta {
|
|
966
996
|
id: string;
|
|
@@ -981,13 +1011,13 @@ interface PublicKeyInfo {
|
|
|
981
1011
|
}
|
|
982
1012
|
interface PermissionEntry {
|
|
983
1013
|
user_id: string;
|
|
984
|
-
role: "owner" | "editor" | "viewer" | "observer";
|
|
1014
|
+
role: "service" | "admin" | "owner" | "editor" | "viewer" | "observer";
|
|
985
1015
|
username: string;
|
|
986
1016
|
display_name: string | null;
|
|
987
1017
|
}
|
|
988
1018
|
interface EffectivePermissionEntry {
|
|
989
1019
|
user_id: string;
|
|
990
|
-
role: "owner" | "editor" | "viewer" | "observer";
|
|
1020
|
+
role: "service" | "admin" | "owner" | "editor" | "viewer" | "observer";
|
|
991
1021
|
username: string;
|
|
992
1022
|
display_name: string | null;
|
|
993
1023
|
source: "direct" | "inherited";
|
|
@@ -1007,10 +1037,23 @@ interface ServerInfo {
|
|
|
1007
1037
|
name?: string;
|
|
1008
1038
|
/** Server version string. */
|
|
1009
1039
|
version?: string;
|
|
1040
|
+
/** Hocuspocus wire protocol version (currently 2). */
|
|
1041
|
+
protocol_version?: number;
|
|
1010
1042
|
/** Entry-point document ID advertised by the server, if configured. */
|
|
1011
1043
|
index_doc_id?: string;
|
|
1012
1044
|
/** Default role assigned to users without explicit permissions. */
|
|
1013
1045
|
default_role?: string;
|
|
1046
|
+
/** Enabled auth methods (e.g. ["crypto", "jwt"]). */
|
|
1047
|
+
auth_methods?: string[];
|
|
1048
|
+
/** Whether open registration is enabled. */
|
|
1049
|
+
registration_allowed?: boolean;
|
|
1050
|
+
/** Whether an invite code is required to register. */
|
|
1051
|
+
invite_only?: boolean;
|
|
1052
|
+
/** Server encryption configuration. */
|
|
1053
|
+
encryption?: {
|
|
1054
|
+
default_mode?: string;
|
|
1055
|
+
minimum_mode?: string;
|
|
1056
|
+
};
|
|
1014
1057
|
}
|
|
1015
1058
|
interface SearchResult {
|
|
1016
1059
|
docId: string;
|
|
@@ -1634,6 +1677,13 @@ declare class BackgroundSyncManager extends EventEmitter {
|
|
|
1634
1677
|
* @returns Cleanup function to stop the periodic sync.
|
|
1635
1678
|
*/
|
|
1636
1679
|
startPeriodicSync(intervalMs?: number): () => void;
|
|
1680
|
+
/**
|
|
1681
|
+
* Clear all offline document data and sync state.
|
|
1682
|
+
* Opens each document's OfflineStore and clears its contents, then
|
|
1683
|
+
* resets the background sync persistence. After calling this, all
|
|
1684
|
+
* documents will need to be re-synced.
|
|
1685
|
+
*/
|
|
1686
|
+
clearAllSyncedData(): Promise<void>;
|
|
1637
1687
|
destroy(): void;
|
|
1638
1688
|
/**
|
|
1639
1689
|
* Build a priority-sorted list of doc IDs:
|
package/package.json
CHANGED
package/src/AbracadabraClient.ts
CHANGED
|
@@ -336,6 +336,11 @@ export class AbracadabraClient {
|
|
|
336
336
|
);
|
|
337
337
|
}
|
|
338
338
|
|
|
339
|
+
/** Broadcast a stateless message to all connected clients on a document (requires manage permission). */
|
|
340
|
+
async broadcast(docId: string, payload: string): Promise<void> {
|
|
341
|
+
await this.request("POST", `/docs/${encodeURIComponent(docId)}/broadcast`, { body: { payload } });
|
|
342
|
+
}
|
|
343
|
+
|
|
339
344
|
// ── Permissions ──────────────────────────────────────────────────────────
|
|
340
345
|
|
|
341
346
|
/** List all permissions for a document (requires read access). */
|
|
@@ -535,6 +540,33 @@ export class AbracadabraClient {
|
|
|
535
540
|
await this.request("DELETE", `/spaces/${encodeURIComponent(spaceId)}`);
|
|
536
541
|
}
|
|
537
542
|
|
|
543
|
+
// ── Admin ───────────────────────────────────────────────────────────────
|
|
544
|
+
|
|
545
|
+
/** List all users (requires elevated role: admin or service). */
|
|
546
|
+
async adminListUsers(): Promise<{ users: (UserProfile & { revoked: boolean; deviceKeys: string[] })[] }> {
|
|
547
|
+
return this.request("GET", "/admin/users");
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/** Promote a user to admin (requires service role). */
|
|
551
|
+
async adminPromote(userId: string): Promise<void> {
|
|
552
|
+
await this.request("POST", `/admin/users/${encodeURIComponent(userId)}/admin`);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/** Demote an admin user back to regular (requires service role). */
|
|
556
|
+
async adminDemote(userId: string): Promise<void> {
|
|
557
|
+
await this.request("DELETE", `/admin/users/${encodeURIComponent(userId)}/admin`);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/** Sweep orphaned file blobs from storage (requires elevated role). */
|
|
561
|
+
async adminStorageSweep(): Promise<{ blobsDeleted: number }> {
|
|
562
|
+
return this.request("POST", "/admin/storage/sweep");
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/** Repair blob ref-counts and sweep orphans (requires elevated role). */
|
|
566
|
+
async adminStorageRepair(): Promise<{ refCountsRepaired: number; blobsSwept: number }> {
|
|
567
|
+
return this.request("POST", "/admin/storage/repair");
|
|
568
|
+
}
|
|
569
|
+
|
|
538
570
|
// ── System ───────────────────────────────────────────────────────────────
|
|
539
571
|
|
|
540
572
|
/** Health check — no auth required. */
|
|
@@ -226,10 +226,11 @@ export class AbracadabraProvider extends AbracadabraBaseProvider {
|
|
|
226
226
|
owner: "owner",
|
|
227
227
|
editor: "editor",
|
|
228
228
|
viewer: "viewer",
|
|
229
|
+
observer: "observer",
|
|
229
230
|
"read-write": "editor",
|
|
230
231
|
readonly: "viewer",
|
|
231
232
|
};
|
|
232
|
-
this.effectiveRole = roleMap[scope] ?? "
|
|
233
|
+
this.effectiveRole = roleMap[scope] ?? "observer";
|
|
233
234
|
|
|
234
235
|
this.offlineStore?.savePermissionSnapshot(this.effectiveRole);
|
|
235
236
|
}
|
|
@@ -303,7 +304,11 @@ export class AbracadabraProvider extends AbracadabraBaseProvider {
|
|
|
303
304
|
}
|
|
304
305
|
|
|
305
306
|
get canWrite(): boolean {
|
|
306
|
-
return this.effectiveRole != null && this.effectiveRole !== "viewer";
|
|
307
|
+
return this.effectiveRole != null && this.effectiveRole !== "viewer" && this.effectiveRole !== "observer";
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
get canAwareness(): boolean {
|
|
311
|
+
return this.effectiveRole != null && this.effectiveRole !== "observer";
|
|
307
312
|
}
|
|
308
313
|
|
|
309
314
|
/** The AbracadabraClient instance for REST API access, if configured. */
|
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
BackgroundSyncPersistence,
|
|
28
28
|
type DocSyncState,
|
|
29
29
|
} from "./BackgroundSyncPersistence.ts";
|
|
30
|
+
import { OfflineStore } from "./OfflineStore.ts";
|
|
30
31
|
import EventEmitter from "./EventEmitter.ts";
|
|
31
32
|
import { E2EAbracadabraProvider } from "./E2EAbracadabraProvider.ts";
|
|
32
33
|
|
|
@@ -191,6 +192,48 @@ export class BackgroundSyncManager extends EventEmitter {
|
|
|
191
192
|
return () => clearInterval(handle);
|
|
192
193
|
}
|
|
193
194
|
|
|
195
|
+
/**
|
|
196
|
+
* Clear all offline document data and sync state.
|
|
197
|
+
* Opens each document's OfflineStore and clears its contents, then
|
|
198
|
+
* resets the background sync persistence. After calling this, all
|
|
199
|
+
* documents will need to be re-synced.
|
|
200
|
+
*/
|
|
201
|
+
async clearAllSyncedData(): Promise<void> {
|
|
202
|
+
// Collect doc IDs from both in-memory state and the tree
|
|
203
|
+
const docIds = new Set<string>(this.syncStates.keys());
|
|
204
|
+
const treeMap = this.rootProvider.document.getMap("doc-tree") as Y.Map<any>;
|
|
205
|
+
for (const docId of treeMap.keys()) {
|
|
206
|
+
docIds.add(docId);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Derive server origin the same way the provider does
|
|
210
|
+
let serverOrigin: string | undefined;
|
|
211
|
+
try {
|
|
212
|
+
serverOrigin = new URL((this.client as any).baseUrl ?? "").hostname;
|
|
213
|
+
} catch {}
|
|
214
|
+
|
|
215
|
+
// Clear each document's offline store contents
|
|
216
|
+
const clearPromises = Array.from(docIds).map(async (docId) => {
|
|
217
|
+
try {
|
|
218
|
+
const store = new OfflineStore(docId, serverOrigin);
|
|
219
|
+
await store.clearAll();
|
|
220
|
+
store.destroy();
|
|
221
|
+
} catch {
|
|
222
|
+
// Ignore per-doc failures
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
await Promise.all(clearPromises);
|
|
226
|
+
|
|
227
|
+
// Clear background sync persistence
|
|
228
|
+
for (const docId of docIds) {
|
|
229
|
+
await this.persistence.deleteState(docId).catch(() => null);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Reset in-memory state
|
|
233
|
+
this.syncStates.clear();
|
|
234
|
+
this._initPromise = null;
|
|
235
|
+
}
|
|
236
|
+
|
|
194
237
|
destroy(): void {
|
|
195
238
|
this._destroyed = true;
|
|
196
239
|
this.removeAllListeners();
|
package/src/OfflineStore.ts
CHANGED
|
@@ -251,6 +251,28 @@ export class OfflineStore {
|
|
|
251
251
|
);
|
|
252
252
|
}
|
|
253
253
|
|
|
254
|
+
/**
|
|
255
|
+
* Clear all stored data (updates, snapshots, state vectors, subdoc queue).
|
|
256
|
+
* The database itself is kept but emptied.
|
|
257
|
+
*/
|
|
258
|
+
async clearAll(): Promise<void> {
|
|
259
|
+
const db = await this.getDb();
|
|
260
|
+
if (!db) return;
|
|
261
|
+
const storeNames = Array.from(db.objectStoreNames);
|
|
262
|
+
if (storeNames.length === 0) return;
|
|
263
|
+
const tx = db.transaction(storeNames, "readwrite");
|
|
264
|
+
await Promise.all(
|
|
265
|
+
storeNames.map(
|
|
266
|
+
(name) =>
|
|
267
|
+
new Promise<void>((resolve, reject) => {
|
|
268
|
+
const req = tx.objectStore(name).clear();
|
|
269
|
+
req.onsuccess = () => resolve();
|
|
270
|
+
req.onerror = () => reject(req.error);
|
|
271
|
+
}),
|
|
272
|
+
),
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
|
|
254
276
|
// ── Lifecycle ─────────────────────────────────────────────────────────────
|
|
255
277
|
|
|
256
278
|
destroy() {
|
package/src/types.ts
CHANGED
|
@@ -40,7 +40,7 @@ export enum WebSocketStatus {
|
|
|
40
40
|
Disconnected = "disconnected",
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
export type AuthorizedScope = "service" | "admin" | "owner" | "editor" | "viewer" | "read-write" | "readonly";
|
|
43
|
+
export type AuthorizedScope = "service" | "admin" | "owner" | "editor" | "viewer" | "observer" | "read-write" | "readonly";
|
|
44
44
|
|
|
45
45
|
export interface OutgoingMessageInterface {
|
|
46
46
|
encoder: Encoder;
|
|
@@ -128,7 +128,7 @@ export type StatesArray = { clientId: number; [key: string | number]: any }[];
|
|
|
128
128
|
|
|
129
129
|
// ── Abracadabra extensions ────────────────────────────────────────────────────
|
|
130
130
|
|
|
131
|
-
export type EffectiveRole = "service" | "admin" | "owner" | "editor" | "viewer" | null;
|
|
131
|
+
export type EffectiveRole = "service" | "admin" | "owner" | "editor" | "viewer" | "observer" | null;
|
|
132
132
|
|
|
133
133
|
/**
|
|
134
134
|
* Ed25519 identity for passwordless crypto auth.
|
|
@@ -173,6 +173,8 @@ export interface UserProfile {
|
|
|
173
173
|
export interface DocumentMeta {
|
|
174
174
|
id: string;
|
|
175
175
|
parent_id: string | null;
|
|
176
|
+
doc_type?: string | null;
|
|
177
|
+
label?: string | null;
|
|
176
178
|
}
|
|
177
179
|
|
|
178
180
|
export interface UploadMeta {
|
|
@@ -197,14 +199,14 @@ export interface PublicKeyInfo {
|
|
|
197
199
|
|
|
198
200
|
export interface PermissionEntry {
|
|
199
201
|
user_id: string;
|
|
200
|
-
role: "owner" | "editor" | "viewer" | "observer";
|
|
202
|
+
role: "service" | "admin" | "owner" | "editor" | "viewer" | "observer";
|
|
201
203
|
username: string;
|
|
202
204
|
display_name: string | null;
|
|
203
205
|
}
|
|
204
206
|
|
|
205
207
|
export interface EffectivePermissionEntry {
|
|
206
208
|
user_id: string;
|
|
207
|
-
role: "owner" | "editor" | "viewer" | "observer";
|
|
209
|
+
role: "service" | "admin" | "owner" | "editor" | "viewer" | "observer";
|
|
208
210
|
username: string;
|
|
209
211
|
display_name: string | null;
|
|
210
212
|
source: "direct" | "inherited";
|
|
@@ -227,10 +229,20 @@ export interface ServerInfo {
|
|
|
227
229
|
name?: string;
|
|
228
230
|
/** Server version string. */
|
|
229
231
|
version?: string;
|
|
232
|
+
/** Hocuspocus wire protocol version (currently 2). */
|
|
233
|
+
protocol_version?: number;
|
|
230
234
|
/** Entry-point document ID advertised by the server, if configured. */
|
|
231
235
|
index_doc_id?: string;
|
|
232
236
|
/** Default role assigned to users without explicit permissions. */
|
|
233
237
|
default_role?: string;
|
|
238
|
+
/** Enabled auth methods (e.g. ["crypto", "jwt"]). */
|
|
239
|
+
auth_methods?: string[];
|
|
240
|
+
/** Whether open registration is enabled. */
|
|
241
|
+
registration_allowed?: boolean;
|
|
242
|
+
/** Whether an invite code is required to register. */
|
|
243
|
+
invite_only?: boolean;
|
|
244
|
+
/** Server encryption configuration. */
|
|
245
|
+
encryption?: { default_mode?: string; minimum_mode?: string };
|
|
234
246
|
}
|
|
235
247
|
|
|
236
248
|
// ── Search ───────────────────────────────────────────────────────────────────
|