mirror of
https://github.com/HeyPuter/puter.git
synced 2026-11-04 13:03:37 +00:00
fix(gui): removing this browser in Settings also forgets its push registration
This commit is contained in:
1 parent
3068059058
commit
2f94ee1088
3 files changed
+83
-2
No files matched your search
+3
-1
@@ -15,6 +15,8 @@ Core does not subscribe the browser to a push service, and it has no `/push/*` r
|
||||
|
||||
`uid` is the notification's uid, a later push with the same `group` replaces the one showing, and `target` and `data` reach the app when the user taps it.
|
||||
|
||||
When the browser replaces the subscription (`pushsubscriptionchange`), `sw.js` posts `{ type: 'puter-push-subscription-changed' }` to every open window on Puter's origin. The worker has no token or session, so the page's push client registers the new subscription.
|
||||
|
||||
## Registering
|
||||
|
||||
The registry is in [src/gui/src/helpers/pushClient.js](../src/gui/src/helpers/pushClient.js). A service script registers its client like this:
|
||||
@@ -44,7 +46,7 @@ interface PushClient {
|
||||
```
|
||||
|
||||
- `enable()` is called from a click, so it may ask for notification permission. It subscribes with the `/sw.js` registration, registers the device with `POST /push/devices` (`platform: 'web'`), and resolves the device `uid` the server returned. It rejects when permission is refused or the device couldn't be registered.
|
||||
- `disable()` unsubscribes and removes this browser's device with `DELETE /push/devices/:uid`.
|
||||
- `disable()` unsubscribes and removes this browser's device with `DELETE /push/devices/:uid`. The tab calls it for Turn off, and for Remove on the device marked as this browser; Remove on any other device is a plain `DELETE`.
|
||||
- `status()` reports the state without prompting. `permission` is `Notification.permission`. `registered` is true when this browser has a device registered for the signed-in user, and `deviceUid` is that device's `uid`.
|
||||
|
||||
After `enable()` resolves, the tab calls `POST /push/devices/:uid/test` and asks the user whether a notification showed up.
|
||||
|
||||
@@ -343,8 +343,11 @@ const TabNotifications = {
|
||||
returnFocusTo: this,
|
||||
});
|
||||
if ( confirmed !== true ) return;
|
||||
// The client also deletes this browser's token and local record.
|
||||
const client = device.current ? getPushClient() : null;
|
||||
try {
|
||||
await removePushDevice(uid);
|
||||
if ( client ) await client.disable();
|
||||
else await removePushDevice(uid);
|
||||
} catch ( e ) {
|
||||
if ( e?.status !== 404 || e?.code === PUSH_UNAVAILABLE ) {
|
||||
console.warn('[push] device removal failed', e);
|
||||
@@ -352,6 +355,10 @@ const TabNotifications = {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if ( client ) {
|
||||
await Promise.all([refreshBrowser(), refreshDevices()]);
|
||||
return;
|
||||
}
|
||||
devices = devices.filter(d => d.uid !== uid);
|
||||
renderDevices();
|
||||
if ( device.current ) refreshBrowser();
|
||||
|
||||
@@ -369,6 +369,78 @@ describe('your devices', () => {
|
||||
expect($('.push-remove-device').attr('data-uid')).toBe('a');
|
||||
});
|
||||
|
||||
it('removes this browser through the client, which also forgets its registration', async () => {
|
||||
const client = fakeClient({ permission: 'granted', registered: true, deviceUid: 'a' });
|
||||
pushClient.registerPushClient(client);
|
||||
const remove = vi.fn(() => reply(200, { removed: true }));
|
||||
let listed = [device({ uid: 'a', current: true }), device({ uid: 'b', label: 'Phone' })];
|
||||
serve({
|
||||
'GET /push/devices': () => reply(200, { devices: listed }),
|
||||
'GET /push/apps': { apps: [] },
|
||||
'GET /push/preferences': { puter: true },
|
||||
'DELETE /push/devices/a': remove,
|
||||
});
|
||||
await open();
|
||||
client.disable.mockImplementation(async () => {
|
||||
listed = listed.filter(d => d.uid !== 'a');
|
||||
client.status.mockResolvedValue({ permission: 'granted', registered: false });
|
||||
});
|
||||
|
||||
$('.push-remove-device[data-uid="a"]').trigger('click');
|
||||
await settle();
|
||||
dialogButton('push_device_remove').trigger('click');
|
||||
await settle();
|
||||
|
||||
expect(client.disable).toHaveBeenCalledTimes(1);
|
||||
expect(remove).not.toHaveBeenCalled();
|
||||
expect(browserCard().hasClass('push-browser-off')).toBe(true);
|
||||
expect($('.push-device-card')).toHaveLength(1);
|
||||
expect($('.push-remove-device').attr('data-uid')).toBe('b');
|
||||
});
|
||||
|
||||
it('removes another device with the server alone', async () => {
|
||||
const client = fakeClient({ permission: 'granted', registered: true, deviceUid: 'a' });
|
||||
pushClient.registerPushClient(client);
|
||||
const remove = vi.fn(() => reply(200, { removed: true }));
|
||||
serve({
|
||||
'GET /push/devices': { devices: [device({ uid: 'a', current: true }), device({ uid: 'b', label: 'Phone' })] },
|
||||
'GET /push/apps': { apps: [] },
|
||||
'GET /push/preferences': { puter: true },
|
||||
'DELETE /push/devices/b': remove,
|
||||
});
|
||||
await open();
|
||||
|
||||
$('.push-remove-device[data-uid="b"]').trigger('click');
|
||||
await settle();
|
||||
dialogButton('push_device_remove').trigger('click');
|
||||
await settle();
|
||||
|
||||
expect(remove).toHaveBeenCalledTimes(1);
|
||||
expect(client.disable).not.toHaveBeenCalled();
|
||||
expect(browserCard().hasClass('push-browser-on')).toBe(true);
|
||||
expect($('.push-remove-device').attr('data-uid')).toBe('a');
|
||||
});
|
||||
|
||||
it('keeps this browser and says so when the client cannot reach a push service', async () => {
|
||||
const client = fakeClient({ permission: 'granted', registered: true, deviceUid: 'a' });
|
||||
client.disable.mockRejectedValue(Object.assign(new Error('unavailable'), { code: 'push_unavailable' }));
|
||||
pushClient.registerPushClient(client);
|
||||
serve({
|
||||
'GET /push/devices': { devices: [device({ uid: 'a', current: true })] },
|
||||
'GET /push/apps': { apps: [] },
|
||||
'GET /push/preferences': { puter: true },
|
||||
});
|
||||
await open();
|
||||
|
||||
$('.push-remove-device[data-uid="a"]').trigger('click');
|
||||
await settle();
|
||||
dialogButton('push_device_remove').trigger('click');
|
||||
await settle();
|
||||
|
||||
expect($('.push-device-card')).toHaveLength(1);
|
||||
expect(dialog('.dashboard-dialog-message').text()).toContain('push_device_remove_failed');
|
||||
});
|
||||
|
||||
it('drops a device the server no longer has', async () => {
|
||||
serve({
|
||||
'GET /push/devices': { devices: [device({ uid: 'a' })] },
|
||||
|
||||
Reference in new issue
Block a user