@fias/arche-sdk 2.19.1 → 2.19.2
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/entity-ops.d.ts +7 -4
- package/dist/entity-ops.d.ts.map +1 -1
- package/dist/entity-ops.js +7 -4
- package/dist/entity-ops.js.map +1 -1
- package/dist/generated/permissions.d.ts +1 -1
- package/dist/generated/permissions.d.ts.map +1 -1
- package/dist/generated/permissions.js +2 -0
- package/dist/generated/permissions.js.map +1 -1
- package/dist/hooks.d.ts +86 -3
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +188 -15
- package/dist/hooks.js.map +1 -1
- package/dist/hooks.test.js +291 -2
- package/dist/hooks.test.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +136 -19
- package/dist/types.d.ts +205 -12
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/templates/default/AGENTS.md +145 -6
- package/templates/default/CLAUDE.md +145 -6
package/dist/entity-ops.d.ts
CHANGED
|
@@ -30,10 +30,13 @@ export declare const PLUGIN_STORE_ENTITY_ID = "ent_intg_plugin_store_0000000000"
|
|
|
30
30
|
/** Vault Documents entity — plugins may reach ONLY its consented-read verbs
|
|
31
31
|
* (user-granted documents via the host picker; `vault:user-documents:read`). */
|
|
32
32
|
export declare const VAULT_USER_DOCUMENTS_ENTITY_ID = "ent_intg_vault_documents_0000000";
|
|
33
|
-
/** Vault Arche Documents — documents THIS arche created.
|
|
34
|
-
* `get_own_document_bytes` (`vault:documents:read`) and
|
|
35
|
-
* `save_own_document_content`
|
|
36
|
-
*
|
|
33
|
+
/** Vault Arche Documents — documents THIS arche created.
|
|
34
|
+
* `get_own_document_bytes` (`vault:documents:read`), and the in-place save:
|
|
35
|
+
* `save_own_document_content` for small text, or — driven by the HOST, since
|
|
36
|
+
* the iframe cannot PUT — `init_own_document_save` /
|
|
37
|
+
* `finalize_own_document_save` for bytes and large text (all three
|
|
38
|
+
* additionally `vault:documents:write`). `useVaultDocuments` already derives
|
|
39
|
+
* both permissions, and `saveContent` picks the lane. */
|
|
37
40
|
export declare const VAULT_ARCHE_DOCUMENTS_ENTITY_ID = "ent_intg_vault_arche_docs_000000";
|
|
38
41
|
/** Community Assets — user-published, moderated images readable by every
|
|
39
42
|
* authenticated user of the SAME arche. `assets:community:read` for the read
|
package/dist/entity-ops.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entity-ops.d.ts","sourceRoot":"","sources":["../src/entity-ops.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAE3C,eAAO,MAAM,wBAAwB,qCAAqC,CAAC;AAC3E,eAAO,MAAM,0BAA0B,qCAAqC,CAAC;AAC7E,eAAO,MAAM,2BAA2B,qCAAqC,CAAC;AAC9E,eAAO,MAAM,sBAAsB,qCAAqC,CAAC;AACzE;gFACgF;AAChF,eAAO,MAAM,8BAA8B,qCAAqC,CAAC;AACjF
|
|
1
|
+
{"version":3,"file":"entity-ops.d.ts","sourceRoot":"","sources":["../src/entity-ops.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAE3C,eAAO,MAAM,wBAAwB,qCAAqC,CAAC;AAC3E,eAAO,MAAM,0BAA0B,qCAAqC,CAAC;AAC7E,eAAO,MAAM,2BAA2B,qCAAqC,CAAC;AAC9E,eAAO,MAAM,sBAAsB,qCAAqC,CAAC;AACzE;gFACgF;AAChF,eAAO,MAAM,8BAA8B,qCAAqC,CAAC;AACjF;;;;;;yDAMyD;AACzD,eAAO,MAAM,+BAA+B,qCAAqC,CAAC;AAClF;;;iEAGiE;AACjE,eAAO,MAAM,0BAA0B,qCAAqC,CAAC;AAE7E;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,6BAA6B,KAAK,CAAC;AAChD,eAAO,MAAM,iCAAiC,KAAK,CAAC;AAEpD;;;;;;;;;;;;GAYG;AACH,wBAAsB,cAAc,CAAC,CAAC,EACpC,MAAM,EAAE,UAAU,EAClB,QAAQ,EAAE,MAAM,EAChB,EAAE,EAAE,MAAM,EACV,IAAI,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GACjC,OAAO,CAAC,CAAC,CAAC,CAMZ;AAED;;;;;;;;GAQG;AACH,wBAAsB,aAAa,CAAC,CAAC,EACnC,MAAM,EAAE,UAAU,EAClB,EAAE,EAAE,MAAM,EACV,IAAI,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GACjC,OAAO,CAAC,CAAC,CAAC,CAKZ"}
|
package/dist/entity-ops.js
CHANGED
|
@@ -10,10 +10,13 @@ exports.PLUGIN_STORE_ENTITY_ID = 'ent_intg_plugin_store_0000000000';
|
|
|
10
10
|
/** Vault Documents entity — plugins may reach ONLY its consented-read verbs
|
|
11
11
|
* (user-granted documents via the host picker; `vault:user-documents:read`). */
|
|
12
12
|
exports.VAULT_USER_DOCUMENTS_ENTITY_ID = 'ent_intg_vault_documents_0000000';
|
|
13
|
-
/** Vault Arche Documents — documents THIS arche created.
|
|
14
|
-
* `get_own_document_bytes` (`vault:documents:read`) and
|
|
15
|
-
* `save_own_document_content`
|
|
16
|
-
*
|
|
13
|
+
/** Vault Arche Documents — documents THIS arche created.
|
|
14
|
+
* `get_own_document_bytes` (`vault:documents:read`), and the in-place save:
|
|
15
|
+
* `save_own_document_content` for small text, or — driven by the HOST, since
|
|
16
|
+
* the iframe cannot PUT — `init_own_document_save` /
|
|
17
|
+
* `finalize_own_document_save` for bytes and large text (all three
|
|
18
|
+
* additionally `vault:documents:write`). `useVaultDocuments` already derives
|
|
19
|
+
* both permissions, and `saveContent` picks the lane. */
|
|
17
20
|
exports.VAULT_ARCHE_DOCUMENTS_ENTITY_ID = 'ent_intg_vault_arche_docs_000000';
|
|
18
21
|
/** Community Assets — user-published, moderated images readable by every
|
|
19
22
|
* authenticated user of the SAME arche. `assets:community:read` for the read
|
package/dist/entity-ops.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entity-ops.js","sourceRoot":"","sources":["../src/entity-ops.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"entity-ops.js","sourceRoot":"","sources":["../src/entity-ops.ts"],"names":[],"mappings":";;;AA4EA,wCAWC;AAWD,sCASC;AAjFY,QAAA,wBAAwB,GAAG,kCAAkC,CAAC;AAC9D,QAAA,0BAA0B,GAAG,kCAAkC,CAAC;AAChE,QAAA,2BAA2B,GAAG,kCAAkC,CAAC;AACjE,QAAA,sBAAsB,GAAG,kCAAkC,CAAC;AACzE;gFACgF;AACnE,QAAA,8BAA8B,GAAG,kCAAkC,CAAC;AACjF;;;;;;yDAMyD;AAC5C,QAAA,+BAA+B,GAAG,kCAAkC,CAAC;AAClF;;;iEAGiE;AACpD,QAAA,0BAA0B,GAAG,kCAAkC,CAAC;AAE7E;;;;;;;;;;;;GAYG;AACU,QAAA,6BAA6B,GAAG,EAAE,CAAC;AACnC,QAAA,iCAAiC,GAAG,EAAE,CAAC;AAEpD;;;;;;;;;;;;GAYG;AACI,KAAK,UAAU,cAAc,CAClC,MAAkB,EAClB,QAAgB,EAChB,EAAU,EACV,OAAgC,EAAE;IAElC,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,OAAO,CAAgB,eAAe,EAAE;QAC/D,QAAQ;QACR,OAAO,EAAE,EAAE,EAAE,EAAE,GAAG,IAAI,EAAE;KACzB,CAAC,CAAC;IACH,OAAO,GAAG,CAAC,MAAM,CAAC;AACpB,CAAC;AAED;;;;;;;;GAQG;AACI,KAAK,UAAU,aAAa,CACjC,MAAkB,EAClB,EAAU,EACV,OAAgC,EAAE;IAElC,OAAO,MAAM,CAAC,OAAO,CAAI,eAAe,EAAE;QACxC,QAAQ,EAAE,8BAAsB;QAChC,OAAO,EAAE,EAAE,EAAE,EAAE,GAAG,IAAI,EAAE;KACzB,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* `tests/architecture/sdk-permissions-match-db-types.test.ts` ensures
|
|
10
10
|
* this file stays in lockstep with the canonical source.
|
|
11
11
|
*/
|
|
12
|
-
export type PluginPermission = 'ai:actions' | 'assets:community:publish' | 'assets:community:read' | 'assets:read' | 'data:search' | 'data:store' | 'data:workspace' | 'entities:audio_generate' | 'entities:client_invoke' | 'entities:image_edit' | 'entities:image_generate' | 'entities:invoke' | 'entities:web_search' | 'navigation:open_arche' | 'sandbox:vendored-libraries' | 'storage:sandbox' | 'store:purchase' | 'theme:read' | 'user:profile:read' | 'vault:documents:read' | 'vault:documents:write' | 'vault:user-documents:read' | 'vault:user-documents:write';
|
|
12
|
+
export type PluginPermission = 'ai:actions' | 'assets:community:publish' | 'assets:community:read' | 'assets:read' | 'data:search' | 'data:store' | 'data:workspace' | 'entities:audio_generate' | 'entities:client_invoke' | 'entities:image_edit' | 'entities:image_generate' | 'entities:invoke' | 'entities:web_search' | 'navigation:open_arche' | 'sandbox:vendored-libraries' | 'storage:sandbox' | 'store:purchase' | 'theme:read' | 'user:profile:read' | 'vault:documents:read' | 'vault:documents:write' | 'vault:user-documents:edit' | 'vault:user-documents:read' | 'vault:user-documents:write';
|
|
13
13
|
export declare const VALID_PLUGIN_PERMISSIONS: readonly PluginPermission[];
|
|
14
14
|
export declare function isValidPluginPermission(perm: string): perm is PluginPermission;
|
|
15
15
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../../src/generated/permissions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,MAAM,MAAM,gBAAgB,GACxB,YAAY,GACZ,0BAA0B,GAC1B,uBAAuB,GACvB,aAAa,GACb,aAAa,GACb,YAAY,GACZ,gBAAgB,GAChB,yBAAyB,GACzB,wBAAwB,GACxB,qBAAqB,GACrB,yBAAyB,GACzB,iBAAiB,GACjB,qBAAqB,GACrB,uBAAuB,GACvB,4BAA4B,GAC5B,iBAAiB,GACjB,gBAAgB,GAChB,YAAY,GACZ,mBAAmB,GACnB,sBAAsB,GACtB,uBAAuB,GACvB,2BAA2B,GAC3B,4BAA4B,CAAC;AAEjC,eAAO,MAAM,wBAAwB,EAAE,SAAS,gBAAgB,
|
|
1
|
+
{"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../../src/generated/permissions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,MAAM,MAAM,gBAAgB,GACxB,YAAY,GACZ,0BAA0B,GAC1B,uBAAuB,GACvB,aAAa,GACb,aAAa,GACb,YAAY,GACZ,gBAAgB,GAChB,yBAAyB,GACzB,wBAAwB,GACxB,qBAAqB,GACrB,yBAAyB,GACzB,iBAAiB,GACjB,qBAAqB,GACrB,uBAAuB,GACvB,4BAA4B,GAC5B,iBAAiB,GACjB,gBAAgB,GAChB,YAAY,GACZ,mBAAmB,GACnB,sBAAsB,GACtB,uBAAuB,GACvB,2BAA2B,GAC3B,2BAA2B,GAC3B,4BAA4B,CAAC;AAEjC,eAAO,MAAM,wBAAwB,EAAE,SAAS,gBAAgB,EAyBtD,CAAC;AAEX,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,IAAI,gBAAgB,CAE9E;AAED;;;;GAIG;AACH,eAAO,MAAM,8BAA8B,EAAE,QAAQ,CAAC,MAAM,CAAC,gBAAgB,EAAE,MAAM,CAAC,CAiCrF,CAAC"}
|
|
@@ -35,6 +35,7 @@ exports.VALID_PLUGIN_PERMISSIONS = [
|
|
|
35
35
|
'user:profile:read',
|
|
36
36
|
'vault:documents:read',
|
|
37
37
|
'vault:documents:write',
|
|
38
|
+
'vault:user-documents:edit',
|
|
38
39
|
'vault:user-documents:read',
|
|
39
40
|
'vault:user-documents:write',
|
|
40
41
|
];
|
|
@@ -68,6 +69,7 @@ exports.PLUGIN_PERMISSION_DESCRIPTIONS = {
|
|
|
68
69
|
'user:profile:read': 'See your display name and avatar.',
|
|
69
70
|
'vault:documents:read': 'Read documents this app itself created in your Vault.',
|
|
70
71
|
'vault:documents:write': 'Create and manage its own documents in your Vault.',
|
|
72
|
+
'vault:user-documents:edit': 'Save changes to files of yours that you let it edit — you choose each file, earlier versions are kept, and you can remove access any time in Vault → App Access.',
|
|
71
73
|
'vault:user-documents:read': 'Read documents YOU choose to share with it from your Vault — you pick each document, and you can remove access any time in Vault → App Access.',
|
|
72
74
|
'vault:user-documents:write': 'Save its work to your Fias files, where you can find and reuse it. You confirm each save.',
|
|
73
75
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"permissions.js","sourceRoot":"","sources":["../../src/generated/permissions.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;GAUG;;;
|
|
1
|
+
{"version":3,"file":"permissions.js","sourceRoot":"","sources":["../../src/generated/permissions.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;GAUG;;;AAuDH,0DAEC;AA7BY,QAAA,wBAAwB,GAAgC;IACnE,YAAY;IACZ,0BAA0B;IAC1B,uBAAuB;IACvB,aAAa;IACb,aAAa;IACb,YAAY;IACZ,gBAAgB;IAChB,yBAAyB;IACzB,wBAAwB;IACxB,qBAAqB;IACrB,yBAAyB;IACzB,iBAAiB;IACjB,qBAAqB;IACrB,uBAAuB;IACvB,4BAA4B;IAC5B,iBAAiB;IACjB,gBAAgB;IAChB,YAAY;IACZ,mBAAmB;IACnB,sBAAsB;IACtB,uBAAuB;IACvB,2BAA2B;IAC3B,2BAA2B;IAC3B,4BAA4B;CACpB,CAAC;AAEX,SAAgB,uBAAuB,CAAC,IAAY;IAClD,OAAQ,gCAA8C,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;AACxE,CAAC;AAED;;;;GAIG;AACU,QAAA,8BAA8B,GAA+C;IACxF,YAAY,EACV,2HAA2H;IAC7H,0BAA0B,EACxB,0GAA0G;IAC5G,uBAAuB,EACrB,sHAAsH;IACxH,aAAa,EAAE,sDAAsD;IACrE,aAAa,EAAE,qEAAqE;IACpF,YAAY,EAAE,0DAA0D;IACxE,gBAAgB,EAAE,kDAAkD;IACpE,yBAAyB,EAAE,kDAAkD;IAC7E,wBAAwB,EACtB,wEAAwE;IAC1E,qBAAqB,EAAE,+CAA+C;IACtE,yBAAyB,EAAE,mDAAmD;IAC9E,iBAAiB,EAAE,wDAAwD;IAC3E,qBAAqB,EAAE,qDAAqD;IAC5E,uBAAuB,EAAE,8CAA8C;IACvE,4BAA4B,EAC1B,8EAA8E;IAChF,iBAAiB,EAAE,+CAA+C;IAClE,gBAAgB,EAAE,qDAAqD;IACvE,YAAY,EAAE,4BAA4B;IAC1C,mBAAmB,EAAE,mCAAmC;IACxD,sBAAsB,EAAE,uDAAuD;IAC/E,uBAAuB,EAAE,oDAAoD;IAC7E,2BAA2B,EACzB,kKAAkK;IACpK,2BAA2B,EACzB,gJAAgJ;IAClJ,4BAA4B,EAC1B,2FAA2F;CAC9F,CAAC"}
|
package/dist/hooks.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ArcheHandoffPayload, FiasUser, FiasTheme, FiasStorageApi, DataSubscriptionState, EntityInvocationApi, ClientEntityApi, ImageGenerationApi, ImageEntityFilter, ImageEntitySummary, NllbLanguageOption, AudioGenerationApi, FiasNavigationApi, StepNavigationApi, StepNavigationOptions, FiasDataStoreApi, FiasWorkspacesApi, FiasStoreApi, VaultDocumentsApi, VaultDocumentDownloadUrl, ArcheAssetsApi, CommunityAssetsApi, FiasAIActionsDeclaration } from './types';
|
|
1
|
+
import type { ArcheHandoffPayload, FiasUser, FiasTheme, FiasStorageApi, DataSubscriptionState, EntityInvocationApi, ClientEntityApi, ImageGenerationApi, ImageEntityFilter, ImageEntitySummary, NllbLanguageOption, AudioGenerationApi, FiasNavigationApi, StepNavigationApi, StepNavigationOptions, FiasDataStoreApi, FiasWorkspacesApi, FiasStoreApi, VaultDocumentsApi, VaultDocumentDownloadUrl, VaultUserDocumentSaveContentResult, ArcheAssetsApi, CommunityAssetsApi, FiasAIActionsDeclaration } from './types';
|
|
2
2
|
/**
|
|
3
3
|
* Access the current user's profile information.
|
|
4
4
|
* Requires the `user:profile:read` permission in fias-plugin.json.
|
|
@@ -412,14 +412,41 @@ export interface VaultUserDocumentSummary {
|
|
|
412
412
|
sensitivity: string;
|
|
413
413
|
createdAt: string;
|
|
414
414
|
updatedAt: string;
|
|
415
|
+
/**
|
|
416
|
+
* What the user lets you do with this document: `'read'` (open it) or
|
|
417
|
+
* `'write'` (also save changes to it — see `saveContent`). Open a `'read'`
|
|
418
|
+
* document read-only, and call `requestEditAccess` when the user starts
|
|
419
|
+
* editing. Optional: absent on hosts that predate edit access — treat
|
|
420
|
+
* absent as `'read'`.
|
|
421
|
+
*/
|
|
422
|
+
access?: 'read' | 'write';
|
|
423
|
+
/**
|
|
424
|
+
* Opaque token naming the document's current CONTENT. REQUIRED by
|
|
425
|
+
* `saveContent` as `expectedRevision` — it is the user's file and they may
|
|
426
|
+
* be changing it elsewhere. Never parse it.
|
|
427
|
+
*/
|
|
428
|
+
revision?: string;
|
|
415
429
|
}
|
|
416
430
|
/** Result of the host picker: what the user picked and confirmed, or a
|
|
417
431
|
* cancel. A cancel is a normal outcome — never treat it as an error. */
|
|
418
432
|
export type VaultUserDocumentPickResult = {
|
|
419
433
|
canceled: true;
|
|
420
434
|
} | {
|
|
421
|
-
documents: Array<Pick<VaultUserDocumentSummary, 'documentId' | 'name' | 'mimeType' | 'sizeBytes'
|
|
435
|
+
documents: Array<Pick<VaultUserDocumentSummary, 'documentId' | 'name' | 'mimeType' | 'sizeBytes'> & {
|
|
436
|
+
/** What the user granted for this document. */
|
|
437
|
+
access?: 'read' | 'write';
|
|
438
|
+
}>;
|
|
422
439
|
};
|
|
440
|
+
/** Input to `useVaultUserDocuments().saveContent`. */
|
|
441
|
+
export interface VaultUserDocumentSaveContentParams {
|
|
442
|
+
documentId: string;
|
|
443
|
+
/** The new content, up to 15 MB: a `string` (UTF-8 text — the document must
|
|
444
|
+
* be a text type) or bytes. Transport is chosen for you. The document's
|
|
445
|
+
* TYPE cannot change, so there is no `contentType`. */
|
|
446
|
+
content: string | Uint8Array | ArrayBuffer | Blob;
|
|
447
|
+
/** REQUIRED. The `revision` you last saw for this document. */
|
|
448
|
+
expectedRevision: string;
|
|
449
|
+
}
|
|
423
450
|
export interface VaultUserDocumentsApi {
|
|
424
451
|
/**
|
|
425
452
|
* Open the HOST's Vault picker + consent sheet. The user hand-picks
|
|
@@ -430,7 +457,55 @@ export interface VaultUserDocumentsApi {
|
|
|
430
457
|
*/
|
|
431
458
|
pick(options?: {
|
|
432
459
|
maxDocuments?: number;
|
|
460
|
+
/** Ask for EDIT access: the user is shown a consent that says your app
|
|
461
|
+
* may CHANGE what they pick. Needs `vault:user-documents:edit` IN
|
|
462
|
+
* ADDITION TO `vault:user-documents:read` — `:edit` does not include
|
|
463
|
+
* `:read`, and every method of this hook (this one included) is gated
|
|
464
|
+
* on `:read`. */
|
|
465
|
+
access?: 'read' | 'write';
|
|
433
466
|
}): Promise<VaultUserDocumentPickResult>;
|
|
467
|
+
/**
|
|
468
|
+
* Ask the user to let you EDIT a document you can already open. The host
|
|
469
|
+
* shows its own consent for that one file — no picker — and resolves with
|
|
470
|
+
* the document (now `access: 'write'`), or `{ canceled: true }`. Call it
|
|
471
|
+
* from a user gesture, when the user actually starts editing — not on open,
|
|
472
|
+
* and not again right after a decline. Needs `vault:user-documents:edit`
|
|
473
|
+
* together with `vault:user-documents:read`.
|
|
474
|
+
* If you hold no grant on the document the host shows the ordinary picker
|
|
475
|
+
* instead, so you learn nothing about files you were not given.
|
|
476
|
+
*
|
|
477
|
+
* CHECK THE RESULT — a resolved promise is not "you may now edit
|
|
478
|
+
* `documentId`". Because of that picker fallback the one document that
|
|
479
|
+
* comes back may be a DIFFERENT one the user chose, and the user may have
|
|
480
|
+
* answered from a sheet that offered view-only. Proceed only when
|
|
481
|
+
* `documents[0].documentId === documentId` and `documents[0].access ===
|
|
482
|
+
* 'write'`; treat anything else (including `{ canceled: true }`) as "no".
|
|
483
|
+
*/
|
|
484
|
+
requestEditAccess(documentId: string): Promise<VaultUserDocumentPickResult>;
|
|
485
|
+
/**
|
|
486
|
+
* Save changes, IN PLACE, to a document the user has given you EDIT access
|
|
487
|
+
* to — same `documentId`, so nothing attached to it detaches. Text or bytes,
|
|
488
|
+
* up to 15 MB; one method, transport chosen for you.
|
|
489
|
+
*
|
|
490
|
+
* Manifest: `vault:user-documents:edit` AND `vault:user-documents:read`.
|
|
491
|
+
* Declaring `:edit` alone is not enough — it does not include `:read`.
|
|
492
|
+
*
|
|
493
|
+
* It is the USER'S file, which makes this stricter than saving your own:
|
|
494
|
+
* `expectedRevision` is REQUIRED (they may be editing it elsewhere — a stale
|
|
495
|
+
* one rejects `VERSION_CONFLICT`: re-read, merge or ask, retry); the file's
|
|
496
|
+
* TYPE cannot change (`MIME_CHANGE_NOT_ALLOWED`); and the platform keeps
|
|
497
|
+
* earlier versions for them, which count against THEIR storage — a save
|
|
498
|
+
* that would exceed it rejects `STORAGE_QUOTA_EXCEEDED` and nothing is lost.
|
|
499
|
+
*
|
|
500
|
+
* Rejects `EDIT_ACCESS_REQUIRED` when you hold view-only access (call
|
|
501
|
+
* `requestEditAccess`), and `DOCUMENT_NOT_FOUND` once the user removes your
|
|
502
|
+
* access in Vault → App Access — which takes effect on your very next save.
|
|
503
|
+
* Handle both. Debounce (a few seconds idle, plus blur and close) and
|
|
504
|
+
* surface `RATE_LIMIT` rather than looping: this lane allows fewer saves per
|
|
505
|
+
* minute than your own documents do. Every save is recorded with your app's
|
|
506
|
+
* name, and the file shows the user that you changed it.
|
|
507
|
+
*/
|
|
508
|
+
saveContent(params: VaultUserDocumentSaveContentParams): Promise<VaultUserDocumentSaveContentResult>;
|
|
434
509
|
/** Documents the user has granted this arche (empty until they pick). */
|
|
435
510
|
list(options?: {
|
|
436
511
|
limit?: number;
|
|
@@ -481,7 +556,15 @@ export interface VaultUserDocumentsApi {
|
|
|
481
556
|
* surface that reaches data your plugin did not create. Requires the
|
|
482
557
|
* `vault:user-documents:read` permission. Every access is user-consented
|
|
483
558
|
* (per-document grants created in the host picker, revocable any time in
|
|
484
|
-
* Vault → App Access) and audited by the platform.
|
|
559
|
+
* Vault → App Access) and audited by the platform.
|
|
560
|
+
*
|
|
561
|
+
* The picker offers the files of the workspace the user is acting in (the
|
|
562
|
+
* host's workspace switcher — Personal, or an org workspace they belong to)
|
|
563
|
+
* and `list()` returns the grants made in it; a document you hold a grant on
|
|
564
|
+
* stays readable by id whichever workspace the user is in, because a grant
|
|
565
|
+
* belongs to the workspace the document lives in. A plugin never names a
|
|
566
|
+
* workspace; the host does. A member's role caps what they can share and
|
|
567
|
+
* read. Proprietary-sensitivity
|
|
485
568
|
* documents are never grantable. For documents your plugin CREATES, use
|
|
486
569
|
* `useVaultDocuments` instead.
|
|
487
570
|
*/
|
package/dist/hooks.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,mBAAmB,EACnB,QAAQ,EACR,SAAS,EACT,cAAc,EACd,qBAAqB,EACrB,mBAAmB,EAGnB,eAAe,EACf,kBAAkB,EAGlB,iBAAiB,EACjB,kBAAkB,EAClB,kBAAkB,EAClB,kBAAkB,EAGlB,iBAAiB,
|
|
1
|
+
{"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"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"}
|
package/dist/hooks.js
CHANGED
|
@@ -185,7 +185,13 @@ function useFiasStorage() {
|
|
|
185
185
|
path,
|
|
186
186
|
});
|
|
187
187
|
}, [bridge]);
|
|
188
|
-
|
|
188
|
+
// Memoized, like useArcheAssets. An unmemoized object literal here is a
|
|
189
|
+
// footgun rather than a style question: the natural way to use this hook is
|
|
190
|
+
// `useEffect(() => { void storage.readFile(...) }, [storage])`, and a fresh
|
|
191
|
+
// identity every render turns that into an infinite loop — effect, setState,
|
|
192
|
+
// render, effect. The inner callbacks were already stable; only the
|
|
193
|
+
// container was not.
|
|
194
|
+
return (0, react_1.useMemo)(() => ({ readFile, writeFile, listFiles, deleteFile }), [readFile, writeFile, listFiles, deleteFile]);
|
|
189
195
|
}
|
|
190
196
|
/**
|
|
191
197
|
* Invoke platform entities (AI models, etc.) through the bridge.
|
|
@@ -573,16 +579,21 @@ function useFiasNavigation() {
|
|
|
573
579
|
bridge.request('navigate', { path });
|
|
574
580
|
}, [bridge]);
|
|
575
581
|
const openArche = (0, react_1.useCallback)((archeId, opts) => {
|
|
576
|
-
// `path`
|
|
577
|
-
// keys, but sending `path: undefined` would add a key to
|
|
578
|
-
// no reason and churn the wire shape shipped plugins
|
|
582
|
+
// `path` / `payload` are spread in only when supplied: an older host
|
|
583
|
+
// ignores unknown keys, but sending `path: undefined` would add a key to
|
|
584
|
+
// every call for no reason and churn the wire shape shipped plugins
|
|
585
|
+
// already emit.
|
|
579
586
|
bridge.request('open_arche', {
|
|
580
587
|
archeId,
|
|
581
588
|
newTab: opts?.newTab === true,
|
|
582
589
|
...(opts?.path ? { path: opts.path } : {}),
|
|
590
|
+
...(opts?.payload ? { payload: opts.payload } : {}),
|
|
583
591
|
});
|
|
584
592
|
}, [bridge]);
|
|
585
|
-
|
|
593
|
+
// Memoized for the same reason as useFiasStorage. `currentPath` is state, so
|
|
594
|
+
// this identity still changes when the path does — which is correct, and is
|
|
595
|
+
// exactly what a consumer depending on it wants.
|
|
596
|
+
return (0, react_1.useMemo)(() => ({ navigateTo, openArche, currentPath }), [navigateTo, openArche, currentPath]);
|
|
586
597
|
}
|
|
587
598
|
/**
|
|
588
599
|
* Like useState, but auto-persists to bridge storage.
|
|
@@ -596,12 +607,22 @@ function usePersistentState(key, initialValue) {
|
|
|
596
607
|
const [value, setValueInternal] = (0, react_1.useState)(initialValue);
|
|
597
608
|
const initializedRef = (0, react_1.useRef)(false);
|
|
598
609
|
const valueRef = (0, react_1.useRef)(initialValue);
|
|
610
|
+
// Set once the plugin calls the setter. A value set BEFORE the mount read
|
|
611
|
+
// resolves is newer than anything in storage, so the read must not
|
|
612
|
+
// overwrite it — it used to, silently reverting e.g. a click that landed
|
|
613
|
+
// while the read was in flight (the debounced write then persisted the
|
|
614
|
+
// user's value while the UI showed the stale one).
|
|
615
|
+
const setByPluginRef = (0, react_1.useRef)(false);
|
|
599
616
|
const writer = (0, react_1.useMemo)(() => createDebouncedWriter(bridge), [bridge]);
|
|
600
617
|
// Load from storage on mount; flush any pending write on unmount/key change
|
|
601
618
|
// so rapid updates in a game loop never lose their last value.
|
|
602
619
|
(0, react_1.useEffect)(() => {
|
|
603
620
|
(0, entity_ops_1.invokeEntityOp)(bridge, entity_ops_1.PLUGIN_STORAGE_ENTITY_ID, 'read', { path: `__state/${key}` })
|
|
604
621
|
.then((result) => {
|
|
622
|
+
if (setByPluginRef.current) {
|
|
623
|
+
initializedRef.current = true;
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
605
626
|
if (result.exists && result.content !== null) {
|
|
606
627
|
try {
|
|
607
628
|
const parsed = JSON.parse(result.content);
|
|
@@ -627,6 +648,7 @@ function usePersistentState(key, initialValue) {
|
|
|
627
648
|
}, [bridge, key, writer]);
|
|
628
649
|
const setValue = (0, react_1.useCallback)((next) => {
|
|
629
650
|
const resolved = typeof next === 'function' ? next(valueRef.current) : next;
|
|
651
|
+
setByPluginRef.current = true;
|
|
630
652
|
valueRef.current = resolved;
|
|
631
653
|
setValueInternal(resolved);
|
|
632
654
|
writer.schedule(`__state/${key}`, JSON.stringify(resolved));
|
|
@@ -1079,9 +1101,7 @@ function useVaultDocuments() {
|
|
|
1079
1101
|
});
|
|
1080
1102
|
}, [bridge]);
|
|
1081
1103
|
const read = (0, react_1.useCallback)(async (documentId) => {
|
|
1082
|
-
return bridge.request('vault_documents_read', {
|
|
1083
|
-
documentId,
|
|
1084
|
-
});
|
|
1104
|
+
return bridge.request('vault_documents_read', { documentId });
|
|
1085
1105
|
}, [bridge]);
|
|
1086
1106
|
const getDownloadUrl = (0, react_1.useCallback)((documentId) => fetchVaultDocumentDownloadUrl(documentId),
|
|
1087
1107
|
// The hook closes over the React context's bridge, but the resolver
|
|
@@ -1098,16 +1118,47 @@ function useVaultDocuments() {
|
|
|
1098
1118
|
};
|
|
1099
1119
|
}, [bridge]);
|
|
1100
1120
|
const saveContent = (0, react_1.useCallback)(async (params) => {
|
|
1101
|
-
const
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1121
|
+
const routed = await routeSaveContent(params.content);
|
|
1122
|
+
let res;
|
|
1123
|
+
if (routed.lane === 'body') {
|
|
1124
|
+
// Small text rides the entity_invoke body.
|
|
1125
|
+
res = await (0, entity_ops_1.invokeEntityOp)(bridge, entity_ops_1.VAULT_ARCHE_DOCUMENTS_ENTITY_ID, 'save_own_document_content', {
|
|
1126
|
+
documentId: params.documentId,
|
|
1127
|
+
content: routed.text,
|
|
1128
|
+
contentType: params.contentType,
|
|
1129
|
+
expectedRevision: params.expectedRevision,
|
|
1130
|
+
});
|
|
1131
|
+
}
|
|
1132
|
+
else {
|
|
1133
|
+
// Bytes — or text too large for the bridge body — go through the
|
|
1134
|
+
// host, which does the presigned PUT the iframe's CSP forbids (same
|
|
1135
|
+
// reason `upload` does; see there). `contentKind: 'text'` keeps the
|
|
1136
|
+
// text lane's must-be-a-text-type rule in force at any size.
|
|
1137
|
+
//
|
|
1138
|
+
// Deliberately NO `name` in this payload: a host that predates
|
|
1139
|
+
// in-place saves requires one for an upload, so it refuses this
|
|
1140
|
+
// message instead of silently creating a NEW document.
|
|
1141
|
+
res = await bridge.request('vault_documents_upload', {
|
|
1142
|
+
saveToDocumentId: params.documentId,
|
|
1143
|
+
bytes: bytesToBase64(routed.bytes),
|
|
1144
|
+
mimeType: params.contentType,
|
|
1145
|
+
contentKind: routed.contentKind,
|
|
1146
|
+
expectedRevision: params.expectedRevision,
|
|
1147
|
+
});
|
|
1148
|
+
}
|
|
1106
1149
|
// The bytes behind any cached download URL just changed.
|
|
1107
1150
|
downloadUrlCache.delete(params.documentId);
|
|
1108
1151
|
return res;
|
|
1109
1152
|
}, [bridge]);
|
|
1110
1153
|
const write = (0, react_1.useCallback)(async (params) => {
|
|
1154
|
+
// `write` has no second lane to fall back to, so say so here rather
|
|
1155
|
+
// than let the request die as an opaque 413 at the bridge.
|
|
1156
|
+
if (params.content.length > SAVE_CONTENT_BODY_LANE_MAX_BYTES ||
|
|
1157
|
+
!fitsBridgeBodyAsJsonText(params.content)) {
|
|
1158
|
+
throw new bridge_1.FiasBridgeError('write() takes small text only: at most 200,000 characters, and small enough to ' +
|
|
1159
|
+
'travel in one bridge request once JSON-escaped (quotes, backslashes and newlines ' +
|
|
1160
|
+
'double). Use upload() for larger documents.', 'INVALID_PARAMS');
|
|
1161
|
+
}
|
|
1111
1162
|
return bridge.request('vault_documents_write', {
|
|
1112
1163
|
name: params.name,
|
|
1113
1164
|
content: params.content,
|
|
@@ -1128,6 +1179,18 @@ function useVaultDocuments() {
|
|
|
1128
1179
|
// drop any cached URL for this document.
|
|
1129
1180
|
downloadUrlCache.delete(documentId);
|
|
1130
1181
|
}, [bridge]);
|
|
1182
|
+
const promoteToUserFiles = (0, react_1.useCallback)(async (documentId) => {
|
|
1183
|
+
// Rides the existing update op with a host-only flag. The HOST
|
|
1184
|
+
// intercepts it, shows the user its own confirmation, and performs the
|
|
1185
|
+
// promote from the user's session — nothing here (or anywhere in the
|
|
1186
|
+
// iframe) can promote a file without the user agreeing.
|
|
1187
|
+
const res = await bridge.request('vault_documents_update', {
|
|
1188
|
+
documentId,
|
|
1189
|
+
userVisible: true,
|
|
1190
|
+
});
|
|
1191
|
+
downloadUrlCache.delete(documentId);
|
|
1192
|
+
return res;
|
|
1193
|
+
}, [bridge]);
|
|
1131
1194
|
const deleteDocument = (0, react_1.useCallback)(async (documentId) => {
|
|
1132
1195
|
await bridge.request('vault_documents_delete', { documentId });
|
|
1133
1196
|
downloadUrlCache.delete(documentId);
|
|
@@ -1204,6 +1267,7 @@ function useVaultDocuments() {
|
|
|
1204
1267
|
getDownloadUrl,
|
|
1205
1268
|
write,
|
|
1206
1269
|
update,
|
|
1270
|
+
promoteToUserFiles,
|
|
1207
1271
|
delete: deleteDocument,
|
|
1208
1272
|
search,
|
|
1209
1273
|
attach,
|
|
@@ -1219,6 +1283,7 @@ function useVaultDocuments() {
|
|
|
1219
1283
|
getDownloadUrl,
|
|
1220
1284
|
write,
|
|
1221
1285
|
update,
|
|
1286
|
+
promoteToUserFiles,
|
|
1222
1287
|
deleteDocument,
|
|
1223
1288
|
search,
|
|
1224
1289
|
attach,
|
|
@@ -1228,6 +1293,74 @@ function useVaultDocuments() {
|
|
|
1228
1293
|
uploadFinalize,
|
|
1229
1294
|
]);
|
|
1230
1295
|
}
|
|
1296
|
+
/** Encoded-size ceiling of the text lane (`save_own_document_content`). Must
|
|
1297
|
+
* match the server's MAX_OWN_DOCUMENT_SAVE_BYTES; content past it is routed
|
|
1298
|
+
* through the host-uploaded lane rather than refused. */
|
|
1299
|
+
const SAVE_CONTENT_BODY_LANE_MAX_BYTES = 200000;
|
|
1300
|
+
/**
|
|
1301
|
+
* What the text may occupy ON THE WIRE. The bridge request body is capped at
|
|
1302
|
+
* 256 KB (the server's `PLUGIN_BRIDGE_BODY_LIMIT_BYTES`) and the text travels
|
|
1303
|
+
* inside it as a JSON string, where every `"`, `\` and newline doubles and
|
|
1304
|
+
* every other control character becomes six bytes. So 200 KB of text can be
|
|
1305
|
+
* 400 KB — or 1.2 MB — of request, and the answer is an opaque 413 rather
|
|
1306
|
+
* than a save. Routing therefore looks at the SERIALIZED size too, with ~16 KB
|
|
1307
|
+
* left for the envelope (ids, revision, content type, JSON keys).
|
|
1308
|
+
*/
|
|
1309
|
+
const BRIDGE_BODY_TEXT_MAX_SERIALIZED_BYTES = 256 * 1024 - 16 * 1024;
|
|
1310
|
+
/** True when `text` fits a bridge request body as a JSON string. Encodes at
|
|
1311
|
+
* most once, and not at all for short strings (a UTF-16 unit is at most six
|
|
1312
|
+
* bytes of JSON). */
|
|
1313
|
+
function fitsBridgeBodyAsJsonText(text, utf8Bytes) {
|
|
1314
|
+
if (text.length * 6 <= BRIDGE_BODY_TEXT_MAX_SERIALIZED_BYTES)
|
|
1315
|
+
return true;
|
|
1316
|
+
// JSON escapes are ASCII, so they cost the same in UTF-16 units as in
|
|
1317
|
+
// bytes: serialized bytes = UTF-8 bytes + the units escaping added.
|
|
1318
|
+
const escapeOverhead = JSON.stringify(text).length - text.length;
|
|
1319
|
+
const bytes = utf8Bytes ?? new TextEncoder().encode(text).length;
|
|
1320
|
+
return bytes + escapeOverhead <= BRIDGE_BODY_TEXT_MAX_SERIALIZED_BYTES;
|
|
1321
|
+
}
|
|
1322
|
+
/** Mirrors the server's `MAX_OWN_DOCUMENT_PRESIGNED_SAVE_BYTES` — the most an
|
|
1323
|
+
* in-place save accepts, pinned to what `readBytes` / `getBytes` can serve
|
|
1324
|
+
* back, so anything saved can be reopened. */
|
|
1325
|
+
const SAVE_CONTENT_MAX_BYTES = 15 * 1024 * 1024;
|
|
1326
|
+
/**
|
|
1327
|
+
* Pick the lane for `saveContent` from the content itself, encoding text AT
|
|
1328
|
+
* MOST ONCE (a UTF-16 code unit is at most 3 UTF-8 bytes, so short strings
|
|
1329
|
+
* are routed without encoding at all) and refusing anything past the cap
|
|
1330
|
+
* BEFORE the base64 step — which would otherwise build a 20 MB string on the
|
|
1331
|
+
* plugin's main thread for a save the server is certain to refuse.
|
|
1332
|
+
*/
|
|
1333
|
+
async function routeSaveContent(content) {
|
|
1334
|
+
let routed;
|
|
1335
|
+
if (typeof content === 'string') {
|
|
1336
|
+
// Short enough that neither limit can be reached, whatever it contains:
|
|
1337
|
+
// six bytes of JSON per unit is the worst case, three of UTF-8.
|
|
1338
|
+
if (content.length * 6 <= SAVE_CONTENT_BODY_LANE_MAX_BYTES) {
|
|
1339
|
+
return { lane: 'body', text: content };
|
|
1340
|
+
}
|
|
1341
|
+
const bytes = new TextEncoder().encode(content);
|
|
1342
|
+
routed =
|
|
1343
|
+
bytes.length <= SAVE_CONTENT_BODY_LANE_MAX_BYTES &&
|
|
1344
|
+
fitsBridgeBodyAsJsonText(content, bytes.length)
|
|
1345
|
+
? { lane: 'body', text: content }
|
|
1346
|
+
: { lane: 'host', bytes, contentKind: 'text' };
|
|
1347
|
+
}
|
|
1348
|
+
else {
|
|
1349
|
+
routed = { lane: 'host', bytes: await toUint8Array(content), contentKind: 'binary' };
|
|
1350
|
+
}
|
|
1351
|
+
if (routed.lane === 'host' && routed.bytes.length > SAVE_CONTENT_MAX_BYTES) {
|
|
1352
|
+
throw new bridge_1.FiasBridgeError(`Content is ${routed.bytes.length} bytes; saveContent accepts at most ` +
|
|
1353
|
+
`${SAVE_CONTENT_MAX_BYTES} bytes (15 MB).`, 'OWN_DOCUMENT_SAVE_TOO_LARGE');
|
|
1354
|
+
}
|
|
1355
|
+
return routed;
|
|
1356
|
+
}
|
|
1357
|
+
async function toUint8Array(content) {
|
|
1358
|
+
if (content instanceof Uint8Array)
|
|
1359
|
+
return content;
|
|
1360
|
+
if (content instanceof ArrayBuffer)
|
|
1361
|
+
return new Uint8Array(content);
|
|
1362
|
+
return new Uint8Array(await content.arrayBuffer());
|
|
1363
|
+
}
|
|
1231
1364
|
/** Decode base64 transport back to bytes (`get_consented_document_bytes`). */
|
|
1232
1365
|
function base64ToBytes(base64) {
|
|
1233
1366
|
const binary = atob(base64);
|
|
@@ -1448,7 +1581,15 @@ function useDataSubscription(collection, onInvalidate, options) {
|
|
|
1448
1581
|
* surface that reaches data your plugin did not create. Requires the
|
|
1449
1582
|
* `vault:user-documents:read` permission. Every access is user-consented
|
|
1450
1583
|
* (per-document grants created in the host picker, revocable any time in
|
|
1451
|
-
* Vault → App Access) and audited by the platform.
|
|
1584
|
+
* Vault → App Access) and audited by the platform.
|
|
1585
|
+
*
|
|
1586
|
+
* The picker offers the files of the workspace the user is acting in (the
|
|
1587
|
+
* host's workspace switcher — Personal, or an org workspace they belong to)
|
|
1588
|
+
* and `list()` returns the grants made in it; a document you hold a grant on
|
|
1589
|
+
* stays readable by id whichever workspace the user is in, because a grant
|
|
1590
|
+
* belongs to the workspace the document lives in. A plugin never names a
|
|
1591
|
+
* workspace; the host does. A member's role caps what they can share and
|
|
1592
|
+
* read. Proprietary-sensitivity
|
|
1452
1593
|
* documents are never grantable. For documents your plugin CREATES, use
|
|
1453
1594
|
* `useVaultDocuments` instead.
|
|
1454
1595
|
*/
|
|
@@ -1457,6 +1598,38 @@ function useVaultUserDocuments() {
|
|
|
1457
1598
|
const pick = (0, react_1.useCallback)(async (options) => {
|
|
1458
1599
|
return bridge.request('vault_documents_pick', {
|
|
1459
1600
|
maxDocuments: options?.maxDocuments,
|
|
1601
|
+
access: options?.access,
|
|
1602
|
+
});
|
|
1603
|
+
}, [bridge]);
|
|
1604
|
+
const requestEditAccess = (0, react_1.useCallback)(async (documentId) => {
|
|
1605
|
+
// The same host-mediated op as `pick`, naming one document. The HOST
|
|
1606
|
+
// decides whether it may skip the picker (only for a document this arche
|
|
1607
|
+
// already holds a grant on) — nothing here can.
|
|
1608
|
+
return bridge.request('vault_documents_pick', {
|
|
1609
|
+
maxDocuments: 1,
|
|
1610
|
+
access: 'write',
|
|
1611
|
+
documentId,
|
|
1612
|
+
});
|
|
1613
|
+
}, [bridge]);
|
|
1614
|
+
const saveContent = (0, react_1.useCallback)(async (params) => {
|
|
1615
|
+
const routed = await routeSaveContent(params.content);
|
|
1616
|
+
if (routed.lane === 'body') {
|
|
1617
|
+
return (0, entity_ops_1.invokeEntityOp)(bridge, entity_ops_1.VAULT_USER_DOCUMENTS_ENTITY_ID, 'save_consented_document_content', {
|
|
1618
|
+
documentId: params.documentId,
|
|
1619
|
+
content: routed.text,
|
|
1620
|
+
expectedRevision: params.expectedRevision,
|
|
1621
|
+
});
|
|
1622
|
+
}
|
|
1623
|
+
// Bytes, or text past the bridge body: the host does the presigned PUT
|
|
1624
|
+
// (the iframe's CSP forbids it). `saveTarget: 'user'` selects the
|
|
1625
|
+
// consented lane; no `name`, so an older host refuses rather than
|
|
1626
|
+
// creating a new document.
|
|
1627
|
+
return bridge.request('vault_documents_upload', {
|
|
1628
|
+
saveToDocumentId: params.documentId,
|
|
1629
|
+
saveTarget: 'user',
|
|
1630
|
+
bytes: bytesToBase64(routed.bytes),
|
|
1631
|
+
contentKind: routed.contentKind,
|
|
1632
|
+
expectedRevision: params.expectedRevision,
|
|
1460
1633
|
});
|
|
1461
1634
|
}, [bridge]);
|
|
1462
1635
|
const list = (0, react_1.useCallback)(async (options) => {
|
|
@@ -1482,7 +1655,7 @@ function useVaultUserDocuments() {
|
|
|
1482
1655
|
contentType: res.contentType,
|
|
1483
1656
|
};
|
|
1484
1657
|
}, [bridge]);
|
|
1485
|
-
return (0, react_1.useMemo)(() => ({ pick, list, get, getDownloadUrl, getBytes }), [pick, list, get, getDownloadUrl, getBytes]);
|
|
1658
|
+
return (0, react_1.useMemo)(() => ({ pick, requestEditAccess, saveContent, list, get, getDownloadUrl, getBytes }), [pick, requestEditAccess, saveContent, list, get, getDownloadUrl, getBytes]);
|
|
1486
1659
|
}
|
|
1487
1660
|
/**
|
|
1488
1661
|
* Refresh a community-asset URL this many ms before it expires. Larger than the
|