mirror of
https://github.com/HeyPuter/puter.git
synced 2026-11-05 05:24:13 +00:00
feat(notifications): enable() asks through Puter's prompt after a revoke
When the user turned an app's notifications off in Puter, the browser's permission on the app's origin no longer re-enables them. enable() asks again through Puter's permission prompt and retries once.
This commit is contained in:
1 parent
1aa7caf44c
commit
1b6de39a13
3 files changed
+74
-4
No files matched your search
@@ -6,7 +6,7 @@ platforms: [websites, apps]
|
||||
|
||||
Turns on push notifications for your app in the browser the user is in. Call it from a click: it asks the browser's permission first, before anything else, because browsers only show that prompt from a user gesture.
|
||||
|
||||
On a website, the browser asks on your own origin, and pushes arrive through your service worker (see [`onPush()`](/Notifications/onPush/)). Puter then sends a confirmation push to that browser and only keeps the subscription once your page hands it back, so a subscription can only come from a browser where the user said yes. Allowing it also lets your app's notifications go out as pushes, which the user can revoke in Puter's settings.
|
||||
On a website, the browser asks on your own origin, and pushes arrive through your service worker (see [`onPush()`](/Notifications/onPush/)). Puter then sends a confirmation push to that browser and only keeps the subscription once your page hands it back, so a subscription can only come from a browser where the user said yes. Allowing it also lets your app's notifications go out as pushes. If the user later turns your app off in Puter's settings, that stands: `enable()` then asks again through Puter's permission prompt rather than relying on the browser's permission.
|
||||
|
||||
Inside the Puter desktop it does what [`enablePush()`](/Notifications/enablePush/) does.
|
||||
|
||||
|
||||
@@ -27,8 +27,14 @@ export async function enable(options = {}) {
|
||||
if ((await asking) !== 'granted') return false;
|
||||
|
||||
if (needsSignIn(puter)) await signInVisitor(puter);
|
||||
await syncWebPush(puter, {
|
||||
worker: workerFor(puter.env, options.serviceWorker),
|
||||
});
|
||||
const worker = workerFor(puter.env, options.serviceWorker);
|
||||
try {
|
||||
await syncWebPush(puter, { worker });
|
||||
} catch (error) {
|
||||
// The user turned this app off in Puter; only Puter's prompt undoes it.
|
||||
if (error?.code !== 'push_revoked') throw error;
|
||||
if (!(await this.requestPermission())) return false;
|
||||
await syncWebPush(puter, { worker });
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -251,3 +251,67 @@ describe('onPush', () => {
|
||||
expect(module.onNotification).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('enable', () => {
|
||||
const withBrowser = (nav, permission = 'granted') => {
|
||||
globalThis.PushManager = function PushManager() {};
|
||||
globalThis.Notification = {
|
||||
permission,
|
||||
requestPermission: vi.fn(async () => 'granted'),
|
||||
};
|
||||
Object.defineProperty(globalThis.navigator, 'serviceWorker', {
|
||||
value: nav.serviceWorker,
|
||||
configurable: true,
|
||||
});
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
delete globalThis.PushManager;
|
||||
delete globalThis.Notification;
|
||||
});
|
||||
|
||||
const makeModule = () =>
|
||||
new NotificationsModule({
|
||||
...puter,
|
||||
onAuthStateChanged: () => () => {},
|
||||
});
|
||||
|
||||
it('asks the browser before anything is awaited', async () => {
|
||||
const nav = makeNav({ existing: makeSubscription() });
|
||||
withBrowser(nav, 'default');
|
||||
mockRequest.mockImplementation(async (_p, method, route) =>
|
||||
route === '/push/web/key'
|
||||
? { publicKey: KEY }
|
||||
: { status: 'active', device: { uid: 'd1' } },
|
||||
);
|
||||
const pending = makeModule().enable();
|
||||
expect(Notification.requestPermission).toHaveBeenCalledTimes(1);
|
||||
await expect(pending).resolves.toBe(true);
|
||||
});
|
||||
|
||||
it('asks through Puter`s prompt when the user turned the app off there', async () => {
|
||||
const nav = makeNav({ existing: makeSubscription() });
|
||||
withBrowser(nav);
|
||||
let revoked = true;
|
||||
mockRequest.mockImplementation(async (_p, method, route) => {
|
||||
if (route === '/push/web/key') return { publicKey: KEY };
|
||||
if (revoked) {
|
||||
const { PuterJSError } =
|
||||
await import('../../lib/PuterJSError.js');
|
||||
throw new PuterJSError('off', 'push_revoked');
|
||||
}
|
||||
return { status: 'active', device: { uid: 'd1' } };
|
||||
});
|
||||
const module = makeModule();
|
||||
module.requestPermission = vi.fn(async () => {
|
||||
revoked = false;
|
||||
return true;
|
||||
});
|
||||
await expect(module.enable()).resolves.toBe(true);
|
||||
expect(module.requestPermission).toHaveBeenCalledTimes(1);
|
||||
|
||||
revoked = true;
|
||||
module.requestPermission = vi.fn(async () => false);
|
||||
await expect(module.enable()).resolves.toBe(false);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user