@colixsystems/widget-sdk 0.30.0 → 0.32.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 +17 -12
- package/dist/contract.cjs +131 -5
- package/dist/contract.js +131 -5
- package/dist/hooks.js +399 -2
- package/dist/index.d.ts +6 -0
- package/dist/index.js +4 -0
- package/dist/index.native.js +4 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -46,27 +46,32 @@ See the design reference for the full architecture: [`docs/architecture/widget-m
|
|
|
46
46
|
|
|
47
47
|
## Status
|
|
48
48
|
|
|
49
|
-
`v0.
|
|
49
|
+
`v0.32.0` — 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**.
|
|
50
50
|
|
|
51
|
-
### What's new in 0.
|
|
51
|
+
### What's new in 0.32.0
|
|
52
52
|
|
|
53
|
-
**New `
|
|
53
|
+
**New `fieldList` propertySchema type (REQ-WBLT-03 / #139).** A per-field repeater that composes a form from author-picked columns of a sibling `tableRef` (default `tableId`, override via `ui.tableProp`). Each row stores `{ id, columnId, kind, label, required, optionsSource, inlineOptions, optionsTableId, optionsValueColumn, optionsLabelColumn }`; `kind` is one of `auto` / `singleChoice` / `multiChoice` and is gated by the column's `data_type` so an array column can't render as a scalar picker. The Studio renders the built-in **Form Builder** widget's editor through `SchemaForm` instead of the hand-rolled block it shipped with — removes the last data widget from `LEGACY_EDITOR_TYPES`. Persisted field shape is unchanged so existing pages keep rendering. **`CONTRACT.version` → `1.22.0`** (additive: one new propertySchema type). No existing export or type changed signature.
|
|
54
54
|
|
|
55
|
-
### What's new in 0.
|
|
55
|
+
### What's new in 0.31.0
|
|
56
56
|
|
|
57
|
-
**
|
|
57
|
+
**Already-signed file state (REQ-SIGN).** So a file browser can show which files are signed (and not re-sign blindly):
|
|
58
|
+
- `useFileSignatures(fileIds)` → `{ signaturesByFileId, loading, error, refetch }` — a batch "is this file signed?" lookup. `signaturesByFileId` maps each signed, accessible file id to its latest `{ signature_id, signer_name, signed_at }`; unsigned files are absent. One request, no N+1. Reads `ctx.filestore.signatures.list` (new `@colixsystems/filestore-client` **0.3.0** method).
|
|
59
|
+
- `useFileSignature(fileId, existingSignatureId?)` gained an optional second arg: pass an already-signed file's signature id to seed the `complete` state and `verify()` it **without** opening a new order. Re-signing then requires an explicit `initiate()`.
|
|
58
60
|
|
|
59
|
-
|
|
61
|
+
`CONTRACT.version` → `1.21.0` (additive — the new arg is optional, no existing hook changed).
|
|
60
62
|
|
|
61
|
-
|
|
63
|
+
### What's new in 0.30.0
|
|
62
64
|
|
|
63
|
-
|
|
65
|
+
**Filestore browsing + BankID file signing for widgets (REQ-FS / REQ-SIGN).** Three new hooks read a newly-injected `ctx.filestore` (the `@colixsystems/filestore-client`, now constructed by both the web and native hosts):
|
|
66
|
+
- `useFilestoreFiles({ spaceType, folderId?, q?, type? })` → `{ files, loading, error, refetch }` — browses the end-user's Filestore space. The hook resolves `owner_id` from the host context (tenant for a project space, the app user for a personal space), so the widget only picks the space.
|
|
67
|
+
- `useFilestoreFolders({ spaceType, parentFolderId?, q?, enabled? })` → `{ folders, loading, error, refetch }` — the folder-navigation companion to `useFilestoreFiles`; pass `enabled:false` to suspend fetching.
|
|
68
|
+
- `useFileSignature(fileId)` → `{ status, qr, signerName, verdict, initiate, refresh, cancel, verify, … }` — drives a BankID signing flow for a file (the backend hashes the bytes server-side, binds the digest into the signature, and verifies the proof offline).
|
|
64
69
|
|
|
65
|
-
|
|
70
|
+
`CONTRACT.version` → `1.20.0` (additive — no existing hook changed).
|
|
66
71
|
|
|
67
|
-
### What's new in 0.
|
|
72
|
+
### What's new in 0.29.0
|
|
68
73
|
|
|
69
|
-
**
|
|
74
|
+
**The SIGNATURE column type and its hook are retired (REQ-SIGN).** `useSignature(tableId, recordId)` and the datastore-client `sign(recordId)` namespace are **removed** — signing a single record/column was the wrong granularity. BankID signing is being rebuilt around a standalone, polymorphic **Signature subject model** (sign a file first, then whole records and policy text), where the signature is cryptographically bound to the exact bytes signed (the file's SHA-256 embedded in the BankID signature) and verified offline against the pinned `BankID Root CA v1`. New SDK hooks for that model will land as the backend ships. `CONTRACT.version` → `1.19.0` (pre-1.0 breaking removal of unreleased hooks; kept monotonic).
|
|
70
75
|
|
|
71
76
|
### What's new in 0.25.0
|
|
72
77
|
|
|
@@ -103,7 +108,7 @@ See the design reference for the full architecture: [`docs/architecture/widget-m
|
|
|
103
108
|
- **`expo-linear-gradient`** (`web`/`native`) — the cross-platform gradient.
|
|
104
109
|
- **`lottie-react-native`** (`native`) + **`lottie-react`** (`web`) — Lottie animations, split-impl.
|
|
105
110
|
- **`react-native-webview`** (`native`) — embedded web content (pair with an `<iframe>` on web).
|
|
106
|
-
- **Parity (
|
|
111
|
+
- **Parity (widget-parity skill):** the compiler now pins the native-module members in the exported Expo app's `package.json` and emits the **`react-native-reanimated/plugin`** in `babel.config.js` **unconditionally** (previously only for the sidebar-drawer shell), so a baked widget that imports any of these resolves and bundles on native exactly as it renders in the web Player.
|
|
107
112
|
- **`CONTRACT.version` → `1.13.0`** (additive: new vetted import entries only). No existing export, type, manifest field, hook, or banned-API list changed.
|
|
108
113
|
|
|
109
114
|
### What's new in 0.22.0
|
package/dist/contract.cjs
CHANGED
|
@@ -99,7 +99,7 @@ const HOOKS = [
|
|
|
99
99
|
'style (flex: 1 / height: "100%"); widgets with no useful filled form ' +
|
|
100
100
|
"may ignore it. Defaults to false wherever the host has not opted the " +
|
|
101
101
|
"widget into filling, so calling it is always safe. The SAME value is " +
|
|
102
|
-
"injected by the web Player and the native export (
|
|
102
|
+
"injected by the web Player and the native export (widget-parity skill), so a " +
|
|
103
103
|
"widget's fill behaviour is identical on both platforms.",
|
|
104
104
|
returnShape: {
|
|
105
105
|
"(returns)": "boolean",
|
|
@@ -155,6 +155,87 @@ const HOOKS = [
|
|
|
155
155
|
requiredContextSlice: ["files.get"],
|
|
156
156
|
scopes: null,
|
|
157
157
|
},
|
|
158
|
+
{
|
|
159
|
+
name: "useFilestoreFiles",
|
|
160
|
+
signature: "useFilestoreFiles({ spaceType, folderId?, q?, type? })",
|
|
161
|
+
description:
|
|
162
|
+
"Browse the end-user's Filestore files in a project or personal space. " +
|
|
163
|
+
"The hook resolves owner_id from the host context (tenant for project, " +
|
|
164
|
+
"app user for personal) — the widget only chooses the space. Reads " +
|
|
165
|
+
"ctx.filestore.files.list and unwraps { data, meta } to the files array.",
|
|
166
|
+
returnShape: {
|
|
167
|
+
files: "FilestoreFile[]",
|
|
168
|
+
loading: "boolean",
|
|
169
|
+
error: "Error | null",
|
|
170
|
+
refetch: "() => Promise<void>",
|
|
171
|
+
},
|
|
172
|
+
requiredContextSlice: ["filestore.files"],
|
|
173
|
+
scopes: ["files.read:*"],
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
name: "useFilestoreFolders",
|
|
177
|
+
signature: "useFilestoreFolders({ spaceType, parentFolderId?, q?, enabled? })",
|
|
178
|
+
description:
|
|
179
|
+
"Browse the end-user's Filestore folders in a project or personal space, " +
|
|
180
|
+
"mirroring useFilestoreFiles for subfolder navigation. The hook resolves " +
|
|
181
|
+
"owner_id from the host context; pass enabled:false to suspend fetching. " +
|
|
182
|
+
"Reads ctx.filestore.folders.list and unwraps { data, meta } to the array.",
|
|
183
|
+
returnShape: {
|
|
184
|
+
folders: "FilestoreFolder[]",
|
|
185
|
+
loading: "boolean",
|
|
186
|
+
error: "Error | null",
|
|
187
|
+
refetch: "() => Promise<void>",
|
|
188
|
+
},
|
|
189
|
+
requiredContextSlice: ["filestore.folders"],
|
|
190
|
+
scopes: ["files.read:*"],
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
name: "useFileSignature",
|
|
194
|
+
signature: "useFileSignature(fileId, existingSignatureId?)",
|
|
195
|
+
description:
|
|
196
|
+
"Drive a BankID signing flow for one Filestore file. initiate() opens a " +
|
|
197
|
+
"sign order (the backend hashes the bytes + binds the digest), refresh() " +
|
|
198
|
+
"polls (fresh QR while pending — render with the Image primitive), " +
|
|
199
|
+
"cancel() aborts, verify() returns the offline verdict. Pass " +
|
|
200
|
+
"existingSignatureId for an already-signed file to seed the complete " +
|
|
201
|
+
"state + verify() without re-signing. Reads " +
|
|
202
|
+
"ctx.filestore.signatures.{initiate,status,cancel,verify}.",
|
|
203
|
+
returnShape: {
|
|
204
|
+
status: "'pending' | 'complete' | 'failed' | 'cancelled' | null",
|
|
205
|
+
qr: "string | null // PNG data-URL of the animated BankID QR",
|
|
206
|
+
autoStartToken: "string | null",
|
|
207
|
+
message: "string | null",
|
|
208
|
+
signerName: "string | null",
|
|
209
|
+
signedAt: "string | null",
|
|
210
|
+
verdict: "{ valid, checks, content_status, ... } | null",
|
|
211
|
+
loading: "boolean",
|
|
212
|
+
error: "PermissionError | null",
|
|
213
|
+
initiate: "() => Promise<{ signature_id, qr, auto_start_token, status }>",
|
|
214
|
+
refresh: "() => Promise<void>",
|
|
215
|
+
cancel: "() => Promise<void>",
|
|
216
|
+
verify: "() => Promise<verdict>",
|
|
217
|
+
},
|
|
218
|
+
requiredContextSlice: ["filestore.signatures"],
|
|
219
|
+
scopes: ["files.write:*"],
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
name: "useFileSignatures",
|
|
223
|
+
signature: "useFileSignatures(fileIds)",
|
|
224
|
+
description:
|
|
225
|
+
"Batch 'is this file signed?' lookup for a set of file ids. Returns a map " +
|
|
226
|
+
"of each signed, accessible file id to its latest signature; unsigned " +
|
|
227
|
+
"files are absent. Lets a file browser mark already-signed files in one " +
|
|
228
|
+
"request. Reads ctx.filestore.signatures.list and unwraps { data, meta }.",
|
|
229
|
+
returnShape: {
|
|
230
|
+
signaturesByFileId:
|
|
231
|
+
"{ [fileId]: { signature_id, signer_name, signed_at } }",
|
|
232
|
+
loading: "boolean",
|
|
233
|
+
error: "Error | null",
|
|
234
|
+
refetch: "() => Promise<void>",
|
|
235
|
+
},
|
|
236
|
+
requiredContextSlice: ["filestore.signatures"],
|
|
237
|
+
scopes: ["files.read:*"],
|
|
238
|
+
},
|
|
158
239
|
{
|
|
159
240
|
name: "useDatastoreQuery",
|
|
160
241
|
signature: "useDatastoreQuery(tableId, options?)",
|
|
@@ -741,6 +822,16 @@ const WIDGET_CONTEXT_SHAPE = {
|
|
|
741
822
|
required: true,
|
|
742
823
|
fields: { get: "function" },
|
|
743
824
|
},
|
|
825
|
+
filestore: {
|
|
826
|
+
description:
|
|
827
|
+
"Injected @colixsystems/filestore-client instance — the end-user file archive (project / personal spaces) + BankID file signing. " +
|
|
828
|
+
"{ files: { list(query) -> Promise<{ data, meta }>, get(id), upload(formData), update(id, body), remove(id), preview(id) }, " +
|
|
829
|
+
"folders: { list, create, update, remove }, shares: { ... }, trash: { ... }, " +
|
|
830
|
+
"signatures: { initiate(fileId), status(id), cancel(id), verify(id) }, objectUrl(token), fetchObject(token) }. " +
|
|
831
|
+
"Backs useFilestoreFiles() + useFilestoreFolders() + useFileSignature(). Optional — a host without an end-user filestore omits it and those hooks throw a clear error.",
|
|
832
|
+
required: false,
|
|
833
|
+
fields: { files: "object", folders: "object", signatures: "object" },
|
|
834
|
+
},
|
|
744
835
|
renderer: {
|
|
745
836
|
description:
|
|
746
837
|
"Host child-node renderer. { renderNode(node) -> ReactElement }. Backs WidgetTree / useChildRenderer; lets a container widget (Tabs, Card, …) render arbitrary author-authored child nodes without importing the host renderer. The closure pre-binds breakpoint + page ctx + parent so the widget passes only the child node.",
|
|
@@ -973,7 +1064,7 @@ const VETTED_IMPORTS = [
|
|
|
973
1064
|
// below runs on web AND native (directly, or via the documented platform-split
|
|
974
1065
|
// counterpart). Native-module packages additionally carry a pinned version in
|
|
975
1066
|
// the compiler's generatePackageJson so a baked marketplace widget resolves in
|
|
976
|
-
// the Expo export (
|
|
1067
|
+
// the Expo export (widget-parity skill parity). See the `adding-vetted-packages` skill
|
|
977
1068
|
// for the full add checklist (contract ×2, version bump, compiler pin, docs).
|
|
978
1069
|
{
|
|
979
1070
|
specifier: "react-native-reanimated",
|
|
@@ -1103,7 +1194,7 @@ const CONTRACT = deepFreeze({
|
|
|
1103
1194
|
// - `allowedBareImports` is now derived from `vettedImports` (same
|
|
1104
1195
|
// shape; same contents grow with each vetted addition).
|
|
1105
1196
|
// Permissive-direction change: minor bump on the contract's own
|
|
1106
|
-
// versioning (per
|
|
1197
|
+
// versioning (per sdk-contract-lockstep skill, pre-1.0 minor is the breaking channel —
|
|
1107
1198
|
// the package.json version bumps accordingly).
|
|
1108
1199
|
//
|
|
1109
1200
|
// 1.6.0: additive — `themeTokens.colors` gains `secondary` + `onSecondary`
|
|
@@ -1191,7 +1282,7 @@ const CONTRACT = deepFreeze({
|
|
|
1191
1282
|
// existing entry changed shape and no other contract field moved, so this
|
|
1192
1283
|
// is additive — minor bump on the pre-1.0 channel. The compiler pins the
|
|
1193
1284
|
// native-module members in the exported Expo app's package.json and now
|
|
1194
|
-
// always emits the react-native-reanimated/plugin (
|
|
1285
|
+
// always emits the react-native-reanimated/plugin (widget-parity skill parity).
|
|
1195
1286
|
//
|
|
1196
1287
|
// 1.14.0: additive (REQ-RT-07) — new `useDatastoreSubscription(tableId,
|
|
1197
1288
|
// handlers, options?)` hook reading ctx.datastore.records(t).subscribe.
|
|
@@ -1209,7 +1300,42 @@ const CONTRACT = deepFreeze({
|
|
|
1209
1300
|
// the widget applies them itself (the host never auto-styles). New
|
|
1210
1301
|
// `useWidgetStyle()` hook returns props.style. No existing field, hook,
|
|
1211
1302
|
// primitive, or token changed shape — minor bump on the pre-1.0 channel.
|
|
1212
|
-
|
|
1303
|
+
// 1.16.0: additive (REQ-SIGN) — new `useSignature(tableId, recordId)` hook
|
|
1304
|
+
// reading ctx.datastore.records(tableId).sign(recordId).{initiate,status,
|
|
1305
|
+
// cancel}. Drives a BankID e-signing flow (QR data-URL + autostart token +
|
|
1306
|
+
// poll). No existing hook, primitive, or field changed — minor bump on the
|
|
1307
|
+
// pre-1.0 channel.
|
|
1308
|
+
// 1.18.0: REQ-SIGN — removed the record-less inline-form signing hook
|
|
1309
|
+
// `useSignatureColumn` (added in 1.17.0) and the datastore-client
|
|
1310
|
+
// `signColumn` namespace it read.
|
|
1311
|
+
// 1.19.0: REQ-SIGN — removed `useSignature` (record/column-scoped signing,
|
|
1312
|
+
// added in 1.16.0) and the datastore-client `sign(recordId)` namespace.
|
|
1313
|
+
// The SIGNATURE column type is retired; signing moves to a standalone
|
|
1314
|
+
// Signature subject model (files first). Pre-1.0 breaking removal of
|
|
1315
|
+
// unreleased hooks — version kept monotonic.
|
|
1316
|
+
// 1.20.0: additive (REQ-SIGN / REQ-FS) — new `useFilestoreFiles` +
|
|
1317
|
+
// `useFilestoreFolders` (browse the end-user's Filestore space) and
|
|
1318
|
+
// `useFileSignature(fileId)` (BankID file signing), all reading the
|
|
1319
|
+
// newly-injected `ctx.filestore` (@colixsystems/filestore-client). No
|
|
1320
|
+
// existing hook changed.
|
|
1321
|
+
// 1.21.0: additive (REQ-SIGN) — new `useFileSignatures(fileIds)` (batch
|
|
1322
|
+
// "is this file signed?" map, reads ctx.filestore.signatures.list) and a
|
|
1323
|
+
// new optional `existingSignatureId` arg on `useFileSignature` so an
|
|
1324
|
+
// already-signed file shows its state + verify()s without re-signing.
|
|
1325
|
+
// Backwards-compatible — the added arg is optional.
|
|
1326
|
+
//
|
|
1327
|
+
// 1.22.0: additive (REQ-WBLT-03 / #139) — new `fieldList` propertySchema type.
|
|
1328
|
+
// A per-field repeater that composes a form from author-picked columns of
|
|
1329
|
+
// a sibling tableRef (default `tableId`, override via `ui.tableProp`). Each
|
|
1330
|
+
// row stores `{ id, columnId, kind, label, required, optionsSource,
|
|
1331
|
+
// inlineOptions, optionsTableId, optionsValueColumn, optionsLabelColumn }`;
|
|
1332
|
+
// `kind` is one of `auto` / `singleChoice` / `multiChoice` (gated by the
|
|
1333
|
+
// column's data_type so an array column can't be rendered as a scalar
|
|
1334
|
+
// picker). The Studio renders the Form Builder editor through SchemaForm
|
|
1335
|
+
// instead of the hand-rolled block it shipped with — removes the last data
|
|
1336
|
+
// widget from `LEGACY_EDITOR_TYPES`. No existing type changed shape, so
|
|
1337
|
+
// this is additive — minor bump on the pre-1.0 channel.
|
|
1338
|
+
version: "1.22.0",
|
|
1213
1339
|
hooks: HOOKS,
|
|
1214
1340
|
primitives: PRIMITIVES,
|
|
1215
1341
|
manifestSchema: MANIFEST_SCHEMA,
|
package/dist/contract.js
CHANGED
|
@@ -99,7 +99,7 @@ const HOOKS = [
|
|
|
99
99
|
'style (flex: 1 / height: "100%"); widgets with no useful filled form ' +
|
|
100
100
|
"may ignore it. Defaults to false wherever the host has not opted the " +
|
|
101
101
|
"widget into filling, so calling it is always safe. The SAME value is " +
|
|
102
|
-
"injected by the web Player and the native export (
|
|
102
|
+
"injected by the web Player and the native export (widget-parity skill), so a " +
|
|
103
103
|
"widget's fill behaviour is identical on both platforms.",
|
|
104
104
|
returnShape: {
|
|
105
105
|
"(returns)": "boolean",
|
|
@@ -155,6 +155,87 @@ const HOOKS = [
|
|
|
155
155
|
requiredContextSlice: ["files.get"],
|
|
156
156
|
scopes: null,
|
|
157
157
|
},
|
|
158
|
+
{
|
|
159
|
+
name: "useFilestoreFiles",
|
|
160
|
+
signature: "useFilestoreFiles({ spaceType, folderId?, q?, type? })",
|
|
161
|
+
description:
|
|
162
|
+
"Browse the end-user's Filestore files in a project or personal space. " +
|
|
163
|
+
"The hook resolves owner_id from the host context (tenant for project, " +
|
|
164
|
+
"app user for personal) — the widget only chooses the space. Reads " +
|
|
165
|
+
"ctx.filestore.files.list and unwraps { data, meta } to the files array.",
|
|
166
|
+
returnShape: {
|
|
167
|
+
files: "FilestoreFile[]",
|
|
168
|
+
loading: "boolean",
|
|
169
|
+
error: "Error | null",
|
|
170
|
+
refetch: "() => Promise<void>",
|
|
171
|
+
},
|
|
172
|
+
requiredContextSlice: ["filestore.files"],
|
|
173
|
+
scopes: ["files.read:*"],
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
name: "useFilestoreFolders",
|
|
177
|
+
signature: "useFilestoreFolders({ spaceType, parentFolderId?, q?, enabled? })",
|
|
178
|
+
description:
|
|
179
|
+
"Browse the end-user's Filestore folders in a project or personal space, " +
|
|
180
|
+
"mirroring useFilestoreFiles for subfolder navigation. The hook resolves " +
|
|
181
|
+
"owner_id from the host context; pass enabled:false to suspend fetching. " +
|
|
182
|
+
"Reads ctx.filestore.folders.list and unwraps { data, meta } to the array.",
|
|
183
|
+
returnShape: {
|
|
184
|
+
folders: "FilestoreFolder[]",
|
|
185
|
+
loading: "boolean",
|
|
186
|
+
error: "Error | null",
|
|
187
|
+
refetch: "() => Promise<void>",
|
|
188
|
+
},
|
|
189
|
+
requiredContextSlice: ["filestore.folders"],
|
|
190
|
+
scopes: ["files.read:*"],
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
name: "useFileSignature",
|
|
194
|
+
signature: "useFileSignature(fileId, existingSignatureId?)",
|
|
195
|
+
description:
|
|
196
|
+
"Drive a BankID signing flow for one Filestore file. initiate() opens a " +
|
|
197
|
+
"sign order (the backend hashes the bytes + binds the digest), refresh() " +
|
|
198
|
+
"polls (fresh QR while pending — render with the Image primitive), " +
|
|
199
|
+
"cancel() aborts, verify() returns the offline verdict. Pass " +
|
|
200
|
+
"existingSignatureId for an already-signed file to seed the complete " +
|
|
201
|
+
"state + verify() without re-signing. Reads " +
|
|
202
|
+
"ctx.filestore.signatures.{initiate,status,cancel,verify}.",
|
|
203
|
+
returnShape: {
|
|
204
|
+
status: "'pending' | 'complete' | 'failed' | 'cancelled' | null",
|
|
205
|
+
qr: "string | null // PNG data-URL of the animated BankID QR",
|
|
206
|
+
autoStartToken: "string | null",
|
|
207
|
+
message: "string | null",
|
|
208
|
+
signerName: "string | null",
|
|
209
|
+
signedAt: "string | null",
|
|
210
|
+
verdict: "{ valid, checks, content_status, ... } | null",
|
|
211
|
+
loading: "boolean",
|
|
212
|
+
error: "PermissionError | null",
|
|
213
|
+
initiate: "() => Promise<{ signature_id, qr, auto_start_token, status }>",
|
|
214
|
+
refresh: "() => Promise<void>",
|
|
215
|
+
cancel: "() => Promise<void>",
|
|
216
|
+
verify: "() => Promise<verdict>",
|
|
217
|
+
},
|
|
218
|
+
requiredContextSlice: ["filestore.signatures"],
|
|
219
|
+
scopes: ["files.write:*"],
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
name: "useFileSignatures",
|
|
223
|
+
signature: "useFileSignatures(fileIds)",
|
|
224
|
+
description:
|
|
225
|
+
"Batch 'is this file signed?' lookup for a set of file ids. Returns a map " +
|
|
226
|
+
"of each signed, accessible file id to its latest signature; unsigned " +
|
|
227
|
+
"files are absent. Lets a file browser mark already-signed files in one " +
|
|
228
|
+
"request. Reads ctx.filestore.signatures.list and unwraps { data, meta }.",
|
|
229
|
+
returnShape: {
|
|
230
|
+
signaturesByFileId:
|
|
231
|
+
"{ [fileId]: { signature_id, signer_name, signed_at } }",
|
|
232
|
+
loading: "boolean",
|
|
233
|
+
error: "Error | null",
|
|
234
|
+
refetch: "() => Promise<void>",
|
|
235
|
+
},
|
|
236
|
+
requiredContextSlice: ["filestore.signatures"],
|
|
237
|
+
scopes: ["files.read:*"],
|
|
238
|
+
},
|
|
158
239
|
{
|
|
159
240
|
name: "useDatastoreQuery",
|
|
160
241
|
signature: "useDatastoreQuery(tableId, options?)",
|
|
@@ -741,6 +822,16 @@ const WIDGET_CONTEXT_SHAPE = {
|
|
|
741
822
|
required: true,
|
|
742
823
|
fields: { get: "function" },
|
|
743
824
|
},
|
|
825
|
+
filestore: {
|
|
826
|
+
description:
|
|
827
|
+
"Injected @colixsystems/filestore-client instance — the end-user file archive (project / personal spaces) + BankID file signing. " +
|
|
828
|
+
"{ files: { list(query) -> Promise<{ data, meta }>, get(id), upload(formData), update(id, body), remove(id), preview(id) }, " +
|
|
829
|
+
"folders: { list, create, update, remove }, shares: { ... }, trash: { ... }, " +
|
|
830
|
+
"signatures: { initiate(fileId), status(id), cancel(id), verify(id) }, objectUrl(token), fetchObject(token) }. " +
|
|
831
|
+
"Backs useFilestoreFiles() + useFilestoreFolders() + useFileSignature(). Optional — a host without an end-user filestore omits it and those hooks throw a clear error.",
|
|
832
|
+
required: false,
|
|
833
|
+
fields: { files: "object", folders: "object", signatures: "object" },
|
|
834
|
+
},
|
|
744
835
|
renderer: {
|
|
745
836
|
description:
|
|
746
837
|
"Host child-node renderer. { renderNode(node) -> ReactElement }. Backs WidgetTree / useChildRenderer; lets a container widget (Tabs, Card, …) render arbitrary author-authored child nodes without importing the host renderer. The closure pre-binds breakpoint + page ctx + parent so the widget passes only the child node.",
|
|
@@ -973,7 +1064,7 @@ const VETTED_IMPORTS = [
|
|
|
973
1064
|
// below runs on web AND native (directly, or via the documented platform-split
|
|
974
1065
|
// counterpart). Native-module packages additionally carry a pinned version in
|
|
975
1066
|
// the compiler's generatePackageJson so a baked marketplace widget resolves in
|
|
976
|
-
// the Expo export (
|
|
1067
|
+
// the Expo export (widget-parity skill parity). See the `adding-vetted-packages` skill
|
|
977
1068
|
// for the full add checklist (contract ×2, version bump, compiler pin, docs).
|
|
978
1069
|
{
|
|
979
1070
|
specifier: "react-native-reanimated",
|
|
@@ -1103,7 +1194,7 @@ const CONTRACT = deepFreeze({
|
|
|
1103
1194
|
// - `allowedBareImports` is now derived from `vettedImports` (same
|
|
1104
1195
|
// shape; same contents grow with each vetted addition).
|
|
1105
1196
|
// Permissive-direction change: minor bump on the contract's own
|
|
1106
|
-
// versioning (per
|
|
1197
|
+
// versioning (per sdk-contract-lockstep skill, pre-1.0 minor is the breaking channel —
|
|
1107
1198
|
// the package.json version bumps accordingly).
|
|
1108
1199
|
//
|
|
1109
1200
|
// 1.6.0: additive — `themeTokens.colors` gains `secondary` + `onSecondary`
|
|
@@ -1191,7 +1282,7 @@ const CONTRACT = deepFreeze({
|
|
|
1191
1282
|
// existing entry changed shape and no other contract field moved, so this
|
|
1192
1283
|
// is additive — minor bump on the pre-1.0 channel. The compiler pins the
|
|
1193
1284
|
// native-module members in the exported Expo app's package.json and now
|
|
1194
|
-
// always emits the react-native-reanimated/plugin (
|
|
1285
|
+
// always emits the react-native-reanimated/plugin (widget-parity skill parity).
|
|
1195
1286
|
//
|
|
1196
1287
|
// 1.14.0: additive (REQ-RT-07) — new `useDatastoreSubscription(tableId,
|
|
1197
1288
|
// handlers, options?)` hook reading ctx.datastore.records(t).subscribe.
|
|
@@ -1209,7 +1300,42 @@ const CONTRACT = deepFreeze({
|
|
|
1209
1300
|
// the widget applies them itself (the host never auto-styles). New
|
|
1210
1301
|
// `useWidgetStyle()` hook returns props.style. No existing field, hook,
|
|
1211
1302
|
// primitive, or token changed shape — minor bump on the pre-1.0 channel.
|
|
1212
|
-
|
|
1303
|
+
// 1.16.0: additive (REQ-SIGN) — new `useSignature(tableId, recordId)` hook
|
|
1304
|
+
// reading ctx.datastore.records(tableId).sign(recordId).{initiate,status,
|
|
1305
|
+
// cancel}. Drives a BankID e-signing flow (QR data-URL + autostart token +
|
|
1306
|
+
// poll). No existing hook, primitive, or field changed — minor bump on the
|
|
1307
|
+
// pre-1.0 channel.
|
|
1308
|
+
// 1.18.0: REQ-SIGN — removed the record-less inline-form signing hook
|
|
1309
|
+
// `useSignatureColumn` (added in 1.17.0) and the datastore-client
|
|
1310
|
+
// `signColumn` namespace it read.
|
|
1311
|
+
// 1.19.0: REQ-SIGN — removed `useSignature` (record/column-scoped signing,
|
|
1312
|
+
// added in 1.16.0) and the datastore-client `sign(recordId)` namespace.
|
|
1313
|
+
// The SIGNATURE column type is retired; signing moves to a standalone
|
|
1314
|
+
// Signature subject model (files first). Pre-1.0 breaking removal of
|
|
1315
|
+
// unreleased hooks — version kept monotonic.
|
|
1316
|
+
// 1.20.0: additive (REQ-SIGN / REQ-FS) — new `useFilestoreFiles` +
|
|
1317
|
+
// `useFilestoreFolders` (browse the end-user's Filestore space) and
|
|
1318
|
+
// `useFileSignature(fileId)` (BankID file signing), all reading the
|
|
1319
|
+
// newly-injected `ctx.filestore` (@colixsystems/filestore-client). No
|
|
1320
|
+
// existing hook changed.
|
|
1321
|
+
// 1.21.0: additive (REQ-SIGN) — new `useFileSignatures(fileIds)` (batch
|
|
1322
|
+
// "is this file signed?" map, reads ctx.filestore.signatures.list) and a
|
|
1323
|
+
// new optional `existingSignatureId` arg on `useFileSignature` so an
|
|
1324
|
+
// already-signed file shows its state + verify()s without re-signing.
|
|
1325
|
+
// Backwards-compatible — the added arg is optional.
|
|
1326
|
+
//
|
|
1327
|
+
// 1.22.0: additive (REQ-WBLT-03 / #139) — new `fieldList` propertySchema type.
|
|
1328
|
+
// A per-field repeater that composes a form from author-picked columns of
|
|
1329
|
+
// a sibling tableRef (default `tableId`, override via `ui.tableProp`). Each
|
|
1330
|
+
// row stores `{ id, columnId, kind, label, required, optionsSource,
|
|
1331
|
+
// inlineOptions, optionsTableId, optionsValueColumn, optionsLabelColumn }`;
|
|
1332
|
+
// `kind` is one of `auto` / `singleChoice` / `multiChoice` (gated by the
|
|
1333
|
+
// column's data_type so an array column can't be rendered as a scalar
|
|
1334
|
+
// picker). The Studio renders the Form Builder editor through SchemaForm
|
|
1335
|
+
// instead of the hand-rolled block it shipped with — removes the last data
|
|
1336
|
+
// widget from `LEGACY_EDITOR_TYPES`. No existing type changed shape, so
|
|
1337
|
+
// this is additive — minor bump on the pre-1.0 channel.
|
|
1338
|
+
version: "1.22.0",
|
|
1213
1339
|
hooks: HOOKS,
|
|
1214
1340
|
primitives: PRIMITIVES,
|
|
1215
1341
|
manifestSchema: MANIFEST_SCHEMA,
|
package/dist/hooks.js
CHANGED
|
@@ -132,7 +132,7 @@ export function useUser() {
|
|
|
132
132
|
* opted the widget into filling, so calling it is always safe.
|
|
133
133
|
*
|
|
134
134
|
* The SAME value is injected by the web Player host and the native export
|
|
135
|
-
* host (
|
|
135
|
+
* host (widget-parity skill), so a widget's fill behaviour is identical on both
|
|
136
136
|
* platforms — there is one source file and one `fill` flag driving it.
|
|
137
137
|
*/
|
|
138
138
|
export function useFill() {
|
|
@@ -961,7 +961,7 @@ export function useRecordPermissions(tableId, recordId) {
|
|
|
961
961
|
* expose `subscribe` (an older host, or a runtime with no WebSocket), the hook
|
|
962
962
|
* resolves to `{ status: "fallback" }` WITHOUT throwing, so a widget that
|
|
963
963
|
* subscribes degrades to polling on both the web Player and the native export
|
|
964
|
-
* rather than crashing at render (
|
|
964
|
+
* rather than crashing at render (widget-parity skill).
|
|
965
965
|
*
|
|
966
966
|
* @param {string} table Bound table id (falsy → no subscription, status "fallback").
|
|
967
967
|
* @param {{ onCreated?, onUpdated?, onDeleted? }} [handlers] Per-event callbacks; each receives the snake_case record.
|
|
@@ -1106,6 +1106,403 @@ export function useFile(fileId) {
|
|
|
1106
1106
|
return { url, file, loading, error, refetch };
|
|
1107
1107
|
}
|
|
1108
1108
|
|
|
1109
|
+
/* ============================================================================
|
|
1110
|
+
* FILESTORE CLIENT — ctx.filestore (@colixsystems/filestore-client)
|
|
1111
|
+
*
|
|
1112
|
+
* The end-user file archive (project / personal spaces) + BankID file signing.
|
|
1113
|
+
* Covers: useFilestoreFiles, useFileSignature.
|
|
1114
|
+
* ==========================================================================*/
|
|
1115
|
+
|
|
1116
|
+
// Resolve the owner_id a filestore query needs from the host context: a PROJECT
|
|
1117
|
+
// space is owned by the tenant (ctx.workspace.id); a PERSONAL space is owned by
|
|
1118
|
+
// the signed-in app user (ctx.user.id). The widget only chooses the space.
|
|
1119
|
+
function _filestoreOwnerId(ctx, spaceType) {
|
|
1120
|
+
const space = String(spaceType || "project").toUpperCase();
|
|
1121
|
+
if (space === "PERSONAL") return (ctx.user && ctx.user.id) || null;
|
|
1122
|
+
return (ctx.workspace && ctx.workspace.id) || null;
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
/**
|
|
1126
|
+
* Browse the end-user's Filestore files. Returns { files, loading, error,
|
|
1127
|
+
* refetch }. The widget passes only the SPACE (`{ spaceType, folderId, q,
|
|
1128
|
+
* type }`); the hook resolves owner_id from the host context (tenant for
|
|
1129
|
+
* project, app user for personal) and calls
|
|
1130
|
+
* `ctx.filestore.files.list({ space_type, owner_id, folder_id, q, type })`,
|
|
1131
|
+
* unwrapping the `{ data, meta }` envelope to the files array. When the owner
|
|
1132
|
+
* can't be resolved (no signed-in user for a personal space) it collapses to an
|
|
1133
|
+
* empty result without a network round-trip.
|
|
1134
|
+
*/
|
|
1135
|
+
export function useFilestoreFiles(options) {
|
|
1136
|
+
const ctx = useWidgetContextOrThrow("useFilestoreFiles");
|
|
1137
|
+
if (
|
|
1138
|
+
!ctx.filestore ||
|
|
1139
|
+
!ctx.filestore.files ||
|
|
1140
|
+
typeof ctx.filestore.files.list !== "function"
|
|
1141
|
+
) {
|
|
1142
|
+
throw new Error(
|
|
1143
|
+
"useFilestoreFiles: host did not inject a filestore client (ctx.filestore.files.list)",
|
|
1144
|
+
);
|
|
1145
|
+
}
|
|
1146
|
+
const { spaceType = "project", folderId = null, q, type } = options || {};
|
|
1147
|
+
const ownerId = _filestoreOwnerId(ctx, spaceType);
|
|
1148
|
+
|
|
1149
|
+
const [files, setFiles] = useState([]);
|
|
1150
|
+
const [loading, setLoading] = useState(true);
|
|
1151
|
+
const [error, setError] = useState(null);
|
|
1152
|
+
|
|
1153
|
+
const filesRef = useRef(ctx.filestore.files);
|
|
1154
|
+
filesRef.current = ctx.filestore.files;
|
|
1155
|
+
const argsRef = useRef({});
|
|
1156
|
+
argsRef.current = { spaceType, ownerId, folderId, q, type };
|
|
1157
|
+
const runRef = useRef(0);
|
|
1158
|
+
|
|
1159
|
+
const doFetch = useCallback(async () => {
|
|
1160
|
+
const myRun = ++runRef.current;
|
|
1161
|
+
const { spaceType: sp, ownerId: oid, folderId: fid, q: qq, type: tt } = argsRef.current;
|
|
1162
|
+
if (!oid) {
|
|
1163
|
+
setLoading(false);
|
|
1164
|
+
setError(null);
|
|
1165
|
+
setFiles([]);
|
|
1166
|
+
return;
|
|
1167
|
+
}
|
|
1168
|
+
setLoading(true);
|
|
1169
|
+
setError(null);
|
|
1170
|
+
try {
|
|
1171
|
+
const res = await filesRef.current.list({
|
|
1172
|
+
space_type: String(sp || "project").toUpperCase(),
|
|
1173
|
+
owner_id: oid,
|
|
1174
|
+
folder_id: fid || undefined,
|
|
1175
|
+
q: qq || undefined,
|
|
1176
|
+
type: tt || undefined,
|
|
1177
|
+
});
|
|
1178
|
+
const rows = res && Array.isArray(res.data) ? res.data : [];
|
|
1179
|
+
if (runRef.current !== myRun) return;
|
|
1180
|
+
setFiles(rows);
|
|
1181
|
+
setLoading(false);
|
|
1182
|
+
} catch (err) {
|
|
1183
|
+
if (runRef.current !== myRun) return;
|
|
1184
|
+
setError(err);
|
|
1185
|
+
setLoading(false);
|
|
1186
|
+
}
|
|
1187
|
+
}, []);
|
|
1188
|
+
|
|
1189
|
+
const key = `${spaceType}|${ownerId}|${folderId}|${q || ""}|${type || ""}`;
|
|
1190
|
+
useEffect(() => {
|
|
1191
|
+
doFetch();
|
|
1192
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1193
|
+
}, [key]);
|
|
1194
|
+
|
|
1195
|
+
const refetch = useCallback(async () => {
|
|
1196
|
+
await doFetch();
|
|
1197
|
+
}, [doFetch]);
|
|
1198
|
+
|
|
1199
|
+
return { files, loading, error, refetch };
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
/**
|
|
1203
|
+
* Browse the end-user's Filestore folders. Returns { folders, loading, error,
|
|
1204
|
+
* refetch }. Mirrors useFilestoreFiles for subfolder navigation: the widget
|
|
1205
|
+
* passes only the SPACE (`{ spaceType, parentFolderId, q, enabled }`); the hook
|
|
1206
|
+
* resolves owner_id from the host context and calls
|
|
1207
|
+
* `ctx.filestore.folders.list({ space_type, owner_id, parent_folder_id, q })`,
|
|
1208
|
+
* unwrapping the `{ data, meta }` envelope. Pass `enabled: false` to suspend
|
|
1209
|
+
* fetching (e.g. when the widget hides subfolders) or when the owner can't be
|
|
1210
|
+
* resolved — the hook then resolves to an empty list without a network call.
|
|
1211
|
+
*/
|
|
1212
|
+
export function useFilestoreFolders(options) {
|
|
1213
|
+
const ctx = useWidgetContextOrThrow("useFilestoreFolders");
|
|
1214
|
+
if (
|
|
1215
|
+
!ctx.filestore ||
|
|
1216
|
+
!ctx.filestore.folders ||
|
|
1217
|
+
typeof ctx.filestore.folders.list !== "function"
|
|
1218
|
+
) {
|
|
1219
|
+
throw new Error(
|
|
1220
|
+
"useFilestoreFolders: host did not inject a filestore client (ctx.filestore.folders.list)",
|
|
1221
|
+
);
|
|
1222
|
+
}
|
|
1223
|
+
const {
|
|
1224
|
+
spaceType = "project",
|
|
1225
|
+
parentFolderId = null,
|
|
1226
|
+
q,
|
|
1227
|
+
enabled = true,
|
|
1228
|
+
} = options || {};
|
|
1229
|
+
const ownerId = _filestoreOwnerId(ctx, spaceType);
|
|
1230
|
+
|
|
1231
|
+
const [folders, setFolders] = useState([]);
|
|
1232
|
+
const [loading, setLoading] = useState(true);
|
|
1233
|
+
const [error, setError] = useState(null);
|
|
1234
|
+
|
|
1235
|
+
const foldersApiRef = useRef(ctx.filestore.folders);
|
|
1236
|
+
foldersApiRef.current = ctx.filestore.folders;
|
|
1237
|
+
const argsRef = useRef({});
|
|
1238
|
+
argsRef.current = { spaceType, ownerId, parentFolderId, q, enabled };
|
|
1239
|
+
const runRef = useRef(0);
|
|
1240
|
+
|
|
1241
|
+
const doFetch = useCallback(async () => {
|
|
1242
|
+
const myRun = ++runRef.current;
|
|
1243
|
+
const {
|
|
1244
|
+
spaceType: sp,
|
|
1245
|
+
ownerId: oid,
|
|
1246
|
+
parentFolderId: pid,
|
|
1247
|
+
q: qq,
|
|
1248
|
+
enabled: en,
|
|
1249
|
+
} = argsRef.current;
|
|
1250
|
+
if (!en || !oid) {
|
|
1251
|
+
setLoading(false);
|
|
1252
|
+
setError(null);
|
|
1253
|
+
setFolders([]);
|
|
1254
|
+
return;
|
|
1255
|
+
}
|
|
1256
|
+
setLoading(true);
|
|
1257
|
+
setError(null);
|
|
1258
|
+
try {
|
|
1259
|
+
const res = await foldersApiRef.current.list({
|
|
1260
|
+
space_type: String(sp || "project").toUpperCase(),
|
|
1261
|
+
owner_id: oid,
|
|
1262
|
+
parent_folder_id: pid || undefined,
|
|
1263
|
+
q: qq || undefined,
|
|
1264
|
+
});
|
|
1265
|
+
const rows = res && Array.isArray(res.data) ? res.data : [];
|
|
1266
|
+
if (runRef.current !== myRun) return;
|
|
1267
|
+
setFolders(rows);
|
|
1268
|
+
setLoading(false);
|
|
1269
|
+
} catch (err) {
|
|
1270
|
+
if (runRef.current !== myRun) return;
|
|
1271
|
+
setError(err);
|
|
1272
|
+
setLoading(false);
|
|
1273
|
+
}
|
|
1274
|
+
}, []);
|
|
1275
|
+
|
|
1276
|
+
const key = `${enabled ? 1 : 0}|${spaceType}|${ownerId}|${parentFolderId}|${q || ""}`;
|
|
1277
|
+
useEffect(() => {
|
|
1278
|
+
doFetch();
|
|
1279
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1280
|
+
}, [key]);
|
|
1281
|
+
|
|
1282
|
+
const refetch = useCallback(async () => {
|
|
1283
|
+
await doFetch();
|
|
1284
|
+
}, [doFetch]);
|
|
1285
|
+
|
|
1286
|
+
return { folders, loading, error, refetch };
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
/**
|
|
1290
|
+
* Drive a BankID signing flow for one Filestore file. Returns
|
|
1291
|
+
* `{ status, qr, autoStartToken, message, signerName, signedAt, verdict,
|
|
1292
|
+
* loading, error, initiate, refresh, cancel, verify }`.
|
|
1293
|
+
*
|
|
1294
|
+
* `initiate()` opens a sign order against the file (the backend hashes its
|
|
1295
|
+
* bytes SERVER-SIDE and binds the digest into the BankID signature); `refresh()`
|
|
1296
|
+
* polls the order (surfacing a fresh animated-QR frame while pending — render
|
|
1297
|
+
* it with the `Image` primitive); `cancel()` aborts it; `verify()` re-runs the
|
|
1298
|
+
* offline verification and returns the verdict (`{ valid, content_status, … }`).
|
|
1299
|
+
* Reads `ctx.filestore.signatures.{initiate,status,cancel,verify}`. When
|
|
1300
|
+
* `fileId` is null/empty the hook collapses to a no-op.
|
|
1301
|
+
*
|
|
1302
|
+
* Pass `existingSignatureId` for a file that is ALREADY signed: the hook seeds
|
|
1303
|
+
* that order id (status `complete`) so the widget can show the signed state and
|
|
1304
|
+
* `verify()` it WITHOUT opening a new order — re-signing then requires an
|
|
1305
|
+
* explicit `initiate()`.
|
|
1306
|
+
*/
|
|
1307
|
+
export function useFileSignature(fileId, existingSignatureId = null) {
|
|
1308
|
+
const ctx = useWidgetContextOrThrow("useFileSignature");
|
|
1309
|
+
if (!ctx.filestore || !ctx.filestore.signatures) {
|
|
1310
|
+
throw new Error(
|
|
1311
|
+
"useFileSignature: host did not inject a filestore client (ctx.filestore.signatures)",
|
|
1312
|
+
);
|
|
1313
|
+
}
|
|
1314
|
+
const ready = Boolean(fileId);
|
|
1315
|
+
const sigRef = useRef(ctx.filestore.signatures);
|
|
1316
|
+
sigRef.current = ctx.filestore.signatures;
|
|
1317
|
+
const fileIdRef = useRef(fileId);
|
|
1318
|
+
fileIdRef.current = fileId;
|
|
1319
|
+
// Seed the order from an already-completed signature so verify() works
|
|
1320
|
+
// without initiating; initiate() (an explicit re-sign) overwrites it.
|
|
1321
|
+
const orderRef = useRef(existingSignatureId || null);
|
|
1322
|
+
|
|
1323
|
+
const [status, setStatus] = useState(existingSignatureId ? "complete" : null);
|
|
1324
|
+
const [qr, setQr] = useState(null);
|
|
1325
|
+
const [autoStartToken, setAutoStartToken] = useState(null);
|
|
1326
|
+
const [message, setMessage] = useState(null);
|
|
1327
|
+
const [signerName, setSignerName] = useState(null);
|
|
1328
|
+
const [signedAt, setSignedAt] = useState(null);
|
|
1329
|
+
const [verdict, setVerdict] = useState(null);
|
|
1330
|
+
const [loading, setLoading] = useState(false);
|
|
1331
|
+
const [error, setError] = useState(null);
|
|
1332
|
+
|
|
1333
|
+
const initiate = useCallback(async () => {
|
|
1334
|
+
if (!fileIdRef.current) return null;
|
|
1335
|
+
setLoading(true);
|
|
1336
|
+
setError(null);
|
|
1337
|
+
setVerdict(null);
|
|
1338
|
+
try {
|
|
1339
|
+
const res = await sigRef.current.initiate(fileIdRef.current);
|
|
1340
|
+
orderRef.current = res && res.signature_id ? res.signature_id : null;
|
|
1341
|
+
setStatus(res && res.status ? res.status : "pending");
|
|
1342
|
+
setQr(res && res.qr ? res.qr : null);
|
|
1343
|
+
setAutoStartToken(res && res.auto_start_token ? res.auto_start_token : null);
|
|
1344
|
+
setSignerName(null);
|
|
1345
|
+
setSignedAt(null);
|
|
1346
|
+
setMessage(null);
|
|
1347
|
+
setLoading(false);
|
|
1348
|
+
return res;
|
|
1349
|
+
} catch (err) {
|
|
1350
|
+
setError(toPermissionError(err));
|
|
1351
|
+
setLoading(false);
|
|
1352
|
+
throw toPermissionError(err);
|
|
1353
|
+
}
|
|
1354
|
+
}, []);
|
|
1355
|
+
|
|
1356
|
+
const refresh = useCallback(async () => {
|
|
1357
|
+
const id = orderRef.current;
|
|
1358
|
+
if (!id) return;
|
|
1359
|
+
try {
|
|
1360
|
+
const res = await sigRef.current.status(id);
|
|
1361
|
+
if (res) {
|
|
1362
|
+
if (res.status != null) setStatus(res.status);
|
|
1363
|
+
if (res.qr !== undefined) setQr(res.qr || null);
|
|
1364
|
+
if (res.message !== undefined) setMessage(res.message || null);
|
|
1365
|
+
if (res.signer_name !== undefined) setSignerName(res.signer_name || null);
|
|
1366
|
+
if (res.signed_at !== undefined) setSignedAt(res.signed_at || null);
|
|
1367
|
+
}
|
|
1368
|
+
} catch (err) {
|
|
1369
|
+
setError(toPermissionError(err));
|
|
1370
|
+
}
|
|
1371
|
+
}, []);
|
|
1372
|
+
|
|
1373
|
+
const cancel = useCallback(async () => {
|
|
1374
|
+
const id = orderRef.current;
|
|
1375
|
+
if (!id) return;
|
|
1376
|
+
try {
|
|
1377
|
+
await sigRef.current.cancel(id);
|
|
1378
|
+
setStatus("cancelled");
|
|
1379
|
+
setQr(null);
|
|
1380
|
+
} catch (err) {
|
|
1381
|
+
throw toPermissionError(err);
|
|
1382
|
+
}
|
|
1383
|
+
}, []);
|
|
1384
|
+
|
|
1385
|
+
const verify = useCallback(async () => {
|
|
1386
|
+
const id = orderRef.current;
|
|
1387
|
+
if (!id) return null;
|
|
1388
|
+
try {
|
|
1389
|
+
const v = await sigRef.current.verify(id);
|
|
1390
|
+
setVerdict(v);
|
|
1391
|
+
return v;
|
|
1392
|
+
} catch (err) {
|
|
1393
|
+
setError(toPermissionError(err));
|
|
1394
|
+
throw toPermissionError(err);
|
|
1395
|
+
}
|
|
1396
|
+
}, []);
|
|
1397
|
+
|
|
1398
|
+
if (!ready) {
|
|
1399
|
+
return {
|
|
1400
|
+
status: null,
|
|
1401
|
+
qr: null,
|
|
1402
|
+
autoStartToken: null,
|
|
1403
|
+
message: null,
|
|
1404
|
+
signerName: null,
|
|
1405
|
+
signedAt: null,
|
|
1406
|
+
verdict: null,
|
|
1407
|
+
loading: false,
|
|
1408
|
+
error: null,
|
|
1409
|
+
initiate: async () => null,
|
|
1410
|
+
refresh: async () => undefined,
|
|
1411
|
+
cancel: async () => undefined,
|
|
1412
|
+
verify: async () => null,
|
|
1413
|
+
};
|
|
1414
|
+
}
|
|
1415
|
+
return {
|
|
1416
|
+
status,
|
|
1417
|
+
qr,
|
|
1418
|
+
autoStartToken,
|
|
1419
|
+
message,
|
|
1420
|
+
signerName,
|
|
1421
|
+
signedAt,
|
|
1422
|
+
verdict,
|
|
1423
|
+
loading,
|
|
1424
|
+
error,
|
|
1425
|
+
initiate,
|
|
1426
|
+
refresh,
|
|
1427
|
+
cancel,
|
|
1428
|
+
verify,
|
|
1429
|
+
};
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
/**
|
|
1433
|
+
* Batch "is this file signed?" lookup for a set of file ids. Returns
|
|
1434
|
+
* `{ signaturesByFileId, loading, error, refetch }` where `signaturesByFileId`
|
|
1435
|
+
* maps each signed, accessible file id to its latest signature
|
|
1436
|
+
* (`{ signature_id, signer_name, signed_at }`); unsigned files are simply
|
|
1437
|
+
* absent. Lets a file browser mark already-signed files in one request (no
|
|
1438
|
+
* N+1). Reads `ctx.filestore.signatures.list(fileIds)` and unwraps the
|
|
1439
|
+
* `{ data, meta }` envelope. An empty/falsy id list resolves to an empty map
|
|
1440
|
+
* without a network round-trip.
|
|
1441
|
+
*/
|
|
1442
|
+
export function useFileSignatures(fileIds) {
|
|
1443
|
+
const ctx = useWidgetContextOrThrow("useFileSignatures");
|
|
1444
|
+
if (
|
|
1445
|
+
!ctx.filestore ||
|
|
1446
|
+
!ctx.filestore.signatures ||
|
|
1447
|
+
typeof ctx.filestore.signatures.list !== "function"
|
|
1448
|
+
) {
|
|
1449
|
+
throw new Error(
|
|
1450
|
+
"useFileSignatures: host did not inject a filestore client (ctx.filestore.signatures.list)",
|
|
1451
|
+
);
|
|
1452
|
+
}
|
|
1453
|
+
const ids = Array.isArray(fileIds) ? fileIds.filter(Boolean) : [];
|
|
1454
|
+
const key = ids.slice().sort().join(",");
|
|
1455
|
+
|
|
1456
|
+
const [signaturesByFileId, setSignaturesByFileId] = useState({});
|
|
1457
|
+
const [loading, setLoading] = useState(ids.length > 0);
|
|
1458
|
+
const [error, setError] = useState(null);
|
|
1459
|
+
|
|
1460
|
+
const sigRef = useRef(ctx.filestore.signatures);
|
|
1461
|
+
sigRef.current = ctx.filestore.signatures;
|
|
1462
|
+
const keyRef = useRef(key);
|
|
1463
|
+
keyRef.current = key;
|
|
1464
|
+
const runRef = useRef(0);
|
|
1465
|
+
|
|
1466
|
+
const doFetch = useCallback(async () => {
|
|
1467
|
+
const myRun = ++runRef.current;
|
|
1468
|
+
const currentIds = keyRef.current ? keyRef.current.split(",") : [];
|
|
1469
|
+
if (currentIds.length === 0) {
|
|
1470
|
+
setLoading(false);
|
|
1471
|
+
setError(null);
|
|
1472
|
+
setSignaturesByFileId({});
|
|
1473
|
+
return;
|
|
1474
|
+
}
|
|
1475
|
+
setLoading(true);
|
|
1476
|
+
setError(null);
|
|
1477
|
+
try {
|
|
1478
|
+
const res = await sigRef.current.list(currentIds);
|
|
1479
|
+
const rows = res && Array.isArray(res.data) ? res.data : [];
|
|
1480
|
+
if (runRef.current !== myRun) return;
|
|
1481
|
+
const map = {};
|
|
1482
|
+
for (const row of rows) {
|
|
1483
|
+
if (row && row.subject_file_id) map[row.subject_file_id] = row;
|
|
1484
|
+
}
|
|
1485
|
+
setSignaturesByFileId(map);
|
|
1486
|
+
setLoading(false);
|
|
1487
|
+
} catch (err) {
|
|
1488
|
+
if (runRef.current !== myRun) return;
|
|
1489
|
+
setError(err);
|
|
1490
|
+
setLoading(false);
|
|
1491
|
+
}
|
|
1492
|
+
}, []);
|
|
1493
|
+
|
|
1494
|
+
useEffect(() => {
|
|
1495
|
+
doFetch();
|
|
1496
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1497
|
+
}, [key]);
|
|
1498
|
+
|
|
1499
|
+
const refetch = useCallback(async () => {
|
|
1500
|
+
await doFetch();
|
|
1501
|
+
}, [doFetch]);
|
|
1502
|
+
|
|
1503
|
+
return { signaturesByFileId, loading, error, refetch };
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1109
1506
|
/* ============================================================================
|
|
1110
1507
|
* DIRECTORY CLIENT — ctx.directory (@colixsystems/directory-client)
|
|
1111
1508
|
*
|
package/dist/index.d.ts
CHANGED
|
@@ -47,6 +47,12 @@ export type WidgetPropertyType =
|
|
|
47
47
|
| "assetList"
|
|
48
48
|
// REQ-WDG-FOLDERREF: Filestore folder picker → folder UUID (or null = root).
|
|
49
49
|
| "folderRef"
|
|
50
|
+
// REQ-WBLT-03 / #139: per-field repeater that composes a form from columns
|
|
51
|
+
// of a sibling tableRef (default `tableId`, override via `ui.tableProp`).
|
|
52
|
+
// Each row: { id, columnId, kind: "auto"|"singleChoice"|"multiChoice",
|
|
53
|
+
// label, required, optionsSource, inlineOptions, optionsTableId,
|
|
54
|
+
// optionsValueColumn, optionsLabelColumn }.
|
|
55
|
+
| "fieldList"
|
|
50
56
|
| "expression"
|
|
51
57
|
| "eventBinding"
|
|
52
58
|
| "object"
|
package/dist/index.js
CHANGED
package/dist/index.native.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@colixsystems/widget-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.32.0",
|
|
4
4
|
"description": "Common widget interface for AppStudio. Implements WidgetManifest, WidgetContext, property schema, and helper hooks.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|