@abraca/dabra 1.0.10 → 1.0.12

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
@@ -362,6 +362,8 @@ declare class AbracadabraClient {
362
362
  }): Promise<DocumentMeta>;
363
363
  /** List all permissions for a document (requires read access). */
364
364
  listPermissions(docId: string): Promise<PermissionEntry[]>;
365
+ /** List effective permissions including inherited ones from ancestor documents. */
366
+ listEffectivePermissions(docId: string): Promise<EffectivePermissionsResponse>;
365
367
  /** Grant or change a user's role on a document (requires Owner). */
366
368
  setPermission(docId: string, opts: {
367
369
  user_id: string;
@@ -971,6 +973,18 @@ interface PermissionEntry {
971
973
  username: string;
972
974
  display_name: string | null;
973
975
  }
976
+ interface EffectivePermissionEntry {
977
+ user_id: string;
978
+ role: "owner" | "editor" | "viewer" | "observer";
979
+ username: string;
980
+ display_name: string | null;
981
+ source: "direct" | "inherited";
982
+ inherited_from_doc_id?: string;
983
+ }
984
+ interface EffectivePermissionsResponse {
985
+ permissions: EffectivePermissionEntry[];
986
+ default_role: string;
987
+ }
974
988
  interface HealthStatus {
975
989
  status: string;
976
990
  version: string;
@@ -983,6 +997,8 @@ interface ServerInfo {
983
997
  version?: string;
984
998
  /** Entry-point document ID advertised by the server, if configured. */
985
999
  index_doc_id?: string;
1000
+ /** Default role assigned to users without explicit permissions. */
1001
+ default_role?: string;
986
1002
  }
987
1003
  interface SearchResult {
988
1004
  docId: string;
@@ -1352,10 +1368,13 @@ declare class FileBlobStore extends EventEmitter {
1352
1368
  private db;
1353
1369
  /** Tracks active object URLs so we can revoke them on destroy. */
1354
1370
  private readonly objectUrls;
1371
+ /** Keys that returned 404 from the server — avoids repeated requests. TTL: 5 min. */
1372
+ private readonly _notFound;
1373
+ private static readonly NOT_FOUND_TTL;
1355
1374
  /** Prevents concurrent flush runs. */
1356
1375
  private _flushing;
1357
1376
  private readonly _onlineHandler;
1358
- constructor(serverOrigin: string, client: AbracadabraClient);
1377
+ constructor(serverOrigin: string, client?: AbracadabraClient | null);
1359
1378
  private getDb;
1360
1379
  private blobKey;
1361
1380
  /**
@@ -2114,4 +2133,4 @@ declare class BroadcastChannelSync extends EventEmitter {
2114
2133
  private handleAwarenessMessage;
2115
2134
  }
2116
2135
  //#endregion
2117
- export { AbracadabraBaseProvider, AbracadabraBaseProviderConfiguration, AbracadabraClient, AbracadabraClientConfig, AbracadabraOutgoingMessageArguments, AbracadabraProvider, AbracadabraProviderConfiguration, AbracadabraWS, AbracadabraWSConfiguration, AbracadabraWebRTC, type AbracadabraWebRTCConfiguration, AbracadabraWebSocketConn, AuthMessageType, AuthorizedScope, AwarenessError, BackgroundSyncManager, type BackgroundSyncManagerOptions, BackgroundSyncPersistence, BroadcastChannelSync, CHANNEL_NAMES, CloseEvent, CompleteAbracadabraBaseProviderConfiguration, CompleteAbracadabraWSConfiguration, CompleteHocuspocusProviderConfiguration, CompleteHocuspocusProviderWebsocketConfiguration, ConnectionTimeout, Constructable, ConstructableOutgoingMessage, CryptoIdentity, CryptoIdentityKeystore, DEFAULT_FILE_CHUNK_SIZE, DEFAULT_ICE_SERVERS, DataChannelRouter, type DocEncryptionInfo, DocKeyManager, type DocSyncState, DocumentCache, type DocumentCacheOptions, DocumentMeta, E2EAbracadabraProvider, E2EEChannel, type E2EEIdentity, E2EOfflineStore, EffectiveRole, EncryptedYMap, EncryptedYText, FileBlobStore, FileTransferChannel, FileTransferHandle, type FileTransferMeta, type FileTransferStatus, Forbidden, HealthStatus, HocusPocusWebSocket, HocuspocusProvider, HocuspocusProviderConfiguration, HocuspocusProviderWebsocket, HocuspocusProviderWebsocketConfiguration, HocuspocusWebSocket, InviteRow, KEY_EXCHANGE_CHANNEL, ManualSignaling, type ManualSignalingBlob, MessageTooBig, MessageType, OfflineStore, OutgoingMessageArguments, OutgoingMessageInterface, PeerConnection, type PeerInfo, type PeerState, PendingSubdoc, PermissionEntry, PublicKeyInfo, ResetConnection, SearchIndex, SearchResult, ServerInfo, type SignalingIncoming, type SignalingOutgoing, SignalingSocket, SpaceMeta, StatesArray, SubdocMessage, SubdocRegisteredEvent, Unauthorized, UploadInfo, UploadMeta, UploadQueueEntry, UploadQueueStatus, UserProfile, WebSocketStatus, WsReadyStates, YjsDataChannel, 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 };
2136
+ export { AbracadabraBaseProvider, AbracadabraBaseProviderConfiguration, AbracadabraClient, AbracadabraClientConfig, AbracadabraOutgoingMessageArguments, AbracadabraProvider, AbracadabraProviderConfiguration, AbracadabraWS, AbracadabraWSConfiguration, AbracadabraWebRTC, type AbracadabraWebRTCConfiguration, AbracadabraWebSocketConn, AuthMessageType, AuthorizedScope, AwarenessError, BackgroundSyncManager, type BackgroundSyncManagerOptions, BackgroundSyncPersistence, BroadcastChannelSync, CHANNEL_NAMES, CloseEvent, CompleteAbracadabraBaseProviderConfiguration, CompleteAbracadabraWSConfiguration, CompleteHocuspocusProviderConfiguration, CompleteHocuspocusProviderWebsocketConfiguration, ConnectionTimeout, Constructable, ConstructableOutgoingMessage, CryptoIdentity, CryptoIdentityKeystore, DEFAULT_FILE_CHUNK_SIZE, DEFAULT_ICE_SERVERS, DataChannelRouter, type DocEncryptionInfo, DocKeyManager, type DocSyncState, DocumentCache, type DocumentCacheOptions, DocumentMeta, E2EAbracadabraProvider, E2EEChannel, type E2EEIdentity, E2EOfflineStore, EffectivePermissionEntry, EffectivePermissionsResponse, EffectiveRole, EncryptedYMap, EncryptedYText, FileBlobStore, FileTransferChannel, FileTransferHandle, type FileTransferMeta, type FileTransferStatus, Forbidden, HealthStatus, HocusPocusWebSocket, HocuspocusProvider, HocuspocusProviderConfiguration, HocuspocusProviderWebsocket, HocuspocusProviderWebsocketConfiguration, HocuspocusWebSocket, InviteRow, KEY_EXCHANGE_CHANNEL, ManualSignaling, type ManualSignalingBlob, MessageTooBig, MessageType, OfflineStore, OutgoingMessageArguments, OutgoingMessageInterface, PeerConnection, type PeerInfo, type PeerState, PendingSubdoc, PermissionEntry, PublicKeyInfo, ResetConnection, SearchIndex, SearchResult, ServerInfo, type SignalingIncoming, type SignalingOutgoing, SignalingSocket, SpaceMeta, StatesArray, SubdocMessage, SubdocRegisteredEvent, Unauthorized, UploadInfo, UploadMeta, UploadQueueEntry, UploadQueueStatus, UserProfile, WebSocketStatus, WsReadyStates, YjsDataChannel, 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": "1.0.10",
3
+ "version": "1.0.12",
4
4
  "description": "abracadabra provider",
5
5
  "keywords": [
6
6
  "abracadabra",
@@ -5,6 +5,7 @@ import type {
5
5
  UploadInfo,
6
6
  PublicKeyInfo,
7
7
  PermissionEntry,
8
+ EffectivePermissionsResponse,
8
9
  HealthStatus,
9
10
  ServerInfo,
10
11
  InviteRow,
@@ -355,6 +356,25 @@ export class AbracadabraClient {
355
356
  return res.permissions;
356
357
  }
357
358
 
359
+ /** List effective permissions including inherited ones from ancestor documents. */
360
+ async listEffectivePermissions(
361
+ docId: string,
362
+ ): Promise<EffectivePermissionsResponse> {
363
+ try {
364
+ return await this.request<EffectivePermissionsResponse>(
365
+ "GET",
366
+ `/docs/${encodeURIComponent(docId)}/effective-permissions`,
367
+ );
368
+ } catch {
369
+ // Fallback for older servers that don't support this endpoint
370
+ const perms = await this.listPermissions(docId);
371
+ return {
372
+ permissions: perms.map((p) => ({ ...p, source: "direct" as const })),
373
+ default_role: "viewer",
374
+ };
375
+ }
376
+ }
377
+
358
378
  /** Grant or change a user's role on a document (requires Owner). */
359
379
  async setPermission(
360
380
  docId: string,
@@ -218,6 +218,13 @@ export class AbracadabraWS extends EventEmitter {
218
218
  }
219
219
 
220
220
  attach(provider: AbracadabraBaseProvider) {
221
+ const existing = this.configuration.providerMap.get(provider.configuration.name);
222
+ if (existing && existing !== provider) {
223
+ console.warn(
224
+ `[AbracadabraWS] attach: overwriting provider for "${provider.configuration.name}". ` +
225
+ `This may indicate a duplicate loadChild for the same document.`
226
+ );
227
+ }
221
228
  this.configuration.providerMap.set(provider.configuration.name, provider);
222
229
 
223
230
  if (this.status === WebSocketStatus.Disconnected && this.shouldConnect) {
@@ -230,12 +237,14 @@ export class AbracadabraWS extends EventEmitter {
230
237
  }
231
238
 
232
239
  detach(provider: AbracadabraBaseProvider) {
233
- if (this.configuration.providerMap.has(provider.configuration.name)) {
234
- provider.send(CloseMessage, {
235
- documentName: provider.configuration.name,
236
- });
237
- this.configuration.providerMap.delete(provider.configuration.name);
240
+ const name = provider.configuration.name;
241
+ const current = this.configuration.providerMap.get(name);
242
+ if (current === provider) {
243
+ provider.send(CloseMessage, { documentName: name });
244
+ this.configuration.providerMap.delete(name);
238
245
  }
246
+ // If current !== provider, another provider owns this slot —
247
+ // don't send CloseMessage or remove it from the map.
239
248
  }
240
249
 
241
250
  public setConfiguration(
@@ -63,22 +63,26 @@ interface BlobCacheEntry {
63
63
 
64
64
  export class FileBlobStore extends EventEmitter {
65
65
  private readonly origin: string;
66
- private readonly client: AbracadabraClient;
66
+ private readonly client: AbracadabraClient | null;
67
67
  private dbPromise: Promise<IDBDatabase | null> | null = null;
68
68
  private db: IDBDatabase | null = null;
69
69
 
70
70
  /** Tracks active object URLs so we can revoke them on destroy. */
71
71
  private readonly objectUrls = new Map<string, string>();
72
72
 
73
+ /** Keys that returned 404 from the server — avoids repeated requests. TTL: 5 min. */
74
+ private readonly _notFound = new Map<string, number>();
75
+ private static readonly NOT_FOUND_TTL = 5 * 60 * 1000;
76
+
73
77
  /** Prevents concurrent flush runs. */
74
78
  private _flushing = false;
75
79
 
76
80
  private readonly _onlineHandler: () => void;
77
81
 
78
- constructor(serverOrigin: string, client: AbracadabraClient) {
82
+ constructor(serverOrigin: string, client?: AbracadabraClient | null) {
79
83
  super();
80
84
  this.origin = serverOrigin;
81
- this.client = client;
85
+ this.client = client ?? null;
82
86
 
83
87
  this._onlineHandler = () => { this.flushQueue().catch(() => null); };
84
88
  if (typeof window !== "undefined") {
@@ -135,11 +139,23 @@ export class FileBlobStore extends EventEmitter {
135
139
  }
136
140
  }
137
141
 
138
- // Not cached — try downloading
142
+ // Not cached — try downloading from server (requires a client)
143
+ if (!this.client) return null;
144
+
145
+ // Skip if we recently got a 404 for this key
146
+ const nfTime = this._notFound.get(key);
147
+ if (nfTime && Date.now() - nfTime < FileBlobStore.NOT_FOUND_TTL) {
148
+ return null;
149
+ }
150
+
139
151
  let blob: Blob;
140
152
  try {
141
153
  blob = await this.client.getUpload(docId, uploadId);
142
- } catch {
154
+ } catch (err: unknown) {
155
+ const status = (err as any)?.status;
156
+ if (status === 404) {
157
+ this._notFound.set(key, Date.now());
158
+ }
143
159
  return null;
144
160
  }
145
161
 
@@ -174,6 +190,7 @@ export class FileBlobStore extends EventEmitter {
174
190
  if (typeof window === "undefined") return URL.createObjectURL(blob);
175
191
 
176
192
  const key = this.blobKey(docId, uploadId);
193
+ this._notFound.delete(key);
177
194
 
178
195
  // Return existing URL if already cached in-memory
179
196
  const existing = this.objectUrls.get(key);
@@ -268,7 +285,7 @@ export class FileBlobStore extends EventEmitter {
268
285
  * Entries that fail are marked with status "error" and left in the queue.
269
286
  */
270
287
  async flushQueue(): Promise<void> {
271
- if (this._flushing) return;
288
+ if (this._flushing || !this.client) return;
272
289
  this._flushing = true;
273
290
 
274
291
  try {
package/src/types.ts CHANGED
@@ -202,6 +202,20 @@ export interface PermissionEntry {
202
202
  display_name: string | null;
203
203
  }
204
204
 
205
+ export interface EffectivePermissionEntry {
206
+ user_id: string;
207
+ role: "owner" | "editor" | "viewer" | "observer";
208
+ username: string;
209
+ display_name: string | null;
210
+ source: "direct" | "inherited";
211
+ inherited_from_doc_id?: string;
212
+ }
213
+
214
+ export interface EffectivePermissionsResponse {
215
+ permissions: EffectivePermissionEntry[];
216
+ default_role: string;
217
+ }
218
+
205
219
  export interface HealthStatus {
206
220
  status: string;
207
221
  version: string;
@@ -215,6 +229,8 @@ export interface ServerInfo {
215
229
  version?: string;
216
230
  /** Entry-point document ID advertised by the server, if configured. */
217
231
  index_doc_id?: string;
232
+ /** Default role assigned to users without explicit permissions. */
233
+ default_role?: string;
218
234
  }
219
235
 
220
236
  // ── Search ───────────────────────────────────────────────────────────────────