Skip to content

fix(push): "rest over" rings only the device that rested - #348

Merged
DuarteSantos8 merged 1 commit into
DuarteSantos8:mainfrom
kurktchiev:gh/rest-alert-device
Oct 7, 2026
Merged

DuarteSantos8 merged 1 commit into
DuarteSantos8:mainfrom
kurktchiev:gh/rest-alert-device

Conversation

@kurktchiev

Copy link
Copy Markdown
Contributor

With push notifications on in two browsers, a rest timer started on one can ring on both, and keep doing it. sendPush aims a rest alert at the subscriptions stored with the resting device's id and falls back to every subscription of the account when none has it. Three paths leave a row without the id:

  1. The service worker's pushsubscriptionchange handler (sw.js ~189) re-registers a rotated subscription with no deviceId. A worker cannot read localStorage, where gym_device lives.
  2. A subscription stored before device ids existed never gains one. The boot sync (push.js ~86) asks GET /api/push/status, sees the endpoint stored, and writes nothing.
  3. POST /api/push/subscribe rebuilds the row from the request alone, so a re-send without a usable id drops the one the row had.

The fixes, in the same order:

  1. The page leaves its device id in a cache of its own (opengym-device, key /opengym-device-id) whenever this browser holds a subscription: on each signed-in boot's sync and when the switch is turned on. The worker reads it and sends it with the rotated subscription. The activate sweep of old build caches leaves that cache alone. Without a Cache API (private mode) the worker sends the subscription alone, as before. Handing the new subscription to an open page would mostly reach nobody, because pushsubscriptionchange usually fires with no page open. IndexedDB would do the same job with a lot more code on both sides, and a query string on the worker's URL would reinstall it and put the id in access logs.
  2. GET /api/push/status also returns the deviceId a stored row carries, null for none. When that is not this browser's id, the boot sync sends the same subscription again, and the server's upsert stores it with the same endpoint, keys and created. An API that does not return the field gets no re-send, because web and api ship as separate images and it would otherwise mean a db.json write on every boot. A browser with no id to give (storage refused) skips it too. deviceId() now replaces a stored id the server would not keep (not deviceIdOf's 8–64 of [A-Za-z0-9_-]); before, such an id went out as it was, the row stayed without one, and the new re-send would repeat on every boot.
  3. Subscribe keeps the id the row already has when the request carries no usable one. A valid id still replaces it.

openapi.yaml describes the new status field and the subscribe rule, and website/api.html is regenerated from it. Running the generator again leaves it unchanged.

Tests:

  • api/test/server-push-status.test.js: 5 pass. New: A legacy row reports deviceId: null and is repaired by the owner's re-send; a re-send with "bad!", an object or no id leaves the row's id alone; a rest started on a device whose row has no id reaches both subscriptions, and only its own once repaired. 3 of the 5 fail on the old server.js.
  • push.test.js 22 pass and sw.test.js 11 pass. New cases cover the re-send under no id and under another id, no write for an older API or without storage, a second boot that writes nothing whatever state the first started from, the four malformed stored ids, the cache the page leaves, and the worker sending it and keeping it through an update. 13 of the 33 fail on the old push.js and sw.js.
  • openapi-routes, server-push-filter, server-push-prune-save and push-messages: 9 pass. sw-media, sw-offline-launch and sw-subpath: 15 pass.

No open PR touches push.js, sw.js or the push routes. #312, #326 and #167 edit other parts of api/openapi.yaml and merge cleanly with this.

🤖 Generated with Claude Code

With push switched on in two browsers, a rest timer started on one could
ring on both, and stay that way. sendPush aims a rest alert at the rows
carrying the resting device's id and falls back to every subscription of
the account when none does. Three paths left a row without the id:

- The service worker's pushsubscriptionchange handler re-registers a
  rotated subscription without a deviceId: it has no localStorage to read
  gym_device from.
- A subscription stored before device ids existed never gains one: the
  boot sync asks GET /api/push/status, sees the endpoint stored, and
  writes nothing.
- POST /api/push/subscribe rebuilt the row from the request alone, so a
  re-send with no usable id dropped the one the row had.

The page now leaves its device id in a cache of its own (opengym-device,
under /opengym-device-id) whenever this browser holds a subscription, and
the worker sends it with the rotated one. The activate sweep keeps that
cache. With no Cache API the worker sends the subscription alone, as
before.

GET /api/push/status names the deviceId a stored row carries, null for
none, and the boot sync sends the same subscription again when that is
not this browser's id; the upsert keeps the row's created date. An api
without the field (web and api ship separately) gets no re-send, nor
does a browser with no id to give. deviceId() replaces a stored id the
server would not keep (the deviceIdOf shape), which would otherwise be
re-sent on every boot. Subscribe keeps the id the row already has when
the request carries none. The spec and website/api.html say so.

Tests: server-push-status.test.js 5 (3 fail on the old server.js),
push.test.js 22 and sw.test.js 11 (13 of those 33 fail on the old
push.js and sw.js).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@DuarteSantos8
DuarteSantos8 merged commit 6833939 into DuarteSantos8:main Oct 7, 2026
4 checks passed
DuarteSantos8 added a commit that referenced this pull request Oct 7, 2026
When the device that started a rest had no subscription of its own, the
alert fell back to every subscription of the account, so the phone rang for
a rest timed on the desktop. It now goes to that device's subscriptions and
to rows from older clients that carry no device id, nobody else.
@DuarteSantos8

Copy link
Copy Markdown
Owner

Merged and released in v1.3.10, thanks @kurktchiev!

https://github.com/DuarteSantos8/openGym/releases/tag/v1.3.10

@kurktchiev
kurktchiev deleted the gh/rest-alert-device branch October 9, 2026 15:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants