#4316
feat(notifications): apps can notify the user they act for
Apps can now create notifications for the user they act for. Part of PUT-2251, the first PR of the push notifications stack.
What
- One app-owned type,
app.message(audienceapp-user, scoped to the app). The kind of message goes in itschannelfield instead of new types. - Notification driver
create:- App actors (app tokens, app workers and tokens an app issued) can create
app.messageonly, and only for their own user. The recipient and app come from the actor; naming another recipient or app is a 400, any other type is a 403. - It isn't silent: it's stored, shown in the notifications panel and delivered live.
- Plain users keep creating the other types silently, as before, and can't create
app.message. - Client rows from
read/selectkeep the existing audience rules.
- App actors (app tokens, app workers and tokens an app issued) can create
- Message format (
services/notification/appMessage.ts):title(1–120),text(≤240),icon(https),target(an in-app path),data(JSON ≤1 KB),group,channel,sensitive,priority(normal),ttl(1 s–1 day, default 1 hour, how long a push provider keeps trying an offline device). Unknown fields are refused. Invisible direction and control characters are removed from title and text. - The server stamps the sending app on the stored value (
app: { uid, name }), and the desktop toast and notifications panel show it above the title. - Limits: 30 a minute and 300 a day per user per app (
notificationAppMessageLimits), checked after validation, so a refused call uses neither window. Documented on the rate limits page.
Tests
$ npx vitest run --config src/backend/vitest.config.ts src/backend/services/notification src/backend/drivers/notification
Test Files 6 passed (6)
Tests 123 passed (123)Covers the type check, the recipient and app, every field rule, both limit windows per user and per app, read-back by the app, and delivery with and without the events fold-in.
Review follow-ups
- The minute window is charged before the day window, so a request the minute window refuses leaves the day window untouched, as the docs say. Test: two concurrent requests at the minute limit.
- The sender name on an app message is capped at the title length (120 code points).
Not verified
Not run end to end in a browser yet; that waits for the rest of the stack.
Coverage Report
⬆️ +0.02 pp
⬆️ +0.02 pp
⬆️ +0.01 pp
⬆️ +0.04 pp
File Coverage
🟰 ±0 pp
🟰 ±0 pp
🟰 ±0 pp
🟰 ±0 pp
⬆️ +1.03 pp
⬇️ -0.52 pp
🟰 ±0 pp
⬆️ +0.85 pp
🟰 ±0 pp
🟰 ±0 pp
🟰 ±0 pp
🟰 ±0 pp