@colixsystems/widget-sdk 0.220.0 → 0.221.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/README.md +23 -8
- package/dist/contract.cjs +31 -13
- package/dist/contract.js +31 -13
- package/dist/hooks.js +21 -11
- package/dist/index.d.ts +1 -1
- package/dist/signing-hooks.js +6 -3
- package/package.json +1 -1
- package/src/contract.js +31 -13
package/README.md
CHANGED
|
@@ -84,7 +84,26 @@ See the design reference for the full architecture: [`docs/architecture/widget-m
|
|
|
84
84
|
|
|
85
85
|
## Status
|
|
86
86
|
|
|
87
|
-
`v0.
|
|
87
|
+
`v0.221.1` — pre-publish. The package surface (types, function names, export paths) is the v1 contract; runtime behaviour for some hooks is stubbed (each hook documents what's wired and what isn't). It is **not yet published to npm**.
|
|
88
|
+
|
|
89
|
+
### What's new in 0.221.1 (contract 1.177.1)
|
|
90
|
+
|
|
91
|
+
**`useFileSignature`: one failed poll no longer outlives the next answer.** `error` is the last call that failed, and a `refresh()` or `verify()` that succeeds now clears it. While `status` is `'pending'` an `error` means a poll failed: keep polling and keep showing the order. Fix only; nothing was added or removed.
|
|
92
|
+
|
|
93
|
+
### What's new in 0.221.0 (contract 1.177.0)
|
|
94
|
+
|
|
95
|
+
**A row's file goes in the row's record space, and `upload()` names the record per call.** A file a widget keeps in a `FILE` column belongs in that row's `record` space: exactly the people who may read the row may read the file, so a private table's attachments stay private, and a table that lets everyone read still serves them to logged-out visitors. A `public` file, by contrast, is readable by anyone holding its link whatever the table's permissions say. A row must exist before a file can be attached to it, and a create form only learns the new row's id inside its submit handler, after the hook rendered. So `upload(file, { recordId })` now takes the record per call, the way it already took `folderId`:
|
|
96
|
+
|
|
97
|
+
```js
|
|
98
|
+
const { create, update } = useDatastoreMutation(tableId);
|
|
99
|
+
const { upload } = useFilestoreUpload({ spaceType: "record" });
|
|
100
|
+
|
|
101
|
+
const row = await create({ Title: title }); // without the file
|
|
102
|
+
const stored = await upload(file, { recordId: row.id }); // into that row's space
|
|
103
|
+
await update(row.id, { Receipt: stored.id });
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
A per-call `recordId` wins over the hook's; outside a `record` space it is ignored. A record upload that names no record now rejects with `useFilestoreUpload: a record space needs a recordId` instead of the misleading `Sign in to upload`. A logged-out visitor cannot upload into a record space, so ask them to sign in to attach a file. `useCamera()` and `useImageEditor()` now point at `useFilestoreUpload` for the upload: their descriptions named `ctx.assets.upload`, which widget code cannot reach. Additive.
|
|
88
107
|
|
|
89
108
|
### What's new in 0.220.0 (contract 1.176.0)
|
|
90
109
|
|
|
@@ -1496,9 +1515,7 @@ const { edit } = useImageEditor();
|
|
|
1496
1515
|
const shot = await capture();
|
|
1497
1516
|
const small = await edit(shot.uri, [{ resize: { width: 800 } }], { format: "jpeg", compress: 0.8 });
|
|
1498
1517
|
|
|
1499
|
-
|
|
1500
|
-
fd.append("file", small.file);
|
|
1501
|
-
await ctx.assets.upload(fd);
|
|
1518
|
+
await upload(small.file); // upload from useFilestoreUpload()
|
|
1502
1519
|
```
|
|
1503
1520
|
|
|
1504
1521
|
`edit(uri, actions, options?)` applies `actions` in order and resolves the SAME normalised asset shape `useCamera()` yields, so capture → edit → upload is one code path on both hosts. Actions are `{ resize: { width?, height? } }`, `{ crop: { originX, originY, width, height } }`, `{ rotate: degrees }` and `{ flip: "horizontal" | "vertical" }`; output is `{ format: "jpeg" | "png" | "webp", compress, base64? }`.
|
|
@@ -1712,12 +1729,10 @@ When you take the `useCanWrite`-only route, omit the USER column for a guest (`i
|
|
|
1712
1729
|
|
|
1713
1730
|
**Dismissing the picker resolves `null`, not an error.** Backing out is the most common outcome, so it is deliberately not a rejection — your happy path needs no `try/catch`. A genuine failure (permission refused, no host broker) rejects with a structured `CameraError` (new named export) carrying a stable `.code` (`PERMISSION_DENIED` / `UNSUPPORTED` / `INTERNAL`).
|
|
1714
1731
|
|
|
1715
|
-
**One upload path on both hosts.** The resolved asset is normalised to `{ uri, name, mimeType, width, height, size, file }`, where `uri` is directly displayable (`<Image source={{ uri }} />`) and `file` is already the right upload part for the platform — a browser `File` on web, expo-file-system's `File` on native. (Never React Native's legacy `{ uri, name, type }` part: the native host streams the file off disk, and Expo's fetch rejects that part outright anyway. Do not branch on the part's type — an expo-file-system `File` is NOT `instanceof Blob` at runtime.) So the same
|
|
1732
|
+
**One upload path on both hosts.** The resolved asset is normalised to `{ uri, name, mimeType, width, height, size, file }`, where `uri` is directly displayable (`<Image source={{ uri }} />`) and `file` is already the right upload part for the platform — a browser `File` on web, expo-file-system's `File` on native. (Never React Native's legacy `{ uri, name, type }` part: the native host streams the file off disk, and Expo's fetch rejects that part outright anyway. Do not branch on the part's type — an expo-file-system `File` is NOT `instanceof Blob` at runtime.) So the same line works everywhere:
|
|
1716
1733
|
|
|
1717
1734
|
```js
|
|
1718
|
-
|
|
1719
|
-
fd.append("file", asset.file);
|
|
1720
|
-
await ctx.assets.upload(fd);
|
|
1735
|
+
await upload(asset.file); // upload from useFilestoreUpload()
|
|
1721
1736
|
```
|
|
1722
1737
|
|
|
1723
1738
|
`options` (`{ allowsEditing, quality }`) are **hints**: the Expo export applies both, the web camera applies `quality` only — so never depend on a cropped result. `reset()` clears the asset and releases it (on web that revokes the blob URL, which otherwise leaks for the life of the document). **Gate your camera button on `supported`** — a host that brokers no camera reports `false` rather than throwing. The web Player brokers it via `getUserMedia` — a live preview with a shutter — on every web host: it is the only camera a DESKTOP browser will open, and a phone reaches the same preview rather than its own camera app. It falls back to a file input (`capture="environment"`) only where getUserMedia is absent up front: an insecure origin or an older browser. Once the permission prompt has been shown the user gesture is spent, so a camera that then fails to open rejects (`PERMISSION_DENIED` if refused, `UNSUPPORTED` if there is no usable device) rather than falling back to a file dialog the browser would refuse to open. The Expo export uses `expo-image-picker`, whose config plugin declares the camera and photo-library permissions the runtime needs.
|
package/dist/contract.cjs
CHANGED
|
@@ -1369,7 +1369,7 @@ const HOOKS = [
|
|
|
1369
1369
|
"from the host context, builds a multipart FormData with the " +
|
|
1370
1370
|
"snake_case fields the backend reads verbatim (`space_type`, " +
|
|
1371
1371
|
"`owner_id`, `folder_id`, plus the binary `file`), and POSTs through " +
|
|
1372
|
-
"ctx.filestore.files.upload. `upload(file, { folderId?, compress?, fileName? })` " +
|
|
1372
|
+
"ctx.filestore.files.upload. `upload(file, { folderId?, recordId?, compress?, fileName? })` " +
|
|
1373
1373
|
"resolves to the created file row or throws the wire error. `file` must be a BLOB — a browser File on web, " +
|
|
1374
1374
|
"expo-file-system's File on native (what FilePicker / useCamera / useImageEditor hand back); Expo's fetch rejects " +
|
|
1375
1375
|
"React Native's legacy { uri, name, type } part with \"Unsupported FormDataPart implementation\". The stored name " +
|
|
@@ -1387,13 +1387,20 @@ const HOOKS = [
|
|
|
1387
1387
|
"ORIGINAL matters (a document archive, a photo the user re-downloads, " +
|
|
1388
1388
|
"anything with an exact-bytes requirement). " +
|
|
1389
1389
|
"ALWAYS pass spaceType explicitly — omitting it falls back to " +
|
|
1390
|
-
"'project', which is unreadable by a logged-out visitor.
|
|
1391
|
-
"
|
|
1392
|
-
"
|
|
1393
|
-
"
|
|
1394
|
-
"
|
|
1390
|
+
"'project', which is unreadable by a logged-out visitor. A file that " +
|
|
1391
|
+
"belongs to a datastore row — anything stored in a FILE column — goes " +
|
|
1392
|
+
"in that row's 'record' space, so exactly the people who may read the " +
|
|
1393
|
+
"row may read the file (logged-out visitors too, when the table lets " +
|
|
1394
|
+
"everyone read). A file can only be attached to a row that exists: " +
|
|
1395
|
+
"create the row, call upload(file, { recordId: row.id }) — the per-call " +
|
|
1396
|
+
"recordId names the row the hook could not know at render — then write " +
|
|
1397
|
+
"the returned id into the FILE column with update(). A logged-out " +
|
|
1398
|
+
"visitor cannot upload into a record space. For a file that belongs to " +
|
|
1399
|
+
"no row: 'public' for content the app shows to anyone browsing, " +
|
|
1400
|
+
"'project' for signed-in workspace users only, 'personal' for a file " +
|
|
1401
|
+
"private to the uploading app user.",
|
|
1395
1402
|
returnShape: {
|
|
1396
|
-
upload: "(file, { folderId?, compress?, fileName? }) => Promise<FilestoreFile>",
|
|
1403
|
+
upload: "(file, { folderId?, recordId?, compress?, fileName? }) => Promise<FilestoreFile>",
|
|
1397
1404
|
uploading: "boolean",
|
|
1398
1405
|
error: "Error | null",
|
|
1399
1406
|
lastUploaded: "FilestoreFile | null",
|
|
@@ -1552,7 +1559,10 @@ const HOOKS = [
|
|
|
1552
1559
|
"be cancelled. cancel() resolves to what became of the order, which is " +
|
|
1553
1560
|
"then still pending or already complete: read status after it, never " +
|
|
1554
1561
|
"assume 'cancelled'. nameRefusal is set when the order failed because " +
|
|
1555
|
-
"the signer's name cannot be shown on the document.
|
|
1562
|
+
"the signer's name cannot be shown on the document. error is the last " +
|
|
1563
|
+
"call that failed, and a refresh() or verify() that succeeds clears it: " +
|
|
1564
|
+
"while status is 'pending' an error means one poll failed, so keep " +
|
|
1565
|
+
"polling and keep showing the order. Reads " +
|
|
1556
1566
|
"ctx.filestore.signatures.{initiate,status,cancel,verify}.",
|
|
1557
1567
|
returnShape: {
|
|
1558
1568
|
status: "'pending' | 'complete' | 'failed' | 'cancelled' | null",
|
|
@@ -2818,8 +2828,8 @@ const HOOKS = [
|
|
|
2818
2828
|
"common case and is deliberately NOT an error, so no try/catch is needed on the happy path. They reject with a " +
|
|
2819
2829
|
"CameraError whose .code is one of PERMISSION_DENIED | UNSUPPORTED | INTERNAL. `asset.file` is already the right " +
|
|
2820
2830
|
"upload part for the host — a BLOB on both (a File on web, expo-file-system's File on native), because Expo's fetch " +
|
|
2821
|
-
"implements the web FormData spec and rejects React Native's legacy { uri, name, type } part outright:
|
|
2822
|
-
"
|
|
2831
|
+
"implements the web FormData spec and rejects React Native's legacy { uri, name, type } part outright: pass it " +
|
|
2832
|
+
"straight to upload(asset.file) from useFilestoreUpload — one code path on both platforms. reset() clears the asset and releases it. Check " +
|
|
2823
2833
|
"`supported` before rendering a camera button. options: { allowsEditing, quality } are HINTS the host honours where it " +
|
|
2824
2834
|
"can — the Expo export applies both, the web camera applies quality only, so never depend on a cropped result. " +
|
|
2825
2835
|
"Behaviour is otherwise identical on web (a getUserMedia preview on EVERY host, phones included; a file input " +
|
|
@@ -2885,7 +2895,7 @@ const HOOKS = [
|
|
|
2885
2895
|
"Editing is IMPERATIVE — call edit() from an event handler, never during render. " +
|
|
2886
2896
|
"edit(uri, actions, options?) applies `actions` IN ORDER and resolves the SAME normalised asset shape useCamera() " +
|
|
2887
2897
|
"yields — { uri, name, mimeType, width, height, size, base64?, file } — so capture -> edit -> upload is ONE code path: " +
|
|
2888
|
-
"
|
|
2898
|
+
"pass result.file to upload() from useFilestoreUpload. " +
|
|
2889
2899
|
"Each action entry carries exactly one of { resize: { width?, height? } } (aspect preserved when only one is given), " +
|
|
2890
2900
|
"{ crop: { originX, originY, width, height } }, { rotate: degrees } (positive is clockwise), or " +
|
|
2891
2901
|
"{ flip: \"horizontal\" | \"vertical\" }. A crop rect must lie INSIDE the surface the earlier actions produced, and " +
|
|
@@ -3022,7 +3032,7 @@ const PRIMITIVES = [
|
|
|
3022
3032
|
},
|
|
3023
3033
|
// sc-1378 — `<FilePicker accept onPick>` lets a widget surface a native
|
|
3024
3034
|
// file-picker on the web Player so end users can upload files to the
|
|
3025
|
-
// backend (paired with useFilestoreUpload
|
|
3035
|
+
// backend (paired with useFilestoreUpload). Web wraps
|
|
3026
3036
|
// a hidden DOM `<input type="file">`; native renders a disabled trigger
|
|
3027
3037
|
// and exposes `FilePicker.isSupported = false` until a vetted Expo
|
|
3028
3038
|
// picker pin lands.
|
|
@@ -5856,7 +5866,15 @@ const CONTRACT = deepFreeze({
|
|
|
5856
5866
|
// a widget never receives it. `account`'s photo actions are now
|
|
5857
5867
|
// optional members, and `user.email` is documented as nullable: an
|
|
5858
5868
|
// account without an email has none.
|
|
5859
|
-
|
|
5869
|
+
// 1.177.0: additive (sc-10937) — `useFilestoreUpload().upload(file,
|
|
5870
|
+
// { recordId })` names the record per call, so a widget can create a row
|
|
5871
|
+
// and attach to it in one handler. A FILE column's file goes in its
|
|
5872
|
+
// row's record space; useCamera / useImageEditor point at
|
|
5873
|
+
// useFilestoreUpload, since widget code cannot reach ctx.assets.
|
|
5874
|
+
// 1.177.1: fix (sc-10410) — a `refresh()` or `verify()` that succeeds
|
|
5875
|
+
// clears `useFileSignature`'s `error`; it used to keep one failed poll's
|
|
5876
|
+
// error for the rest of the order.
|
|
5877
|
+
version: "1.177.1",
|
|
5860
5878
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
5861
5879
|
hooks: HOOKS,
|
|
5862
5880
|
primitives: PRIMITIVES,
|
package/dist/contract.js
CHANGED
|
@@ -1369,7 +1369,7 @@ const HOOKS = [
|
|
|
1369
1369
|
"from the host context, builds a multipart FormData with the " +
|
|
1370
1370
|
"snake_case fields the backend reads verbatim (`space_type`, " +
|
|
1371
1371
|
"`owner_id`, `folder_id`, plus the binary `file`), and POSTs through " +
|
|
1372
|
-
"ctx.filestore.files.upload. `upload(file, { folderId?, compress?, fileName? })` " +
|
|
1372
|
+
"ctx.filestore.files.upload. `upload(file, { folderId?, recordId?, compress?, fileName? })` " +
|
|
1373
1373
|
"resolves to the created file row or throws the wire error. `file` must be a BLOB — a browser File on web, " +
|
|
1374
1374
|
"expo-file-system's File on native (what FilePicker / useCamera / useImageEditor hand back); Expo's fetch rejects " +
|
|
1375
1375
|
"React Native's legacy { uri, name, type } part with \"Unsupported FormDataPart implementation\". The stored name " +
|
|
@@ -1387,13 +1387,20 @@ const HOOKS = [
|
|
|
1387
1387
|
"ORIGINAL matters (a document archive, a photo the user re-downloads, " +
|
|
1388
1388
|
"anything with an exact-bytes requirement). " +
|
|
1389
1389
|
"ALWAYS pass spaceType explicitly — omitting it falls back to " +
|
|
1390
|
-
"'project', which is unreadable by a logged-out visitor.
|
|
1391
|
-
"
|
|
1392
|
-
"
|
|
1393
|
-
"
|
|
1394
|
-
"
|
|
1390
|
+
"'project', which is unreadable by a logged-out visitor. A file that " +
|
|
1391
|
+
"belongs to a datastore row — anything stored in a FILE column — goes " +
|
|
1392
|
+
"in that row's 'record' space, so exactly the people who may read the " +
|
|
1393
|
+
"row may read the file (logged-out visitors too, when the table lets " +
|
|
1394
|
+
"everyone read). A file can only be attached to a row that exists: " +
|
|
1395
|
+
"create the row, call upload(file, { recordId: row.id }) — the per-call " +
|
|
1396
|
+
"recordId names the row the hook could not know at render — then write " +
|
|
1397
|
+
"the returned id into the FILE column with update(). A logged-out " +
|
|
1398
|
+
"visitor cannot upload into a record space. For a file that belongs to " +
|
|
1399
|
+
"no row: 'public' for content the app shows to anyone browsing, " +
|
|
1400
|
+
"'project' for signed-in workspace users only, 'personal' for a file " +
|
|
1401
|
+
"private to the uploading app user.",
|
|
1395
1402
|
returnShape: {
|
|
1396
|
-
upload: "(file, { folderId?, compress?, fileName? }) => Promise<FilestoreFile>",
|
|
1403
|
+
upload: "(file, { folderId?, recordId?, compress?, fileName? }) => Promise<FilestoreFile>",
|
|
1397
1404
|
uploading: "boolean",
|
|
1398
1405
|
error: "Error | null",
|
|
1399
1406
|
lastUploaded: "FilestoreFile | null",
|
|
@@ -1552,7 +1559,10 @@ const HOOKS = [
|
|
|
1552
1559
|
"be cancelled. cancel() resolves to what became of the order, which is " +
|
|
1553
1560
|
"then still pending or already complete: read status after it, never " +
|
|
1554
1561
|
"assume 'cancelled'. nameRefusal is set when the order failed because " +
|
|
1555
|
-
"the signer's name cannot be shown on the document.
|
|
1562
|
+
"the signer's name cannot be shown on the document. error is the last " +
|
|
1563
|
+
"call that failed, and a refresh() or verify() that succeeds clears it: " +
|
|
1564
|
+
"while status is 'pending' an error means one poll failed, so keep " +
|
|
1565
|
+
"polling and keep showing the order. Reads " +
|
|
1556
1566
|
"ctx.filestore.signatures.{initiate,status,cancel,verify}.",
|
|
1557
1567
|
returnShape: {
|
|
1558
1568
|
status: "'pending' | 'complete' | 'failed' | 'cancelled' | null",
|
|
@@ -2818,8 +2828,8 @@ const HOOKS = [
|
|
|
2818
2828
|
"common case and is deliberately NOT an error, so no try/catch is needed on the happy path. They reject with a " +
|
|
2819
2829
|
"CameraError whose .code is one of PERMISSION_DENIED | UNSUPPORTED | INTERNAL. `asset.file` is already the right " +
|
|
2820
2830
|
"upload part for the host — a BLOB on both (a File on web, expo-file-system's File on native), because Expo's fetch " +
|
|
2821
|
-
"implements the web FormData spec and rejects React Native's legacy { uri, name, type } part outright:
|
|
2822
|
-
"
|
|
2831
|
+
"implements the web FormData spec and rejects React Native's legacy { uri, name, type } part outright: pass it " +
|
|
2832
|
+
"straight to upload(asset.file) from useFilestoreUpload — one code path on both platforms. reset() clears the asset and releases it. Check " +
|
|
2823
2833
|
"`supported` before rendering a camera button. options: { allowsEditing, quality } are HINTS the host honours where it " +
|
|
2824
2834
|
"can — the Expo export applies both, the web camera applies quality only, so never depend on a cropped result. " +
|
|
2825
2835
|
"Behaviour is otherwise identical on web (a getUserMedia preview on EVERY host, phones included; a file input " +
|
|
@@ -2885,7 +2895,7 @@ const HOOKS = [
|
|
|
2885
2895
|
"Editing is IMPERATIVE — call edit() from an event handler, never during render. " +
|
|
2886
2896
|
"edit(uri, actions, options?) applies `actions` IN ORDER and resolves the SAME normalised asset shape useCamera() " +
|
|
2887
2897
|
"yields — { uri, name, mimeType, width, height, size, base64?, file } — so capture -> edit -> upload is ONE code path: " +
|
|
2888
|
-
"
|
|
2898
|
+
"pass result.file to upload() from useFilestoreUpload. " +
|
|
2889
2899
|
"Each action entry carries exactly one of { resize: { width?, height? } } (aspect preserved when only one is given), " +
|
|
2890
2900
|
"{ crop: { originX, originY, width, height } }, { rotate: degrees } (positive is clockwise), or " +
|
|
2891
2901
|
"{ flip: \"horizontal\" | \"vertical\" }. A crop rect must lie INSIDE the surface the earlier actions produced, and " +
|
|
@@ -3022,7 +3032,7 @@ const PRIMITIVES = [
|
|
|
3022
3032
|
},
|
|
3023
3033
|
// sc-1378 — `<FilePicker accept onPick>` lets a widget surface a native
|
|
3024
3034
|
// file-picker on the web Player so end users can upload files to the
|
|
3025
|
-
// backend (paired with useFilestoreUpload
|
|
3035
|
+
// backend (paired with useFilestoreUpload). Web wraps
|
|
3026
3036
|
// a hidden DOM `<input type="file">`; native renders a disabled trigger
|
|
3027
3037
|
// and exposes `FilePicker.isSupported = false` until a vetted Expo
|
|
3028
3038
|
// picker pin lands.
|
|
@@ -5852,7 +5862,15 @@ const CONTRACT = deepFreeze({
|
|
|
5852
5862
|
// a widget never receives it. `account`'s photo actions are now
|
|
5853
5863
|
// optional members, and `user.email` is documented as nullable: an
|
|
5854
5864
|
// account without an email has none.
|
|
5855
|
-
|
|
5865
|
+
// 1.177.0: additive (sc-10937) — `useFilestoreUpload().upload(file,
|
|
5866
|
+
// { recordId })` names the record per call, so a widget can create a row
|
|
5867
|
+
// and attach to it in one handler. A FILE column's file goes in its
|
|
5868
|
+
// row's record space; useCamera / useImageEditor point at
|
|
5869
|
+
// useFilestoreUpload, since widget code cannot reach ctx.assets.
|
|
5870
|
+
// 1.177.1: fix (sc-10410) — a `refresh()` or `verify()` that succeeds
|
|
5871
|
+
// clears `useFileSignature`'s `error`; it used to keep one failed poll's
|
|
5872
|
+
// error for the rest of the order.
|
|
5873
|
+
version: "1.177.1",
|
|
5856
5874
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
5857
5875
|
hooks: HOOKS,
|
|
5858
5876
|
primitives: PRIMITIVES,
|
package/dist/hooks.js
CHANGED
|
@@ -4700,7 +4700,10 @@ export function useFilestoreFile(fileId) {
|
|
|
4700
4700
|
* same way the read hooks do (tenant for PROJECT, app user for PERSONAL, the
|
|
4701
4701
|
* record for RECORD) and posts a multipart FormData to `ctx.filestore.files.upload`.
|
|
4702
4702
|
*
|
|
4703
|
-
* `upload(file, {
|
|
4703
|
+
* sc-10937: `upload(file, { recordId })` names the record per call, so a
|
|
4704
|
+
* widget can create a row and attach to it in the same handler.
|
|
4705
|
+
*
|
|
4706
|
+
* `upload(file, { folderId?, recordId?, fileName? })` takes a browser `File` on the web
|
|
4704
4707
|
* Player and expo-file-system's `File` on the Expo export, as `<FilePicker>`,
|
|
4705
4708
|
* `useCamera()` and `useImageEditor()` all hand back. Never React Native's
|
|
4706
4709
|
* legacy `{ uri, name, type }` part: the native host streams the file off
|
|
@@ -4734,7 +4737,8 @@ export function useFilestoreUpload(options) {
|
|
|
4734
4737
|
folderId: defaultFolderId = null,
|
|
4735
4738
|
compress: defaultCompress = true,
|
|
4736
4739
|
} = options || {};
|
|
4737
|
-
const
|
|
4740
|
+
const isRecordSpace = String(spaceType || "project").toUpperCase() === "RECORD";
|
|
4741
|
+
const defaultOwnerId = _filestoreOwnerId(ctx, spaceType, recordId);
|
|
4738
4742
|
|
|
4739
4743
|
const [uploading, setUploading] = useState(false);
|
|
4740
4744
|
const [error, setError] = useState(null);
|
|
@@ -4746,19 +4750,25 @@ export function useFilestoreUpload(options) {
|
|
|
4746
4750
|
const upload = useCallback(
|
|
4747
4751
|
async (file, overrides) => {
|
|
4748
4752
|
if (!file) throw new Error("useFilestoreUpload: file is required");
|
|
4753
|
+
const hasOverride = (key) =>
|
|
4754
|
+
Boolean(overrides) && Object.prototype.hasOwnProperty.call(overrides, key);
|
|
4755
|
+
const ownerId =
|
|
4756
|
+
isRecordSpace && hasOverride("recordId")
|
|
4757
|
+
? overrides.recordId || null
|
|
4758
|
+
: defaultOwnerId;
|
|
4759
|
+
if (isRecordSpace && !ownerId) {
|
|
4760
|
+
// Not "Sign in": the widget forgot which row to attach to.
|
|
4761
|
+
const err = new Error("useFilestoreUpload: a record space needs a recordId");
|
|
4762
|
+
setError(err);
|
|
4763
|
+
throw err;
|
|
4764
|
+
}
|
|
4749
4765
|
if (!ownerId) {
|
|
4750
4766
|
const err = new Error("Sign in to upload");
|
|
4751
4767
|
setError(err);
|
|
4752
4768
|
throw err;
|
|
4753
4769
|
}
|
|
4754
|
-
const folderId =
|
|
4755
|
-
|
|
4756
|
-
? overrides.folderId
|
|
4757
|
-
: defaultFolderId;
|
|
4758
|
-
const compress =
|
|
4759
|
-
overrides && Object.prototype.hasOwnProperty.call(overrides, "compress")
|
|
4760
|
-
? overrides.compress
|
|
4761
|
-
: defaultCompress;
|
|
4770
|
+
const folderId = hasOverride("folderId") ? overrides.folderId : defaultFolderId;
|
|
4771
|
+
const compress = hasOverride("compress") ? overrides.compress : defaultCompress;
|
|
4762
4772
|
const form = new FormData();
|
|
4763
4773
|
form.append("space_type", String(spaceType || "project").toUpperCase());
|
|
4764
4774
|
form.append("owner_id", ownerId);
|
|
@@ -4800,7 +4810,7 @@ export function useFilestoreUpload(options) {
|
|
|
4800
4810
|
throw err;
|
|
4801
4811
|
}
|
|
4802
4812
|
},
|
|
4803
|
-
[
|
|
4813
|
+
[defaultOwnerId, isRecordSpace, defaultFolderId, defaultCompress, spaceType],
|
|
4804
4814
|
);
|
|
4805
4815
|
|
|
4806
4816
|
return { upload, uploading, error, lastUploaded };
|
package/dist/index.d.ts
CHANGED
|
@@ -2452,7 +2452,7 @@ export interface CameraAsset {
|
|
|
2452
2452
|
/**
|
|
2453
2453
|
* Ready-to-upload part — a Blob on both hosts: a `File` on web,
|
|
2454
2454
|
* expo-file-system's `File` on native. Expo's fetch rejects any other shape.
|
|
2455
|
-
*
|
|
2455
|
+
* Pass it to `upload(file)` from `useFilestoreUpload`.
|
|
2456
2456
|
*/
|
|
2457
2457
|
file: unknown;
|
|
2458
2458
|
}
|
package/dist/signing-hooks.js
CHANGED
|
@@ -40,7 +40,9 @@ const userIdOf = (ctx) => (ctx.user && ctx.user.id) || "";
|
|
|
40
40
|
* with `hintCode: "sealing"`. `hintCode` is where the order stands;
|
|
41
41
|
* `nameRefusal` (`{ reason, characters }`) is set when it failed because the
|
|
42
42
|
* signer's name cannot be shown. `autoStartToken` is set only while opening
|
|
43
|
-
* BankID can still help.
|
|
43
|
+
* BankID can still help. `error` is the last call that failed; a `refresh()`
|
|
44
|
+
* or `verify()` that succeeds clears it, so one failed poll does not outlive
|
|
45
|
+
* the next answer. Reads
|
|
44
46
|
* `ctx.filestore.signatures.{initiate,status,cancel,verify}`. When `fileId` is
|
|
45
47
|
* null/empty the hook collapses to a no-op.
|
|
46
48
|
*
|
|
@@ -128,7 +130,8 @@ export function useFileSignature(fileId, existingSignatureId = null) {
|
|
|
128
130
|
const res = await sigRef.current.status(id);
|
|
129
131
|
// A cancel or a new order since this poll was sent: its answer is old news.
|
|
130
132
|
if (answersRef.current !== asked) return;
|
|
131
|
-
|
|
133
|
+
// An answer means the order can be reached again: an earlier failure is over.
|
|
134
|
+
apply(forScope, (base) => ({ order: signOrderStateAfterAnswer(base.order, res), error: null }));
|
|
132
135
|
} catch (err) {
|
|
133
136
|
if (answersRef.current !== asked) return;
|
|
134
137
|
apply(forScope, () => ({ error: toPermissionError(err) }));
|
|
@@ -156,7 +159,7 @@ export function useFileSignature(fileId, existingSignatureId = null) {
|
|
|
156
159
|
if (!id) return null;
|
|
157
160
|
try {
|
|
158
161
|
const v = await sigRef.current.verify(id);
|
|
159
|
-
apply(forScope, () => ({ verdict: v }));
|
|
162
|
+
apply(forScope, () => ({ verdict: v, error: null }));
|
|
160
163
|
return v;
|
|
161
164
|
} catch (err) {
|
|
162
165
|
const refusal = toPermissionError(err);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@colixsystems/widget-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.221.1",
|
|
4
4
|
"description": "Common widget interface for AppStudio. Implements WidgetManifest, WidgetContext, property schema, and helper hooks.",
|
|
5
5
|
"homepage": "https://github.com/Colix-AB/AppStudio",
|
|
6
6
|
"type": "module",
|
package/src/contract.js
CHANGED
|
@@ -1369,7 +1369,7 @@ const HOOKS = [
|
|
|
1369
1369
|
"from the host context, builds a multipart FormData with the " +
|
|
1370
1370
|
"snake_case fields the backend reads verbatim (`space_type`, " +
|
|
1371
1371
|
"`owner_id`, `folder_id`, plus the binary `file`), and POSTs through " +
|
|
1372
|
-
"ctx.filestore.files.upload. `upload(file, { folderId?, compress?, fileName? })` " +
|
|
1372
|
+
"ctx.filestore.files.upload. `upload(file, { folderId?, recordId?, compress?, fileName? })` " +
|
|
1373
1373
|
"resolves to the created file row or throws the wire error. `file` must be a BLOB — a browser File on web, " +
|
|
1374
1374
|
"expo-file-system's File on native (what FilePicker / useCamera / useImageEditor hand back); Expo's fetch rejects " +
|
|
1375
1375
|
"React Native's legacy { uri, name, type } part with \"Unsupported FormDataPart implementation\". The stored name " +
|
|
@@ -1387,13 +1387,20 @@ const HOOKS = [
|
|
|
1387
1387
|
"ORIGINAL matters (a document archive, a photo the user re-downloads, " +
|
|
1388
1388
|
"anything with an exact-bytes requirement). " +
|
|
1389
1389
|
"ALWAYS pass spaceType explicitly — omitting it falls back to " +
|
|
1390
|
-
"'project', which is unreadable by a logged-out visitor.
|
|
1391
|
-
"
|
|
1392
|
-
"
|
|
1393
|
-
"
|
|
1394
|
-
"
|
|
1390
|
+
"'project', which is unreadable by a logged-out visitor. A file that " +
|
|
1391
|
+
"belongs to a datastore row — anything stored in a FILE column — goes " +
|
|
1392
|
+
"in that row's 'record' space, so exactly the people who may read the " +
|
|
1393
|
+
"row may read the file (logged-out visitors too, when the table lets " +
|
|
1394
|
+
"everyone read). A file can only be attached to a row that exists: " +
|
|
1395
|
+
"create the row, call upload(file, { recordId: row.id }) — the per-call " +
|
|
1396
|
+
"recordId names the row the hook could not know at render — then write " +
|
|
1397
|
+
"the returned id into the FILE column with update(). A logged-out " +
|
|
1398
|
+
"visitor cannot upload into a record space. For a file that belongs to " +
|
|
1399
|
+
"no row: 'public' for content the app shows to anyone browsing, " +
|
|
1400
|
+
"'project' for signed-in workspace users only, 'personal' for a file " +
|
|
1401
|
+
"private to the uploading app user.",
|
|
1395
1402
|
returnShape: {
|
|
1396
|
-
upload: "(file, { folderId?, compress?, fileName? }) => Promise<FilestoreFile>",
|
|
1403
|
+
upload: "(file, { folderId?, recordId?, compress?, fileName? }) => Promise<FilestoreFile>",
|
|
1397
1404
|
uploading: "boolean",
|
|
1398
1405
|
error: "Error | null",
|
|
1399
1406
|
lastUploaded: "FilestoreFile | null",
|
|
@@ -1552,7 +1559,10 @@ const HOOKS = [
|
|
|
1552
1559
|
"be cancelled. cancel() resolves to what became of the order, which is " +
|
|
1553
1560
|
"then still pending or already complete: read status after it, never " +
|
|
1554
1561
|
"assume 'cancelled'. nameRefusal is set when the order failed because " +
|
|
1555
|
-
"the signer's name cannot be shown on the document.
|
|
1562
|
+
"the signer's name cannot be shown on the document. error is the last " +
|
|
1563
|
+
"call that failed, and a refresh() or verify() that succeeds clears it: " +
|
|
1564
|
+
"while status is 'pending' an error means one poll failed, so keep " +
|
|
1565
|
+
"polling and keep showing the order. Reads " +
|
|
1556
1566
|
"ctx.filestore.signatures.{initiate,status,cancel,verify}.",
|
|
1557
1567
|
returnShape: {
|
|
1558
1568
|
status: "'pending' | 'complete' | 'failed' | 'cancelled' | null",
|
|
@@ -2818,8 +2828,8 @@ const HOOKS = [
|
|
|
2818
2828
|
"common case and is deliberately NOT an error, so no try/catch is needed on the happy path. They reject with a " +
|
|
2819
2829
|
"CameraError whose .code is one of PERMISSION_DENIED | UNSUPPORTED | INTERNAL. `asset.file` is already the right " +
|
|
2820
2830
|
"upload part for the host — a BLOB on both (a File on web, expo-file-system's File on native), because Expo's fetch " +
|
|
2821
|
-
"implements the web FormData spec and rejects React Native's legacy { uri, name, type } part outright:
|
|
2822
|
-
"
|
|
2831
|
+
"implements the web FormData spec and rejects React Native's legacy { uri, name, type } part outright: pass it " +
|
|
2832
|
+
"straight to upload(asset.file) from useFilestoreUpload — one code path on both platforms. reset() clears the asset and releases it. Check " +
|
|
2823
2833
|
"`supported` before rendering a camera button. options: { allowsEditing, quality } are HINTS the host honours where it " +
|
|
2824
2834
|
"can — the Expo export applies both, the web camera applies quality only, so never depend on a cropped result. " +
|
|
2825
2835
|
"Behaviour is otherwise identical on web (a getUserMedia preview on EVERY host, phones included; a file input " +
|
|
@@ -2885,7 +2895,7 @@ const HOOKS = [
|
|
|
2885
2895
|
"Editing is IMPERATIVE — call edit() from an event handler, never during render. " +
|
|
2886
2896
|
"edit(uri, actions, options?) applies `actions` IN ORDER and resolves the SAME normalised asset shape useCamera() " +
|
|
2887
2897
|
"yields — { uri, name, mimeType, width, height, size, base64?, file } — so capture -> edit -> upload is ONE code path: " +
|
|
2888
|
-
"
|
|
2898
|
+
"pass result.file to upload() from useFilestoreUpload. " +
|
|
2889
2899
|
"Each action entry carries exactly one of { resize: { width?, height? } } (aspect preserved when only one is given), " +
|
|
2890
2900
|
"{ crop: { originX, originY, width, height } }, { rotate: degrees } (positive is clockwise), or " +
|
|
2891
2901
|
"{ flip: \"horizontal\" | \"vertical\" }. A crop rect must lie INSIDE the surface the earlier actions produced, and " +
|
|
@@ -3022,7 +3032,7 @@ const PRIMITIVES = [
|
|
|
3022
3032
|
},
|
|
3023
3033
|
// sc-1378 — `<FilePicker accept onPick>` lets a widget surface a native
|
|
3024
3034
|
// file-picker on the web Player so end users can upload files to the
|
|
3025
|
-
// backend (paired with useFilestoreUpload
|
|
3035
|
+
// backend (paired with useFilestoreUpload). Web wraps
|
|
3026
3036
|
// a hidden DOM `<input type="file">`; native renders a disabled trigger
|
|
3027
3037
|
// and exposes `FilePicker.isSupported = false` until a vetted Expo
|
|
3028
3038
|
// picker pin lands.
|
|
@@ -5852,7 +5862,15 @@ const CONTRACT = deepFreeze({
|
|
|
5852
5862
|
// a widget never receives it. `account`'s photo actions are now
|
|
5853
5863
|
// optional members, and `user.email` is documented as nullable: an
|
|
5854
5864
|
// account without an email has none.
|
|
5855
|
-
|
|
5865
|
+
// 1.177.0: additive (sc-10937) — `useFilestoreUpload().upload(file,
|
|
5866
|
+
// { recordId })` names the record per call, so a widget can create a row
|
|
5867
|
+
// and attach to it in one handler. A FILE column's file goes in its
|
|
5868
|
+
// row's record space; useCamera / useImageEditor point at
|
|
5869
|
+
// useFilestoreUpload, since widget code cannot reach ctx.assets.
|
|
5870
|
+
// 1.177.1: fix (sc-10410) — a `refresh()` or `verify()` that succeeds
|
|
5871
|
+
// clears `useFileSignature`'s `error`; it used to keep one failed poll's
|
|
5872
|
+
// error for the rest of the order.
|
|
5873
|
+
version: "1.177.1",
|
|
5856
5874
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
5857
5875
|
hooks: HOOKS,
|
|
5858
5876
|
primitives: PRIMITIVES,
|