@brftech/filex-app-ui 0.53.0 → 0.55.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/dist/index.d.ts CHANGED
@@ -6,6 +6,123 @@ export declare interface BridgeError {
6
6
  message?: string;
7
7
  }
8
8
 
9
+ /** `coedit.append` params: the host seals it and the relay places it. */
10
+ export declare interface CoEditAppendParams {
11
+ kind: CoEditKind;
12
+ body: unknown;
13
+ }
14
+
15
+ export declare interface CoEditAppendResult {
16
+ /** Where it landed; it comes back as a `coedit.entry` too. */
17
+ seq: number;
18
+ }
19
+
20
+ export declare interface CoEditBlobGetParams {
21
+ name: string;
22
+ }
23
+
24
+ export declare interface CoEditBlobGetResult {
25
+ name: string;
26
+ bytes: ArrayBuffer;
27
+ }
28
+
29
+ /** `coedit.blob.put` / `coedit.blob.get`: the session's sealed blobs (the
30
+ * base document, images added while editing). A name, not a path. */
31
+ export declare interface CoEditBlobPutParams {
32
+ name: string;
33
+ data: ArrayBuffer;
34
+ }
35
+
36
+ /** `coedit.cursor` event data. */
37
+ export declare interface CoEditCursor {
38
+ client: string;
39
+ cursor: unknown;
40
+ }
41
+
42
+ /** `coedit.cursor` params: passed to the other members, not kept. */
43
+ export declare interface CoEditCursorParams {
44
+ cursor: unknown;
45
+ }
46
+
47
+ /** One entry of the session's log, opened and checked by filex. */
48
+ export declare type CoEditEntry = (CoEditEntryBase & {
49
+ kind: CoEditKind;
50
+ body: unknown;
51
+ }) | (CoEditEntryBase & {
52
+ kind: 'join';
53
+ member: CoEditMember;
54
+ }) | (CoEditEntryBase & {
55
+ kind: 'leave';
56
+ }) | (CoEditEntryBase & {
57
+ kind: 'saved';
58
+ through: number;
59
+ });
60
+
61
+ declare interface CoEditEntryBase {
62
+ seq: number;
63
+ /** The relay's time, Unix ms - the same for every member. */
64
+ at: number;
65
+ client: string;
66
+ }
67
+
68
+ /**
69
+ * What `coedit.join` answers: who the app is in the session and how far
70
+ * the log has gone. The entries come as `coedit.entry` events once the app
71
+ * subscribes (`coedit.subscribe` from 0 for the whole log). `unavailable`
72
+ * when filex offers no editing together here (filex 0.55, a vault, a file
73
+ * that cannot be shared): the app edits alone.
74
+ */
75
+ export declare interface CoEditHello {
76
+ session: string;
77
+ me: CoEditMember;
78
+ /** The last entry's seq (0: an empty log). */
79
+ head: number;
80
+ /** The last `changes` entry's seq. */
81
+ changesHead: number;
82
+ /** The last entry the file on the storage holds. */
83
+ savedThrough: number;
84
+ }
85
+
86
+ /** `coedit.join` params: join (or start) the session of an opened file. */
87
+ export declare interface CoEditJoinParams {
88
+ index?: number;
89
+ }
90
+
91
+ /** What a member appends: the editor's changes, a lock request, a release. */
92
+ export declare type CoEditKind = 'changes' | 'lock' | 'release';
93
+
94
+ /** `coedit.lease`: the changes lease (one writer at a time, one that has
95
+ * seen every change). */
96
+ export declare interface CoEditLeaseParams {
97
+ op: 'acquire' | 'release';
98
+ /** The seq of the last `changes` entry the app applied. */
99
+ changesSeen: number;
100
+ }
101
+
102
+ export declare interface CoEditLeaseResult {
103
+ granted: boolean;
104
+ }
105
+
106
+ /** A member of the session. */
107
+ export declare interface CoEditMember {
108
+ /** The relay's id for the connection. */
109
+ client: string;
110
+ /** An opaque id of the person, the same for each of their connections in
111
+ * this session; never an account id. */
112
+ user: string;
113
+ /** The person's display name. */
114
+ name: string;
115
+ /** The editor's per-session user index: given once, never twice. */
116
+ indexUser: number;
117
+ /** May append changes (an editor role and the right to change the file). */
118
+ canEdit: boolean;
119
+ }
120
+
121
+ /** `coedit.subscribe` params: entries after `from` arrive as `coedit.entry`. */
122
+ export declare interface CoEditSubscribeParams {
123
+ from: number;
124
+ }
125
+
9
126
  export declare interface ConfirmParams {
10
127
  title?: string;
11
128
  text: string;
@@ -92,6 +209,19 @@ export declare interface FileInfo {
92
209
  /** It cannot be saved over: read-only storage, view-only opening, or the
93
210
  * app holds no `files:write`. */
94
211
  readOnly: boolean;
212
+ /**
213
+ * 0.55.0: the file is end-to-end encrypted - `folder` in an encrypted
214
+ * folder, `vault` in a vault (where only one person edits at a time),
215
+ * `file` for a single encrypted file (`.fxe`). As the server says it;
216
+ * ABSENT for a file that is not encrypted.
217
+ *
218
+ * ⚠ filex 0.55 decrypts nothing for an app: the server holds such a file
219
+ * only as ciphertext and has no key, so `file.read` is refused
220
+ * (`failed` with `encrypted`), a save is refused, and `readOnly` is true.
221
+ * The explorer offers no app an encrypted file in the first place; the
222
+ * field is information, not a promise of plaintext.
223
+ */
224
+ encrypted?: 'folder' | 'vault' | 'file';
95
225
  }
96
226
 
97
227
  export declare interface FilexApp {
@@ -120,6 +250,32 @@ export declare interface FilexApp {
120
250
  * filex asks the person first. `cancelled` when they say no.
121
251
  */
122
252
  download(name: string, data: SaveData, mime?: string): Promise<DownloadResult>;
253
+ /**
254
+ * Print a PDF (filex 0.55; the app needs `ui.print` in its manifest): a
255
+ * sandboxed frame may not open the print dialog, so filex prints it from
256
+ * its own page. filex asks the person every time, and the dialog opens on
257
+ * their click on filex's Allow; at most LIMITS.maxPrintBytes. filex 0.54
258
+ * and older answer `unknown_method`, and a host whose print page may not
259
+ * be shown `unavailable`: hand the PDF over with `download` then.
260
+ */
261
+ print(name: string, pdf: Blob | ArrayBuffer | ArrayBufferView | ReadableStream<Uint8Array>): Promise<PrintResult>;
262
+ /**
263
+ * Editing together (filex 0.55: defined, not offered - every call answers
264
+ * `unavailable`, and the app edits alone). filex seals what the app
265
+ * appends, orders it through its relay and hands every member the same
266
+ * entries in the same order (`coedit.entry` events after `subscribe`);
267
+ * the app never holds a key.
268
+ */
269
+ coedit: {
270
+ join(index?: number): Promise<CoEditHello>;
271
+ subscribe(from: number): Promise<void>;
272
+ append(kind: CoEditKind, body: unknown): Promise<CoEditAppendResult>;
273
+ lease(op: 'acquire' | 'release', changesSeen: number): Promise<boolean>;
274
+ cursor(cursor: unknown): void;
275
+ putBlob(name: string, data: Blob | ArrayBuffer | ArrayBufferView): Promise<void>;
276
+ getBlob(name: string): Promise<ArrayBuffer>;
277
+ leave(): Promise<void>;
278
+ };
123
279
  /** Call the app's own module (`ui_call` export) with the opened files. */
124
280
  call<T = unknown>(method: string, params?: unknown): Promise<T>;
125
281
  /** Queue one of the app's actions on the opened files; answers the op. */
@@ -176,7 +332,17 @@ export declare type HostEvent =
176
332
  /** The person closed the frame's surroundings; last chance to say so. */
177
333
  | 'close.request'
178
334
  /** An administrator installed a new version of this app: reload to use it. */
179
- | 'app.updated';
335
+ | 'app.updated'
336
+ /** Editing together: the next entry of the session's log, opened. `data: CoEditEntry`. */
337
+ | 'coedit.entry'
338
+ /** Editing together: another member's cursor (not kept). `data: CoEditCursor`. */
339
+ | 'coedit.cursor'
340
+ /**
341
+ * Editing together: the host stopped handing entries over (the app fell
342
+ * behind, or the connection dropped). `data: {from}` - subscribe again
343
+ * from the last entry the app holds.
344
+ */
345
+ | 'coedit.dropped';
180
346
 
181
347
  /** What the host may ask the app (and wait for). */
182
348
  export declare type HostRequest =
@@ -244,12 +410,28 @@ export declare const LIMITS: {
244
410
  readonly connectTimeoutMs: 10000;
245
411
  /** The largest file `ui.download` hands the person (streamed or not). */
246
412
  readonly maxDownloadBytes: number;
413
+ /**
414
+ * The largest PDF `ui.print` prints (filex 0.55). Lower than a download:
415
+ * a download can stream to disk, a print is held whole in the page (one
416
+ * Blob) until the print dialog has read it, and a document worth printing
417
+ * is far smaller.
418
+ */
419
+ readonly maxPrintBytes: number;
247
420
  };
248
421
 
249
422
  /** What the app may ask the host. Anything else is `unknown_method`. */
250
423
  export declare type Method = 'session.get' | 'file.read' | 'file.save' | 'file.saveAs' | 'ui.dirty' | 'ui.title' | 'ui.toast' | 'ui.confirm' | 'ui.close' | 'clipboard.write' | 'ui.download' | 'engine.call' | 'job.submit' | 'state.get' | 'state.set'
251
424
  /** 0.52.0: what the app's license says (a paid app from a store), `LicenseInfo`. */
252
- | 'license.get';
425
+ | 'license.get'
426
+ /** 0.55.0: filex prints a PDF the app hands it (`PrintParams`, grant `ui:print`). */
427
+ | 'ui.print'
428
+ /**
429
+ * 0.55.0, editing together (`coedit.*`, task #189): the session's log the
430
+ * host seals, orders through filex's relay and opens again. Defined so an
431
+ * app can be written against them; filex 0.55 offers no relay yet, so each
432
+ * answers `unavailable` - edit alone then (see `CoEditHello`).
433
+ */
434
+ | 'coedit.join' | 'coedit.subscribe' | 'coedit.append' | 'coedit.lease' | 'coedit.cursor' | 'coedit.blob.put' | 'coedit.blob.get' | 'coedit.leave';
253
435
 
254
436
  export declare const METHODS: readonly Method[];
255
437
 
@@ -270,6 +452,31 @@ export declare interface PortMessage {
270
452
  v: number;
271
453
  }
272
454
 
455
+ /**
456
+ * `ui.print` (filex 0.55.0): a PDF for the browser's print dialog. A
457
+ * sandboxed frame may not open that dialog (measured: Chromium ignores
458
+ * `print()` there and says so, Firefox ignores it), so filex prints the PDF
459
+ * from a page of its own, with the app's `ui:print` grant, never over
460
+ * LIMITS.maxPrintBytes. filex asks every time, above the frame, and the
461
+ * dialog opens only on the person's click on filex's Allow. The print dialog
462
+ * can save the PDF too, so `ui:print` is the same kind of grant as
463
+ * `ui:download`. filex 0.54 and older answer `unknown_method`.
464
+ */
465
+ export declare interface PrintParams {
466
+ /** The document's name, for the question filex may ask - a name, not a path. */
467
+ name: string;
468
+ /** The PDF: a transferred ArrayBuffer or ReadableStream<Uint8Array>. */
469
+ data: ReadableStream<Uint8Array> | ArrayBuffer;
470
+ /** `application/pdf` when given; nothing else is printed. */
471
+ mime?: string;
472
+ }
473
+
474
+ export declare interface PrintResult {
475
+ /** The browser's print dialog was opened with the PDF. */
476
+ printed: true;
477
+ size: number;
478
+ }
479
+
273
480
  /** `file.read` params. */
274
481
  export declare interface ReadParams {
275
482
  index?: number;
@@ -326,6 +533,12 @@ export declare interface SaveParams {
326
533
  /** A transferred ReadableStream<Uint8Array>, an ArrayBuffer or a string. */
327
534
  data: ReadableStream<Uint8Array> | ArrayBuffer | string;
328
535
  mime?: string;
536
+ /**
537
+ * 0.55.0, editing together: the last log entry (`CoEditEntry.seq`) the
538
+ * saved document holds, so the session knows what is saved. Ignored
539
+ * outside a session.
540
+ */
541
+ through?: number;
329
542
  }
330
543
 
331
544
  export declare interface SaveResult {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brftech/filex-app-ui",
3
- "version": "0.53.0",
3
+ "version": "0.55.0",
4
4
  "description": "filex app interface SDK: the bridge between an app's own sandboxed interface and filex (open, save, dirty, call the app's module)",
5
5
  "type": "module",
6
6
  "main": "./dist/filex-app-ui.js",
package/src/index.ts CHANGED
@@ -36,6 +36,12 @@ import {
36
36
  type ToastParams,
37
37
  type DownloadResult,
38
38
  type LicenseInfo,
39
+ type PrintResult,
40
+ type CoEditHello,
41
+ type CoEditKind,
42
+ type CoEditAppendResult,
43
+ type CoEditLeaseResult,
44
+ type CoEditBlobGetResult,
39
45
  } from './protocol';
40
46
 
41
47
  export * from './protocol';
@@ -97,6 +103,32 @@ export interface FilexApp {
97
103
  * filex asks the person first. `cancelled` when they say no.
98
104
  */
99
105
  download(name: string, data: SaveData, mime?: string): Promise<DownloadResult>;
106
+ /**
107
+ * Print a PDF (filex 0.55; the app needs `ui.print` in its manifest): a
108
+ * sandboxed frame may not open the print dialog, so filex prints it from
109
+ * its own page. filex asks the person every time, and the dialog opens on
110
+ * their click on filex's Allow; at most LIMITS.maxPrintBytes. filex 0.54
111
+ * and older answer `unknown_method`, and a host whose print page may not
112
+ * be shown `unavailable`: hand the PDF over with `download` then.
113
+ */
114
+ print(name: string, pdf: Blob | ArrayBuffer | ArrayBufferView | ReadableStream<Uint8Array>): Promise<PrintResult>;
115
+ /**
116
+ * Editing together (filex 0.55: defined, not offered - every call answers
117
+ * `unavailable`, and the app edits alone). filex seals what the app
118
+ * appends, orders it through its relay and hands every member the same
119
+ * entries in the same order (`coedit.entry` events after `subscribe`);
120
+ * the app never holds a key.
121
+ */
122
+ coedit: {
123
+ join(index?: number): Promise<CoEditHello>;
124
+ subscribe(from: number): Promise<void>;
125
+ append(kind: CoEditKind, body: unknown): Promise<CoEditAppendResult>;
126
+ lease(op: 'acquire' | 'release', changesSeen: number): Promise<boolean>;
127
+ cursor(cursor: unknown): void;
128
+ putBlob(name: string, data: Blob | ArrayBuffer | ArrayBufferView): Promise<void>;
129
+ getBlob(name: string): Promise<ArrayBuffer>;
130
+ leave(): Promise<void>;
131
+ };
100
132
  /** Call the app's own module (`ui_call` export) with the opened files. */
101
133
  call<T = unknown>(method: string, params?: unknown): Promise<T>;
102
134
  /** Queue one of the app's actions on the opened files; answers the op. */
@@ -311,6 +343,40 @@ function bridge(port: MessagePort, opts: ConnectOptions): FilexApp {
311
343
  const p = await payload(data);
312
344
  return request<DownloadResult>('ui.download', { name, data: p.data, mime }, p.transfer);
313
345
  },
346
+ async print(name, pdf) {
347
+ const p = await payload(pdf);
348
+ return request<PrintResult>('ui.print', { name, data: p.data, mime: 'application/pdf' }, p.transfer);
349
+ },
350
+ coedit: {
351
+ join(index = 0) {
352
+ return request<CoEditHello>('coedit.join', { index });
353
+ },
354
+ subscribe(from: number) {
355
+ return request<void>('coedit.subscribe', { from });
356
+ },
357
+ append(kind: CoEditKind, body: unknown) {
358
+ return request<CoEditAppendResult>('coedit.append', { kind, body });
359
+ },
360
+ async lease(op: 'acquire' | 'release', changesSeen: number) {
361
+ const r = await request<CoEditLeaseResult>('coedit.lease', { op, changesSeen });
362
+ return !!r?.granted;
363
+ },
364
+ cursor(cursor: unknown) {
365
+ notify('coedit.cursor', { cursor });
366
+ },
367
+ async putBlob(name: string, data: Blob | ArrayBuffer | ArrayBufferView) {
368
+ const p = await payload(data);
369
+ if (!(p.data instanceof ArrayBuffer)) throw new FilexError({ code: 'invalid', message: 'a blob is bytes' });
370
+ await request<void>('coedit.blob.put', { name, data: p.data }, p.transfer);
371
+ },
372
+ async getBlob(name: string) {
373
+ const r = await request<CoEditBlobGetResult>('coedit.blob.get', { name });
374
+ return r.bytes;
375
+ },
376
+ async leave() {
377
+ await request<void>('coedit.leave');
378
+ },
379
+ },
314
380
  dirty(on) {
315
381
  notify('ui.dirty', { dirty: !!on });
316
382
  },
package/src/protocol.ts CHANGED
@@ -52,7 +52,23 @@ export type Method =
52
52
  | 'state.get'
53
53
  | 'state.set'
54
54
  /** 0.52.0: what the app's license says (a paid app from a store), `LicenseInfo`. */
55
- | 'license.get';
55
+ | 'license.get'
56
+ /** 0.55.0: filex prints a PDF the app hands it (`PrintParams`, grant `ui:print`). */
57
+ | 'ui.print'
58
+ /**
59
+ * 0.55.0, editing together (`coedit.*`, task #189): the session's log the
60
+ * host seals, orders through filex's relay and opens again. Defined so an
61
+ * app can be written against them; filex 0.55 offers no relay yet, so each
62
+ * answers `unavailable` - edit alone then (see `CoEditHello`).
63
+ */
64
+ | 'coedit.join'
65
+ | 'coedit.subscribe'
66
+ | 'coedit.append'
67
+ | 'coedit.lease'
68
+ | 'coedit.cursor'
69
+ | 'coedit.blob.put'
70
+ | 'coedit.blob.get'
71
+ | 'coedit.leave';
56
72
 
57
73
  export const METHODS: readonly Method[] = [
58
74
  'session.get',
@@ -71,6 +87,15 @@ export const METHODS: readonly Method[] = [
71
87
  'state.get',
72
88
  'state.set',
73
89
  'license.get',
90
+ 'ui.print',
91
+ 'coedit.join',
92
+ 'coedit.subscribe',
93
+ 'coedit.append',
94
+ 'coedit.lease',
95
+ 'coedit.cursor',
96
+ 'coedit.blob.put',
97
+ 'coedit.blob.get',
98
+ 'coedit.leave',
74
99
  ];
75
100
 
76
101
  /** What the host tells the app, unasked. */
@@ -84,7 +109,17 @@ export type HostEvent =
84
109
  /** The person closed the frame's surroundings; last chance to say so. */
85
110
  | 'close.request'
86
111
  /** An administrator installed a new version of this app: reload to use it. */
87
- | 'app.updated';
112
+ | 'app.updated'
113
+ /** Editing together: the next entry of the session's log, opened. `data: CoEditEntry`. */
114
+ | 'coedit.entry'
115
+ /** Editing together: another member's cursor (not kept). `data: CoEditCursor`. */
116
+ | 'coedit.cursor'
117
+ /**
118
+ * Editing together: the host stopped handing entries over (the app fell
119
+ * behind, or the connection dropped). `data: {from}` - subscribe again
120
+ * from the last entry the app holds.
121
+ */
122
+ | 'coedit.dropped';
88
123
 
89
124
  /** What the host may ask the app (and wait for). */
90
125
  export type HostRequest =
@@ -178,6 +213,19 @@ export interface FileInfo {
178
213
  /** It cannot be saved over: read-only storage, view-only opening, or the
179
214
  * app holds no `files:write`. */
180
215
  readOnly: boolean;
216
+ /**
217
+ * 0.55.0: the file is end-to-end encrypted - `folder` in an encrypted
218
+ * folder, `vault` in a vault (where only one person edits at a time),
219
+ * `file` for a single encrypted file (`.fxe`). As the server says it;
220
+ * ABSENT for a file that is not encrypted.
221
+ *
222
+ * ⚠ filex 0.55 decrypts nothing for an app: the server holds such a file
223
+ * only as ciphertext and has no key, so `file.read` is refused
224
+ * (`failed` with `encrypted`), a save is refused, and `readOnly` is true.
225
+ * The explorer offers no app an encrypted file in the first place; the
226
+ * field is information, not a promise of plaintext.
227
+ */
228
+ encrypted?: 'folder' | 'vault' | 'file';
181
229
  }
182
230
 
183
231
  /** What `session.get` answers — what the app knows about where it runs. */
@@ -221,6 +269,12 @@ export interface SaveParams {
221
269
  /** A transferred ReadableStream<Uint8Array>, an ArrayBuffer or a string. */
222
270
  data: ReadableStream<Uint8Array> | ArrayBuffer | string;
223
271
  mime?: string;
272
+ /**
273
+ * 0.55.0, editing together: the last log entry (`CoEditEntry.seq`) the
274
+ * saved document holds, so the session knows what is saved. Ignored
275
+ * outside a session.
276
+ */
277
+ through?: number;
224
278
  }
225
279
 
226
280
  export interface SaveResult {
@@ -246,6 +300,143 @@ export interface DownloadResult {
246
300
  size: number;
247
301
  }
248
302
 
303
+ /**
304
+ * `ui.print` (filex 0.55.0): a PDF for the browser's print dialog. A
305
+ * sandboxed frame may not open that dialog (measured: Chromium ignores
306
+ * `print()` there and says so, Firefox ignores it), so filex prints the PDF
307
+ * from a page of its own, with the app's `ui:print` grant, never over
308
+ * LIMITS.maxPrintBytes. filex asks every time, above the frame, and the
309
+ * dialog opens only on the person's click on filex's Allow. The print dialog
310
+ * can save the PDF too, so `ui:print` is the same kind of grant as
311
+ * `ui:download`. filex 0.54 and older answer `unknown_method`.
312
+ */
313
+ export interface PrintParams {
314
+ /** The document's name, for the question filex may ask - a name, not a path. */
315
+ name: string;
316
+ /** The PDF: a transferred ArrayBuffer or ReadableStream<Uint8Array>. */
317
+ data: ReadableStream<Uint8Array> | ArrayBuffer;
318
+ /** `application/pdf` when given; nothing else is printed. */
319
+ mime?: string;
320
+ }
321
+
322
+ export interface PrintResult {
323
+ /** The browser's print dialog was opened with the PDF. */
324
+ printed: true;
325
+ size: number;
326
+ }
327
+
328
+ /* ── editing together (coedit.*, filex 0.55.0: defined, not offered) ────── */
329
+
330
+ /** What a member appends: the editor's changes, a lock request, a release. */
331
+ export type CoEditKind = 'changes' | 'lock' | 'release';
332
+
333
+ /** A member of the session. */
334
+ export interface CoEditMember {
335
+ /** The relay's id for the connection. */
336
+ client: string;
337
+ /** An opaque id of the person, the same for each of their connections in
338
+ * this session; never an account id. */
339
+ user: string;
340
+ /** The person's display name. */
341
+ name: string;
342
+ /** The editor's per-session user index: given once, never twice. */
343
+ indexUser: number;
344
+ /** May append changes (an editor role and the right to change the file). */
345
+ canEdit: boolean;
346
+ }
347
+
348
+ /** `coedit.join` params: join (or start) the session of an opened file. */
349
+ export interface CoEditJoinParams {
350
+ index?: number;
351
+ }
352
+
353
+ /**
354
+ * What `coedit.join` answers: who the app is in the session and how far
355
+ * the log has gone. The entries come as `coedit.entry` events once the app
356
+ * subscribes (`coedit.subscribe` from 0 for the whole log). `unavailable`
357
+ * when filex offers no editing together here (filex 0.55, a vault, a file
358
+ * that cannot be shared): the app edits alone.
359
+ */
360
+ export interface CoEditHello {
361
+ session: string;
362
+ me: CoEditMember;
363
+ /** The last entry's seq (0: an empty log). */
364
+ head: number;
365
+ /** The last `changes` entry's seq. */
366
+ changesHead: number;
367
+ /** The last entry the file on the storage holds. */
368
+ savedThrough: number;
369
+ }
370
+
371
+ /** `coedit.subscribe` params: entries after `from` arrive as `coedit.entry`. */
372
+ export interface CoEditSubscribeParams {
373
+ from: number;
374
+ }
375
+
376
+ /** `coedit.append` params: the host seals it and the relay places it. */
377
+ export interface CoEditAppendParams {
378
+ kind: CoEditKind;
379
+ body: unknown;
380
+ }
381
+
382
+ export interface CoEditAppendResult {
383
+ /** Where it landed; it comes back as a `coedit.entry` too. */
384
+ seq: number;
385
+ }
386
+
387
+ /** `coedit.lease`: the changes lease (one writer at a time, one that has
388
+ * seen every change). */
389
+ export interface CoEditLeaseParams {
390
+ op: 'acquire' | 'release';
391
+ /** The seq of the last `changes` entry the app applied. */
392
+ changesSeen: number;
393
+ }
394
+
395
+ export interface CoEditLeaseResult {
396
+ granted: boolean;
397
+ }
398
+
399
+ /** `coedit.cursor` params: passed to the other members, not kept. */
400
+ export interface CoEditCursorParams {
401
+ cursor: unknown;
402
+ }
403
+
404
+ /** `coedit.blob.put` / `coedit.blob.get`: the session's sealed blobs (the
405
+ * base document, images added while editing). A name, not a path. */
406
+ export interface CoEditBlobPutParams {
407
+ name: string;
408
+ data: ArrayBuffer;
409
+ }
410
+
411
+ export interface CoEditBlobGetParams {
412
+ name: string;
413
+ }
414
+
415
+ export interface CoEditBlobGetResult {
416
+ name: string;
417
+ bytes: ArrayBuffer;
418
+ }
419
+
420
+ interface CoEditEntryBase {
421
+ seq: number;
422
+ /** The relay's time, Unix ms - the same for every member. */
423
+ at: number;
424
+ client: string;
425
+ }
426
+
427
+ /** One entry of the session's log, opened and checked by filex. */
428
+ export type CoEditEntry =
429
+ | (CoEditEntryBase & { kind: CoEditKind; body: unknown })
430
+ | (CoEditEntryBase & { kind: 'join'; member: CoEditMember })
431
+ | (CoEditEntryBase & { kind: 'leave' })
432
+ | (CoEditEntryBase & { kind: 'saved'; through: number });
433
+
434
+ /** `coedit.cursor` event data. */
435
+ export interface CoEditCursor {
436
+ client: string;
437
+ cursor: unknown;
438
+ }
439
+
249
440
  /**
250
441
  * `file.saveAs`: a NEW file, in a folder the person picks in filex's own
251
442
  * folder dialog (the one its Move to… uses), which opens in the opened
@@ -325,6 +516,13 @@ export const LIMITS = {
325
516
  connectTimeoutMs: 10_000,
326
517
  /** The largest file `ui.download` hands the person (streamed or not). */
327
518
  maxDownloadBytes: 256 << 20,
519
+ /**
520
+ * The largest PDF `ui.print` prints (filex 0.55). Lower than a download:
521
+ * a download can stream to disk, a print is held whole in the page (one
522
+ * Blob) until the print dialog has read it, and a document worth printing
523
+ * is far smaller.
524
+ */
525
+ maxPrintBytes: 64 << 20,
328
526
  } as const;
329
527
 
330
528
  /* ── guards ────────────────────────────────────────────────────────────── */