Data og stabil drift
Offline data og synkronisering: undgå dubletter og tabte ændringer
En grøn “gemt”-besked kan være misvisende, hvis ændringen kun ligger på telefonen. Når forbindelsen vender tilbage, kan samme handling blive sendt flere gange, møde nyere data eller blive afvist, fordi brugerens adgang har ændret sig. En robust app viser forskellen på lokal og synkroniseret data og har en bevidst regel for hvert udfald.
Vælg præcist hvad der skal virke uden net
“Appen virker offline” er ikke et testbart krav. Beskriv i stedet de konkrete handlinger. En tekniker kan måske læse dagens opgaver og gemme en kladde uden net, mens godkendelse, betaling eller ændring af rettigheder skal vente på et bekræftet serversvar. Det reducerer både risiko og unødig kompleksitet.
| Handling | Mulig offline-adfærd | Vigtig afklaring |
|---|---|---|
| Læs opgaveliste | Vis senest hentede data | Vis tidspunkt og om listen kan være ufuldstændig |
| Udfyld rapport | Gem lokal kladde | Skeln mellem kladde på enheden og modtaget på serveren |
| Upload foto | Kø metadata og fil | Størrelse, lagerplads, adgang og delvis upload |
| Godkend eller betal | Kræv normalt online-bekræftelse | Må handlingen ske på gammel status eller pris? |
| Slet post | Registrér en særskilt slettehensigt | Må nyere ændringer slettes, når enheden kommer online? |
Brug fire tydelige tilstande i brugerfladen
Brugeren skal kunne se, om arbejdet er sikkert modtaget. Netværksikonet alene er ikke nok: MDN dokumenterer, at navigator.onLinekan melde online, selv om internettet eller appens API ikke kan nås. Lad derfor det faktiske serversvar og køens tilstand være facit.
- Kladde
- Arbejdet findes kun lokalt og kan stadig redigeres eller gå tabt med enheden.
- Afventer
- Handlingen ligger i kø og er endnu ikke bekræftet af serveren.
- Synkroniseret
- Serveren har accepteret handlingen og returneret et entydigt resultat.
- Kræver handling
- Køen er stoppet af konflikt, manglende adgang, ugyldige data eller en varig fejl.
Vis antallet af afventende handlinger, tidspunktet for seneste bekræftede synkronisering og en vej til at se fejlene. “Gem igen” må ikke oprette endnu en forretningshændelse, hvis den oprindelige måske allerede er modtaget.
Gem hensigter i en kø – ikke bare en kopi af skærmen
Strukturerede kladder og køposter kan ligge i en klientdatabase som IndexedDB, hvis webappen skal arbejde uden net. IndexedDB understøtter større strukturerede data og lokale transaktioner. Browserlagring er dog ikke produktionens database eller en garanteret backup: lager kan være best-effort, blive ryddet under lagerpres eller forsvinde, når brugeren rydder browserdata.
En køpost bør typisk kunne svare på:
- Hvilken handling? Et stabilt operation-id, type, tidspunkt og payloadversion.
- På hvilket objekt? Et klientgenereret eller eksisterende stabilt id – ikke kun placeringen i en liste.
- I hvilken kontekst? Bruger, virksomhed og eventuel basisversion som information; serveren skal stadig verificere dem ved modtagelse.
- Hvad er status? Afventer, i gang, bekræftet, varig fejl eller konflikt samt seneste forsøg.
- Hvornår opgives automatisk retry? Alder, antal forsøg og hvilke fejl der kræver et menneskeligt valg.
Gør serveren sikker at kalde igen
En klient kan miste svaret efter serveren har gemt handlingen. Den eneste sikre reaktion er ofte at sende igen. Hver forretningshandling, der ikke må fordobles, skal derfor have et stabilt id, som serveren registrerer sammen med resultatet.
- Generér id før første forsøg. Genbrug samme operation-id ved timeout, genstart og manuel retry.
- Bind id'et til rette kontekst. Samme id fra en anden bruger, virksomhed eller handling skal ikke kunne hente eller påvirke det første resultat.
- Gem udfald atomisk. Forretningsændringen og markeringen af operationen skal lykkes samlet eller kunne afstemmes sikkert.
- Returnér det tidligere resultat. En dublet skal ikke oprette en ny ordre, rapport, fil eller notifikation.
- Bevar id længe nok. Perioden skal passe til, hvor længe en enhed realistisk kan være offline og stadig forsøge igen.
HTTP beskriver blandt andet PUT og DELETE som idempotente i deres tilsigtede effekt, men et endpoint bliver ikke automatisk sikkert, fordi metoden hedder PUT. Serverens forretningslogik, sideeffekter og operation-id skal tilsammen tåle gentagelsen.
Vælg en konfliktregel – “seneste vinder” er også et valg
Cloud Firestore synkroniserer lokale ændringer, når forbindelsen vender tilbage, og dokumenterer “last write wins” ved flere ændringer af samme dokument. Det er praktisk, men kan skjule, at en gammel offline-enhed overskriver nyere arbejde. Transaktioner, der først skal læse serverens aktuelle data, fejler desuden, mens klienten er offline.
Automatisk sammenlægning
Kan passe til uafhængige felter eller tilføjelser, når reglerne er entydige og testede.
Afvis gammel basisversion
Serveren sammenligner version eller opdateringstid og beder brugeren tage stilling til den nyere post.
Domænespecifik regel
Lagerantal, booking, status og økonomi kræver ofte en servertransaktion eller en særskilt kommando.
Manuel konflikt
Vis den lokale og den aktuelle serverversion uden at overskrive nogen af dem, indtil en ansvarlig vælger.
Sletning kræver særlig behandling. Bevar en slettehændelse eller tombstone længe nok til, at en gammel enhed ikke kan genoprette posten ved næste sync. Hvis posten er ændret siden slettehensigten blev lavet, skal serveren følge en dokumenteret regel frem for at gætte.
Kontrollér adgang igen ved hver synkronisering
En lokal køpost er ubetroet input. Brugeren kan være flyttet til en anden virksomhed, have mistet sin rolle eller være logget ud, siden handlingen blev oprettet. Serveren skal derfor validere identitet, tenant, rettighed, felter og den aktuelle forretningstilstand ved hvert forsøg – ikke stole på den gamle lokale kontekst.
- Gem ikke sessionshemmeligheder i localStorage. OWASP fremhæver, at JavaScript på samme origin kan læse lageret, og at en XSS-fejl dermed kan stjæle indholdet.
- Ryd brugerdata ved logout. Kladder, cache, filer og køposter må ikke åbne for den næste bruger på en delt enhed.
- Aftal hvad der sker med usynkroniseret arbejde. Advar før logout, og tilbyd kun eksport eller overførsel, hvis det kan ske uden at omgå adgangsregler.
- Begræns lokale persondata. Gem kun det, offline-flowet kræver, og dokumentér sletning, udløb og håndtering af mistet enhed.
- Behandl lokale data som manipulerbare. Valider dem på serveren på samme måde som et almindeligt API-request.
Planlæg opdateringer mens gamle køposter stadig findes
En enhed kan komme online efter en ny appversion og en ændret datamodel er udgivet. Køposten skal derfor have en payloadversion, og serveren skal enten forstå den gamle version, migrere den kontrolleret eller afvise den med en brugbar vej videre. Fjern ikke gamle API-felter eller konfliktregler, før den aftalte offline-periode er udløbet og fastlåste klienter kan opdages.
Samme princip gælder kryptering, rettigheder og reference-id'er. En deployment er ikke fuldt bagudkompatibel, hvis den kun virker for browsere, der allerede har genindlæst.
Test med afbrydelser – ikke kun med offline-knappen
Testen skal kunne vise, at en handling ender én gang og i den rigtige tilstand. Brug isolerede testdata og observer både brugerflade, lokal kø, API, database og logs.
- Gå offline før indlæsning, efter indlæsning og midt i en gemmehandling.
- Lad serveren gennemføre, men fjern svaret, så klienten er nødt til at retry samme operation-id.
- Lav flere lokale ændringer og genstart browseren eller enheden før synkronisering.
- Redigér samme post fra en anden enhed, før den offline enhed vender tilbage.
- Slet posten online, mens en anden enhed stadig har en gammel lokal kopi.
- Luk brugerens adgang, skift rolle eller virksomhed, før køen afspilles.
- Udgiv en ny payloadversion, mens en gammel køpost afventer.
- Fyld lokal lagerkvote, ryd browserdata og afprøv en privat browserkontekst.
- Lad et request få 400, 401, 403, 409, 429 og 500, og kontrollér hvilke fejl der stopper, venter eller retries.
- Log ud med usynkroniseret arbejde, log ind som en anden bruger og kontrollér at data ikke krydser konti.
Typiske fejl
- “Online” betyder “serveren kan nås”: UI aktiverer handlinger ud fra et upålideligt netværkssignal.
- Alt hedder gemt: Brugeren lukker appen uden at vide, at arbejdet stadig kun ligger lokalt.
- Retry opretter en ny post: Hvert forsøg får nyt id, så serveren ikke kan genkende dubletten.
- Sidste write vinder ubemærket: En gammel enhed overskriver nyere oplysninger uden konfliktbesked.
- Sletning er kun fravær: En gammel cache genskaber den slettede post ved næste synkronisering.
- Køen følger den gamle rolle: Serveren accepterer handlingen uden en ny rettighedskontrol.
- Browserlager kaldes backup: Kritiske kladder kan forsvinde ved rydning, lagerpres eller enhedstab.
- Ny version forstår kun nye payloads: Gammel kø går permanent i stå efter deployment.
Tjekliste før offline-flowet bruges i drift
- ☐ Hver kritisk handling er klassificeret som onlinekrævet, lokal kladde eller købar handling.
- ☐ UI skelner mellem kladde, afventer, synkroniseret og kræver handling.
- ☐ Køposter har stabilt operation-id, objekt-id, kontekst, payloadversion og status.
- ☐ Serveren kan modtage samme operation-id igen uden dobbelt effekt.
- ☐ Konfliktreglen er beskrevet pr. datatype; “seneste vinder” bruges kun bevidst.
- ☐ Sletninger kan ikke ophæves af en gammel offline-kopi uden en udtrykkelig regel.
- ☐ Identitet, virksomhed, rettighed og input valideres igen ved sync.
- ☐ Lokale persondata, lagerperiode, logout og delt enhed er håndteret.
- ☐ Lagerfejl og mistede lokale data giver en tydelig, sikker besked.
- ☐ Gamle køpayloads er kompatible med den serverversion, de kan møde.
- ☐ Test dækker afbrudt svar, dublet, konflikt, sletning, ny rolle og ny appversion.
- ☐ Drift kan se køalder, fejltyper, konflikter og sidste vellykkede sync uden følsom payload.
Relaterede guides
Officielle kilder
Fakta er kontrolleret mod aktuelle browserstandarder, sikkerhedsvejledning og officiel dokumentation. Offline-adfærd varierer mellem browsere, SDK'er og datalagre, så kontrollér altid den konkrete stack og de versioner, appen bruger.
- Firebase: Offline data, synkronisering og metadata i Cloud Firestore
- Firebase: Transaktioner, genkørsel og begrænsninger uden netværk
- MDN: navigator.onLine er kun et signal – ikke et sikkert bevis på forbindelse
- MDN: IndexedDB til strukturerede lokale data og transaktioner
- MDN: Lagerkvoter, best-effort-lagring og risiko for rydning
- Chrome for Developers: Workbox Background Sync og genafspilning af requests
- IETF RFC 9110: Idempotente HTTP-metoder og sikker gentagelse
- OWASP: Sikkerhed ved browserlagring og klientdatabaser
Virker appen på kontoret, men ikke roligt i felten?
Startklar kan hjælpe med at afgrænse det offline-flow, der faktisk er nødvendigt, og gennemgå lokal lagring, synkroniseringskø, adgang, konflikter og drift i den stack, appen allerede bruger.
Se Startklar-forløbet