@brftech/filex-app-ui 0.54.0 → 0.56.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/README.md +10 -5
- package/dist/filex-app-ui.iife.js +1 -1
- package/dist/filex-app-ui.iife.js.map +1 -1
- package/dist/filex-app-ui.js +154 -99
- package/dist/filex-app-ui.js.map +1 -1
- package/dist/index.d.ts +296 -4
- package/package.json +1 -1
- package/src/index.ts +83 -4
- package/src/protocol.ts +267 -2
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
|
+
* Editing together (`coedit.*`, task #189): the session's log the host
|
|
60
|
+
* seals, orders through filex's relay and opens again. Defined in filex
|
|
61
|
+
* 0.55, which answers each `unavailable`; offered from 0.56 to an app
|
|
62
|
+
* granted `files:co-edit` (see `CoEditHello`). `unavailable` = edit alone.
|
|
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,18 @@ 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. `data:
|
|
119
|
+
* CoEditDropped` - `gone`: the membership ended (join again); `broken`: an
|
|
120
|
+
* entry did not check out (stop editing together). A connection that drops
|
|
121
|
+
* for a moment is the host's own: it reconnects and says nothing.
|
|
122
|
+
*/
|
|
123
|
+
| 'coedit.dropped';
|
|
88
124
|
|
|
89
125
|
/** What the host may ask the app (and wait for). */
|
|
90
126
|
export type HostRequest =
|
|
@@ -178,6 +214,49 @@ export interface FileInfo {
|
|
|
178
214
|
/** It cannot be saved over: read-only storage, view-only opening, or the
|
|
179
215
|
* app holds no `files:write`. */
|
|
180
216
|
readOnly: boolean;
|
|
217
|
+
/**
|
|
218
|
+
* 0.55.0: the file is end-to-end encrypted - `folder` in an encrypted
|
|
219
|
+
* folder, `vault` in a vault (where only one person edits at a time),
|
|
220
|
+
* `file` for a single encrypted file (`.fxe`). As the server says it;
|
|
221
|
+
* ABSENT for a file that is not encrypted.
|
|
222
|
+
*
|
|
223
|
+
* ⚠ The server holds such a file only as ciphertext and has no key. Unless
|
|
224
|
+
* `plaintext` is true, filex hands the app nothing of it: `file.read` is
|
|
225
|
+
* refused (`failed` with `encrypted`), a save is refused, and `readOnly`
|
|
226
|
+
* is true.
|
|
227
|
+
*/
|
|
228
|
+
encrypted?: 'folder' | 'vault' | 'file';
|
|
229
|
+
/**
|
|
230
|
+
* 0.56.0: filex hands this encrypted file to the app in the clear. The
|
|
231
|
+
* person's browser, which holds the folder key while the folder is
|
|
232
|
+
* unlocked, decrypts it for `file.read` and encrypts what `file.save`
|
|
233
|
+
* hands over before it leaves the browser; the server sees only
|
|
234
|
+
* ciphertext. Only with `encrypted: 'folder'` or `'vault'`, for an app
|
|
235
|
+
* whose manifest says `encrypted_folders: {"open": true}` (the grant
|
|
236
|
+
* `files:e2e-plaintext`) - never for a `.fxe`. ABSENT otherwise.
|
|
237
|
+
*
|
|
238
|
+
* In a vault (`encrypted: 'vault'`) the browser reads the file from the
|
|
239
|
+
* vault's packs and writes a save as the vault's next generation, under
|
|
240
|
+
* the vault's write lock (one writer at a time). A save is refused -
|
|
241
|
+
* `failed` with `vault_locked` - while somebody else writes the vault, and
|
|
242
|
+
* with `vault_lock_lost` when this session's write lock ended before the
|
|
243
|
+
* save was committed: nothing was written, save again. The vault locked
|
|
244
|
+
* meanwhile (its keys left the browser): `unavailable`, and filex closes
|
|
245
|
+
* the app.
|
|
246
|
+
*
|
|
247
|
+
* What changes for the app: a read and a save are held whole in the page
|
|
248
|
+
* (at most LIMITS.maxEncryptedBytes); a save is written only over the file
|
|
249
|
+
* as it was opened (`failed` with `changed` when it changed meanwhile);
|
|
250
|
+
* `file.saveAs` answers `unavailable` (a copy elsewhere would leave the
|
|
251
|
+
* folder decrypted); the app's module (`engine.call`, `job.submit`) is
|
|
252
|
+
* never handed the file; `state.get` / `state.set` use a store of their
|
|
253
|
+
* own for this folder, sealed in the person's browser (the server keeps it
|
|
254
|
+
* and cannot read it; apart from the app's ordinary store, a little smaller
|
|
255
|
+
* as it is measured sealed). Everything the app holds is plaintext: keep it
|
|
256
|
+
* in the frame. In a folder whose names are encrypted too, `name` and
|
|
257
|
+
* `ext` are the decrypted name's - the server never sees them.
|
|
258
|
+
*/
|
|
259
|
+
plaintext?: true;
|
|
181
260
|
}
|
|
182
261
|
|
|
183
262
|
/** What `session.get` answers — what the app knows about where it runs. */
|
|
@@ -221,6 +300,12 @@ export interface SaveParams {
|
|
|
221
300
|
/** A transferred ReadableStream<Uint8Array>, an ArrayBuffer or a string. */
|
|
222
301
|
data: ReadableStream<Uint8Array> | ArrayBuffer | string;
|
|
223
302
|
mime?: string;
|
|
303
|
+
/**
|
|
304
|
+
* 0.55.0, editing together: the last log entry (`CoEditEntry.seq`) the
|
|
305
|
+
* saved document holds, so the session knows what is saved. Ignored
|
|
306
|
+
* outside a session.
|
|
307
|
+
*/
|
|
308
|
+
through?: number;
|
|
224
309
|
}
|
|
225
310
|
|
|
226
311
|
export interface SaveResult {
|
|
@@ -246,6 +331,172 @@ export interface DownloadResult {
|
|
|
246
331
|
size: number;
|
|
247
332
|
}
|
|
248
333
|
|
|
334
|
+
/**
|
|
335
|
+
* `ui.print` (filex 0.55.0): a PDF for the browser's print dialog. A
|
|
336
|
+
* sandboxed frame may not open that dialog (measured: Chromium ignores
|
|
337
|
+
* `print()` there and says so, Firefox ignores it), so filex prints the PDF
|
|
338
|
+
* from a page of its own, with the app's `ui:print` grant, never over
|
|
339
|
+
* LIMITS.maxPrintBytes. filex asks every time, above the frame, and the
|
|
340
|
+
* dialog opens only on the person's click on filex's Allow. The print dialog
|
|
341
|
+
* can save the PDF too, so `ui:print` is the same kind of grant as
|
|
342
|
+
* `ui:download`. filex 0.54 and older answer `unknown_method`.
|
|
343
|
+
*/
|
|
344
|
+
export interface PrintParams {
|
|
345
|
+
/** The document's name, for the question filex may ask - a name, not a path. */
|
|
346
|
+
name: string;
|
|
347
|
+
/** The PDF: a transferred ArrayBuffer or ReadableStream<Uint8Array>. */
|
|
348
|
+
data: ReadableStream<Uint8Array> | ArrayBuffer;
|
|
349
|
+
/** `application/pdf` when given; nothing else is printed. */
|
|
350
|
+
mime?: string;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
export interface PrintResult {
|
|
354
|
+
/** The browser's print dialog was opened with the PDF. */
|
|
355
|
+
printed: true;
|
|
356
|
+
size: number;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/* ── editing together (coedit.*: filex 0.55 defined, 0.56 offers) ──────── */
|
|
360
|
+
|
|
361
|
+
/** What a member appends: the editor's changes, a lock request, a release. */
|
|
362
|
+
export type CoEditKind = 'changes' | 'lock' | 'release';
|
|
363
|
+
|
|
364
|
+
/** A member of the session. */
|
|
365
|
+
export interface CoEditMember {
|
|
366
|
+
/** The relay's id for the connection. */
|
|
367
|
+
client: string;
|
|
368
|
+
/** An opaque id of the person, the same for each of their connections in
|
|
369
|
+
* this session; never an account id. */
|
|
370
|
+
user: string;
|
|
371
|
+
/** The person's display name. */
|
|
372
|
+
name: string;
|
|
373
|
+
/** The editor's per-session user index: given once, never twice. */
|
|
374
|
+
indexUser: number;
|
|
375
|
+
/** May append changes (an editor role and the right to change the file). */
|
|
376
|
+
canEdit: boolean;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/** `coedit.join` params: join (or start) the session of an opened file. */
|
|
380
|
+
export interface CoEditJoinParams {
|
|
381
|
+
index?: number;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* What `coedit.join` answers: who the app is in the session and how far
|
|
386
|
+
* the log has gone. The entries come as `coedit.entry` events once the app
|
|
387
|
+
* subscribes (`coedit.subscribe` from 0 for the whole log). `unavailable`
|
|
388
|
+
* when filex offers no editing together here (filex 0.55, editing together
|
|
389
|
+
* turned off, an encrypted file, a vault): the app edits alone - the message
|
|
390
|
+
* says why (`not_ready` after waiting for a session's creator, `encrypted`,
|
|
391
|
+
* `nothing_to_watch` for a person who may only read the file while nobody
|
|
392
|
+
* edits it, or the server's code: `coedit_off`, `coedit_build`,
|
|
393
|
+
* `coedit_file_changed`).
|
|
394
|
+
*/
|
|
395
|
+
export interface CoEditHello {
|
|
396
|
+
session: string;
|
|
397
|
+
me: CoEditMember;
|
|
398
|
+
/**
|
|
399
|
+
* The last entry's seq when filex handed the session over (0: an empty
|
|
400
|
+
* log). filex 0.56: the changes this browser kept from an earlier opening
|
|
401
|
+
* of the file (a lost connection, a closed tab), which filex sends before
|
|
402
|
+
* it answers, are in the log up to here - an editor that starts once it
|
|
403
|
+
* has read this far opens with them.
|
|
404
|
+
*/
|
|
405
|
+
head: number;
|
|
406
|
+
/** The last `changes` entry's seq, as far as `head`. */
|
|
407
|
+
changesHead: number;
|
|
408
|
+
/** The last entry the file on the storage holds. */
|
|
409
|
+
savedThrough: number;
|
|
410
|
+
/**
|
|
411
|
+
* filex 0.56: this member started the session. Before anybody else can
|
|
412
|
+
* join it puts the base document - EXACTLY the bytes it opened - with
|
|
413
|
+
* `coedit.blob.put('base', bytes)`. A member that did not start it opens
|
|
414
|
+
* `coedit.blob.get('base')`, never the file: the file may hold a save of
|
|
415
|
+
* the session already, and the log applies to the base.
|
|
416
|
+
*/
|
|
417
|
+
created: boolean;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/** `coedit.dropped` event data (filex 0.56). */
|
|
421
|
+
export interface CoEditDropped {
|
|
422
|
+
/** The last entry the host handed over. */
|
|
423
|
+
from: number;
|
|
424
|
+
/** `gone`: the membership ended (the session closed, or the page was away
|
|
425
|
+
* too long) - join again. `broken`: an entry did not check out (missing,
|
|
426
|
+
* moved, altered, replayed) - stop editing together. */
|
|
427
|
+
reason: 'gone' | 'broken';
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/** `coedit.subscribe` params: entries after `from` arrive as `coedit.entry`. */
|
|
431
|
+
export interface CoEditSubscribeParams {
|
|
432
|
+
from: number;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/** `coedit.append` params: the host seals it and the relay places it. */
|
|
436
|
+
export interface CoEditAppendParams {
|
|
437
|
+
kind: CoEditKind;
|
|
438
|
+
body: unknown;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
export interface CoEditAppendResult {
|
|
442
|
+
/** Where it landed; it comes back as a `coedit.entry` too. */
|
|
443
|
+
seq: number;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/** `coedit.lease`: the changes lease (one writer at a time, one that has
|
|
447
|
+
* seen every change). */
|
|
448
|
+
export interface CoEditLeaseParams {
|
|
449
|
+
op: 'acquire' | 'release';
|
|
450
|
+
/** The seq of the last `changes` entry the app applied. */
|
|
451
|
+
changesSeen: number;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
export interface CoEditLeaseResult {
|
|
455
|
+
granted: boolean;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/** `coedit.cursor` params: passed to the other members, not kept. The host
|
|
459
|
+
* sends a few a second at most (the last one wins). */
|
|
460
|
+
export interface CoEditCursorParams {
|
|
461
|
+
cursor: unknown;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/** `coedit.blob.put` / `coedit.blob.get`: the session's sealed blobs (the
|
|
465
|
+
* base document, images added while editing). A name, not a path. */
|
|
466
|
+
export interface CoEditBlobPutParams {
|
|
467
|
+
name: string;
|
|
468
|
+
data: ArrayBuffer;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
export interface CoEditBlobGetParams {
|
|
472
|
+
name: string;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
export interface CoEditBlobGetResult {
|
|
476
|
+
name: string;
|
|
477
|
+
bytes: ArrayBuffer;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
interface CoEditEntryBase {
|
|
481
|
+
seq: number;
|
|
482
|
+
/** The relay's time, Unix ms - the same for every member. */
|
|
483
|
+
at: number;
|
|
484
|
+
client: string;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/** One entry of the session's log, opened and checked by filex. */
|
|
488
|
+
export type CoEditEntry =
|
|
489
|
+
| (CoEditEntryBase & { kind: CoEditKind; body: unknown })
|
|
490
|
+
| (CoEditEntryBase & { kind: 'join'; member: CoEditMember })
|
|
491
|
+
| (CoEditEntryBase & { kind: 'leave' })
|
|
492
|
+
| (CoEditEntryBase & { kind: 'saved'; through: number });
|
|
493
|
+
|
|
494
|
+
/** `coedit.cursor` event data. */
|
|
495
|
+
export interface CoEditCursor {
|
|
496
|
+
client: string;
|
|
497
|
+
cursor: unknown;
|
|
498
|
+
}
|
|
499
|
+
|
|
249
500
|
/**
|
|
250
501
|
* `file.saveAs`: a NEW file, in a folder the person picks in filex's own
|
|
251
502
|
* folder dialog (the one its Move to… uses), which opens in the opened
|
|
@@ -325,6 +576,20 @@ export const LIMITS = {
|
|
|
325
576
|
connectTimeoutMs: 10_000,
|
|
326
577
|
/** The largest file `ui.download` hands the person (streamed or not). */
|
|
327
578
|
maxDownloadBytes: 256 << 20,
|
|
579
|
+
/**
|
|
580
|
+
* The largest PDF `ui.print` prints (filex 0.55). Lower than a download:
|
|
581
|
+
* a download can stream to disk, a print is held whole in the page (one
|
|
582
|
+
* Blob) until the print dialog has read it, and a document worth printing
|
|
583
|
+
* is far smaller.
|
|
584
|
+
*/
|
|
585
|
+
maxPrintBytes: 64 << 20,
|
|
586
|
+
/**
|
|
587
|
+
* The largest file of an encrypted folder filex hands an app in the clear
|
|
588
|
+
* or takes back from it (filex 0.56, `FileInfo.plaintext`): it is
|
|
589
|
+
* decrypted and encrypted whole in the page, as filex's own explorer does
|
|
590
|
+
* for a file of this size.
|
|
591
|
+
*/
|
|
592
|
+
maxEncryptedBytes: 200 << 20,
|
|
328
593
|
} as const;
|
|
329
594
|
|
|
330
595
|
/* ── guards ────────────────────────────────────────────────────────────── */
|