mirror of
https://github.com/HeyPuter/puter.git
synced 2026-11-04 04:53:39 +00:00
fix(notifications): notif.written hands out a copy with the audience
Listeners get their own copy of the value, so what the socket carries is what was written. Both emits name the audience, and an update names the row's app when the row or the value says which.
This commit is contained in:
1 parent
20c63b6282
commit
bbf375db14
3 files changed
+105
-15
No files matched your search
@@ -552,13 +552,17 @@ export type EventMap = {
|
||||
/** Events handler runs behind whatever created it. */
|
||||
handlerDepth?: number;
|
||||
};
|
||||
/** Every non-silent write or update, only on the node that wrote it. */
|
||||
/**
|
||||
* Every non-silent write or update, only on the node that wrote it. The
|
||||
* value is the listener's own copy of what the socket carries.
|
||||
*/
|
||||
'notif.written': {
|
||||
userId: number;
|
||||
uid: string;
|
||||
type: string;
|
||||
/** The row's app. Absent on an update, which keeps the row's own. */
|
||||
appUid?: string | null;
|
||||
audience: string;
|
||||
/** The row's app; null when it names none or an update could not tell. */
|
||||
appUid: string | null;
|
||||
value: Record<string, unknown>;
|
||||
/** An earlier notification rewritten in place. */
|
||||
updated?: true;
|
||||
|
||||
@@ -3,18 +3,19 @@
|
||||
*
|
||||
* This file is part of Puter.
|
||||
*
|
||||
* Puter is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* Puter is free software: you can redistribute it and/or modify it under the
|
||||
* terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, either version 3 of the License, or (at your option) any
|
||||
* later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
* along with this program. If not, see
|
||||
* [https://www.gnu.org/licenses/](https://www.gnu.org/licenses/).
|
||||
*/
|
||||
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
@@ -361,6 +362,7 @@ describe("NotificationService — 'notif.written'", () => {
|
||||
userId: u.id,
|
||||
uid: rows[i][0].uid,
|
||||
type: 'app.events.ended',
|
||||
audience: 'app-user',
|
||||
appUid,
|
||||
value: { title: 'ended', type: 'app.events.ended' },
|
||||
}));
|
||||
@@ -404,11 +406,82 @@ describe("NotificationService — 'notif.written'", () => {
|
||||
userId: user.id,
|
||||
uid,
|
||||
type: 'share.received',
|
||||
audience: 'account',
|
||||
appUid: null,
|
||||
value: { title: 'two shares', type: 'share.received' },
|
||||
updated: true,
|
||||
});
|
||||
written.stop();
|
||||
});
|
||||
|
||||
it("names the row's app on a rewrite", async () => {
|
||||
const user = await makeUser();
|
||||
const uid = uuidv4();
|
||||
const appUid = `app-${uuidv4()}`;
|
||||
await server.stores.notification.insert({
|
||||
userId: user.id,
|
||||
value: { title: 'one', type: 'share.received' },
|
||||
uid,
|
||||
type: 'share.received',
|
||||
audience: 'account',
|
||||
appUid,
|
||||
});
|
||||
const written = collect('notif.written');
|
||||
|
||||
await notifications.notifyUpdate(
|
||||
uid,
|
||||
user.id,
|
||||
{ title: 'two' },
|
||||
{ type: 'share.received' },
|
||||
);
|
||||
await waitFor(() => written.seen.length === 1);
|
||||
|
||||
expect(written.seen[0]).toMatchObject({ uid, appUid, updated: true });
|
||||
written.stop();
|
||||
});
|
||||
|
||||
it('hands each listener its own copy of the value', async () => {
|
||||
const user = await makeUser();
|
||||
const handler = (_k: string, data: unknown) => {
|
||||
const value = (data as { value: Record<string, unknown> }).value;
|
||||
value.title = 'changed by a listener';
|
||||
(value.fields as Record<string, unknown>).path = '/elsewhere';
|
||||
};
|
||||
server.clients.event.on('notif.written', handler);
|
||||
const pushed = collect('outer.gui.notif.message');
|
||||
try {
|
||||
const uid = await notifications.notify(
|
||||
[user.id],
|
||||
{ title: 'one share', fields: { path: '/a' } },
|
||||
{ type: 'share.received' },
|
||||
);
|
||||
await notifications.notifyUpdate(
|
||||
uid!,
|
||||
user.id,
|
||||
{ title: 'two shares', fields: { path: '/b' } },
|
||||
{ type: 'share.received' },
|
||||
);
|
||||
await waitFor(() => pushed.seen.length === 2);
|
||||
} finally {
|
||||
server.clients.event.off?.('notif.written', handler);
|
||||
pushed.stop();
|
||||
}
|
||||
|
||||
expect(
|
||||
pushed.seen.map((p) => (p as NotifPush).response.notification),
|
||||
).toEqual([
|
||||
{
|
||||
title: 'one share',
|
||||
fields: { path: '/a' },
|
||||
type: 'share.received',
|
||||
},
|
||||
{
|
||||
title: 'two shares',
|
||||
fields: { path: '/b' },
|
||||
type: 'share.received',
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('NotificationService.markAcknowledged', () => {
|
||||
@@ -608,7 +681,10 @@ describe('NotificationService — shown on delivery', () => {
|
||||
{ type: 'share.received' },
|
||||
);
|
||||
|
||||
const [row] = await server.stores.notification.listByUserId(user.id, {});
|
||||
const [row] = await server.stores.notification.listByUserId(
|
||||
user.id,
|
||||
{},
|
||||
);
|
||||
expect(row.uid).toBe(uid);
|
||||
expect(row.shown).toBeTruthy();
|
||||
|
||||
|
||||
@@ -170,14 +170,16 @@ export class NotificationService extends PuterService {
|
||||
}
|
||||
|
||||
if (!opts.silent) {
|
||||
// Listeners get their own copy; the socket sends `payload`.
|
||||
this.clients.event.emit(
|
||||
'notif.written',
|
||||
{
|
||||
userId,
|
||||
uid,
|
||||
type: registered.type,
|
||||
audience: registered.audience,
|
||||
appUid,
|
||||
value: payload,
|
||||
value: structuredClone(payload),
|
||||
},
|
||||
{},
|
||||
);
|
||||
@@ -229,13 +231,21 @@ export class NotificationService extends PuterService {
|
||||
// to anything deduplicating on `event.id`.
|
||||
this.#socket().message(userId, uid, payload);
|
||||
this.#markDelivered(uid, userId);
|
||||
const row = await this.stores.notification.getByUid(uid, {
|
||||
userId,
|
||||
});
|
||||
const app = payload.app as { uid?: unknown } | undefined;
|
||||
this.clients.event.emit(
|
||||
'notif.written',
|
||||
{
|
||||
userId,
|
||||
uid,
|
||||
type: registered.type,
|
||||
value: payload,
|
||||
audience: registered.audience,
|
||||
appUid:
|
||||
row?.app_uid ??
|
||||
(typeof app?.uid === 'string' ? app.uid : null),
|
||||
value: structuredClone(payload),
|
||||
updated: true,
|
||||
},
|
||||
{},
|
||||
|
||||
Reference in new issue
Block a user