@abraca/dabra 1.9.1 → 2.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/abracadabra-provider.cjs +12728 -9142
- package/dist/abracadabra-provider.cjs.map +1 -1
- package/dist/abracadabra-provider.esm.js +12746 -9210
- package/dist/abracadabra-provider.esm.js.map +1 -1
- package/dist/index.d.ts +1510 -118
- package/package.json +1 -1
- package/src/AbracadabraBaseProvider.ts +70 -2
- package/src/AbracadabraClient.ts +572 -66
- package/src/AbracadabraProvider.ts +22 -7
- package/src/AbracadabraWS.ts +1 -1
- package/src/ChatClient.ts +193 -113
- package/src/ContentManager.ts +80 -12
- package/src/CryptoIdentityKeystore.ts +3 -3
- package/src/DocConverters.ts +161 -6
- package/src/DocKeyManager.ts +60 -12
- package/src/DocTypes.ts +10 -0
- package/src/DocumentManager.ts +62 -85
- package/src/EncryptedChatClient.ts +173 -0
- package/src/EncryptedY.ts +2 -2
- package/src/IdentityDoc.ts +25 -0
- package/src/MnemonicKeyDerivation.ts +4 -4
- package/src/NotificationsClient.ts +120 -98
- package/src/OutgoingMessages/SubdocMessage.ts +2 -2
- package/src/RpcClient.ts +659 -0
- package/src/TreeManager.ts +61 -17
- package/src/TreeTimestamps.ts +28 -25
- package/src/index.ts +71 -1
- package/src/messageRecord.ts +121 -0
- package/src/types.ts +235 -16
- package/src/webrtc/AbracadabraWebRTC.ts +2 -2
- package/src/webrtc/DataChannelRouter.ts +2 -2
- package/src/webrtc/E2EEChannel.ts +3 -3
- package/src/webrtc/FileTransferChannel.ts +9 -2
package/src/types.ts
CHANGED
|
@@ -183,20 +183,68 @@ export interface UserProfile {
|
|
|
183
183
|
role: string;
|
|
184
184
|
/** Account-level Ed25519 public key (base64url). Canonical user identity. */
|
|
185
185
|
publicKey: string | null;
|
|
186
|
+
/**
|
|
187
|
+
* Whether the user has a password set. `false` for key-based soft
|
|
188
|
+
* identities until they opt in via {@link AbracadabraClient.setPassword}.
|
|
189
|
+
* Drives the UI's "Set password" vs "Change password" branching.
|
|
190
|
+
*/
|
|
191
|
+
hasPassword?: boolean;
|
|
186
192
|
}
|
|
187
193
|
|
|
188
194
|
export interface DocumentMeta {
|
|
189
195
|
id: string;
|
|
190
196
|
parent_id: string | null;
|
|
197
|
+
/**
|
|
198
|
+
* Renderer hint — `"kanban"`, `"checklist"`, `"outline"`, `"graph"`,
|
|
199
|
+
* `"sheet"`, `"mindmap"`, `"doc"`, etc. Drives the dashboard's choice of
|
|
200
|
+
* Vue component for this doc. Orthogonal to {@link kind}.
|
|
201
|
+
*/
|
|
191
202
|
doc_type?: string | null;
|
|
192
203
|
label?: string | null;
|
|
193
204
|
description?: string | null;
|
|
194
205
|
public_access?: string | null;
|
|
195
206
|
owner_id?: string | null;
|
|
196
|
-
|
|
207
|
+
/**
|
|
208
|
+
* Structural role hint — `"server"` for the reserved server-root doc,
|
|
209
|
+
* `"space"` for top-level workspace containers, `null`/absent otherwise.
|
|
210
|
+
* Distinct from {@link doc_type}: a Space (`kind: "space"`) can contain
|
|
211
|
+
* many Kanban boards (each `doc_type: "kanban"`).
|
|
212
|
+
*/
|
|
213
|
+
kind?: string | null;
|
|
197
214
|
updated_at?: number | null;
|
|
198
215
|
}
|
|
199
216
|
|
|
217
|
+
/**
|
|
218
|
+
* Well-known structural roles for the {@link DocumentMeta.kind} field. These
|
|
219
|
+
* are conventions only — the server treats `kind` as an opaque string. Use
|
|
220
|
+
* these constants instead of string literals to avoid typos.
|
|
221
|
+
*/
|
|
222
|
+
export const Kind = {
|
|
223
|
+
/** The reserved server root document. There is exactly one per server. */
|
|
224
|
+
Server: "server",
|
|
225
|
+
/** A top-level workspace container — what the dashboard calls a "Space". */
|
|
226
|
+
Space: "space",
|
|
227
|
+
/** A regular content page rendered by the editor. */
|
|
228
|
+
Page: "page",
|
|
229
|
+
/** A group chat container under a Space. */
|
|
230
|
+
Channel: "channel",
|
|
231
|
+
/** A direct-message container at the server root with two-member permissions. */
|
|
232
|
+
Dm: "dm",
|
|
233
|
+
/** A child of channel/dm holding the messages Y.Array. */
|
|
234
|
+
ChannelPeriod: "channel-period",
|
|
235
|
+
/** A per-user inbox doc holding inbox entries; child of server root. */
|
|
236
|
+
Inbox: "inbox",
|
|
237
|
+
} as const;
|
|
238
|
+
export type Kind = typeof Kind[keyof typeof Kind];
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Hardcoded UUID of the reserved server root document. Every doc in the tree
|
|
242
|
+
* chains up to this row, so a permission grant on this doc cascades into
|
|
243
|
+
* every descendant. Mirrors the constant in the Rust crate
|
|
244
|
+
* (`crate::types::SERVER_ROOT_ID_STR`).
|
|
245
|
+
*/
|
|
246
|
+
export const SERVER_ROOT_ID = "00000000-0000-0000-0000-000000000000";
|
|
247
|
+
|
|
200
248
|
export interface UploadMeta {
|
|
201
249
|
id: string;
|
|
202
250
|
doc_id: string;
|
|
@@ -250,10 +298,30 @@ export interface SnapshotMeta {
|
|
|
250
298
|
label?: string | null;
|
|
251
299
|
created_by?: string | null;
|
|
252
300
|
created_at: number;
|
|
301
|
+
/** Number of uploads referenced by this snapshot's CRDT state.
|
|
302
|
+
* Always 0 on servers older than the `snapshot_files` migration, or for
|
|
303
|
+
* snapshots created before that migration ran (until the operator runs
|
|
304
|
+
* `POST /admin/snapshots/backfill-refs`). */
|
|
305
|
+
file_count?: number;
|
|
306
|
+
/** Signed byte delta vs. the previous-version snapshot for this doc.
|
|
307
|
+
* `null` for the first snapshot of a doc (or when the previous version
|
|
308
|
+
* has been pruned). Lets the UI render `+12 KB` / `−3 KB`. */
|
|
309
|
+
delta_bytes?: number | null;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
export interface SnapshotFileEntry {
|
|
313
|
+
id: string;
|
|
314
|
+
doc_id: string;
|
|
315
|
+
filename: string;
|
|
316
|
+
mime_type?: string | null;
|
|
317
|
+
size?: number | null;
|
|
253
318
|
}
|
|
254
319
|
|
|
255
320
|
export interface SnapshotData extends SnapshotMeta {
|
|
256
321
|
data: string;
|
|
322
|
+
/** Populated only when fetched with `?include=files`. Each entry is an
|
|
323
|
+
* upload referenced by this snapshot's CRDT state. */
|
|
324
|
+
files?: SnapshotFileEntry[];
|
|
257
325
|
}
|
|
258
326
|
|
|
259
327
|
export interface SnapshotCreateResult {
|
|
@@ -285,10 +353,23 @@ export interface ServerInfo {
|
|
|
285
353
|
version?: string;
|
|
286
354
|
/** Hocuspocus wire protocol version (currently 2). */
|
|
287
355
|
protocol_version?: number;
|
|
288
|
-
/**
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
356
|
+
/**
|
|
357
|
+
* The reserved server root document id. Every doc in the tree chains up
|
|
358
|
+
* to this row; granting Admin here cascades to every doc. Hardcoded by
|
|
359
|
+
* the server — clients can also use {@link SERVER_ROOT_ID} directly.
|
|
360
|
+
*/
|
|
361
|
+
root_doc_id?: string;
|
|
362
|
+
/**
|
|
363
|
+
* Server-wide access policy. Drives client-side decisions like "show the
|
|
364
|
+
* sign-up CTA?" (anonymous mode) or "warn before publishing?" (anonymous
|
|
365
|
+
* grants).
|
|
366
|
+
*/
|
|
367
|
+
access?: {
|
|
368
|
+
/** `"none"` rejects unauthenticated requests; `"observer"` allows public read. */
|
|
369
|
+
anonymous?: "none" | "observer";
|
|
370
|
+
/** Server-wide floor for authed users with no explicit grant. */
|
|
371
|
+
authenticated?: "none" | "observer" | "viewer" | "editor";
|
|
372
|
+
};
|
|
292
373
|
/** Enabled auth methods (e.g. ["crypto", "jwt"]). */
|
|
293
374
|
auth_methods?: string[];
|
|
294
375
|
/** Whether open registration is enabled. */
|
|
@@ -297,6 +378,13 @@ export interface ServerInfo {
|
|
|
297
378
|
invite_only?: boolean;
|
|
298
379
|
/** Server encryption configuration. */
|
|
299
380
|
encryption?: { default_mode?: string; minimum_mode?: string };
|
|
381
|
+
/**
|
|
382
|
+
* Ed25519 public key (base64url, no padding) the server uses to sign every
|
|
383
|
+
* accepted `messages:*` record (`server_sig`). Clients can verify message
|
|
384
|
+
* placement / ordering by checking each record's `server_sig` against this
|
|
385
|
+
* key over the canonical `{ msg_id, period_id, ts, client_sig }` payload.
|
|
386
|
+
*/
|
|
387
|
+
messages_signer_pubkey?: string;
|
|
300
388
|
}
|
|
301
389
|
|
|
302
390
|
// ── Search ───────────────────────────────────────────────────────────────────
|
|
@@ -307,21 +395,152 @@ export interface SearchResult {
|
|
|
307
395
|
score: number;
|
|
308
396
|
}
|
|
309
397
|
|
|
310
|
-
|
|
398
|
+
/**
|
|
399
|
+
* A single hit returned by the server's full-text search endpoint
|
|
400
|
+
* (`GET /docs/search`). Distinct from {@link SearchResult}, which is the
|
|
401
|
+
* client-side trigram index in `SearchIndex.ts`.
|
|
402
|
+
*
|
|
403
|
+
* `snippet` is server-rendered HTML with `<mark>` wrappers around the
|
|
404
|
+
* matched tokens — safe to inject after standard sanitization. `rank` is
|
|
405
|
+
* a backend-specific score (SQLite bm25 vs Postgres ts_rank); the only
|
|
406
|
+
* guarantee is that hits arrive in best-first order.
|
|
407
|
+
*/
|
|
408
|
+
export interface DocSearchHit {
|
|
409
|
+
doc_id: string;
|
|
410
|
+
parent_id: string | null;
|
|
411
|
+
label: string | null;
|
|
412
|
+
kind: string | null;
|
|
413
|
+
/** HTML-safe snippet with `<mark>` markers around the matched tokens. */
|
|
414
|
+
snippet: string;
|
|
415
|
+
rank: number;
|
|
416
|
+
}
|
|
311
417
|
|
|
312
|
-
|
|
418
|
+
// ── Audit log (admin) ────────────────────────────────────────────────────────
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* A single row from `GET /admin/audit`. Mirrors the server's audit log
|
|
422
|
+
* row format (see `audit::AuditLogRow`). `metadata` is the parsed JSON
|
|
423
|
+
* object the server stored, or `null` if the row had no metadata. Each
|
|
424
|
+
* row carries an internal `prev_hash` / `row_hash` chain that admins
|
|
425
|
+
* verify with {@link AuditVerifyResult}.
|
|
426
|
+
*/
|
|
427
|
+
export interface AuditLogEntry {
|
|
313
428
|
id: string;
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
429
|
+
ts: number;
|
|
430
|
+
event_type: string;
|
|
431
|
+
actor_user_id: string | null;
|
|
432
|
+
actor_ip: string | null;
|
|
433
|
+
request_id: string | null;
|
|
434
|
+
target_type: string | null;
|
|
435
|
+
target_id: string | null;
|
|
436
|
+
metadata: Record<string, unknown> | null;
|
|
437
|
+
outcome: string;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/** Filters for `GET /admin/audit`. All optional and AND-combined. */
|
|
441
|
+
export interface AuditQueryOpts {
|
|
442
|
+
event_type?: string;
|
|
443
|
+
actor_user_id?: string;
|
|
444
|
+
target_type?: string;
|
|
445
|
+
target_id?: string;
|
|
446
|
+
since_ts?: number;
|
|
447
|
+
until_ts?: number;
|
|
448
|
+
limit?: number;
|
|
449
|
+
offset?: number;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/** Response of `GET /admin/audit/verify`. `status: "broken"` carries `break`. */
|
|
453
|
+
export interface AuditVerifyResult {
|
|
454
|
+
status: "ok" | "broken";
|
|
455
|
+
message?: string;
|
|
456
|
+
break?: { rows_checked: number; row_id: string; reason: string };
|
|
323
457
|
}
|
|
324
458
|
|
|
459
|
+
// ── Admin: runtime config store ───────────────────────────────────────────────
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Where the current value of a config field came from.
|
|
463
|
+
*
|
|
464
|
+
* * `default` — compiled-in default; nobody touched it.
|
|
465
|
+
* * `env` — set at startup by an `ABRA_*` env var.
|
|
466
|
+
* * `global_override` — admin runtime override via `/admin/config`.
|
|
467
|
+
* * `route_override` — per-route override (Phase 3).
|
|
468
|
+
*/
|
|
469
|
+
export type AdminConfigOriginKind =
|
|
470
|
+
| "default"
|
|
471
|
+
| "env"
|
|
472
|
+
| "global_override"
|
|
473
|
+
| "route_override";
|
|
474
|
+
|
|
475
|
+
/** One entry returned by `GET /admin/config` and `GET /admin/config/fields/:path`. */
|
|
476
|
+
export interface AdminConfigField {
|
|
477
|
+
path: string;
|
|
478
|
+
section: string;
|
|
479
|
+
description: string;
|
|
480
|
+
env_var: string;
|
|
481
|
+
runtime_mutable: boolean;
|
|
482
|
+
value: unknown;
|
|
483
|
+
origin_kind: AdminConfigOriginKind;
|
|
484
|
+
/** Provenance — present only when origin_kind is a `*_override`. */
|
|
485
|
+
set_at?: number;
|
|
486
|
+
set_by?: string;
|
|
487
|
+
route?: string;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* One row from `GET /admin/config/env-snapshot.items[]`. Reports whether
|
|
492
|
+
* each registry-known `ABRA_*` env var was set in the running process at
|
|
493
|
+
* boot, with secrets reported as `value: null` and `redacted: true`.
|
|
494
|
+
*/
|
|
495
|
+
export interface EnvSnapshotItem {
|
|
496
|
+
env_var: string;
|
|
497
|
+
path: string;
|
|
498
|
+
section: string;
|
|
499
|
+
present: boolean;
|
|
500
|
+
/** Verbatim env value, or `null` for redacted (secrets) or unset. */
|
|
501
|
+
value: string | null;
|
|
502
|
+
redacted: boolean;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* One row from `GET /admin/config/env-snapshot.extensions[]`. Extension
|
|
507
|
+
* overrides (`ABRA_EXT_<name>_<field>`) sit outside the registry and are
|
|
508
|
+
* reported by name with values inline.
|
|
509
|
+
*/
|
|
510
|
+
export interface EnvSnapshotExtension {
|
|
511
|
+
env_var: string;
|
|
512
|
+
value: string;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* `GET /admin/config/env-snapshot` response.
|
|
517
|
+
*
|
|
518
|
+
* `unknown` lists `ABRA_*` env vars present in the process that don't map
|
|
519
|
+
* to any registered field — i.e. typos. Operators that see their tweak
|
|
520
|
+
* land here instead of in `items` know they misspelled the var.
|
|
521
|
+
*/
|
|
522
|
+
export interface EnvSnapshotResponse {
|
|
523
|
+
items: EnvSnapshotItem[];
|
|
524
|
+
extensions: EnvSnapshotExtension[];
|
|
525
|
+
unknown: string[];
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// ── Readiness ────────────────────────────────────────────────────────────────
|
|
529
|
+
|
|
530
|
+
/** Response of `GET /readyz`. HTTP 200 when ready, 503 when not. */
|
|
531
|
+
export interface ReadyzStatus {
|
|
532
|
+
status: "ready" | "unready";
|
|
533
|
+
version: string;
|
|
534
|
+
checks: { database: "ok" | "error" };
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// ── Spaces ───────────────────────────────────────────────────────────────────
|
|
538
|
+
//
|
|
539
|
+
// Spaces are documents whose `parent_id` is the server root and whose `kind`
|
|
540
|
+
// is `"space"`. Listing them is `client.listSpaces()`; they come back as
|
|
541
|
+
// regular `DocumentMeta` objects. Visibility is encoded in `public_access`:
|
|
542
|
+
// `null` (or `"none"`) = private, `"observer"` = public read-only.
|
|
543
|
+
|
|
325
544
|
// ── Invites ──────────────────────────────────────────────────────────────────
|
|
326
545
|
|
|
327
546
|
export interface InviteRow {
|
|
@@ -527,7 +527,7 @@ export class AbracadabraWebRTC extends EventEmitter {
|
|
|
527
527
|
pendingKeyExchangeChannel = channel;
|
|
528
528
|
return;
|
|
529
529
|
}
|
|
530
|
-
channel.send(e2ee.getKeyExchangeMessage());
|
|
530
|
+
channel.send(e2ee.getKeyExchangeMessage() as unknown as ArrayBuffer);
|
|
531
531
|
});
|
|
532
532
|
|
|
533
533
|
this.resolveE2ee().then(async (identity) => {
|
|
@@ -541,7 +541,7 @@ export class AbracadabraWebRTC extends EventEmitter {
|
|
|
541
541
|
|
|
542
542
|
// Drain buffered key-exchange channel open
|
|
543
543
|
if (pendingKeyExchangeChannel) {
|
|
544
|
-
pendingKeyExchangeChannel.send(e2ee.getKeyExchangeMessage());
|
|
544
|
+
pendingKeyExchangeChannel.send(e2ee.getKeyExchangeMessage() as unknown as ArrayBuffer);
|
|
545
545
|
}
|
|
546
546
|
// Drain buffered messages
|
|
547
547
|
for (const msg of pendingMessages) {
|
|
@@ -100,9 +100,9 @@ export class DataChannelRouter extends EventEmitter {
|
|
|
100
100
|
|
|
101
101
|
if (this.encryptor?.isEstablished && !this.plaintextChannels.has(name)) {
|
|
102
102
|
const encrypted = await this.encryptor.encrypt(data);
|
|
103
|
-
channel.send(encrypted);
|
|
103
|
+
channel.send(encrypted as unknown as ArrayBuffer);
|
|
104
104
|
} else {
|
|
105
|
-
channel.send(data);
|
|
105
|
+
channel.send(data as unknown as ArrayBuffer);
|
|
106
106
|
}
|
|
107
107
|
return true;
|
|
108
108
|
}
|
|
@@ -98,7 +98,7 @@ export class E2EEChannel extends EventEmitter {
|
|
|
98
98
|
|
|
99
99
|
this.sessionKey = await crypto.subtle.importKey(
|
|
100
100
|
"raw",
|
|
101
|
-
keyBytes,
|
|
101
|
+
keyBytes as BufferSource,
|
|
102
102
|
{ name: "AES-GCM" },
|
|
103
103
|
false,
|
|
104
104
|
["encrypt", "decrypt"],
|
|
@@ -130,9 +130,9 @@ export class E2EEChannel extends EventEmitter {
|
|
|
130
130
|
const nonce = crypto.getRandomValues(new Uint8Array(NONCE_BYTES));
|
|
131
131
|
const ciphertext = new Uint8Array(
|
|
132
132
|
await crypto.subtle.encrypt(
|
|
133
|
-
{ name: "AES-GCM", iv: nonce },
|
|
133
|
+
{ name: "AES-GCM", iv: nonce as BufferSource },
|
|
134
134
|
this.sessionKey,
|
|
135
|
-
plaintext,
|
|
135
|
+
plaintext as BufferSource,
|
|
136
136
|
),
|
|
137
137
|
);
|
|
138
138
|
|
|
@@ -45,6 +45,13 @@ export class FileTransferHandle extends EventEmitter {
|
|
|
45
45
|
_setStatus(s: FileTransferStatus): void {
|
|
46
46
|
this.status = s;
|
|
47
47
|
}
|
|
48
|
+
|
|
49
|
+
/** @internal — public alias for the protected emit so the parent
|
|
50
|
+
* `FileTransferChannel` (a different class instance) can dispatch events
|
|
51
|
+
* on this handle. Protected access is by-class, not by-hierarchy. */
|
|
52
|
+
_emit(event: string, ...args: unknown[]): void {
|
|
53
|
+
this.emit(event, ...args);
|
|
54
|
+
}
|
|
48
55
|
}
|
|
49
56
|
|
|
50
57
|
interface ReceiveState {
|
|
@@ -102,7 +109,7 @@ export class FileTransferChannel extends EventEmitter {
|
|
|
102
109
|
const channel = this.router.getChannel(CHANNEL_NAMES.FILE_TRANSFER);
|
|
103
110
|
if (!channel || channel.readyState !== "open") {
|
|
104
111
|
handle._setStatus("error");
|
|
105
|
-
handle.
|
|
112
|
+
handle._emit("error", new Error("File transfer channel not open"));
|
|
106
113
|
return handle;
|
|
107
114
|
}
|
|
108
115
|
|
|
@@ -174,7 +181,7 @@ export class FileTransferChannel extends EventEmitter {
|
|
|
174
181
|
channel.send(completeMsg);
|
|
175
182
|
|
|
176
183
|
handle._setStatus("complete");
|
|
177
|
-
handle.
|
|
184
|
+
handle._emit("complete");
|
|
178
185
|
|
|
179
186
|
return handle;
|
|
180
187
|
}
|