* fix(puter-js): background push syncs never prompt for sign-in The load-time sync and the resync after the browser replaces a subscription ask for the key and announce the subscription without opening Puter's sign-in; only enable() may. The confirmation and the removal never open it, and looking up the user for the opt-in uses a non-interactive /whoami. A rejected token during a background sync is not reported as a sync failure. * fix(puter-js): turning push off in one tab stops other tabs' syncs Tabs of a site take a shared Web Lock to subscribe and announce, and again to confirm, without holding it while the confirmation push is on its way. A load sync re-reads the opt-in inside the lock and stops when it is gone. disable() tells the other tabs first, so their syncs and any enable() in flight stop, then forgets the opt-ins and removes the subscription inside the same lock. Without Web Locks the re-reads and the message still apply. * fix(puter-js): wait for a push confirmation only once the lock is held A sync queued behind another tab's lock started its 30 second confirmation timer and listener before it got the lock, so a long wait failed the sync or left the timeout unhandled. The listener now starts inside the locked step, right before the subscription is announced. * test(puter-js): reach the push sync in the boot sign-in e2e The push page now has the user's opt-in and cached user from an earlier load, starts its sync before boot's own requests come back, and gets a rejected token from the push routes, so the spec covers the key request that must not open a sign-in. * fix(puter-js): sign-out and disable() remove only Puter's push subscription Removal now looks at the subscription's application server key and ends it only when it is Puter's: the key stored at the last subscribe, or the server's key when none is stored. Any other subscription on the site is left as it is and the server is not told about it. Without a named worker, as at sign-out or disable() without serviceWorker, every registration on the origin is checked, so a subscription under a folder worker is the one removed. * fix(puter-js): sign-out and a user change end this browser's pushes Sign-out stops this tab's syncs, tells the site's other tabs, and inside the push lock forgets every opt-in and tap, ends Puter's subscription in the browser, then sends the removal with the leaving user's token as a keepalive request so it is still sent if the page reloads. puter.auth.signOut() returns a promise that resolves once that is done; the token is still discarded at once. A different user signing in over a session, or after the token was dropped, gets the same cleanup with the token of the user signed in before. A new token for the same user, or a dropped token, keeps everything. A page load finding a Puter subscription with no opt-in for the signed-in user, or with nobody signed in, ends it in the browser. Opt-in records that can't be read leave it as it is. * fix(puter-js): record a push opt-in before other tabs can check it enable() now writes the user's opt-in inside the push lock, right after the server has the subscription and before the lock is released. A tab loading at the same time waits for the lock, reads the opt-in, and keeps the new subscription. A failed announce, or push turned off while it ran, records nothing. * fix(puter-js): push cleanup runs without the lock where Web Locks refuse In a frame where navigator.locks.request rejects, such as a sandboxed frame, sign-out, disable(), the load-time check and syncs now run their step without the lock instead of rejecting, so signOut() still resolves and the subscription is still ended. * fix(puter-js): send the sign-out removal again when keepalive is refused A keepalive request that needs a CORS preflight is refused by some browsers. The removal at sign-out is then sent once more as a normal request with the same token and without a sign-in prompt, after the browser has already ended the subscription. * test(puter-js): run the push boot test on full Chromium and skip it without push support The headless shell keeps Notification.permission at 'denied' after the permission is granted, so onPush() never synced there and the test saw no key request. The test now launches full Chromium itself, since a channel can't be set per test, and is skipped on WebKit and wherever the page has no PushManager or notification permission. * fix(puter-js): a user change finishes its push cleanup before enable() enable() waits for the cleanup a user change started. When the token was dropped and a different user signs in from enable(), the pushes of the user signed in before end first, and the new user keeps their new opt-in and subscription. A token that sign-out already cleaned up is not cleaned up again when the next user signs in. * fix(puter-js): a tap without a URL opens the site the push came from A page that calls onPush() or enable() saves where taps resolve for its worker: the worker's folder when the page is inside it, else the site's root. push-sw.js and the notifications a page shows itself read relative URLs from it, and fall back to the worker's scope when nothing is saved. * fix(puter-js): push taps go to top-level windows only push-sw.js routes taps, and holds back a page's notification for a focused window, among top-level windows only, as the GUI's worker does. Challenges and subscription changes still reach every window of the origin. * fix(puter-js): deliver a push tap to the page once push-sw.js keeps a tap before posting it to an open page. The page runs its click handler only after taking that copy out of IndexedDB in one readwrite transaction, and acknowledges only then. When no acknowledgement comes in time, the worker takes the copy itself and opens the tap's URL only if the page hadn't. The wait starts at the post and ends at most 800 ms after the click, so openWindow still runs in time. Pages and workers without this keep today's behaviour. * fix(puter-js): clear stored push taps that nobody reads push-sw.js removes taps kept over five minutes on every push and tap, and a page sweeps them once when onPush() syncs. A page opened for a tap ignores a kept tap older than that, and removes it when no click handler is added within 10 seconds of loading. * fix(puter-js): a late page already at the tap's URL still gets the tap When no acknowledgement comes in time and the window is already at the tap's URL, push-sw.js leaves the copy it kept for that page instead of taking it back, so the page's click handler still runs when it answers late. A copy nobody takes is removed with the other old taps. * fix(puter-js): a push tap runs at most once when IndexedDB fails A page that can't take a kept tap out of IndexedDB leaves it to the worker instead of running it, and a worker whose claim fails leaves it to the page instead of opening the tap's URL. * fix(puter-js): take a tap back and keep it for the next page in one step When no acknowledgement comes in time, push-sw.js takes its copy back and keeps it, without the id, for the page it opens in one readwrite transaction, leaving more of Firefox's window-opening allowance for openWindow. * test(puter-js): shorten the fake IndexedDB comment * test(puter-js): keep the push boot test comment to one line * fix(puter-js): a user signing back in with the same token gets the user-change cleanup again The token that sign-out already cleaned up is skipped only for the next user change. Once the same token is set again, a later sign-in by a different user ends its pushes as usual. * fix(puter-js): a page load checks push status instead of registering again * fix(puter-js): subscriptions made with an earlier Puter key are replaced * fix(puter-js): a replaced browser subscription is picked up on the next visit * fix(puter-js): a page load that only checks push status still looks for a newer worker * fix(puter-js): the worker subscribes again only where a Puter page used it * docs(puter-js): soften the note on subscriptions Puter made earlier * fix(puter-js): a status answer on load leaves a subscription another tab just turned on
The Open-Source Internet Computer!
« LIVE DEMO »
Puter.com
·
App Store
·
AI Builder
·
Developers
·
X
Puter
Puter is an advanced, open-source, self-hostable internet computer designed to be feature-rich, fast, and highly extensible.
For Users
Puter's goal is to provide you with every app and feature you need to work, create, and play under one roof. From a simple Notepad and Voice Recorder to Spreadsheet and Camera, Puter wants to be the all-in-one solution for your digital life.
For Developers
Puter provides everything you need to build and publish web apps and games. From AI to Cloud Storage and Database to Serverless Workers, Puter has you covered. Puter also helps you get users! Once you build your app, you can publish it on our App Store to reach and monetize users.
Getting Started
💻 Local Development
git clone https://github.com/HeyPuter/puter
cd puter
npm install
npm start
→ This should launch Puter at http://puter.localhost:4100
To run this checkout with Docker, follow Building from source. Create a local docker-compose.override.yml to select the local build; keeping these settings out of docker-compose.yml avoids conflicts when pulling updates and keeps local configuration out of pull requests.
🚀 Self-Hosting
Linux/macOS
curl -fsSL https://puter.com/selfhost | sh
Windows
irm https://puter.com/selfhost?os=windows | iex
→ For more details, see Self-Hosting Puter.
☁️ Puter.com
Puter is available as a hosted service at puter.com.
Support
Connect with the maintainers and community through these channels:
- Bug report or feature request? Please open an issue.
- X (Twitter): x.com/HeyPuter
- Security issues or abuse reports? security@puter.com
- Email maintainers at hi@puter.com
We are always happy to help you with any questions you may have. Don't hesitate to ask!
License
This repository, including all its contents, sub-projects, modules, and components, is licensed under AGPL-3.0 unless explicitly stated otherwise. Third-party libraries included in this repository may be subject to their own licenses.
Translations
- Arabic / العربية
- Armenian / Հայերեն
- Bengali / বাংলা
- Chinese / 中文
- Danish / Dansk
- English
- Farsi / فارسی
- Finnish / Suomi
- French / Français
- German / Deutsch
- Hebrew/ עברית
- Hindi / हिंदी
- Hungarian / Magyar
- Indonesian / Bahasa Indonesia
- Italian / Italiano
- Japanese / 日本語
- Korean / 한국어
- Malay / Bahasa Malaysia
- Malayalam / മലയാളം
- Dutch / Nederlands
- Polish / Polski
- Odia / ଓଡ଼ିଆ
- Portuguese / Português
- Punjabi / ਪੰਜਾਬੀ
- Romanian / Română
- Russian / Русский
- Spanish / Español
- Swedish / Svenska
- Tamil / தமிழ்
- Telugu / తెలుగు
- Thai / ไทย
- Turkish / Türkçe
- Ukrainian / Українська
- Urdu / اردو
- Vietnamese / Tiếng Việt

