@colixsystems/widget-sdk 0.33.0 → 0.35.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 +15 -2
- package/dist/contract.cjs +103 -5
- package/dist/contract.js +103 -5
- package/dist/hooks.js +351 -0
- package/dist/index.d.ts +57 -0
- package/dist/index.js +3 -0
- package/dist/index.native.js +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ The data layer lives in **four separate domain-client packages**, each instantia
|
|
|
11
11
|
| `ctx.files` | `@colixsystems/files-client` | the Asset Manager: `get(id)`, `list(query)`, `upload(formData)` over `/files` — what `useFile()` resolves |
|
|
12
12
|
| `ctx.payments` | `@colixsystems/payments-client` | `requestPayment(body)`, `getPayment(id)` |
|
|
13
13
|
|
|
14
|
-
**Wire / casing: snake_case end to end.** The clients send and return snake_case **verbatim** (`created_at`, `group_ids`, `can_read`, `amount_cents`, `data_type`, `is_active`, …). There is **no
|
|
14
|
+
**Wire / casing: snake_case end to end.** The clients send and return snake_case **verbatim** (`created_at`, `group_ids`, `can_read`, `amount_cents`, `data_type`, `is_active`, …). There is **no case transform anywhere** — not on the client and not in the backend; the only casing boundary is Prisma `@map` (snake_case field → camelCase column). Author-defined record column values pass through verbatim. Every `list(...)` returns the `{ data, meta }` envelope; the read hooks unwrap `res.data` for you.
|
|
15
15
|
|
|
16
16
|
**Hooks read the injected clients** — they do not hold their own HTTP. This is the **complete** hook surface (18 hooks), grouped by the domain client each one reads. **CORE** hooks read host state directly off `WidgetContext` (no data client); the rest delegate to one of the four injected clients. The grouping mirrors the banner sections in [`src/hooks.js`](src/hooks.js).
|
|
17
17
|
|
|
@@ -36,6 +36,7 @@ The data layer lives in **four separate domain-client packages**, each instantia
|
|
|
36
36
|
| **DIRECTORY** (`ctx.directory`) | `useDirectory(query?)` | `{ users, loading, error, refetch }` | `directory.users.list` — `directory.read:users` |
|
|
37
37
|
| **DIRECTORY** | `useUsers(query?)` | `{ users, loading, error, refetch, invite, deactivate, reactivate, remove }` | `directory.users.*` — `users.read:*` (mutations also `users.write:*`) |
|
|
38
38
|
| **DIRECTORY** | `useGroups(query?)` | `{ groups, loading, error, refetch, create, remove, addMember, removeMember }` | `directory.groups.*` — `groups.read:*` (mutations also `groups.write:*`) |
|
|
39
|
+
| **DIRECTORY** | `useBankIdLink()` | `{ linked, available, status, qr, message, startLink, refresh, cancel, unlink, refetchStatus, … }` | `directory.bankid.*` — no scope (JWT-gated self-service) |
|
|
39
40
|
| **PAYMENTS** (`ctx.payments`) | `usePayments()` | `{ requestPayment, getPayment }` | `ctx.payments.*` — `payments.charge:appUser` |
|
|
40
41
|
|
|
41
42
|
All list calls return the `{ data, meta }` envelope; the read hooks unwrap `res.data` for you. There is no `useWorkspace()` or `useLogger()` hook — read the theme via `useTheme()` and the locale via `useI18n()`; the host logger lives on `ctx.logger` (`{ debug, info, warn, error }`).
|
|
@@ -46,7 +47,19 @@ See the design reference for the full architecture: [`docs/architecture/widget-m
|
|
|
46
47
|
|
|
47
48
|
## Status
|
|
48
49
|
|
|
49
|
-
`v0.
|
|
50
|
+
`v0.35.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**.
|
|
51
|
+
|
|
52
|
+
### What's new in 0.35.0
|
|
53
|
+
|
|
54
|
+
**Folder-ACL + signer-roster hooks (REQ-FSH / REQ-SIGN).** Two admin/MANAGE-gated hooks reading the existing `ctx.filestore`:
|
|
55
|
+
- `useFileRoster(fileId, { limit?, offset?, enabled? })` → `{ roster, total, signedCount, loading, error, refetch }` — the signer roster for one file: the folder audience (every member of an open project folder, or the granted users/groups of a restricted one), each annotated `signed` with `signer_name` / `signed_at`. Reads `ctx.filestore.signatures.roster` (new `@colixsystems/filestore-client` **0.5.0** method); the host 403s a non-manager, so use `error` to hide the panel for non-admins.
|
|
56
|
+
- `useFolderPermissions(folderId, { enabled? })` → `{ grants, loading, busy, error, refetch, grant, revoke, setVisibility }` — manage a folder's permissions (the Filestore ACL): `grant(subjectType, subjectId, 'VIEW'|'DOWNLOAD'|'MANAGE')`, `revoke(shareId)`, `setVisibility('INHERIT'|'RESTRICTED')`. Pair the user/group picker with `useUsers` / `useGroups`.
|
|
57
|
+
|
|
58
|
+
Also: `useFileSignatures(fileIds)` is now **self-scoped** (the caller's own signatures only) — the backend tightened; the hook's shape is unchanged. **`CONTRACT.version` → `1.25.0`** (additive: two new hooks; the underlying `filestore-client` → `0.5.0`, shares folder-scoped).
|
|
59
|
+
|
|
60
|
+
### What's new in 0.34.0
|
|
61
|
+
|
|
62
|
+
**`useBankIdLink()` — link / unlink BankID from a widget (REQ-BANKID-AUTH).** A new DIRECTORY hook reading the new `ctx.directory.bankid` namespace on `@colixsystems/directory-client` **0.2.0** (`status` / `startLink` / `collect` / `cancel` / `unlink`). It lets a signed-in app-user attach a BankID identity to their account (or remove it) via an animated-QR poll — `startLink()` opens the order, `refresh()` polls it (drive on a ~2s interval while `status === "pending"`; render `qr` with the `Image` primitive), `unlink()` removes it. `available` is `false` when BankID can't be used in the app (provider disabled or no platform cert) — hide the affordance then. Self-service + JWT-gated, so **no `requestedScopes` entry is required**. Backs the built-in **User** widget's "Link BankID" section. `CONTRACT.version` → `1.24.0`. Additive — no existing export or type changed.
|
|
50
63
|
|
|
51
64
|
### What's new in 0.33.0
|
|
52
65
|
|
package/dist/contract.cjs
CHANGED
|
@@ -236,6 +236,51 @@ const HOOKS = [
|
|
|
236
236
|
requiredContextSlice: ["filestore.signatures"],
|
|
237
237
|
scopes: ["files.read:*"],
|
|
238
238
|
},
|
|
239
|
+
{
|
|
240
|
+
name: "useFileRoster",
|
|
241
|
+
signature: "useFileRoster(fileId, { limit?, offset?, enabled? }?)",
|
|
242
|
+
description:
|
|
243
|
+
"MANAGE-gated signer roster for one Filestore file: the people expected " +
|
|
244
|
+
"to be able to sign it (the file's folder audience) each annotated " +
|
|
245
|
+
"signed/not-signed, plus a distinct signed_count. Reads " +
|
|
246
|
+
"ctx.filestore.signatures.roster and unwraps { data, meta }. The host 403s " +
|
|
247
|
+
"a caller who can see the file but does not MANAGE its folder — surface " +
|
|
248
|
+
"that as 'not an admin here' via `error`.",
|
|
249
|
+
returnShape: {
|
|
250
|
+
roster:
|
|
251
|
+
"{ app_user_id, name, signed, signature_id, signer_name, signed_at }[]",
|
|
252
|
+
total: "number",
|
|
253
|
+
signedCount: "number",
|
|
254
|
+
loading: "boolean",
|
|
255
|
+
error: "Error | null",
|
|
256
|
+
refetch: "() => Promise<void>",
|
|
257
|
+
},
|
|
258
|
+
requiredContextSlice: ["filestore.signatures"],
|
|
259
|
+
scopes: ["files.read:*"],
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
name: "useFolderPermissions",
|
|
263
|
+
signature: "useFolderPermissions(folderId, { enabled? }?)",
|
|
264
|
+
description:
|
|
265
|
+
"Manage a folder's permissions (the Filestore ACL — folder-scoped). Lists " +
|
|
266
|
+
"the folder's grants and exposes grant(subjectType, subjectId, permission) " +
|
|
267
|
+
"/ revoke(shareId) / setVisibility('INHERIT' | 'RESTRICTED') — all MANAGE " +
|
|
268
|
+
"actions the host enforces. Reads ctx.filestore.shares + ctx.filestore." +
|
|
269
|
+
"folders; pair the user/group picker with useUsers / useGroups.",
|
|
270
|
+
returnShape: {
|
|
271
|
+
grants:
|
|
272
|
+
"{ id, folder_id, subject_type, subject_id, permission, created_at }[]",
|
|
273
|
+
loading: "boolean",
|
|
274
|
+
busy: "boolean",
|
|
275
|
+
error: "Error | null",
|
|
276
|
+
refetch: "() => Promise<void>",
|
|
277
|
+
grant: "(subjectType, subjectId, permission) => Promise<share>",
|
|
278
|
+
revoke: "(shareId) => Promise<void>",
|
|
279
|
+
setVisibility: "(visibility) => Promise<folder>",
|
|
280
|
+
},
|
|
281
|
+
requiredContextSlice: ["filestore.shares", "filestore.folders"],
|
|
282
|
+
scopes: ["files.read:*", "files.write:*"],
|
|
283
|
+
},
|
|
239
284
|
{
|
|
240
285
|
name: "useDatastoreQuery",
|
|
241
286
|
signature: "useDatastoreQuery(tableId, options?)",
|
|
@@ -377,6 +422,42 @@ const HOOKS = [
|
|
|
377
422
|
requiredContextSlice: ["directory.groups"],
|
|
378
423
|
scopes: ["groups.read:*"],
|
|
379
424
|
},
|
|
425
|
+
// REQ-BANKID-AUTH — link / unlink a BankID identity to the signed-in
|
|
426
|
+
// app-user. Self-service + JWT-gated (no widget scope). Mirror of contract.js.
|
|
427
|
+
{
|
|
428
|
+
name: "useBankIdLink",
|
|
429
|
+
signature: "useBankIdLink()",
|
|
430
|
+
description:
|
|
431
|
+
"Link / unlink a BankID identity to the signed-in app-user via the " +
|
|
432
|
+
"injected directory-client at ctx.directory.bankid.{status,startLink," +
|
|
433
|
+
"collect,cancel,unlink}. Returns { linked, available, status, qr, message, " +
|
|
434
|
+
"loading, statusLoading, error, startLink, refresh, cancel, unlink, " +
|
|
435
|
+
"refetchStatus }. On mount it reads the link status; `available` is false " +
|
|
436
|
+
"when BankID can't be used here (provider disabled or no platform cert) — " +
|
|
437
|
+
"hide the Link affordance then. The link flow is an animated-QR poll like " +
|
|
438
|
+
"useFileSignature: startLink() opens an order (status 'pending' + qr — a " +
|
|
439
|
+
"PNG data-URL, render with the Image primitive); refresh() polls (drive on " +
|
|
440
|
+
"a ~2s interval while pending; sets linked=true on complete); cancel() " +
|
|
441
|
+
"aborts; unlink() removes the link (rejects DirectoryError LAST_AUTH_METHOD " +
|
|
442
|
+
"when BankID is the only sign-in method). No requestedScopes entry needed.",
|
|
443
|
+
returnShape: {
|
|
444
|
+
linked: "boolean | null // null until the status loads",
|
|
445
|
+
available: "boolean // false → hide the Link affordance",
|
|
446
|
+
status: "'pending' | 'complete' | 'failed' | 'cancelled' | null",
|
|
447
|
+
qr: "string | null // PNG data-URL of the animated BankID QR",
|
|
448
|
+
message: "string | null",
|
|
449
|
+
loading: "boolean",
|
|
450
|
+
statusLoading: "boolean",
|
|
451
|
+
error: "DirectoryError | null",
|
|
452
|
+
startLink: "() => Promise<{ order_ref, qr, auto_start_token, status }>",
|
|
453
|
+
refresh: "() => Promise<void>",
|
|
454
|
+
cancel: "() => Promise<void>",
|
|
455
|
+
unlink: "() => Promise<{ linked: boolean }> // rejects with DirectoryError",
|
|
456
|
+
refetchStatus: "() => Promise<{ linked, available }>",
|
|
457
|
+
},
|
|
458
|
+
requiredContextSlice: ["directory.bankid"],
|
|
459
|
+
scopes: null,
|
|
460
|
+
},
|
|
380
461
|
// REQ-ACL-06 / REQ-ACL-RELINHERIT-05 — per-record VirtualPermission
|
|
381
462
|
// management for a single record. Mirror of contract.js.
|
|
382
463
|
{
|
|
@@ -809,10 +890,11 @@ const WIDGET_CONTEXT_SHAPE = {
|
|
|
809
890
|
"Injected @colixsystems/directory-client instance. " +
|
|
810
891
|
"{ me(), users: { list(query?) -> Promise<{ data, meta }>, get(id), invite(body), deactivate(id), reactivate(id) }, " +
|
|
811
892
|
"groups: { list(query?) -> Promise<{ data, meta }>, create(body), remove(id), addMember(groupId, userId), removeMember(groupId, userId), listMine() }, " +
|
|
812
|
-
"invites: { list(), revoke(id), resend(id) }
|
|
813
|
-
"
|
|
893
|
+
"invites: { list(), revoke(id), resend(id) }, " +
|
|
894
|
+
"bankid: { status() -> { linked, available }, startLink() -> { order_ref, qr, ... }, collect(orderRef), cancel(orderRef), unlink() } }. " +
|
|
895
|
+
"users backs useDirectory() + useUsers(); groups backs useGroups(); bankid backs useBankIdLink() (REQ-BANKID-AUTH — self-service account linking, JWT-gated, no widget scope). List methods return the { data, meta } envelope verbatim (hooks unwrap res.data); rows/bodies are snake_case. Reads gated by directory.read:users / users.read:* / groups.read:*; mutations by users.write:* / groups.write:*.",
|
|
814
896
|
required: true,
|
|
815
|
-
fields: { users: "object", groups: "object" },
|
|
897
|
+
fields: { users: "object", groups: "object", bankid: "object" },
|
|
816
898
|
},
|
|
817
899
|
files: {
|
|
818
900
|
description:
|
|
@@ -830,7 +912,7 @@ const WIDGET_CONTEXT_SHAPE = {
|
|
|
830
912
|
"signatures: { initiate(fileId), status(id), cancel(id), verify(id) }, objectUrl(token), fetchObject(token) }. " +
|
|
831
913
|
"Backs useFilestoreFiles() + useFilestoreFolders() + useFileSignature(). Optional — a host without an end-user filestore omits it and those hooks throw a clear error.",
|
|
832
914
|
required: false,
|
|
833
|
-
fields: { files: "object", folders: "object", signatures: "object" },
|
|
915
|
+
fields: { files: "object", folders: "object", shares: "object", signatures: "object" },
|
|
834
916
|
},
|
|
835
917
|
renderer: {
|
|
836
918
|
description:
|
|
@@ -1349,7 +1431,23 @@ const CONTRACT = deepFreeze({
|
|
|
1349
1431
|
// form-root tableId switches) and `ui.defaultFactory: "tabId"`
|
|
1350
1432
|
// (per-instance dynamic default). No existing field, hook, primitive,
|
|
1351
1433
|
// or token changed shape — minor bump on the pre-1.0 channel.
|
|
1352
|
-
|
|
1434
|
+
//
|
|
1435
|
+
// 1.24.0: additive (REQ-BANKID-AUTH) — new `useBankIdLink()` hook reading the
|
|
1436
|
+
// new `ctx.directory.bankid` namespace (status/startLink/collect/cancel/
|
|
1437
|
+
// unlink) on the @colixsystems/directory-client (0.2.0). Lets the built-in
|
|
1438
|
+
// User widget link / unlink a BankID identity to the signed-in app-user
|
|
1439
|
+
// (animated-QR poll). Self-service + JWT-gated — no requestedScopes entry.
|
|
1440
|
+
// No existing hook, primitive, or field changed shape — minor bump on the
|
|
1441
|
+
// pre-1.0 channel.
|
|
1442
|
+
//
|
|
1443
|
+
// 1.25.0: additive (REQ-FSH / REQ-SIGN) — new `useFileRoster(fileId)`
|
|
1444
|
+
// (MANAGE-gated signer roster: the folder audience annotated
|
|
1445
|
+
// signed/not-signed) and `useFolderPermissions(folderId)` (manage the
|
|
1446
|
+
// folder ACL: list grants + grant/revoke a VIEW/DOWNLOAD/MANAGE tier +
|
|
1447
|
+
// setVisibility). Both read the existing `ctx.filestore`; the underlying
|
|
1448
|
+
// `filestore-client` 0.5.0 made shares folder-scoped + added
|
|
1449
|
+
// `signatures.roster`. No existing hook changed.
|
|
1450
|
+
version: "1.25.0",
|
|
1353
1451
|
hooks: HOOKS,
|
|
1354
1452
|
primitives: PRIMITIVES,
|
|
1355
1453
|
manifestSchema: MANIFEST_SCHEMA,
|
package/dist/contract.js
CHANGED
|
@@ -236,6 +236,51 @@ const HOOKS = [
|
|
|
236
236
|
requiredContextSlice: ["filestore.signatures"],
|
|
237
237
|
scopes: ["files.read:*"],
|
|
238
238
|
},
|
|
239
|
+
{
|
|
240
|
+
name: "useFileRoster",
|
|
241
|
+
signature: "useFileRoster(fileId, { limit?, offset?, enabled? }?)",
|
|
242
|
+
description:
|
|
243
|
+
"MANAGE-gated signer roster for one Filestore file: the people expected " +
|
|
244
|
+
"to be able to sign it (the file's folder audience) each annotated " +
|
|
245
|
+
"signed/not-signed, plus a distinct signed_count. Reads " +
|
|
246
|
+
"ctx.filestore.signatures.roster and unwraps { data, meta }. The host 403s " +
|
|
247
|
+
"a caller who can see the file but does not MANAGE its folder — surface " +
|
|
248
|
+
"that as 'not an admin here' via `error`.",
|
|
249
|
+
returnShape: {
|
|
250
|
+
roster:
|
|
251
|
+
"{ app_user_id, name, signed, signature_id, signer_name, signed_at }[]",
|
|
252
|
+
total: "number",
|
|
253
|
+
signedCount: "number",
|
|
254
|
+
loading: "boolean",
|
|
255
|
+
error: "Error | null",
|
|
256
|
+
refetch: "() => Promise<void>",
|
|
257
|
+
},
|
|
258
|
+
requiredContextSlice: ["filestore.signatures"],
|
|
259
|
+
scopes: ["files.read:*"],
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
name: "useFolderPermissions",
|
|
263
|
+
signature: "useFolderPermissions(folderId, { enabled? }?)",
|
|
264
|
+
description:
|
|
265
|
+
"Manage a folder's permissions (the Filestore ACL — folder-scoped). Lists " +
|
|
266
|
+
"the folder's grants and exposes grant(subjectType, subjectId, permission) " +
|
|
267
|
+
"/ revoke(shareId) / setVisibility('INHERIT' | 'RESTRICTED') — all MANAGE " +
|
|
268
|
+
"actions the host enforces. Reads ctx.filestore.shares + ctx.filestore." +
|
|
269
|
+
"folders; pair the user/group picker with useUsers / useGroups.",
|
|
270
|
+
returnShape: {
|
|
271
|
+
grants:
|
|
272
|
+
"{ id, folder_id, subject_type, subject_id, permission, created_at }[]",
|
|
273
|
+
loading: "boolean",
|
|
274
|
+
busy: "boolean",
|
|
275
|
+
error: "Error | null",
|
|
276
|
+
refetch: "() => Promise<void>",
|
|
277
|
+
grant: "(subjectType, subjectId, permission) => Promise<share>",
|
|
278
|
+
revoke: "(shareId) => Promise<void>",
|
|
279
|
+
setVisibility: "(visibility) => Promise<folder>",
|
|
280
|
+
},
|
|
281
|
+
requiredContextSlice: ["filestore.shares", "filestore.folders"],
|
|
282
|
+
scopes: ["files.read:*", "files.write:*"],
|
|
283
|
+
},
|
|
239
284
|
{
|
|
240
285
|
name: "useDatastoreQuery",
|
|
241
286
|
signature: "useDatastoreQuery(tableId, options?)",
|
|
@@ -377,6 +422,42 @@ const HOOKS = [
|
|
|
377
422
|
requiredContextSlice: ["directory.groups"],
|
|
378
423
|
scopes: ["groups.read:*"],
|
|
379
424
|
},
|
|
425
|
+
// REQ-BANKID-AUTH — link / unlink a BankID identity to the signed-in
|
|
426
|
+
// app-user. Self-service + JWT-gated (no widget scope). Mirror of contract.cjs.
|
|
427
|
+
{
|
|
428
|
+
name: "useBankIdLink",
|
|
429
|
+
signature: "useBankIdLink()",
|
|
430
|
+
description:
|
|
431
|
+
"Link / unlink a BankID identity to the signed-in app-user via the " +
|
|
432
|
+
"injected directory-client at ctx.directory.bankid.{status,startLink," +
|
|
433
|
+
"collect,cancel,unlink}. Returns { linked, available, status, qr, message, " +
|
|
434
|
+
"loading, statusLoading, error, startLink, refresh, cancel, unlink, " +
|
|
435
|
+
"refetchStatus }. On mount it reads the link status; `available` is false " +
|
|
436
|
+
"when BankID can't be used here (provider disabled or no platform cert) — " +
|
|
437
|
+
"hide the Link affordance then. The link flow is an animated-QR poll like " +
|
|
438
|
+
"useFileSignature: startLink() opens an order (status 'pending' + qr — a " +
|
|
439
|
+
"PNG data-URL, render with the Image primitive); refresh() polls (drive on " +
|
|
440
|
+
"a ~2s interval while pending; sets linked=true on complete); cancel() " +
|
|
441
|
+
"aborts; unlink() removes the link (rejects DirectoryError LAST_AUTH_METHOD " +
|
|
442
|
+
"when BankID is the only sign-in method). No requestedScopes entry needed.",
|
|
443
|
+
returnShape: {
|
|
444
|
+
linked: "boolean | null // null until the status loads",
|
|
445
|
+
available: "boolean // false → hide the Link affordance",
|
|
446
|
+
status: "'pending' | 'complete' | 'failed' | 'cancelled' | null",
|
|
447
|
+
qr: "string | null // PNG data-URL of the animated BankID QR",
|
|
448
|
+
message: "string | null",
|
|
449
|
+
loading: "boolean",
|
|
450
|
+
statusLoading: "boolean",
|
|
451
|
+
error: "DirectoryError | null",
|
|
452
|
+
startLink: "() => Promise<{ order_ref, qr, auto_start_token, status }>",
|
|
453
|
+
refresh: "() => Promise<void>",
|
|
454
|
+
cancel: "() => Promise<void>",
|
|
455
|
+
unlink: "() => Promise<{ linked: boolean }> // rejects with DirectoryError",
|
|
456
|
+
refetchStatus: "() => Promise<{ linked, available }>",
|
|
457
|
+
},
|
|
458
|
+
requiredContextSlice: ["directory.bankid"],
|
|
459
|
+
scopes: null,
|
|
460
|
+
},
|
|
380
461
|
// REQ-ACL-06 / REQ-ACL-RELINHERIT-05 — per-record VirtualPermission
|
|
381
462
|
// management for a single record. Mirror of contract.js.
|
|
382
463
|
{
|
|
@@ -809,10 +890,11 @@ const WIDGET_CONTEXT_SHAPE = {
|
|
|
809
890
|
"Injected @colixsystems/directory-client instance. " +
|
|
810
891
|
"{ me(), users: { list(query?) -> Promise<{ data, meta }>, get(id), invite(body), deactivate(id), reactivate(id) }, " +
|
|
811
892
|
"groups: { list(query?) -> Promise<{ data, meta }>, create(body), remove(id), addMember(groupId, userId), removeMember(groupId, userId), listMine() }, " +
|
|
812
|
-
"invites: { list(), revoke(id), resend(id) }
|
|
813
|
-
"
|
|
893
|
+
"invites: { list(), revoke(id), resend(id) }, " +
|
|
894
|
+
"bankid: { status() -> { linked, available }, startLink() -> { order_ref, qr, ... }, collect(orderRef), cancel(orderRef), unlink() } }. " +
|
|
895
|
+
"users backs useDirectory() + useUsers(); groups backs useGroups(); bankid backs useBankIdLink() (REQ-BANKID-AUTH — self-service account linking, JWT-gated, no widget scope). List methods return the { data, meta } envelope verbatim (hooks unwrap res.data); rows/bodies are snake_case. Reads gated by directory.read:users / users.read:* / groups.read:*; mutations by users.write:* / groups.write:*.",
|
|
814
896
|
required: true,
|
|
815
|
-
fields: { users: "object", groups: "object" },
|
|
897
|
+
fields: { users: "object", groups: "object", bankid: "object" },
|
|
816
898
|
},
|
|
817
899
|
files: {
|
|
818
900
|
description:
|
|
@@ -830,7 +912,7 @@ const WIDGET_CONTEXT_SHAPE = {
|
|
|
830
912
|
"signatures: { initiate(fileId), status(id), cancel(id), verify(id) }, objectUrl(token), fetchObject(token) }. " +
|
|
831
913
|
"Backs useFilestoreFiles() + useFilestoreFolders() + useFileSignature(). Optional — a host without an end-user filestore omits it and those hooks throw a clear error.",
|
|
832
914
|
required: false,
|
|
833
|
-
fields: { files: "object", folders: "object", signatures: "object" },
|
|
915
|
+
fields: { files: "object", folders: "object", shares: "object", signatures: "object" },
|
|
834
916
|
},
|
|
835
917
|
renderer: {
|
|
836
918
|
description:
|
|
@@ -1349,7 +1431,23 @@ const CONTRACT = deepFreeze({
|
|
|
1349
1431
|
// form-root tableId switches) and `ui.defaultFactory: "tabId"`
|
|
1350
1432
|
// (per-instance dynamic default). No existing field, hook, primitive,
|
|
1351
1433
|
// or token changed shape — minor bump on the pre-1.0 channel.
|
|
1352
|
-
|
|
1434
|
+
//
|
|
1435
|
+
// 1.24.0: additive (REQ-BANKID-AUTH) — new `useBankIdLink()` hook reading the
|
|
1436
|
+
// new `ctx.directory.bankid` namespace (status/startLink/collect/cancel/
|
|
1437
|
+
// unlink) on the @colixsystems/directory-client (0.2.0). Lets the built-in
|
|
1438
|
+
// User widget link / unlink a BankID identity to the signed-in app-user
|
|
1439
|
+
// (animated-QR poll). Self-service + JWT-gated — no requestedScopes entry.
|
|
1440
|
+
// No existing hook, primitive, or field changed shape — minor bump on the
|
|
1441
|
+
// pre-1.0 channel.
|
|
1442
|
+
//
|
|
1443
|
+
// 1.25.0: additive (REQ-FSH / REQ-SIGN) — new `useFileRoster(fileId)`
|
|
1444
|
+
// (MANAGE-gated signer roster: the folder audience annotated
|
|
1445
|
+
// signed/not-signed) and `useFolderPermissions(folderId)` (manage the
|
|
1446
|
+
// folder ACL: list grants + grant/revoke a VIEW/DOWNLOAD/MANAGE tier +
|
|
1447
|
+
// setVisibility). Both read the existing `ctx.filestore`; the underlying
|
|
1448
|
+
// `filestore-client` 0.5.0 made shares folder-scoped + added
|
|
1449
|
+
// `signatures.roster`. No existing hook changed.
|
|
1450
|
+
version: "1.25.0",
|
|
1353
1451
|
hooks: HOOKS,
|
|
1354
1452
|
primitives: PRIMITIVES,
|
|
1355
1453
|
manifestSchema: MANIFEST_SCHEMA,
|
package/dist/hooks.js
CHANGED
|
@@ -1503,6 +1503,193 @@ export function useFileSignatures(fileIds) {
|
|
|
1503
1503
|
return { signaturesByFileId, loading, error, refetch };
|
|
1504
1504
|
}
|
|
1505
1505
|
|
|
1506
|
+
/**
|
|
1507
|
+
* REQ-SIGN: MANAGE-gated signer roster for one file — the people expected to be
|
|
1508
|
+
* able to sign it (the file's folder audience) each annotated signed/not-signed.
|
|
1509
|
+
* Reads ctx.filestore.signatures.roster. The host backend 403s a caller who can
|
|
1510
|
+
* see the file but doesn't manage its folder, so `error` (a ForbiddenError) is
|
|
1511
|
+
* the "you're not an admin here" signal the widget hides on. Returns
|
|
1512
|
+
* { roster, total, signedCount, loading, error, refetch }. `roster` items are
|
|
1513
|
+
* { app_user_id, name, signed, signature_id, signer_name, signed_at }.
|
|
1514
|
+
*/
|
|
1515
|
+
export function useFileRoster(fileId, options = {}) {
|
|
1516
|
+
const ctx = useWidgetContextOrThrow("useFileRoster");
|
|
1517
|
+
if (
|
|
1518
|
+
!ctx.filestore ||
|
|
1519
|
+
!ctx.filestore.signatures ||
|
|
1520
|
+
typeof ctx.filestore.signatures.roster !== "function"
|
|
1521
|
+
) {
|
|
1522
|
+
throw new Error(
|
|
1523
|
+
"useFileRoster: host did not inject a filestore client (ctx.filestore.signatures.roster)",
|
|
1524
|
+
);
|
|
1525
|
+
}
|
|
1526
|
+
const { limit = 50, offset = 0, enabled = true } = options;
|
|
1527
|
+
const [roster, setRoster] = useState([]);
|
|
1528
|
+
const [total, setTotal] = useState(0);
|
|
1529
|
+
const [signedCount, setSignedCount] = useState(0);
|
|
1530
|
+
const [loading, setLoading] = useState(Boolean(fileId) && enabled);
|
|
1531
|
+
const [error, setError] = useState(null);
|
|
1532
|
+
|
|
1533
|
+
const sigRef = useRef(ctx.filestore.signatures);
|
|
1534
|
+
sigRef.current = ctx.filestore.signatures;
|
|
1535
|
+
const argsRef = useRef({ fileId, limit, offset, enabled });
|
|
1536
|
+
argsRef.current = { fileId, limit, offset, enabled };
|
|
1537
|
+
const runRef = useRef(0);
|
|
1538
|
+
|
|
1539
|
+
const doFetch = useCallback(async () => {
|
|
1540
|
+
const myRun = ++runRef.current;
|
|
1541
|
+
const { fileId: fid, limit: lim, offset: off, enabled: en } = argsRef.current;
|
|
1542
|
+
if (!fid || !en) {
|
|
1543
|
+
setLoading(false);
|
|
1544
|
+
setError(null);
|
|
1545
|
+
setRoster([]);
|
|
1546
|
+
setTotal(0);
|
|
1547
|
+
setSignedCount(0);
|
|
1548
|
+
return;
|
|
1549
|
+
}
|
|
1550
|
+
setLoading(true);
|
|
1551
|
+
setError(null);
|
|
1552
|
+
try {
|
|
1553
|
+
const res = await sigRef.current.roster(fid, { limit: lim, offset: off });
|
|
1554
|
+
if (runRef.current !== myRun) return;
|
|
1555
|
+
const rows = res && Array.isArray(res.data) ? res.data : [];
|
|
1556
|
+
const meta = (res && res.meta) || {};
|
|
1557
|
+
setRoster(rows);
|
|
1558
|
+
setTotal(typeof meta.total === "number" ? meta.total : rows.length);
|
|
1559
|
+
setSignedCount(typeof meta.signed_count === "number" ? meta.signed_count : 0);
|
|
1560
|
+
setLoading(false);
|
|
1561
|
+
} catch (err) {
|
|
1562
|
+
if (runRef.current !== myRun) return;
|
|
1563
|
+
setError(err);
|
|
1564
|
+
setLoading(false);
|
|
1565
|
+
}
|
|
1566
|
+
}, []);
|
|
1567
|
+
|
|
1568
|
+
useEffect(() => {
|
|
1569
|
+
doFetch();
|
|
1570
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1571
|
+
}, [fileId, limit, offset, enabled]);
|
|
1572
|
+
|
|
1573
|
+
const refetch = useCallback(async () => {
|
|
1574
|
+
await doFetch();
|
|
1575
|
+
}, [doFetch]);
|
|
1576
|
+
|
|
1577
|
+
return { roster, total, signedCount, loading, error, refetch };
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1580
|
+
/**
|
|
1581
|
+
* REQ-FSH: folder-permission management for one folder (the Filestore ACL).
|
|
1582
|
+
* Reads ctx.filestore.shares + ctx.filestore.folders. Lists the folder's grants
|
|
1583
|
+
* and exposes grant / revoke / setVisibility mutations — all MANAGE actions the
|
|
1584
|
+
* host backend enforces (a non-manager's mutation rejects). Pair the user/group
|
|
1585
|
+
* picker with useUsers / useGroups. Returns { grants, loading, busy, error,
|
|
1586
|
+
* refetch, grant, revoke, setVisibility }.
|
|
1587
|
+
*/
|
|
1588
|
+
export function useFolderPermissions(folderId, options = {}) {
|
|
1589
|
+
const ctx = useWidgetContextOrThrow("useFolderPermissions");
|
|
1590
|
+
if (
|
|
1591
|
+
!ctx.filestore ||
|
|
1592
|
+
!ctx.filestore.shares ||
|
|
1593
|
+
typeof ctx.filestore.shares.list !== "function" ||
|
|
1594
|
+
!ctx.filestore.folders ||
|
|
1595
|
+
typeof ctx.filestore.folders.update !== "function"
|
|
1596
|
+
) {
|
|
1597
|
+
throw new Error(
|
|
1598
|
+
"useFolderPermissions: host did not inject a filestore client (ctx.filestore.shares / ctx.filestore.folders)",
|
|
1599
|
+
);
|
|
1600
|
+
}
|
|
1601
|
+
const { enabled = true } = options;
|
|
1602
|
+
const [grants, setGrants] = useState([]);
|
|
1603
|
+
const [loading, setLoading] = useState(Boolean(folderId) && enabled);
|
|
1604
|
+
const [busy, setBusy] = useState(false);
|
|
1605
|
+
const [error, setError] = useState(null);
|
|
1606
|
+
|
|
1607
|
+
const sharesRef = useRef(ctx.filestore.shares);
|
|
1608
|
+
sharesRef.current = ctx.filestore.shares;
|
|
1609
|
+
const foldersRef = useRef(ctx.filestore.folders);
|
|
1610
|
+
foldersRef.current = ctx.filestore.folders;
|
|
1611
|
+
const idRef = useRef({ folderId, enabled });
|
|
1612
|
+
idRef.current = { folderId, enabled };
|
|
1613
|
+
const runRef = useRef(0);
|
|
1614
|
+
|
|
1615
|
+
const doFetch = useCallback(async () => {
|
|
1616
|
+
const myRun = ++runRef.current;
|
|
1617
|
+
const { folderId: fid, enabled: en } = idRef.current;
|
|
1618
|
+
if (!fid || !en) {
|
|
1619
|
+
setLoading(false);
|
|
1620
|
+
setError(null);
|
|
1621
|
+
setGrants([]);
|
|
1622
|
+
return;
|
|
1623
|
+
}
|
|
1624
|
+
setLoading(true);
|
|
1625
|
+
setError(null);
|
|
1626
|
+
try {
|
|
1627
|
+
const res = await sharesRef.current.list({ folder_id: fid });
|
|
1628
|
+
if (runRef.current !== myRun) return;
|
|
1629
|
+
setGrants(res && Array.isArray(res.data) ? res.data : []);
|
|
1630
|
+
setLoading(false);
|
|
1631
|
+
} catch (err) {
|
|
1632
|
+
if (runRef.current !== myRun) return;
|
|
1633
|
+
setError(err);
|
|
1634
|
+
setLoading(false);
|
|
1635
|
+
}
|
|
1636
|
+
}, []);
|
|
1637
|
+
|
|
1638
|
+
useEffect(() => {
|
|
1639
|
+
doFetch();
|
|
1640
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1641
|
+
}, [folderId, enabled]);
|
|
1642
|
+
|
|
1643
|
+
const refetch = useCallback(async () => {
|
|
1644
|
+
await doFetch();
|
|
1645
|
+
}, [doFetch]);
|
|
1646
|
+
|
|
1647
|
+
const grant = useCallback(
|
|
1648
|
+
async (subjectType, subjectId, permission) => {
|
|
1649
|
+
const { folderId: fid } = idRef.current;
|
|
1650
|
+
setBusy(true);
|
|
1651
|
+
try {
|
|
1652
|
+
const row = await sharesRef.current.create({
|
|
1653
|
+
folder_id: fid,
|
|
1654
|
+
subject_type: subjectType,
|
|
1655
|
+
subject_id: subjectId,
|
|
1656
|
+
permission,
|
|
1657
|
+
});
|
|
1658
|
+
await doFetch();
|
|
1659
|
+
return row;
|
|
1660
|
+
} finally {
|
|
1661
|
+
setBusy(false);
|
|
1662
|
+
}
|
|
1663
|
+
},
|
|
1664
|
+
[doFetch],
|
|
1665
|
+
);
|
|
1666
|
+
|
|
1667
|
+
const revoke = useCallback(
|
|
1668
|
+
async (shareId) => {
|
|
1669
|
+
setBusy(true);
|
|
1670
|
+
try {
|
|
1671
|
+
await sharesRef.current.remove(shareId);
|
|
1672
|
+
await doFetch();
|
|
1673
|
+
} finally {
|
|
1674
|
+
setBusy(false);
|
|
1675
|
+
}
|
|
1676
|
+
},
|
|
1677
|
+
[doFetch],
|
|
1678
|
+
);
|
|
1679
|
+
|
|
1680
|
+
const setVisibility = useCallback(async (visibility) => {
|
|
1681
|
+
const { folderId: fid } = idRef.current;
|
|
1682
|
+
setBusy(true);
|
|
1683
|
+
try {
|
|
1684
|
+
return await foldersRef.current.update(fid, { visibility });
|
|
1685
|
+
} finally {
|
|
1686
|
+
setBusy(false);
|
|
1687
|
+
}
|
|
1688
|
+
}, []);
|
|
1689
|
+
|
|
1690
|
+
return { grants, loading, busy, error, refetch, grant, revoke, setVisibility };
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1506
1693
|
/* ============================================================================
|
|
1507
1694
|
* DIRECTORY CLIENT — ctx.directory (@colixsystems/directory-client)
|
|
1508
1695
|
*
|
|
@@ -1850,6 +2037,170 @@ export function useGroups(query) {
|
|
|
1850
2037
|
return { groups, loading, error, refetch, create, remove, addMember, removeMember };
|
|
1851
2038
|
}
|
|
1852
2039
|
|
|
2040
|
+
/**
|
|
2041
|
+
* REQ-BANKID-AUTH — link / unlink a BankID identity to the signed-in app-user,
|
|
2042
|
+
* and read whether BankID is available + already linked. Returns
|
|
2043
|
+
* `{ linked, available, status, qr, message, loading, statusLoading, error,
|
|
2044
|
+
* startLink, refresh, cancel, unlink, refetchStatus }`.
|
|
2045
|
+
*
|
|
2046
|
+
* Reads the injected `@colixsystems/directory-client` at `ctx.directory.bankid`
|
|
2047
|
+
* (a self-service, JWT-gated surface — no widget scope needed). On mount it
|
|
2048
|
+
* fetches the link status; `available` is false when BankID can't be used in
|
|
2049
|
+
* this app (provider disabled or no platform cert), so a widget should hide the
|
|
2050
|
+
* Link affordance entirely when `!available`.
|
|
2051
|
+
*
|
|
2052
|
+
* The link flow is an animated-QR poll, mirroring `useFileSignature`:
|
|
2053
|
+
* - `startLink()` opens an order → sets `status: "pending"` + `qr` (a PNG
|
|
2054
|
+
* data-URL; render it with the `Image` primitive).
|
|
2055
|
+
* - `refresh()` polls the order — refreshing the QR frame while pending and,
|
|
2056
|
+
* on completion, setting `linked: true` + `status: "complete"`. The widget
|
|
2057
|
+
* drives the cadence (e.g. a 2s interval while `status === "pending"`).
|
|
2058
|
+
* - `cancel()` aborts the in-flight order.
|
|
2059
|
+
* - `unlink()` removes the link (rejects with a DirectoryError carrying
|
|
2060
|
+
* `code: "LAST_AUTH_METHOD"` / status 409 when BankID is the user's only
|
|
2061
|
+
* sign-in method).
|
|
2062
|
+
*
|
|
2063
|
+
* No `requestedScopes` entry is required — the endpoints are gated by the
|
|
2064
|
+
* app-user session the host already holds.
|
|
2065
|
+
*/
|
|
2066
|
+
export function useBankIdLink() {
|
|
2067
|
+
const ctx = useWidgetContextOrThrow("useBankIdLink");
|
|
2068
|
+
if (
|
|
2069
|
+
!ctx.directory ||
|
|
2070
|
+
!ctx.directory.bankid ||
|
|
2071
|
+
typeof ctx.directory.bankid.status !== "function"
|
|
2072
|
+
) {
|
|
2073
|
+
throw new Error(
|
|
2074
|
+
"useBankIdLink: host did not inject a directory client (ctx.directory.bankid)",
|
|
2075
|
+
);
|
|
2076
|
+
}
|
|
2077
|
+
// `ctx` is a fresh identity each host render — hold the namespace in a ref so
|
|
2078
|
+
// the callbacks stay stable.
|
|
2079
|
+
const apiRef = useRef(ctx.directory.bankid);
|
|
2080
|
+
apiRef.current = ctx.directory.bankid;
|
|
2081
|
+
|
|
2082
|
+
const [linked, setLinked] = useState(null); // null until the status loads
|
|
2083
|
+
const [available, setAvailable] = useState(false);
|
|
2084
|
+
const [statusLoading, setStatusLoading] = useState(true);
|
|
2085
|
+
const [status, setStatus] = useState(null); // null | pending | complete | failed | cancelled
|
|
2086
|
+
const [qr, setQr] = useState(null);
|
|
2087
|
+
const [message, setMessage] = useState(null);
|
|
2088
|
+
const [loading, setLoading] = useState(false);
|
|
2089
|
+
const [error, setError] = useState(null);
|
|
2090
|
+
const orderRef = useRef(null);
|
|
2091
|
+
|
|
2092
|
+
const refetchStatus = useCallback(async () => {
|
|
2093
|
+
setStatusLoading(true);
|
|
2094
|
+
try {
|
|
2095
|
+
const res = await apiRef.current.status();
|
|
2096
|
+
setLinked(Boolean(res && res.linked));
|
|
2097
|
+
setAvailable(Boolean(res && res.available));
|
|
2098
|
+
setStatusLoading(false);
|
|
2099
|
+
return res;
|
|
2100
|
+
} catch (err) {
|
|
2101
|
+
setError(toDirectoryError(err));
|
|
2102
|
+
setStatusLoading(false);
|
|
2103
|
+
return null;
|
|
2104
|
+
}
|
|
2105
|
+
}, []);
|
|
2106
|
+
|
|
2107
|
+
useEffect(() => {
|
|
2108
|
+
refetchStatus();
|
|
2109
|
+
}, [refetchStatus]);
|
|
2110
|
+
|
|
2111
|
+
const startLink = useCallback(async () => {
|
|
2112
|
+
setLoading(true);
|
|
2113
|
+
setError(null);
|
|
2114
|
+
try {
|
|
2115
|
+
const res = await apiRef.current.startLink();
|
|
2116
|
+
orderRef.current = res && res.order_ref ? res.order_ref : null;
|
|
2117
|
+
setStatus(res && res.status ? res.status : "pending");
|
|
2118
|
+
setQr(res && res.qr ? res.qr : null);
|
|
2119
|
+
setMessage(null);
|
|
2120
|
+
setLoading(false);
|
|
2121
|
+
return res;
|
|
2122
|
+
} catch (err) {
|
|
2123
|
+
const e = toDirectoryError(err);
|
|
2124
|
+
setError(e);
|
|
2125
|
+
setStatus("failed");
|
|
2126
|
+
setMessage(e.message);
|
|
2127
|
+
setLoading(false);
|
|
2128
|
+
throw e;
|
|
2129
|
+
}
|
|
2130
|
+
}, []);
|
|
2131
|
+
|
|
2132
|
+
const refresh = useCallback(async () => {
|
|
2133
|
+
const id = orderRef.current;
|
|
2134
|
+
if (!id) return null;
|
|
2135
|
+
try {
|
|
2136
|
+
const res = await apiRef.current.collect(id);
|
|
2137
|
+
if (res) {
|
|
2138
|
+
if (res.status != null) setStatus(res.status);
|
|
2139
|
+
if (res.qr !== undefined) setQr(res.qr || null);
|
|
2140
|
+
if (res.message !== undefined) setMessage(res.message || null);
|
|
2141
|
+
if (res.status === "complete") setLinked(true);
|
|
2142
|
+
}
|
|
2143
|
+
return res;
|
|
2144
|
+
} catch (err) {
|
|
2145
|
+
// A terminal client error (e.g. 409 BANKID_ALREADY_LINKED) carries a
|
|
2146
|
+
// helpful message — surface it as a failed order rather than throwing
|
|
2147
|
+
// into the widget's poll interval.
|
|
2148
|
+
const e = toDirectoryError(err);
|
|
2149
|
+
setError(e);
|
|
2150
|
+
setStatus("failed");
|
|
2151
|
+
setMessage(e.message);
|
|
2152
|
+
return null;
|
|
2153
|
+
}
|
|
2154
|
+
}, []);
|
|
2155
|
+
|
|
2156
|
+
const cancel = useCallback(async () => {
|
|
2157
|
+
const id = orderRef.current;
|
|
2158
|
+
orderRef.current = null;
|
|
2159
|
+
setStatus(null);
|
|
2160
|
+
setQr(null);
|
|
2161
|
+
setMessage(null);
|
|
2162
|
+
if (id) {
|
|
2163
|
+
try {
|
|
2164
|
+
await apiRef.current.cancel(id);
|
|
2165
|
+
} catch {
|
|
2166
|
+
// best-effort — the order may have already expired.
|
|
2167
|
+
}
|
|
2168
|
+
}
|
|
2169
|
+
}, []);
|
|
2170
|
+
|
|
2171
|
+
const unlink = useCallback(async () => {
|
|
2172
|
+
setLoading(true);
|
|
2173
|
+
setError(null);
|
|
2174
|
+
try {
|
|
2175
|
+
const res = await apiRef.current.unlink();
|
|
2176
|
+
setLinked(Boolean(res && res.linked));
|
|
2177
|
+
setLoading(false);
|
|
2178
|
+
return res;
|
|
2179
|
+
} catch (err) {
|
|
2180
|
+
const e = toDirectoryError(err);
|
|
2181
|
+
setError(e);
|
|
2182
|
+
setLoading(false);
|
|
2183
|
+
throw e;
|
|
2184
|
+
}
|
|
2185
|
+
}, []);
|
|
2186
|
+
|
|
2187
|
+
return {
|
|
2188
|
+
linked,
|
|
2189
|
+
available,
|
|
2190
|
+
status,
|
|
2191
|
+
qr,
|
|
2192
|
+
message,
|
|
2193
|
+
loading,
|
|
2194
|
+
statusLoading,
|
|
2195
|
+
error,
|
|
2196
|
+
startLink,
|
|
2197
|
+
refresh,
|
|
2198
|
+
cancel,
|
|
2199
|
+
unlink,
|
|
2200
|
+
refetchStatus,
|
|
2201
|
+
};
|
|
2202
|
+
}
|
|
2203
|
+
|
|
1853
2204
|
/* ============================================================================
|
|
1854
2205
|
* PAYMENTS CLIENT — ctx.payments (@colixsystems/payments-client)
|
|
1855
2206
|
*
|
package/dist/index.d.ts
CHANGED
|
@@ -354,6 +354,24 @@ export interface DirectoryClient {
|
|
|
354
354
|
revoke(inviteId: string): Promise<void>;
|
|
355
355
|
resend(inviteId: string): Promise<unknown>;
|
|
356
356
|
};
|
|
357
|
+
/** REQ-BANKID-AUTH — backs `useBankIdLink`. */
|
|
358
|
+
bankid: {
|
|
359
|
+
status(): Promise<{ linked: boolean; available: boolean }>;
|
|
360
|
+
startLink(): Promise<{
|
|
361
|
+
order_ref: string;
|
|
362
|
+
auto_start_token: string | null;
|
|
363
|
+
qr: string | null;
|
|
364
|
+
status: "pending";
|
|
365
|
+
}>;
|
|
366
|
+
collect(orderRef: string): Promise<{
|
|
367
|
+
status: "pending" | "complete" | "failed" | "cancelled";
|
|
368
|
+
qr?: string | null;
|
|
369
|
+
message?: string;
|
|
370
|
+
linked?: boolean;
|
|
371
|
+
}>;
|
|
372
|
+
cancel(orderRef: string): Promise<{ status: "cancelled" }>;
|
|
373
|
+
unlink(): Promise<{ linked: boolean }>;
|
|
374
|
+
};
|
|
357
375
|
}
|
|
358
376
|
|
|
359
377
|
/**
|
|
@@ -924,6 +942,45 @@ export interface GroupsApi {
|
|
|
924
942
|
*/
|
|
925
943
|
export function useGroups(query?: GroupsQuery): GroupsApi;
|
|
926
944
|
|
|
945
|
+
// ----------------------------------------------------- useBankIdLink
|
|
946
|
+
//
|
|
947
|
+
// REQ-BANKID-AUTH — link / unlink a BankID identity to the signed-in app-user.
|
|
948
|
+
// Reads the injected directory-client at `ctx.directory.bankid`. Self-service,
|
|
949
|
+
// JWT-gated — no requestedScopes entry needed.
|
|
950
|
+
|
|
951
|
+
export interface BankIdLinkApi {
|
|
952
|
+
/** Whether the user currently has BankID linked (null until the status loads). */
|
|
953
|
+
linked: boolean | null;
|
|
954
|
+
/** Whether BankID linking can be used in this app (provider enabled + platform cert). */
|
|
955
|
+
available: boolean;
|
|
956
|
+
/** The active link order's state, or null when no order is in flight. */
|
|
957
|
+
status: "pending" | "complete" | "failed" | "cancelled" | null;
|
|
958
|
+
/** PNG data-URL of the animated BankID QR while pending (render with the Image primitive). */
|
|
959
|
+
qr: string | null;
|
|
960
|
+
message: string | null;
|
|
961
|
+
loading: boolean;
|
|
962
|
+
statusLoading: boolean;
|
|
963
|
+
error: DirectoryError | null;
|
|
964
|
+
/** Open a link order → sets status "pending" + qr. */
|
|
965
|
+
startLink(): Promise<unknown>;
|
|
966
|
+
/** Poll the open order; on completion sets linked=true. Drive on an interval while pending. */
|
|
967
|
+
refresh(): Promise<unknown>;
|
|
968
|
+
/** Abort the in-flight order. */
|
|
969
|
+
cancel(): Promise<void>;
|
|
970
|
+
/** Remove the link (rejects with DirectoryError code LAST_AUTH_METHOD when it is the only method). */
|
|
971
|
+
unlink(): Promise<{ linked: boolean }>;
|
|
972
|
+
/** Re-read { linked, available }. */
|
|
973
|
+
refetchStatus(): Promise<unknown>;
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
/**
|
|
977
|
+
* Link / unlink a BankID identity to the signed-in app-user and read the
|
|
978
|
+
* current link + availability state. Reads `ctx.directory.bankid`. No
|
|
979
|
+
* requestedScopes entry is required — the endpoints are gated by the app-user
|
|
980
|
+
* session the host holds. Hide the Link affordance when `available` is false.
|
|
981
|
+
*/
|
|
982
|
+
export function useBankIdLink(): BankIdLinkApi;
|
|
983
|
+
|
|
927
984
|
// ----------------------------------------------------- useRecordPermissions
|
|
928
985
|
//
|
|
929
986
|
// REQ-ACL-06 / REQ-ACL-RELINHERIT-05 — per-record VirtualPermission
|
package/dist/index.js
CHANGED
|
@@ -19,10 +19,13 @@ export {
|
|
|
19
19
|
useFilestoreFolders,
|
|
20
20
|
useFileSignature,
|
|
21
21
|
useFileSignatures,
|
|
22
|
+
useFileRoster,
|
|
23
|
+
useFolderPermissions,
|
|
22
24
|
useDatastoreMutation,
|
|
23
25
|
useDirectory,
|
|
24
26
|
useUsers,
|
|
25
27
|
useGroups,
|
|
28
|
+
useBankIdLink,
|
|
26
29
|
useRecordPermissions,
|
|
27
30
|
useDatastoreSubscription,
|
|
28
31
|
useWidgetEvent,
|
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.35.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",
|