@colixsystems/widget-sdk 0.52.0 → 0.54.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 -6
- package/dist/contract.cjs +46 -3
- package/dist/contract.js +46 -3
- package/dist/hooks.js +84 -3
- package/dist/index.d.ts +7 -7
- package/dist/index.js +1 -0
- package/dist/index.native.js +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@ The data layer lives in **four separate domain-client packages**, each instantia
|
|
|
19
19
|
| ----- | ---------------- | ------- | ------------- |
|
|
20
20
|
| **CORE** | `useTheme()` | `{ colors, spacing, radii, typography }` | `ctx.workspace.theme` — no scope |
|
|
21
21
|
| **CORE** | `useWidgetStyle()` | `{ [styleField]: value }` | `ctx.props.style` — no scope. The author-set per-widget style values declared in `manifest.styleSchema`; apply each onto whatever element you choose. |
|
|
22
|
-
| **CORE** | `useUser()` | `{ id, email,
|
|
22
|
+
| **CORE** | `useUser()` | `{ id, email, displayName, roles, groupIds }` | `ctx.user` (host-built context, **camelCase** — not a wire payload; `id` null when anonymous) — no scope |
|
|
23
23
|
| **CORE** | `useNavigation()` | `{ goTo, goBack, push, replace, back, currentRoute }` | `ctx.navigation` — no scope (external URLs use the `Linking` primitive) |
|
|
24
24
|
| **CORE** | `useWidgetEvent(name)` | `(payload?) => void` | `ctx.events.emit` — no scope |
|
|
25
25
|
| **CORE** | `useChildRenderer()` | `{ renderNode(node) }` | `ctx.renderer` — no scope (prefer the `WidgetTree` component) |
|
|
@@ -40,6 +40,7 @@ The data layer lives in **four separate domain-client packages**, each instantia
|
|
|
40
40
|
| **DIRECTORY** | `useUsers(query?)` | `{ users, loading, error, refetch, invite, deactivate, reactivate, remove }` | `directory.users.*` — `users.read:*` (edits also `users.write:*`; `remove()` also `users.delete:*`) |
|
|
41
41
|
| **DIRECTORY** | `useGroups(query?)` | `{ groups, loading, error, refetch, create, remove, addMember, removeMember }` | `directory.groups.*` — `groups.read:*` (mutations also `groups.write:*`) |
|
|
42
42
|
| **DIRECTORY** | `useBankIdLink()` | `{ linked, available, status, qr, message, startLink, refresh, cancel, unlink, refetchStatus, … }` | `directory.bankid.*` — no scope (JWT-gated self-service) |
|
|
43
|
+
| **FILESTORE** (`ctx.filestore`) | `usePdfExport({ spaceType, folderId? })` | `{ exportToPdf, exporting, error, lastExported }` | `ctx.filestore.files.exportPdf` — `files.write:*`. `exportToPdf(html, { fileName?, folderId? })` renders the HTML to a PDF server-side and saves it as a file (`application/pdf`); same server-side renderer on web + native. |
|
|
43
44
|
| **PAYMENTS** (`ctx.payments`) | `usePayments()` | `{ requestPayment, getPayment }` | `ctx.payments.*` — `payments.charge:appUser` |
|
|
44
45
|
| **NOTIFICATIONS** (`ctx.notifications`) | `useSendNotification()` | `{ send, sending, error }` | `ctx.notifications.send` — `notifications.send:appUser`. `send({ recipient_user_id, title, body, link?, payload? })` notifies one app user in the same workspace; call from an event handler (never render); rejects with `NotificationError`. |
|
|
45
46
|
|
|
@@ -51,7 +52,15 @@ See the design reference for the full architecture: [`docs/architecture/widget-m
|
|
|
51
52
|
|
|
52
53
|
## Status
|
|
53
54
|
|
|
54
|
-
`v0.
|
|
55
|
+
`v0.54.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**.
|
|
56
|
+
|
|
57
|
+
### What's new in 0.54.0
|
|
58
|
+
|
|
59
|
+
**Generate & save PDFs from a widget (sc-2314).** New `usePdfExport({ spaceType, folderId? })` hook. `exportToPdf(html, { fileName?, folderId? })` renders the HTML to a PDF **server-side** (the platform's headless-Chromium pipeline) and saves it into the end-user's Filestore via `ctx.filestore.files.exportPdf`, resolving to the created file row (`application/pdf`). It reuses the filestore owner_id resolution + per-folder write gate and the existing `files.write:*` scope. Because the rendering is server-side, the capability behaves identically on the web Player and the native Expo export — no browser-only PDF library is added to the vetted set. Pairs with `@colixsystems/filestore-client@0.6.0`'s new `files.exportPdf(...)`. `CONTRACT.version` → `1.38.0`. Additive — no existing hook, primitive, manifest field, or token changed shape.
|
|
60
|
+
|
|
61
|
+
### What's new in 0.53.0
|
|
62
|
+
|
|
63
|
+
**Server-action scripts gain a `connectors` global (REQ-ACTION-CONNECTORS, sc-2162).** A `scriptSource` action can now call `connectors.call(slug, { method, path, query, body, headers })` to invoke a tenant-configured REST connector by slug; it returns the upstream's `{ status, headers, body }`. Auth and SSRF protection are handled by the platform host — the script names only a slug (never a tenant or base URL), and the tenant is bound host-side. An unknown slug / SSRF rejection / timeout throws a catchable Error. New entry in `CONTRACT.actionScriptGlobals`; `CONTRACT.version` → `1.37.0`. Additive — no widget hook, primitive, manifest field, or token changed shape.
|
|
55
64
|
|
|
56
65
|
### What's new in 0.52.0
|
|
57
66
|
|
|
@@ -274,7 +283,7 @@ Also: `useFileSignatures(fileIds)` is now **self-scoped** (the caller's own sign
|
|
|
274
283
|
|
|
275
284
|
- **The SDK no longer owns any data facade.** The bespoke per-hook facades that used to live on `WidgetContext` (`ctx.datastore` as an opaque host object, `ctx.directory.listUsers`, `ctx.users`, `ctx.groups`, `ctx.recordPermissions`, `ctx.assets.get`) are replaced by four host-instantiated, host-injected domain clients: `ctx.datastore` (`@colixsystems/datastore-client`), `ctx.directory` (`@colixsystems/directory-client`), `ctx.assets` (`@colixsystems/assets-client`, flattened), `ctx.payments` (`@colixsystems/payments-client`). The SDK imports none of them and ships no HTTP.
|
|
276
285
|
- **`ctx.recordPermissions`, `ctx.users`, `ctx.groups` are removed.** Per-record permission management moved under `ctx.datastore.records(tableId).permissions(recordId)`; user / group administration moved under `ctx.directory.users` / `ctx.directory.groups`. The hooks (`useRecordPermissions`, `useUsers`, `useGroups`) keep the same names and signatures — only the client slice they read changed.
|
|
277
|
-
- **snake_case end to end, no client-side transform.** Clients send and return snake_case verbatim (`group_ids`, `can_read`, `is_active`, `amount_cents`, `data_type`, `created_at`, …). The SDK passes bodies straight through and unwraps the `{ data, meta }` list envelope without renaming a single field.
|
|
286
|
+
- **snake_case end to end, no client-side transform.** Clients send and return snake_case verbatim (`group_ids`, `can_read`, `is_active`, `amount_cents`, `data_type`, `created_at`, …). The SDK passes bodies straight through and unwraps the `{ data, meta }` list envelope without renaming a single field. Wire-payload hook return rows are therefore snake_case (`is_active`, `member_count`, and `useRecordPermissions` rows carry `user_id` / `group_id` / `can_read` / `can_write` / `can_delete` / `can_grant`). The one exception is `useUser()`: it reads the host-built `ctx.user` context object, not a wire payload, so its fields are **camelCase** (`displayName`, `groupIds`).
|
|
278
287
|
- **Companion package versions:** `datastore-client 0.5.0`, `assets-client 0.4.0`, `directory-client 0.1.0`, `payments-client 0.1.0`.
|
|
279
288
|
- **`CONTRACT.version` → `1.9.0`.** Breaking for `WidgetContext` consumers (removed slices, renamed wire fields); the hook export surface is unchanged.
|
|
280
289
|
|
|
@@ -284,7 +293,7 @@ Two additive features land in this version.
|
|
|
284
293
|
|
|
285
294
|
**A widget may declare server-side actions in its manifest.**
|
|
286
295
|
|
|
287
|
-
- **`WidgetManifest.actions` is now part of the public contract.** An optional array; each entry is `{ key, name, description?, triggerType, scheduleCron?, timeoutMs?, scriptSource }`. `triggerType` is one of `schedule`, `record_created`, `record_updated`, `record_deleted`; `scheduleCron` is required when `triggerType === "schedule"`. The `scriptSource` (≤ 200 KiB) runs in the **shared isolated-vm action runner** — against `datastore` / `fetch` / `console` / `record` / `tenantId` (the runner surface, **not** the React/SDK widget surface, so SDK imports and hooks are unavailable there and the component linter does not scan it). Actions never run in the rendered app, so they have **no effect on Player ↔ export parity**.
|
|
296
|
+
- **`WidgetManifest.actions` is now part of the public contract.** An optional array; each entry is `{ key, name, description?, triggerType, scheduleCron?, timeoutMs?, scriptSource }`. `triggerType` is one of `schedule`, `record_created`, `record_updated`, `record_deleted`; `scheduleCron` is required when `triggerType === "schedule"`. The `scriptSource` (≤ 200 KiB) runs in the **shared isolated-vm action runner** — against `datastore` / `fetch` / `connectors` / `console` / `record` / `tenantId` (the runner surface, **not** the React/SDK widget surface, so SDK imports and hooks are unavailable there and the component linter does not scan it). `connectors.call(slug, { method, path, query, body, headers })` resolves a tenant-configured REST connector by slug and returns `{ status, headers, body }` (auth + SSRF handled by the platform; an unknown slug / SSRF / timeout throws a catchable Error). Actions never run in the rendered app, so they have **no effect on Player ↔ export parity**.
|
|
288
297
|
- **Operators enable actions per tenant** from the Properties Panel. An enabled action materialises a tenant `Action` row **DISABLED** until the operator binds an integration API key (and, for `record_*` triggers, a target table) in the Actions admin page — those bindings are tenant-local, so `triggerTableId` / `apiKeyId` must **not** appear in the manifest (the validator and linter reject them).
|
|
289
298
|
- **New contract fields** `CONTRACT.actionTriggerTypes`, `CONTRACT.actionScriptGlobals`, `CONTRACT.actionScriptMaxBytes` expose the grammar so the Developer page, the AI agent prompt, and `validateManifest` derive it from one source. `validateManifest` now structurally validates `actions`; the marketplace linter rejects malformed / oversized declarations.
|
|
290
299
|
- **New manifest category `ADMINISTRATION`** for app-administration widgets such as User Management. Added to `CONTRACT.manifestCategories`, `validateManifest`, the `WidgetCategory` type, the marketplace category list, and the master-DB `WidgetCategory` enum.
|
|
@@ -357,7 +366,7 @@ The "split-implementation + vetted package list" pivot.
|
|
|
357
366
|
|
|
358
367
|
### What's new in 0.9.0
|
|
359
368
|
|
|
360
|
-
- **`usePayments()` — incoming app-user payments.** Returns `{ requestPayment, getPayment }`. `requestPayment({ amountCents, currency?, description, metadata?, returnPath? })` triggers a one-time charge from the signed-in app user and resolves to `{ id, status, checkoutUrl? }`: when `checkoutUrl` is present the widget opens it (web: navigate; native: `expo-web-browser`) — the user pays in **hosted
|
|
369
|
+
- **`usePayments()` — incoming app-user payments.** Returns `{ requestPayment, getPayment }`. `requestPayment({ amountCents, currency?, description, metadata?, returnPath? })` triggers a one-time charge from the signed-in app user and resolves to `{ id, status, checkoutUrl? }`: when `checkoutUrl` is present the widget opens it (web: navigate; native: `expo-web-browser`) — the user pays in the provider's **hosted checkout**; when absent (the platform's built-in **mock** provider, the default until a real provider is configured) the charge auto-confirms (`status: "PAID"`). `getPayment(id)` polls the terminal status. Backed by a new `WidgetContext.payments` slice and gated by the new `payments.charge:appUser` scope. **No card data ever touches the widget** — never collect card fields yourself. The charge settles to the workspace owner; the amount is bounded by a platform per-charge cap. Rejections are a structured `PaymentError` (also a new named export) with a stable `.code`.
|
|
361
370
|
- **`CONTRACT.version` → `1.2.0`** (additive: one new hook, one new context slice, one new scope, one new error class). No existing export changed signature.
|
|
362
371
|
|
|
363
372
|
### What was in 0.8.0
|
|
@@ -419,7 +428,7 @@ import { defineWidget, validateManifest, useDatastoreQuery, Text, View } from "@
|
|
|
419
428
|
|
|
420
429
|
- `defineWidget({ manifest, component })` — validates the manifest and produces a widget module the host can register.
|
|
421
430
|
- `validateManifest(m)` / `validatePropertySchema(s)` / `validateProps(schema, props)` — shape validation; no third-party deps.
|
|
422
|
-
- `useDatastoreQuery`, `useDatastoreRecord`, `useDatastoreSchema`, `useDatastoreMutation`, `useDirectory`, `useUsers`, `useGroups`, `useRecordPermissions`, `useAsset`, `useWidgetEvent`, `usePayments`, `useSendNotification`, `useTheme`, `useI18n`, `useUser`, `useNavigation`, `useChildRenderer`, `useClipboard`, `useToast` — hooks that read from the host-provided `WidgetContext` (or, for `useClipboard`, the platform clipboard API directly). `useDirectory(query?)` returns `{ users, loading, error, refetch }` (each user `{ id, name, role }`) and requires the `directory.read:users` scope. `useUsers(query?)` returns `{ users, loading, error, refetch, invite, deactivate, reactivate, remove }` and requires `users.read:*` (mutations also need `users.write:*`); rejections are a `DirectoryError`. `useGroups(query?)` returns `{ groups, loading, error, refetch, create, remove, addMember, removeMember }` and requires `groups.read:*` (mutations also need `groups.write:*`). `usePayments()` returns `{ requestPayment, getPayment }` and requires the `payments.charge:appUser` scope; `requestPayment(...)` rejects with a `PaymentError`. `useSendNotification()` returns `{ send, sending, error }` and requires the `notifications.send:appUser` scope; `send({ recipient_user_id, title, body, link?, payload? })` notifies one app user in the same workspace (cross-workspace `recipient_user_id` is rejected), must be called from an event handler rather than render, and rejects with a `NotificationError`. `useUser()` returns the active end-user identity `{ id, email,
|
|
431
|
+
- `useDatastoreQuery`, `useDatastoreRecord`, `useDatastoreSchema`, `useDatastoreMutation`, `useDirectory`, `useUsers`, `useGroups`, `useRecordPermissions`, `useAsset`, `useWidgetEvent`, `usePayments`, `useSendNotification`, `useTheme`, `useI18n`, `useUser`, `useNavigation`, `useChildRenderer`, `useClipboard`, `useToast` — hooks that read from the host-provided `WidgetContext` (or, for `useClipboard`, the platform clipboard API directly). `useDirectory(query?)` returns `{ users, loading, error, refetch }` (each user `{ id, name, role }`) and requires the `directory.read:users` scope. `useUsers(query?)` returns `{ users, loading, error, refetch, invite, deactivate, reactivate, remove }` and requires `users.read:*` (mutations also need `users.write:*`); rejections are a `DirectoryError`. `useGroups(query?)` returns `{ groups, loading, error, refetch, create, remove, addMember, removeMember }` and requires `groups.read:*` (mutations also need `groups.write:*`). `usePayments()` returns `{ requestPayment, getPayment }` and requires the `payments.charge:appUser` scope; `requestPayment(...)` rejects with a `PaymentError`. `useSendNotification()` returns `{ send, sending, error }` and requires the `notifications.send:appUser` scope; `send({ recipient_user_id, title, body, link?, payload? })` notifies one app user in the same workspace (cross-workspace `recipient_user_id` is rejected), must be called from an event handler rather than render, and rejects with a `NotificationError`. `useUser()` returns the active end-user identity `{ id, email, displayName, roles, groupIds }` (camelCase — the host-built context object, not a wire payload; `id` is `null` for anonymous / preview). `useNavigation()` returns `{ goTo, goBack, push, replace, back, currentRoute }` for internal page navigation — for external URLs use the `Linking` primitive (`Linking.openURL(url)`). `useDatastoreRecord(tableId, recordId)` returns `{ data, loading, error, refetch }` for a single record (data is one row or null). `useDatastoreSchema(tableId)` returns `{ schema, loading, error, refetch }` where `schema` is `{ id, name, columns: [{ id, name, data_type, required, relation_type, target_table_id, is_identification }] }` (structure only, no row data; snake_case verbatim) — use it to resolve a stored `columnId` to its column type at runtime; requires the `datastore.read:<table>` scope. `useAsset(fileId)` returns `{ url, file, loading, error, refetch }` — the `url` is an absolute URL composed against the host's API base. `useChildRenderer()` returns `{ renderNode(node) }` — container widgets call it to render arbitrary child page-tree nodes (prefer the `WidgetTree` component for the common case).
|
|
423
432
|
- `WidgetTree({ node })` — component that renders an author-authored child node through the host's renderer; used by Tabs / Card / custom containers to host arbitrary child widgets.
|
|
424
433
|
- `Text`, `View`, `Pressable`, `Image`, `ScrollView`, `TextInput`, `FlatList`, `SectionList`, `ActivityIndicator`, `Switch`, `StyleSheet`, `Linking`, `Icon`, `DateTimePicker` — re-exported from `react-native` (the RN primitives) or implemented in the SDK (`Icon` wraps `lucide-react-native`; `DateTimePicker` wraps `@react-native-community/datetimepicker` on native and renders `<input type="date|time|datetime-local">` directly on web because the RN library has no react-native-web mapping). The web build aliases `react-native` to `react-native-web` so the RN-re-exported primitives render in the browser without any per-platform code; the exported Expo app's Metro bundler resolves the real `react-native` library. `Linking` is a static API (`Linking.openURL(url)`) — use it for external URLs, and use `useNavigation().goTo(pageId)` for internal page navigation. See https://reactnative.dev/docs/ for per-component props.
|
|
425
434
|
- `WidgetContextProvider` — React context provider that the host (Studio, Player, exported app) wraps widgets with.
|
package/dist/contract.cjs
CHANGED
|
@@ -235,6 +235,29 @@ const HOOKS = [
|
|
|
235
235
|
requiredContextSlice: ["filestore.files"],
|
|
236
236
|
scopes: ["files.write:*"],
|
|
237
237
|
},
|
|
238
|
+
{
|
|
239
|
+
name: "usePdfExport",
|
|
240
|
+
signature: "usePdfExport({ spaceType, folderId? })",
|
|
241
|
+
description:
|
|
242
|
+
"Render an HTML string to a PDF server-side and SAVE it as a file in " +
|
|
243
|
+
"the end-user's Filestore space. The widget passes the SPACE " +
|
|
244
|
+
"(`{ spaceType, folderId? }`); the hook resolves owner_id from the host " +
|
|
245
|
+
"context and POSTs JSON through ctx.filestore.files.exportPdf. " +
|
|
246
|
+
"`exportToPdf(html, { fileName?, folderId? })` resolves to the created " +
|
|
247
|
+
"file row (mime_type `application/pdf`) or throws the wire error; a 404 " +
|
|
248
|
+
"means the destination folder denied a write, a 413 that the rendered " +
|
|
249
|
+
"PDF exceeded the size cap. The HTML is rendered by the host's PDF " +
|
|
250
|
+
"service (headless Chromium) — the SAME server-side pipeline on the web " +
|
|
251
|
+
"Player and the native export, so no browser-only PDF library is used.",
|
|
252
|
+
returnShape: {
|
|
253
|
+
exportToPdf: "(html, { fileName?, folderId? }) => Promise<FilestoreFile>",
|
|
254
|
+
exporting: "boolean",
|
|
255
|
+
error: "Error | null",
|
|
256
|
+
lastExported: "FilestoreFile | null",
|
|
257
|
+
},
|
|
258
|
+
requiredContextSlice: ["filestore.files"],
|
|
259
|
+
scopes: ["files.write:*"],
|
|
260
|
+
},
|
|
238
261
|
{
|
|
239
262
|
name: "useFilestoreFolders",
|
|
240
263
|
signature: "useFilestoreFolders({ spaceType, parentFolderId?, q?, enabled? })",
|
|
@@ -818,6 +841,7 @@ const ACTION_TRIGGER_TYPES = [
|
|
|
818
841
|
const ACTION_SCRIPT_GLOBALS = [
|
|
819
842
|
"datastore",
|
|
820
843
|
"fetch",
|
|
844
|
+
"connectors",
|
|
821
845
|
"console",
|
|
822
846
|
"record",
|
|
823
847
|
"tenantId",
|
|
@@ -974,9 +998,9 @@ const WIDGET_CONTEXT_SHAPE = {
|
|
|
974
998
|
},
|
|
975
999
|
user: {
|
|
976
1000
|
description:
|
|
977
|
-
"Signed-in user, host-
|
|
1001
|
+
"Signed-in user, host-built context object (camelCase: { id, email, displayName, roles, groupIds }) — NOT a wire payload, so it is the one camelCase island among the hooks. Not a data-client.",
|
|
978
1002
|
required: true,
|
|
979
|
-
fields: { id: "string", email: "string",
|
|
1003
|
+
fields: { id: "string", email: "string", displayName: "string" },
|
|
980
1004
|
},
|
|
981
1005
|
workspace: {
|
|
982
1006
|
description:
|
|
@@ -1815,7 +1839,26 @@ const CONTRACT = deepFreeze({
|
|
|
1815
1839
|
// `notifications.send:appUser` scope it gates on. No existing hook,
|
|
1816
1840
|
// primitive, manifest field, or token changed shape — minor bump on the
|
|
1817
1841
|
// pre-1.0 channel.
|
|
1818
|
-
|
|
1842
|
+
//
|
|
1843
|
+
// 1.37.0: additive (REQ-ACTION-CONNECTORS, sc-2162) — server-action scripts
|
|
1844
|
+
// gain a `connectors` global. `connectors.call(slug, { method, path,
|
|
1845
|
+
// query, body, headers })` resolves a tenant-configured REST connector by
|
|
1846
|
+
// slug and returns `{ status, headers, body }`; auth + SSRF are handled by
|
|
1847
|
+
// the platform host, and an unknown slug / SSRF / timeout throws a
|
|
1848
|
+
// catchable Error. New entry in ACTION_SCRIPT_GLOBALS only — no widget
|
|
1849
|
+
// hook, primitive, manifest field, or token changed shape; minor bump.
|
|
1850
|
+
//
|
|
1851
|
+
// 1.38.0: additive (sc-2314) — new `usePdfExport({ spaceType, folderId? })`
|
|
1852
|
+
// hook. `exportToPdf(html, { fileName?, folderId? })` renders the HTML to
|
|
1853
|
+
// a PDF server-side (the host's headless-Chromium pipeline) and SAVES it
|
|
1854
|
+
// into the Filestore via `ctx.filestore.files.exportPdf`, resolving to the
|
|
1855
|
+
// created file row (`application/pdf`). Reuses the filestore owner_id
|
|
1856
|
+
// resolution + per-folder write gate; gated on the existing
|
|
1857
|
+
// `files.write:*` scope. The same server-side renderer backs the web
|
|
1858
|
+
// Player and the native export, so no browser-only PDF library is added
|
|
1859
|
+
// to the vetted set. No existing hook, primitive, manifest field, or
|
|
1860
|
+
// token changed shape — minor bump.
|
|
1861
|
+
version: "1.38.0",
|
|
1819
1862
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
1820
1863
|
hooks: HOOKS,
|
|
1821
1864
|
primitives: PRIMITIVES,
|
package/dist/contract.js
CHANGED
|
@@ -235,6 +235,29 @@ const HOOKS = [
|
|
|
235
235
|
requiredContextSlice: ["filestore.files"],
|
|
236
236
|
scopes: ["files.write:*"],
|
|
237
237
|
},
|
|
238
|
+
{
|
|
239
|
+
name: "usePdfExport",
|
|
240
|
+
signature: "usePdfExport({ spaceType, folderId? })",
|
|
241
|
+
description:
|
|
242
|
+
"Render an HTML string to a PDF server-side and SAVE it as a file in " +
|
|
243
|
+
"the end-user's Filestore space. The widget passes the SPACE " +
|
|
244
|
+
"(`{ spaceType, folderId? }`); the hook resolves owner_id from the host " +
|
|
245
|
+
"context and POSTs JSON through ctx.filestore.files.exportPdf. " +
|
|
246
|
+
"`exportToPdf(html, { fileName?, folderId? })` resolves to the created " +
|
|
247
|
+
"file row (mime_type `application/pdf`) or throws the wire error; a 404 " +
|
|
248
|
+
"means the destination folder denied a write, a 413 that the rendered " +
|
|
249
|
+
"PDF exceeded the size cap. The HTML is rendered by the host's PDF " +
|
|
250
|
+
"service (headless Chromium) — the SAME server-side pipeline on the web " +
|
|
251
|
+
"Player and the native export, so no browser-only PDF library is used.",
|
|
252
|
+
returnShape: {
|
|
253
|
+
exportToPdf: "(html, { fileName?, folderId? }) => Promise<FilestoreFile>",
|
|
254
|
+
exporting: "boolean",
|
|
255
|
+
error: "Error | null",
|
|
256
|
+
lastExported: "FilestoreFile | null",
|
|
257
|
+
},
|
|
258
|
+
requiredContextSlice: ["filestore.files"],
|
|
259
|
+
scopes: ["files.write:*"],
|
|
260
|
+
},
|
|
238
261
|
{
|
|
239
262
|
name: "useFilestoreFolders",
|
|
240
263
|
signature: "useFilestoreFolders({ spaceType, parentFolderId?, q?, enabled? })",
|
|
@@ -818,6 +841,7 @@ const ACTION_TRIGGER_TYPES = [
|
|
|
818
841
|
const ACTION_SCRIPT_GLOBALS = [
|
|
819
842
|
"datastore",
|
|
820
843
|
"fetch",
|
|
844
|
+
"connectors",
|
|
821
845
|
"console",
|
|
822
846
|
"record",
|
|
823
847
|
"tenantId",
|
|
@@ -974,9 +998,9 @@ const WIDGET_CONTEXT_SHAPE = {
|
|
|
974
998
|
},
|
|
975
999
|
user: {
|
|
976
1000
|
description:
|
|
977
|
-
"Signed-in user, host-
|
|
1001
|
+
"Signed-in user, host-built context object (camelCase: { id, email, displayName, roles, groupIds }) — NOT a wire payload, so it is the one camelCase island among the hooks. Not a data-client.",
|
|
978
1002
|
required: true,
|
|
979
|
-
fields: { id: "string", email: "string",
|
|
1003
|
+
fields: { id: "string", email: "string", displayName: "string" },
|
|
980
1004
|
},
|
|
981
1005
|
workspace: {
|
|
982
1006
|
description:
|
|
@@ -1815,7 +1839,26 @@ const CONTRACT = deepFreeze({
|
|
|
1815
1839
|
// `notifications.send:appUser` scope it gates on. No existing hook,
|
|
1816
1840
|
// primitive, manifest field, or token changed shape — minor bump on the
|
|
1817
1841
|
// pre-1.0 channel.
|
|
1818
|
-
|
|
1842
|
+
//
|
|
1843
|
+
// 1.37.0: additive (REQ-ACTION-CONNECTORS, sc-2162) — server-action scripts
|
|
1844
|
+
// gain a `connectors` global. `connectors.call(slug, { method, path,
|
|
1845
|
+
// query, body, headers })` resolves a tenant-configured REST connector by
|
|
1846
|
+
// slug and returns `{ status, headers, body }`; auth + SSRF are handled by
|
|
1847
|
+
// the platform host, and an unknown slug / SSRF / timeout throws a
|
|
1848
|
+
// catchable Error. New entry in ACTION_SCRIPT_GLOBALS only — no widget
|
|
1849
|
+
// hook, primitive, manifest field, or token changed shape; minor bump.
|
|
1850
|
+
//
|
|
1851
|
+
// 1.38.0: additive (sc-2314) — new `usePdfExport({ spaceType, folderId? })`
|
|
1852
|
+
// hook. `exportToPdf(html, { fileName?, folderId? })` renders the HTML to
|
|
1853
|
+
// a PDF server-side (the host's headless-Chromium pipeline) and SAVES it
|
|
1854
|
+
// into the Filestore via `ctx.filestore.files.exportPdf`, resolving to the
|
|
1855
|
+
// created file row (`application/pdf`). Reuses the filestore owner_id
|
|
1856
|
+
// resolution + per-folder write gate; gated on the existing
|
|
1857
|
+
// `files.write:*` scope. The same server-side renderer backs the web
|
|
1858
|
+
// Player and the native export, so no browser-only PDF library is added
|
|
1859
|
+
// to the vetted set. No existing hook, primitive, manifest field, or
|
|
1860
|
+
// token changed shape — minor bump.
|
|
1861
|
+
version: "1.38.0",
|
|
1819
1862
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
1820
1863
|
hooks: HOOKS,
|
|
1821
1864
|
primitives: PRIMITIVES,
|
package/dist/hooks.js
CHANGED
|
@@ -113,7 +113,7 @@ export function useWidgetStyle() {
|
|
|
113
113
|
|
|
114
114
|
/**
|
|
115
115
|
* Returns the active end-user identity VERBATIM from the host, e.g.
|
|
116
|
-
* `{ id, email,
|
|
116
|
+
* `{ id, email, displayName, roles, groupIds }` (camelCase — host-built context, not a wire payload).
|
|
117
117
|
*
|
|
118
118
|
* `id` is `null` for anonymous visitors (and on the Studio canvas preview,
|
|
119
119
|
* which renders widgets as if signed-out so the public branch shows). The
|
|
@@ -1578,6 +1578,87 @@ export function useFilestoreUpload(options) {
|
|
|
1578
1578
|
return { upload, uploading, error, lastUploaded };
|
|
1579
1579
|
}
|
|
1580
1580
|
|
|
1581
|
+
/**
|
|
1582
|
+
* sc-2314 — render an HTML string to a PDF server-side and SAVE it into the
|
|
1583
|
+
* end-user's Filestore space. Returns `{ exportToPdf, exporting, error,
|
|
1584
|
+
* lastExported }`. The widget passes the SPACE (`{ spaceType, folderId? }`);
|
|
1585
|
+
* the hook resolves `owner_id` the same way the upload hook does (tenant for
|
|
1586
|
+
* PROJECT, app user for PERSONAL) and posts JSON to
|
|
1587
|
+
* `ctx.filestore.files.exportPdf`.
|
|
1588
|
+
*
|
|
1589
|
+
* `exportToPdf(html, { fileName?, folderId? })` resolves to the created file
|
|
1590
|
+
* record (`{ id, name, mime_type: "application/pdf", presigned_url, … }`).
|
|
1591
|
+
* `fileName` defaults to `document.pdf` and is forced to a `.pdf` suffix by the
|
|
1592
|
+
* backend. The HTML is rendered by the host's PDF service (headless Chromium)
|
|
1593
|
+
* — the SAME server-side pipeline backs the web Player and the native export,
|
|
1594
|
+
* so the capability behaves identically on both platforms (no browser-only PDF
|
|
1595
|
+
* library is involved).
|
|
1596
|
+
*
|
|
1597
|
+
* A 404 means the destination folder denied a write; a 413 means the rendered
|
|
1598
|
+
* PDF exceeded the size cap.
|
|
1599
|
+
*/
|
|
1600
|
+
export function usePdfExport(options) {
|
|
1601
|
+
const ctx = useWidgetContextOrThrow("usePdfExport");
|
|
1602
|
+
if (
|
|
1603
|
+
!ctx.filestore ||
|
|
1604
|
+
!ctx.filestore.files ||
|
|
1605
|
+
typeof ctx.filestore.files.exportPdf !== "function"
|
|
1606
|
+
) {
|
|
1607
|
+
throw new Error(
|
|
1608
|
+
"usePdfExport: host did not inject a filestore client (ctx.filestore.files.exportPdf)",
|
|
1609
|
+
);
|
|
1610
|
+
}
|
|
1611
|
+
const { spaceType = "project", folderId: defaultFolderId = null } = options || {};
|
|
1612
|
+
const ownerId = _filestoreOwnerId(ctx, spaceType);
|
|
1613
|
+
|
|
1614
|
+
const [exporting, setExporting] = useState(false);
|
|
1615
|
+
const [error, setError] = useState(null);
|
|
1616
|
+
const [lastExported, setLastExported] = useState(null);
|
|
1617
|
+
|
|
1618
|
+
const filesRef = useRef(ctx.filestore.files);
|
|
1619
|
+
filesRef.current = ctx.filestore.files;
|
|
1620
|
+
|
|
1621
|
+
const exportToPdf = useCallback(
|
|
1622
|
+
async (html, overrides) => {
|
|
1623
|
+
if (typeof html !== "string" || html.trim().length === 0) {
|
|
1624
|
+
throw new Error("usePdfExport: html is required");
|
|
1625
|
+
}
|
|
1626
|
+
if (!ownerId) {
|
|
1627
|
+
const err = new Error("Sign in to save a PDF");
|
|
1628
|
+
setError(err);
|
|
1629
|
+
throw err;
|
|
1630
|
+
}
|
|
1631
|
+
const folderId =
|
|
1632
|
+
overrides && Object.prototype.hasOwnProperty.call(overrides, "folderId")
|
|
1633
|
+
? overrides.folderId
|
|
1634
|
+
: defaultFolderId;
|
|
1635
|
+
const fileName = overrides && overrides.fileName;
|
|
1636
|
+
const body = {
|
|
1637
|
+
html,
|
|
1638
|
+
space_type: String(spaceType || "project").toUpperCase(),
|
|
1639
|
+
owner_id: ownerId,
|
|
1640
|
+
};
|
|
1641
|
+
if (folderId) body.folder_id = folderId;
|
|
1642
|
+
if (fileName) body.file_name = fileName;
|
|
1643
|
+
setExporting(true);
|
|
1644
|
+
setError(null);
|
|
1645
|
+
try {
|
|
1646
|
+
const created = await filesRef.current.exportPdf(body);
|
|
1647
|
+
setLastExported(created || null);
|
|
1648
|
+
setExporting(false);
|
|
1649
|
+
return created;
|
|
1650
|
+
} catch (err) {
|
|
1651
|
+
setError(err);
|
|
1652
|
+
setExporting(false);
|
|
1653
|
+
throw err;
|
|
1654
|
+
}
|
|
1655
|
+
},
|
|
1656
|
+
[ownerId, defaultFolderId, spaceType],
|
|
1657
|
+
);
|
|
1658
|
+
|
|
1659
|
+
return { exportToPdf, exporting, error, lastExported };
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1581
1662
|
/**
|
|
1582
1663
|
* Browse the end-user's Filestore folders. Returns { folders, loading, error,
|
|
1583
1664
|
* refetch }. Mirrors useFilestoreFiles for subfolder navigation: the widget
|
|
@@ -2638,8 +2719,8 @@ function toPaymentError(err) {
|
|
|
2638
2719
|
* requestPayment({ amountCents, currency?, description, metadata? })
|
|
2639
2720
|
* → Promise<{ id, status, checkoutUrl?, ... }>. The host either
|
|
2640
2721
|
* auto-confirms (mock provider, `status: "PAID"`, no redirect) or
|
|
2641
|
-
* returns a hosted-
|
|
2642
|
-
* (
|
|
2722
|
+
* returns a hosted-checkout `checkoutUrl` the widget should open
|
|
2723
|
+
* (Mollie provider, `status: "PENDING"`). Rejects with a
|
|
2643
2724
|
* `PaymentError`.
|
|
2644
2725
|
* getPayment(paymentId) → Promise<payment> — poll the terminal status.
|
|
2645
2726
|
*
|
package/dist/index.d.ts
CHANGED
|
@@ -424,13 +424,13 @@ export interface WidgetContext<TProps = unknown> {
|
|
|
424
424
|
* Absent / `false` everywhere the host has not opted the widget into filling.
|
|
425
425
|
*/
|
|
426
426
|
fill?: boolean;
|
|
427
|
-
/** Active end-user identity,
|
|
427
|
+
/** Active end-user identity from the host-built context (camelCase, not a wire payload). `id` is null when anonymous. */
|
|
428
428
|
user: {
|
|
429
429
|
id: string | null;
|
|
430
430
|
email: string | null;
|
|
431
|
-
|
|
431
|
+
displayName: string | null;
|
|
432
432
|
roles: string[];
|
|
433
|
-
|
|
433
|
+
groupIds: string[];
|
|
434
434
|
};
|
|
435
435
|
workspace: {
|
|
436
436
|
id: string;
|
|
@@ -624,8 +624,8 @@ export interface PaymentResult {
|
|
|
624
624
|
currency?: string;
|
|
625
625
|
description?: string;
|
|
626
626
|
/**
|
|
627
|
-
* Present (
|
|
628
|
-
*
|
|
627
|
+
* Present (Mollie provider) when the app user must complete a hosted
|
|
628
|
+
* checkout: the widget should open this URL. Absent under the mock
|
|
629
629
|
* provider, where the charge auto-confirms (`status: "PAID"`).
|
|
630
630
|
*/
|
|
631
631
|
checkoutUrl?: string | null;
|
|
@@ -883,9 +883,9 @@ export function useI18n(): {
|
|
|
883
883
|
export function useUser(): {
|
|
884
884
|
id: string | null;
|
|
885
885
|
email: string | null;
|
|
886
|
-
|
|
886
|
+
displayName: string | null;
|
|
887
887
|
roles: string[];
|
|
888
|
-
|
|
888
|
+
groupIds: string[];
|
|
889
889
|
};
|
|
890
890
|
|
|
891
891
|
/**
|
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.54.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",
|