mirror of
https://github.com/HeyPuter/puter.git
synced 2026-10-02 01:51:55 +00:00
059fae3d15f20bd8286aa4ad7198a6d64f4fd34c
6
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
b784b51cf3 |
fix: harden the events stack for flag-on (#3752)
* fix: harden events dispatch, single delivery and KV share handles Dispatch: a filtered subscription used the anchor path stored at subscribe time, so renaming or moving the anchor folder silently ended its deliveries; dispatch now resolves the anchor's live path from the event's own ancestor chain. A move out of a watched folder now reaches that folder's subscribers, with `from` only for rows that watched the source side. Gap markers are authorized like deliveries and coalesced per subscription and subject instead of fanning per lost event. Session subscriptions: the per-socket cap decides on the write, not before it; an orphaned watched-set token heals on refresh; durable rows keep their watch window when a session subscribe touches the same keys. `self` is false when the acting user is unknown. Single delivery: a subscription in backoff or suspended with a backlog pinned the sweeper's head and starved everyone behind it — the sweep now defers it. Only a settled handler run bills a delivery. A socket-only account row no longer wedges after two attempts nobody received. The lease is twice the handler timeout; remote candidates have their own attempt counter; the region depth reconcile runs once a minute region-wide with a bounded scan. KV share handles: a grantee no longer sees the owner's namespace and absolute prefix on the subscribe answer or listing, nor in the delivery token; revoking a wider handle retires the handles it covers; minting the same handle twice returns the existing one, after the delegation check; a row whose event cannot be re-based onto its handle is dropped rather than delivered raw. * fix: presence survives replication, long sessions and region churn One presence item per (user, app) with per-region map fields lost a region whenever two regions joined inside the replication window, and nothing ever put it back. Presence is now one item per (user, app, region): each region writes only its own, a leave or repair retires it conditionally on its own write stamp, and a read is a prefix query. Items carry a 48 h ttl refreshed by a claim-gated write off the existing socket renew path, at most once per 12 h, so a tab that stays connected keeps its region in the row. A region that answered "no socket" or completed a leave releases a shared pin, so a reconnect on another node rejoins and a flapping client cannot force a replicated write per cycle. Cached rows expire after a minute; unaddressable region names are filtered and pruned; relayed acks settle under a bounded concurrency; the forward queue is bounded in bytes as well as items. * feat: indexes for the event_subscriptions hot queries Handler publish, remove and listing, and the hourly expiry and suspension sweeps, all scanned `event_subscriptions`. Adds (app_uid, handler_name), (expires_at) and (suspended_at, id), guarded on every engine. Existing migrations: the postgres widens are now guarded so a boot does not take an exclusive lock for a no-op, the kv_share_handles grantee FK gets an index, the sqlite notification rebuild is transactional and idempotent. * fix: notification writes go through the registry The driver's `create` bypassed the type registry, producing uncatalogued rows with no size bound; it now requires a registered type, caps the payload, and answers 400 rather than 500 for a bad one. `mark_acknowledged` emits the ack other tabs listen for, and only when a row was actually changed. * fix: the handler scanner, unsubscribe, and the in-tab handler environment The free-variable scanner skipped arrows inside a declaration's initializer, so `const ids = event.items.map(x => x.id)` was refused, and treated a name after a comma in a nested initializer as bound, so a real free variable slipped through to fail on first delivery. `unsubscribe()` now drops the durable routing entry so the events socket can close. A broadcast handler running in the tab gets `user` and `fetch` like the worker gives it. `single` without a handler name is refused before the round trip. * docs: events limits, error codes and the background-workers section Retention is deployment-configured rather than a fixed 14 days, and the template no longer ships it armed. Documents `events_terminal`, the two per-event gap reasons, the subject length and listing caps, the `from` field on moves, and the handle-relative anchor. The sessions manager hides the background-workers section when the server has none to show. * feat: a background handler acts as the app does for its user A handler's `user` was a five-minute access token scoped to the subscription's `list` grant, which could stat the changed file but not read it, and could not reach the app's KV or AppData — so an app told that a file was written could do nothing with it. It now runs with the same authority the app has for that user in a tab: an app-under-user worker session, one row per (user, app) named `events:handlers`, visible and revocable in the sessions list. The `events:background` consent is what authorizes running it unattended, and is re-checked before every mint. The wider token exposed two things: puter.js opens a filesystem socket the moment it has a token, which would have parked the isolate in the app's own delivery room and steered deliveries at it; the events client now opts out of sockets (and the per-open bookkeeping) before construction, and is memoized per token in the isolate. And four filesystem operations assumed a socket exists; they no longer do. |
||
|
|
f30baa2a1c |
feat: the per-app events worker runtime (#3697)
* feat: bake published handlers into a generated events worker
* feat: deploy and address the per-app events worker behind a flag
* test: single delivery end to end through a real local worker
* feat: events workers run their own runtime, in their own namespace
An events worker was being deployed as an ordinary worker: default dispatch
namespace, a `subdomains` row, the router preamble, and an app-scoped worker
token baked in. The public dispatcher resolves any script in that namespace
straight off the hostname, so the worker answered at `<name>.puter.work`, and
the only thing in front of it was an unguessable name plus a check that a
`puter-auth` header was present — which the router never validates. Anyone who
learned the hostname could run an app's handlers with a body of their choosing,
in an isolate holding the owner's token as `me`.
Instead:
- Handlers run on their own runtime (`src/worker/src/events-runtime.js`), which
provides no `router` and no `me`, owns the single invoke route, and hands a
handler only `{ event, ctx, user, fetch, ack }`. `user` is built from the
invocation's delivery token, so a handler acts as the subscriber whose
delivery it is and nothing wider. The preamble build emits one bundle per
runtime; the shared half of the template is now included by both.
- The deploy target carries the runtime to prepend, the source to deploy, and
whether to mint a worker token at all, so an events worker deploys into the
`events` dispatch namespace from generated source with no token binding, no
`subdomains` row, and no claim on the owner's worker quota or worker list.
- An invocation carries a key derived from the deployment secret and the script
name, bound as a secret and checked in constant time inside the isolate,
which reads it once and drops it before handler code runs.
- Scripts are named after the handler set they contain, so publishing writes
rows and deploys nothing: a set is deployed the first time a delivery needs
it, and a changed set is a new script rather than an overwrite of a running
one. Publish responses keep the shape they had before the runtime existed.
- Invocations reach a worker only through the events dispatcher, which has no
zone route and requires the internal secret; the backend's own deploy path is
the rehydrate route the dispatcher calls on a namespace miss. Locally there is
no dispatcher, so the controller hands the service an in-process transport
that deploys on miss itself.
The SDK stops allowlisting `puter` as a handler global — a handler that reaches
for an ambient SDK is now refused at publish time, naming `user` instead, rather
than passing the scan and failing on its first delivery.
Requires `events.workerNamespace`, `events.dispatcherUrl` and
`events.internalSecret`; without them nothing is addressable and background
deliveries stay retriable, as they did with the runtime off.
* fix: a handler's delivery token gets through the read routes
An events handler acts as the subscriber through the access token its
invocation carried, but every FS read route refused scoped access tokens
outright, so `user.fs.stat(event.path)` — the design's own example — answered
403 inside the worker. The read-side routes now admit them; the ACL each
handler already runs intersects the token's grant with its issuer's, which is
the check that keeps a token to what it was minted for. The end-to-end suite
asserts the stat from inside the isolate.
* fix: shorthand-method handlers publish as functions
`{ ingest({ event }) { … } }` stringifies without the `function` keyword, so
its source is not an expression and the events worker baked it as a broken
stub — every delivery a retriable 500 until the subscription suspended, with
nothing at publish time to say why. The SDK now gives a shorthand method the
keyword before hashing and sending; getters, setters and computed names are
left for the server-side check to refuse.
* feat: an app's events worker is listable and destroyable
An app with published handlers has an events worker, and hosted deployments
bill it monthly per app, so its owner needs to see it and be able to take it
down. The core announces the lifecycle on the bus — `events.worker.create`
when an app's first handler is published, `events.worker.destroy` when its last
one goes — with the owner as the actor, so pricing can plug in from outside.
`GET /events/workers` lists the caller's workers (paginated, with the script
each set deploys as) and `POST /events/workers/destroy` removes every handler
of an app under the same owner scoping as the handler routes, suspending the
subscriptions bound to them. `puter.events.workers.list/destroy` in the SDK,
a docs page, and a 5 MB cap on an app's combined handler source
(`events_worker_too_large`) so a set that publishes can always deploy.
* fix: harden the events worker runtime for production
- A 4xx is terminal only when it carries the handled marker the runtime (and
the dispatcher) stamp on every answer that came from a script; an unmarked
4xx — an edge 404 for a wrong dispatcher hostname, a WAF page — stays
retriable and is logged, once per script per minute, with the runtime's
reason header.
- Script names are scoped to this backend's exposed API origin, so two
backends sharing a namespace never resolve one script with the wrong
endpoint binding or key. Shape unchanged.
- Each handler is validated in the exact context it is emitted into and the
whole generated file is compiled once; a source that would break the script
marks every handler broken instead of deploying a SyntaxError.
- Locally, events scripts live under their own registry key: the public local
worker host cannot reach them and an ordinary worker cannot take their name.
- A suspended or deleted app owner stops invocations; deploys are throttled
per app per hour; in-flight deploys are keyed by app and script; the
upstream deploy call times out; the generated source is size-capped with a
margin over the publish cap; boot fails when the runtime is on but its
preamble is not built. Byte-length secret compare, appUid shape check,
dispatcher URL prefix preserved, wider connection pool.
* feat: background workers are listed in the sessions manager
A user paying for an app's events worker needs somewhere to see it and take it
down. The sessions manager gets a section listing the apps that run event
handlers in the background, with a Destroy action that removes their published
handlers.
|
||
|
|
7208f86723 |
bind btoa and atob in emulated worker globalspace (#3302)
* bind btoa and atob in emulated worker globalspace * Create working directory for workers |
||
|
|
30432b931a | Add PUT and DELETE to preflight allowed methods (#3278) | ||
|
|
c60626f772 |
Add preamble versioning (#3147)
* Add preamble versioning * update migrations index |
||
|
|
d4d78ac7db |
rework: change backend and backend extensions to use simpler code structure and patterns (#2815)
* fix: dynamodb health checks and client recreation (#2789) * wip: no nanoServices groundwork * feat: data clients in new shape * wip: auth and perms in new system * more wip * middlewaters mainly done * wip: fsv2 in new layout * old fs v2 migration * driver system * driver and old fs fixes * ai drivers wip * stream support * metering in ai chat driver * wip: new auth * rate limit and auth routes * captcha and anti csrf * fix: types * auth store * app logic * wip most other dricvers * fs * mostly kill all legacy stuff * fs finish * fix: redis usage * ai controller * driver cleanup * socket io in v2 * broadcast and crudq stuff * subdomains * notifcations and shares * fix bad syntaxes * auth wip Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * extensions * extension setup * more routes * sql migrations and default services * home router * tier 7 * everything else * everything else * remaining missing bits * server health * logs * cleanup * deps * cleanup 2 * more cleanup 2 * boot * fix launch * config fix * move file * fix: tsconfig things * fix: extension loading * launching * fix: drivers * fix: others * fix: icons * fix: file uploads * fs fixes * fix: fs api * fix: dev-center * config * add back telemetry * lint stuff * husky hooks * fix: fs oss * fix: config migration * config migration * migrate scripts + replicate * runner * fix: merge defafult config * fix: default region * fix: api domain * fix paths in readfile * fix fs entry default s3 * NS: Remove Referral && Entri Service * dep cleanups * fix: static assets * fix: kv and perms * fix: driver registrations * fix: home mapping * fix: rao * adding back 500 alarm * fix: build paths * fix: fs and kv shapes * fix: kv shape * more kv coercing and ai chat matching format as prior * fix: private app gates * private app caches * fix: whole bunch of legacy shape issues * update template jsonc * fix caching partial oidc and fs signed paths * more oidc fixes * fix: wip * fix: private apps * admin route fixes * fix: last few things hopefully * claude uploads * fix security for app only routes * fix kv system namespace * stuff * fix: app and kv and suggested apps * fix:open item * fix: FS operations * fix: default app icons * add back token-read and WSL support * metering fixes * fix: fsEntry * perm scanners and implicators * proper download endpoint * fix: download * fix anti csrft on v2 * fix file extensions, app icons * fold in v1 fixes from origin/main into v2 equivalents Re-applies the v1 fixes that landed on origin/main into their v2 counterparts since the v1 files were deleted on DS/wip during the v2 migration. v1 commits referenced below. - SQLBatcher: flush immediately when queue hits maxBatchSize instead of racing the timer (v1 12f48238). - RedisClient: drop maxRetriesPerRequest from 2 to 1 to shrink failure window (v1 |