From 2b150a79942e99ee928e941bcb6b3f9535de247a Mon Sep 17 00:00:00 2001 From: Juan Castro Date: Fri, 23 Oct 2026 15:35:16 -0400 Subject: [PATCH] fix(push): stop after shutdown starts and keep eight title characters --- .../push/PushService.delivery.test.ts | 24 +++++++++++++++++++ src/backend/services/push/PushService.ts | 2 +- src/backend/services/push/pushMessage.test.ts | 17 +++++++++++++ src/backend/services/push/pushMessage.ts | 6 ++++- 4 files changed, 47 insertions(+), 2 deletions(-) diff --git a/src/backend/services/push/PushService.delivery.test.ts b/src/backend/services/push/PushService.delivery.test.ts index 04d94040f..4f120ace2 100644 --- a/src/backend/services/push/PushService.delivery.test.ts +++ b/src/backend/services/push/PushService.delivery.test.ts @@ -560,6 +560,30 @@ describe('PushService while shutting down', () => { }); }); +describe('PushService when shutdown starts mid-delivery', () => { + const provider = new FakeProvider(); + const state = bootWith({ push: { enabled: true } }, provider); + + it('sends nothing once its devices are read', async () => { + const user = await makeUser(state.server); + await registerDevice(state.server, user.id, `tok-${uuidv4()}`); + const store = state.server.stores.pushDevice; + const original = store.listForDelivery.bind(store); + const listed = vi + .spyOn(store, 'listForDelivery') + .mockImplementation(async (...args) => { + const devices = await original(...args); + state.server.services.push.onServerPrepareShutdown(); + return devices; + }); + await shareReceived(state.server, user.id); + await vi.waitFor(() => expect(listed).toHaveBeenCalled()); + await settle(); + listed.mockRestore(); + expect(provider.calls).toHaveLength(0); + }); +}); + describe('PushService with push off', () => { const provider = new FakeProvider(); const state = bootWith({}, provider); diff --git a/src/backend/services/push/PushService.ts b/src/backend/services/push/PushService.ts index 8f457604c..054c0a66f 100644 --- a/src/backend/services/push/PushService.ts +++ b/src/backend/services/push/PushService.ts @@ -156,7 +156,7 @@ export class PushService extends PuterService { limit: MAX_DEVICES_PER_PUSH, }) ).filter((device) => this.#providers.has(device.platform)); - if (devices.length === 0) return; + if (devices.length === 0 || this.#stopping) return; if (this.#inFlight >= MAX_IN_FLIGHT) { this.#noteSkipped(); diff --git a/src/backend/services/push/pushMessage.test.ts b/src/backend/services/push/pushMessage.test.ts index 90e115c11..224b8a52a 100644 --- a/src/backend/services/push/pushMessage.test.ts +++ b/src/backend/services/push/pushMessage.test.ts @@ -272,6 +272,23 @@ describe('fitPushMessage', () => { expect(kept.split(family).every((part) => part === '')).toBe(true); }); + it('keeps at least eight title characters before the ellipsis', () => { + const title = 'a long title that has to be cut'; + const kept = fitPushMessage({ + ...base, + title, + data: { blob: 'd'.repeat(3707) }, + }); + expect(kept!.title).toBe(`${title.slice(0, 8)}…`); + expect( + fitPushMessage({ + ...base, + title, + data: { blob: 'd'.repeat(3708) }, + }), + ).toBeNull(); + }); + it('sends no push when the title would keep only a few characters', () => { expect( fitPushMessage({ diff --git a/src/backend/services/push/pushMessage.ts b/src/backend/services/push/pushMessage.ts index b08141f19..d2d52ff8f 100644 --- a/src/backend/services/push/pushMessage.ts +++ b/src/backend/services/push/pushMessage.ts @@ -216,6 +216,10 @@ export const fitPushMessage = (message: PushMessage): PushMessage | null => { const titleLen = longestFitting(titleChars.length, (n) => build(n, 0, false), ); - if (titleLen < Math.min(MIN_TITLE_CHARS, titleChars.length)) return null; + if (titleLen < 0) return null; + // A cut title is `titleLen - 1` characters and the ellipsis. + if (titleLen < titleChars.length && titleLen - 1 < MIN_TITLE_CHARS) { + return null; + } return build(titleLen, 0, false); };