diff --git a/docs/.well-known/llms-full.txt b/docs/.well-known/llms-full.txt index e763b165..6faeccdf 100644 --- a/docs/.well-known/llms-full.txt +++ b/docs/.well-known/llms-full.txt @@ -7685,7 +7685,7 @@ Write and run JavaScript code directly on the server or client to perform automa 5. App action execution time limits depend on environment: **120 seconds** for server-side (`server`) and **30 seconds** for client-side V3 (`client`). When the limit is exceeded, the action is killed and a timeout error is returned and saved in the logs. 6. On-demand actions accept a `payload` object that is serialized to JSON and sent to the execution backend. The effective maximum payload size is constrained by the transport layer used to invoke the action (in practice, this is typically limited by the maximum URL/query-string length when the payload is sent as a URL-encoded query parameter). Keep payloads small; for larger inputs, store data elsewhere (e.g., a data source or file) and pass a reference (IDs/keys) instead. 7. The result sent from an on-demand app action is **limited to 6MB**. -8. Scheduled app actions only run the **published (production)** version of an action. On-demand actions run the version from the same environment they are fired from (e.g., Fliplet Viewer runs the master version, live apps run the production version). +8. An active app action with a `frequency` can run on schedule even before publication. Master and production copies can each have a schedule; see [Master vs production lifecycle](#master-vs-production-lifecycle). On-demand actions run the version from the same environment they are fired from (e.g., Fliplet Viewer runs the master version, live apps run the production version). 9. An action must have `active` set to `true` to be executed. Inactive actions do **not** run regardless of whether they are on-demand, scheduled, or triggered by events. 10. If a scheduled action fails, the error is logged and the execution is skipped. Scheduled actions do **not** retry on failure — they wait for the next cron tick. 11. Every **create**, **update**, and **restore** snapshots the action's configuration to a **version history**. Up to **100** versions are retained per action; older snapshots are pruned automatically. Snapshotting is best-effort and never blocks the create/update response. See [Version history](#version-history). @@ -7704,12 +7704,12 @@ Write and run JavaScript code directly on the server or client to perform automa Actions have two versions: **master** (development) and **production** (live). -- `create()` always creates a **master** action. +- `create()` always creates a **master** action. Creating, updating or restoring a master with `active: true` and a `frequency` can start its schedule before publication. - `update()` can only update the **master** action. You can **not** update a production action directly — update the master and republish. -- `publish()` copies the current master action to production. The app itself must be published first. +- `publish()` copies one master action to production. The app itself must already be published. - `unpublish()` removes the production version. The master version remains. - `remove()` deletes the master action. If a production version exists, it is also deleted. You can **not** delete a production action directly. -- **Scheduled actions** (`schedule` trigger) only run the **published (production)** version. +- **Scheduled actions** can run from the master as well as a published copy. Publishing is not a boundary that keeps a scheduled draft from running. Inspect both copies before enabling or changing a schedule. - **On-demand actions** (`manual` trigger) run the version from the environment they are fired from (master in Fliplet Viewer, production in live apps). - **Log-triggered actions** run the **published (production)** version on the server. - **Analytics-triggered actions** run the version loaded in the client (production in live apps, master in Fliplet Viewer). @@ -8082,6 +8082,40 @@ async function execute(context) { // Requires dependency: fliplet-notifications ``` +## Authorize callers before accessing private data + +A server action keeps provider credentials off the device; it does not automatically restrict who can receive the action's result. Decide which operations are public and which require an app user. Reading public information need not require login. Reading private account data or performing restricted writes requires an authorization check inside the action, before contacting the provider. A hidden button or a protected screen is not that check. + +For an on-demand action using Data Source login, include `fliplet-session` in `dependencies`. Read the calling session with `Fliplet.Session.run({ url: 'v1/session', cache: false })` and inspect `response.session.entries.dataSource`. Check its `dataSourceId` against the intended login Data Source and require its `id` (the authenticated entry ID). A session existing, or an email matching a row, is not sufficient. Do not accept caller identity or permission claims from `context.payload` or session `client` fields. + +```js +// Action environment: 'server'; dependencies: ['fliplet-session']. +// Replace 123 with the app's intended login Data Source ID. +async function execute(context) { + try { + const response = await Fliplet.Session.run({ url: 'v1/session', cache: false }); + const session = response && response.session; + const entry = session && session.entries && session.entries.dataSource; + + if (!entry || entry.dataSourceId !== 123 || !entry.id) { + return { success: false, error: 'Access denied' }; + } + + // Identity check only. Apply the operation's account/ownership policy + // before adding a private provider request here. + return { success: true, identityVerified: true }; + } catch (error) { + return { success: false, error: 'Unable to verify access' }; + } +} +``` + +This fresh read rejects request failures instead of returning a stored session; see [fresh session reads](../fliplet-session#read-the-session-without-a-stored-session-fallback). It establishes the authenticated entry, not necessarily its current permissions. If access depends on current account status, role or record ownership, read the authoritative records and apply that policy before the provider request. Session entry data can reflect an earlier login; do not treat a snapshot as proof that an account is still active. Add `fliplet-datasources` when using `Fliplet.DataSources` for those checks. + +Scheduled actions have no interactive caller. Give a scheduled service operation its own explicit permitted resources and operations. Never interpret a missing on-demand caller as permission to run the scheduled path. Likewise, keep caller authorization separate from the provider credential: a valid API key does not establish the caller's access. + +Verify both permitted and denied calls: the intended user succeeds, while signed-out users, a login from another Data Source, forged payload identity and failed session reads make no provider request. Verify current-account or ownership restrictions separately when the app requires them. + ## Dependencies If your action code uses Fliplet APIs, you must include the corresponding package in the `dependencies` array. Dependencies can be Fliplet package names or external URLs. @@ -8100,6 +8134,7 @@ dependencies: [ | Package | Required when using | Description | API Reference | |---------|---------------------|-------------|---------------| +| `fliplet-session` | `Fliplet.Session` | Session API — read the calling session | [Session JS API](../fliplet-session) | | `fliplet-datasources` | `Fliplet.DataSources` | Data Sources API — connect, find, insert, update, remove entries | [Data Sources JS API](https://developers.fliplet.com/API/fliplet-datasources.html) | | `fliplet-media` | `Fliplet.Media` | Media API — upload, retrieve, and manage media files | [Media JS API](https://developers.fliplet.com/API/fliplet-media.html) | | `fliplet-communicate` | `Fliplet.Communicate` | Communication API — send emails, push notifications, SMS | [Communicate JS API](https://developers.fliplet.com/API/fliplet-communicate.html) | @@ -8182,6 +8217,7 @@ await Fliplet.Media.Files.upload({ folderId: folderId, file: fileBlob }); When writing action code, **match each `Fliplet.*` namespace used in the `execute` function to its corresponding package** and include it in the `dependencies` array: +- Code uses `Fliplet.Session` → add `fliplet-session` - Code uses `Fliplet.DataSources` → add `fliplet-datasources` - Code uses `Fliplet.Communicate` → add `fliplet-communicate` - Code uses `Fliplet.Media` → add `fliplet-media` @@ -8219,7 +8255,9 @@ Each trigger in the `triggers` array is an object with these properties: ### `manual` trigger -The `manual` trigger has no `where` clause. It fires when you call `run()` or `runWithResult()`. +The `manual` trigger has no `where` clause. Direct calls to `run()` or `runWithResult()` do not require this trigger. + +Trigger configuration does not authorize callers. Removing `manual`, or using an empty `triggers` array, does not prevent direct invocation. For restricted operations, [authorize callers inside the server action](#authorize-callers-before-accessing-private-data). ```js triggers: [{ trigger: 'manual' }] @@ -8395,7 +8433,7 @@ triggers: [ ## Create an action -Use `Fliplet.App.V3.Actions.create()` to create a V3 action. This always creates a **master** (development) version. To run the action in production/live apps, you must also publish it using `publish()`. +Use `Fliplet.App.V3.Actions.create()` to create a V3 action. This always creates a **master** (development) version. To make it available in production/live apps, use `publish()` after the app has been published. This does not prevent a master action from running on schedule. ### Parameters @@ -8455,7 +8493,7 @@ Example timezones: ### Create a scheduled action -A scheduled action requires: `frequency` (cron expression), `triggers: [{ trigger: 'schedule' }]`, and `environment` set to `"server"` or `"any"`. The action only runs in production after being published with `publish()`. +Configure a scheduled action with `active: true`, `frequency` (cron expression), `triggers: [{ trigger: 'schedule' }]` and `environment` set to `"server"` or `"any"`. Creating this active master can start its schedule immediately; publication is not required. Use `active: false` while preparing it, and enable it only when its scheduled effects are authorized. Check any published copy too, because it has separate schedule configuration. ```js var result = await Fliplet.App.V3.Actions.create({ @@ -8480,13 +8518,15 @@ var result = await Fliplet.App.V3.Actions.create({ // result — the created action object (returned directly, not wrapped) // result.id — use this ID to publish, update, or delete the action -// IMPORTANT: You must publish the action for the schedule to be active in production: +// This active master can now run on schedule, even before publication. +// To create or update a production copy in an already-published app: // await Fliplet.App.V3.Actions.publish(result.id); +// Inspect both copies to avoid unintended repeated work. ``` ### Create an on-demand (manual) action -
To run an action on-demand (e.g., triggered by a user), you must include the manual trigger. Without it, run() and runWithResult() cannot execute the action.
Only actions with a manual trigger can be run via run() or runWithResult(). Calling these methods on an action without a manual trigger results in an error. The action must also have active: true.
Rate limiting: the run action endpoint is limited to 30 requests per minute. Contact the Fliplet team for more details.
Payload size limit: the input payload is serialized to JSON and sent to the execution backend. The effective maximum size is constrained by the transport/infrastructure (commonly the maximum URL/query-string length if the payload is passed as a URL-encoded query parameter). Keep payloads small and pass references (IDs/keys) for large inputs.
@@ -8794,13 +8853,16 @@ var result = await Fliplet.App.V3.Actions.update(12345, {
## Temporarily deactivate an action
-Update an action with `active: false` to disable it. Inactive actions do **not** run on any trigger (manual, schedule, log, or analytics). To reactivate, set `active: true` and republish if needed.
+Updating the master action with `active: false` disables the master only. If a production copy exists, publish that disabled action to apply the change to it too. Keep its existing `frequency` until the schedules have been stopped. Alternatively, `unpublish()` removes the production copy and its schedule, but leaves the master; unpublishing alone does not stop an active master schedule. Verify both copies and their run logs. A request already in progress can still finish.
```js
var result = await Fliplet.App.V3.Actions.update(12345, {
active: false
});
-// Action is now inactive — it will not run until active is set back to true
+// For an already-published action, apply the disabled state to production:
+await Fliplet.App.V3.Actions.publish(12345);
+// Verify the master and production schedules have stopped.
+// Requests already in progress can still finish.
```
## Delete an action
@@ -8819,7 +8881,7 @@ await Fliplet.App.V3.Actions.remove(12345);
## Publish an action
-Use `Fliplet.App.V3.Actions.publish()` to copy the master action to production. Only published actions run in live apps. The app itself must be published first.
+Use `Fliplet.App.V3.Actions.publish()` to copy one master action to production after the app itself has been published. This can also start the production copy's schedule when it is active and has a `frequency`; the master schedule is separate. See [Master vs production lifecycle](#master-vs-production-lifecycle).
- **Parameters:** `id` (Number) — The action ID
- **Returns:** Promise resolving to `{ action: Successful scheduled log entries do not include a Skip any of these constraints and the app fails at boot, works in dev but breaks in production, or is rejected by the boot-HTML lint.result property, even if the execute() function returns a value.