11import {
2+ canPersistSnapshot ,
23 compareSpecs ,
4+ DEFAULT_MAX_SNAPSHOT_BYTES ,
5+ DEFAULT_TTL_MS ,
6+ filterHistoryByTtl ,
37 getStorageKey ,
48 hashSpec ,
9+ isOversizedSnapshotMarker ,
510 SNAPSHOT_PREFIX ,
611 STORAGE_PREFIX ,
12+ unwrapSnapshot ,
713 VIEWED_PREFIX ,
14+ wrapOversizedSnapshotMarker ,
15+ wrapSnapshot ,
816} from "./fn"
917
1018function readJson ( key , fallback ) {
@@ -17,7 +25,27 @@ function readJson(key, fallback) {
1725}
1826
1927function writeJson ( key , value ) {
20- localStorage . setItem ( key , JSON . stringify ( value ) )
28+ try {
29+ localStorage . setItem ( key , JSON . stringify ( value ) )
30+ return true
31+ } catch ( err ) {
32+ console . warn (
33+ "Swagger UI: unable to persist change history to localStorage" ,
34+ err
35+ )
36+ return false
37+ }
38+ }
39+
40+ function removeKey ( key ) {
41+ try {
42+ localStorage . removeItem ( key )
43+ } catch ( err ) {
44+ console . warn (
45+ "Swagger UI: unable to remove change history from localStorage" ,
46+ err
47+ )
48+ }
2149}
2250
2351function getHistoryStorageKey ( storageKey ) {
@@ -32,6 +60,12 @@ function getViewedStorageKey(storageKey) {
3260 return `${ VIEWED_PREFIX } :${ storageKey } `
3361}
3462
63+ function clearPersistedHistory ( storageKey ) {
64+ removeKey ( getHistoryStorageKey ( storageKey ) )
65+ removeKey ( getSnapshotStorageKey ( storageKey ) )
66+ removeKey ( getViewedStorageKey ( storageKey ) )
67+ }
68+
3569export const recordSpecLoad = ( ) => ( system ) => {
3670 const {
3771 specSelectors,
@@ -54,22 +88,51 @@ export const recordSpecLoad = () => (system) => {
5488 const storageKey = getStorageKey ( url , spec )
5589 const specHash = hashSpec ( spec )
5690 const maxEntries = configs . changeHistoryMaxEntries || 20
91+ const maxSnapshotBytes =
92+ configs . changeHistoryMaxSnapshotBytes ?? DEFAULT_MAX_SNAPSHOT_BYTES
93+ const ttlMs = configs . changeHistoryTtlMs ?? DEFAULT_TTL_MS
94+ const now = Date . now ( )
5795
5896 changeHistoryActions . setStorageKey ( storageKey )
5997
6098 const historyKey = getHistoryStorageKey ( storageKey )
6199 const snapshotKey = getSnapshotStorageKey ( storageKey )
62100 const viewedKey = getViewedStorageKey ( storageKey )
63101
64- const existingHistory = readJson ( historyKey , [ ] )
65- const previousSnapshot = readJson ( snapshotKey , null )
102+ const existingHistory = filterHistoryByTtl (
103+ readJson ( historyKey , [ ] ) ,
104+ ttlMs ,
105+ now
106+ )
107+ const rawSnapshot = readJson ( snapshotKey , null )
108+ const previousSnapshotPayload = unwrapSnapshot ( rawSnapshot , {
109+ ttlMs,
110+ now,
111+ } )
112+ const previousSnapshot = previousSnapshotPayload
113+ ? previousSnapshotPayload . spec
114+ : null
66115 const lastViewedAt = readJson ( viewedKey , 0 )
116+ const oversizedMarker = isOversizedSnapshotMarker ( rawSnapshot , {
117+ ttlMs,
118+ now,
119+ } )
120+
121+ // Drop expired / invalid snapshot payloads from disk
122+ if ( rawSnapshot && ! previousSnapshotPayload && ! oversizedMarker ) {
123+ removeKey ( snapshotKey )
124+ }
67125
68- if ( previousSnapshot && hashSpec ( previousSnapshot ) === specHash ) {
126+ if (
127+ ( previousSnapshot && hashSpec ( previousSnapshot ) === specHash ) ||
128+ ( oversizedMarker && rawSnapshot . specHash === specHash )
129+ ) {
69130 changeHistoryActions . setHistory ( existingHistory )
70131 changeHistoryActions . setUnseenChanges (
71132 existingHistory . some ( ( entry ) => entry . timestamp > lastViewedAt )
72133 )
134+ // Keep history pruned on disk even when the snapshot is unchanged
135+ writeJson ( historyKey , existingHistory )
73136 return
74137 }
75138
@@ -81,8 +144,8 @@ export const recordSpecLoad = () => (system) => {
81144
82145 if ( ! previousSnapshot || changes . length ) {
83146 const entry = {
84- id : `${ Date . now ( ) } ` ,
85- timestamp : Date . now ( ) ,
147+ id : `${ now } ` ,
148+ timestamp : now ,
86149 version : spec ?. info ?. version || null ,
87150 title : spec ?. info ?. title || null ,
88151 specHash,
@@ -92,7 +155,16 @@ export const recordSpecLoad = () => (system) => {
92155
93156 const nextHistory = [ entry , ...existingHistory ] . slice ( 0 , maxEntries )
94157 writeJson ( historyKey , nextHistory )
95- writeJson ( snapshotKey , spec )
158+
159+ if ( canPersistSnapshot ( spec , maxSnapshotBytes , now ) ) {
160+ writeJson ( snapshotKey , wrapSnapshot ( spec , now ) )
161+ } else {
162+ console . warn (
163+ "Swagger UI: change-history snapshot exceeds size limit; skipping localStorage snapshot persistence"
164+ )
165+ // Keep a tiny marker so we do not re-baseline on every reload
166+ writeJson ( snapshotKey , wrapOversizedSnapshotMarker ( specHash , now ) )
167+ }
96168
97169 changeHistoryActions . setHistory ( nextHistory )
98170
@@ -102,6 +174,7 @@ export const recordSpecLoad = () => (system) => {
102174 }
103175 } else {
104176 changeHistoryActions . setHistory ( existingHistory )
177+ writeJson ( historyKey , existingHistory )
105178 }
106179}
107180
@@ -118,10 +191,29 @@ export const restoreHistory = () => (system) => {
118191 return
119192 }
120193
194+ const ttlMs = configs . changeHistoryTtlMs ?? DEFAULT_TTL_MS
195+ const now = Date . now ( )
121196 const historyKey = getHistoryStorageKey ( storageKey )
197+ const snapshotKey = getSnapshotStorageKey ( storageKey )
122198 const viewedKey = getViewedStorageKey ( storageKey )
123- const existingHistory = readJson ( historyKey , [ ] )
199+ const existingHistory = filterHistoryByTtl (
200+ readJson ( historyKey , [ ] ) ,
201+ ttlMs ,
202+ now
203+ )
124204 const lastViewedAt = readJson ( viewedKey , 0 )
205+ const rawSnapshot = readJson ( snapshotKey , null )
206+
207+ if (
208+ rawSnapshot &&
209+ ! unwrapSnapshot ( rawSnapshot , { ttlMs, now } ) &&
210+ ! isOversizedSnapshotMarker ( rawSnapshot , { ttlMs, now } )
211+ ) {
212+ removeKey ( snapshotKey )
213+ }
214+
215+ // Persist pruned history so expired entries don't linger
216+ writeJson ( historyKey , existingHistory )
125217
126218 changeHistoryActions . setHistory ( existingHistory )
127219 changeHistoryActions . setUnseenChanges (
@@ -163,9 +255,7 @@ export const clearHistory = () => (system) => {
163255 return
164256 }
165257
166- localStorage . removeItem ( getHistoryStorageKey ( storageKey ) )
167- localStorage . removeItem ( getSnapshotStorageKey ( storageKey ) )
168- localStorage . removeItem ( getViewedStorageKey ( storageKey ) )
258+ clearPersistedHistory ( storageKey )
169259
170260 changeHistoryActions . setHistory ( [ ] )
171261 changeHistoryActions . setUnseenChanges ( false )
0 commit comments