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.

HandlingMulig offline-adfærdVigtig afklaring
Læs opgavelisteVis senest hentede dataVis tidspunkt og om listen kan være ufuldstændig
Udfyld rapportGem lokal kladdeSkeln mellem kladde på enheden og modtaget på serveren
Upload fotoKø metadata og filStørrelse, lagerplads, adgang og delvis upload
Godkend eller betalKræv normalt online-bekræftelseMå handlingen ske på gammel status eller pris?
Slet postRegistrér en særskilt slettehensigtMå 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.
Baggrundssynkronisering er hjælp – ikke en garanti. Workbox kan gemme fejlede requests i IndexedDB og forsøge igen senere. Dens standardplugin køer netværksfejl; et modtaget 4xx- eller 5xx-svar bliver ikke automatisk betragtet som samme type fejl. Appen skal stadig klassificere svar og vise fastlåste køposter.

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.

  1. Generér id før første forsøg. Genbrug samme operation-id ved timeout, genstart og manuel retry.
  2. 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.
  3. Gem udfald atomisk. Forretningsændringen og markeringen af operationen skal lykkes samlet eller kunne afstemmes sikkert.
  4. Returnér det tidligere resultat. En dublet skal ikke oprette en ny ordre, rapport, fil eller notifikation.
  5. 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.

  1. Gå offline før indlæsning, efter indlæsning og midt i en gemmehandling.
  2. Lad serveren gennemføre, men fjern svaret, så klienten er nødt til at retry samme operation-id.
  3. Lav flere lokale ændringer og genstart browseren eller enheden før synkronisering.
  4. Redigér samme post fra en anden enhed, før den offline enhed vender tilbage.
  5. Slet posten online, mens en anden enhed stadig har en gammel lokal kopi.
  6. Luk brugerens adgang, skift rolle eller virksomhed, før køen afspilles.
  7. Udgiv en ny payloadversion, mens en gammel køpost afventer.
  8. Fyld lokal lagerkvote, ryd browserdata og afprøv en privat browserkontekst.
  9. Lad et request få 400, 401, 403, 409, 429 og 500, og kontrollér hvilke fejl der stopper, venter eller retries.
  10. 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.

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