SIAX Technology (sax3l)

@siax/mobile-notifications (0.1.1)

Published 2026-08-10 12:03:21 +00:00 by admin

Installation

@siax:registry=https://git.cloud.siax.io/api/packages/sax3l/npm/
npm install @siax/mobile-notifications@0.1.1
"@siax/mobile-notifications": "0.1.1"

About this package

N0TIFY-registrering + djuplänkning. All notislogik ligger i N0TIFY (PUSH-01).

@siax/mobile-notifications

Klientytan för push i K6-appar: enhetsregistrering, användarens notisval, djuplänkning som överlever kallstart, och en validator som hindrar persondata från att någonsin lämna appen i en nyttolast.

Status: implementerad och testad. 74/74 tester gröna (node --test), tsc --strict rent. Inte publicerad till Giteas npm-registry. services/device-registry är inte rest (M9) och n0tify-web är oroutad (B-M0B-3) — därför är varje transport injicerbar och paketet fungerar fullt ut mot påhittade transporter i test.

Kärnbussgränsen (MOB-02 / PUSH-01)

All notislogik ligger i N0TIFY. Regler, mallar, schemaläggning, tystnadsperioder, sammanslagning, frekvenstak, retries och de faktiska APNs/FCM-transporterna — allt där. Ingen push-tjänst byggs här.

Det som hör hemma på enheten, och bara där:

Del Vad den gör Vad den inte gör
PushRegistrar registrerar enhet + token mot services/device-registry, idempotent äger inte enhetsregistret, skickar inga notiser
NotificationPreferencesClient speglar användarens val per notisslag (PUSH-07) utvärderar inga regler — N0TIFY har sista ordet
DeepLinkQueue 🔴 PUSH-08: länkar som överlever kallstart äger ingen router
validateWakeUpPayload 🔴 PUSH-05: väckningsdata endast ersätter inte N0TIFY:s serversidiga kontroll

Inga hemligheter finns i eller nära paketet (MSEC-01): ingen APNs-p8, inget FCM-servicekonto, ingen projektnyckel. De bor i Infisical och används av N0TIFY.

🔴 PUSH-05 — persondata skickas ALDRIG i nyttolasten

Notisen är en väckning: "det finns något nytt av typ X med referens Y". Innehållet hämtar appen över den autentiserade kanalen.

Regeln är hård eftersom en push passerar Apples och Googles servrar, ligger i klartext på låsskärmen, hamnar i OS-loggar, i notishistoriken, i en Android-backup och på ett parat wearable. Ingen av de platserna omfattas av vårt personuppgiftsbiträdesavtal. Det finns ingen säker "bara förnamnet"-nivå.

Mekaniken är deny-by-default på nyckelnamn — den enda regeln som håller över tid. Tillåtna nycklar: v, type, ref, deepLink, notificationId, sentAt, badge, collapseKey, priority, ttlSeconds, titleKey, bodyKey, contentAvailable, threadKey. Allt annat avvisas oavsett innehåll.

Ovanpå det ligger fyra lager till:

  • Formkrav — type måste vara en händelsetyp (order.updated), ref och notificationId ogenomskinliga id, titleKey/bodyKey katalognycklar och inte översatt text.
  • PII-avkänning i allowlistade strängvärden: e-post, telefonnummer, svenskt personnummer, IBAN och Luhn-giltiga kortnummer. Egna mönster kan skjutas in.
  • Djuplänkskontroll — javascript:/data:/file: avvisas alltid, och query-parametrar med känsliga namn (?email=…) fångas.
  • Storleksgräns 3800 byte. En väckning som är för stor bär nästan alltid innehåll som inte hör hemma där.
import { assertWakeUpPayload } from '@siax/mobile-notifications';

assertWakeUpPayload({ type: 'order.updated', ref: 'ord_01HX3' }); // ok
assertWakeUpPayload({ title: 'Anna Andersson', body: 'Ring mig' }); // kastar

validateWakeUpPayload() returnerar hela listan på en gång i stället för att kasta, så att den som skriver en ny notistyp ser alla fel i ett testutfall.

🔴 PUSH-08 — djuplänkar överlever kallstart

Problemet: användaren trycker på en notis medan appen är helt död. OS:et startar processen och levererar länken innan navigationsträdet är monterat. En variabel i minnet tappar den varje gång appen hinner dödas igen.

Lösningen: kön persisteras. enqueue() returnerar inte förrän länken ligger i lagringen. En ny process över samma lagring hittar den och levererar den så snart en hanterare registrerats.

const queue = new DeepLinkQueue({
  storage: minAsyncStorageAdapter,   // MÅSTE vara persistent
  validator: createDeepLinkValidator({
    allowedSchemes: ['https', 'siaxexempel'],
    allowedHosts: ['app.exempel.test'],
    allowedPathPrefixes: ['/order', '/faktura'],
  }),
});

// Tidigast möjliga punkt i appstarten, före navigationsträdet:
const initial = await Notifications.getLastNotificationResponseAsync();
if (initial) {
  await queue.enqueue({ url: …, source: 'cold-start', notificationId: … });
}

// När routern är monterad:
await queue.setHandler((link) => { router.navigate(link.url); return true; });

Garantier, alla testade:

  • at-least-once — hanteraren måste returnera true. Säger den false eller kastar den behålls länken för nästa försök. En tyst förlust är värre än ett extra försök.
  • deduplicering på notificationId, som överlever omstart via en levererat-historik. OS:et får leverera samma notis två gånger.
  • FIFO på mottagningstid, utgång efter 7 dygn, överfyllnadsskydd vid 32 poster (äldst faller ut, med händelse).
  • korrupt kö slängs — en förlorad djuplänk är en olägenhet, en app som inte startar är ett haveri.
  • clear() vid utloggning — köade länkar kan peka på förra användarens data.

Säkerhet: en djuplänk kommer från nätet, alltså är den en navigationsinjektion tills motsatsen bevisats. Validatorn är deny-by-default på scheman, avvisar alltid javascript:/data:/file:/blob:/about:, avvisar inbäddade uppgifter (https://app.exempel.test@angripare.example) och kan begränsa värdar och sökvägsprefix. Avvisade länkar persisteras aldrig.

Kör kön på minneslagring och den skickar ephemeral-storage-warning — då är PUSH-08 inte uppfylld, och det ska synas.

Enhetsregistrering

PushRegistrar registrerar bara när något faktiskt ändrats (token, plattform, appversion, byggnummer, OS-version, språk, tidszon, attesteringsstatus) eller när revalideringsfönstret (7 dygn, eller serverns expiresAt) löpt ut. Kastar aldrig — returnerar status: 'failed' med felet, så att en död server inte fäller appstarten.

  • PUSH-04, klientsidan: invalidate('apns-410-unregistered') glömmer den lokala posten när en leverans avvisats, så att nästa start registrerar om sig. Utan det tror klienten för alltid att den är registrerad.
  • Utloggning: unregister() rensar lokalt även när servern felar — att tro sig registrerad utan att vara det är det värre feltillståndet.
  • Push-token maskeras i alla händelser (redactPushToken). Den är en enhetsidentifierare och en kapningsvektor, inte loggdata.

Inställningar per notisslag (PUSH-07)

Spegling av N0TIFY, inte en egen regelmotor. Cache först så skärmen renderar direkt, sedan servern. Optimistiska ändringar rullas tillbaka när servern nekar — ett reglage som står kvar i fel läge får användaren att tro att hen är avanmäld. required-märkta slag (säkerhet, betalningsfel) kan inte stängas av och muteAll tystar dem inte.

Filer

Fil Ansvar
src/payload.ts PUSH-05-validatorn
src/deep-links.ts PUSH-08-kön + länkvalidatorn
src/registrar.ts enhets- och tokenregistrering
src/preferences.ts PUSH-07-speglingen
src/types.ts delade typer, lagringsadapter, tokenmaskering

Tester

cd packages/mobile-notifications
node --test        # 74 tester, inga beroenden, ingen npm install
tsc -p tsconfig.json

Kör från repo-roten: node --test "packages/mobile-notifications/**/*.test.ts". (Ett katalogargument fungerar inte på Node 24 — positionsargument tolkas som globmönster.)

Kallstartstestet simulerar en riktig processomstart: två DeepLinkQueue-objekt över samma lagring, där det andra aldrig sett det första.

Kräver Node ≥ 22.18 för inbyggd typradering. Node 20 kan inte köra testerna.

Medvetet ogjort

  • Ej publicerad till https://git.siax.io/api/packages/sax3l/npm/.
  • Inga OS-bindningar. Paketet rör varken expo-notifications, @react-native-firebase/messaging eller PushNotificationIOS — det hade låst varje K6-app till ett val och lagt ett nativt beroende i ett rent TypeScript-paket. Appen kopplar OS-hookarna till queue.enqueue() och registrar.register(). Den limkoden finns inte här och hör till M2:s exempelapp.
  • Ingen behörighetsbegäran (requestPermissions) — den är OS-specifik och hör till samma limlager.
  • Ingen HTTP-transport mot enhetsregistret eller N0TIFY, till skillnad från @siax/mobile-config som har en. Skälet: services/device-registry saknar ännu kontrakt i contracts/ (M9), och att gissa dess URL-form vore att skriva fast en osann premiss.
  • Ingen exempelapp (MOB-13 kräver en i CI) — hör till M2.
  • Typkontroll av testfilerna kräver @types/node, medvetet inte installerat i denna omgång. tsconfig.json täcker src/**; testerna är verifierade separat och är rena bortsett från saknade Node-typer.
Details
npm
2026-08-10 12:03:21 +00:00
3
UNLICENSED
latest
22 KiB
Assets (1)
Versions (2) View all
0.1.1 2026-08-10
0.1.0 2026-08-10