@@ -33,7 +33,7 @@ import { hrefToUrl, mergeDataIntoQueryString, urlWithoutHash } from './url'
33
33
34
34
const isServer = typeof window === 'undefined'
35
35
const isChromeIOS = ! isServer && / C r i O S / . test ( window . navigator . userAgent )
36
- const cloneSerializable = < T > ( obj : T ) : T => JSON . parse ( JSON . stringify ( obj ) )
36
+ const allStateIdsLocalStorageKey = 'inertia_state_ids' ;
37
37
const nextFrame = ( callback : ( ) => void ) => {
38
38
requestAnimationFrame ( ( ) => {
39
39
requestAnimationFrame ( callback )
@@ -85,8 +85,9 @@ export class Router {
85
85
}
86
86
87
87
protected clearRememberedStateOnReload ( ) : void {
88
- if ( this . navigationType === 'reload' && window . history . state ?. rememberedState ) {
89
- delete window . history . state . rememberedState
88
+ if ( this . navigationType === 'reload' && this . getHistoryState ( ) ?. rememberedState ) {
89
+ const { rememberedState, ...newHistoryState } = this . getHistoryState ( ) ?? { } ;
90
+ rememberedState && this . replaceState ( newHistoryState ) ;
90
91
}
91
92
}
92
93
@@ -165,17 +166,27 @@ export class Router {
165
166
}
166
167
167
168
protected isBackForwardVisit ( ) : boolean {
168
- return window . history . state && this . navigationType === 'back_forward'
169
+ return this . getHistoryState ( ) && this . navigationType === 'back_forward'
169
170
}
170
171
171
172
protected handleBackForwardVisit ( page : Page ) : void {
172
- window . history . state . version = page . version
173
- this . setPage ( window . history . state , { preserveScroll : true , preserveState : true } ) . then ( ( ) => {
173
+ this . setPage ( { ...this . getHistoryState ( ) , version : page . version } , { preserveScroll : true , preserveState : true } ) . then ( ( ) => {
174
174
this . restoreScrollPositions ( )
175
175
fireNavigateEvent ( page )
176
176
} )
177
177
}
178
178
179
+ protected getHistoryState ( stateId : string = null ) : Page | null {
180
+ const currentId : string | undefined = stateId ?? window . history . state ?. _id ;
181
+ if ( currentId ) {
182
+ const currentState = window . sessionStorage . getItem ( currentId ) ;
183
+ if ( currentState ) {
184
+ return JSON . parse ( currentState ) ;
185
+ }
186
+ }
187
+ return null ;
188
+ }
189
+
179
190
protected locationVisit ( url : URL , preserveScroll : LocationVisit [ 'preserveScroll' ] ) : boolean | void {
180
191
try {
181
192
const locationVisit : LocationVisit = { preserveScroll }
@@ -201,8 +212,8 @@ export class Router {
201
212
const locationVisit : LocationVisit = JSON . parse ( window . sessionStorage . getItem ( 'inertiaLocationVisit' ) || '' )
202
213
window . sessionStorage . removeItem ( 'inertiaLocationVisit' )
203
214
page . url += window . location . hash
204
- page . rememberedState = window . history . state ?. rememberedState ?? { }
205
- page . scrollRegions = window . history . state ?. scrollRegions ?? [ ]
215
+ page . rememberedState = this . getHistoryState ( ) ?. rememberedState ?? { }
216
+ page . scrollRegions = this . getHistoryState ( ) ?. scrollRegions ?? [ ]
206
217
this . setPage ( page , { preserveScroll : locationVisit . preserveScroll , preserveState : true } ) . then ( ( ) => {
207
218
if ( locationVisit . preserveScroll ) {
208
219
this . restoreScrollPositions ( )
@@ -405,8 +416,8 @@ export class Router {
405
416
}
406
417
preserveScroll = this . resolvePreserveOption ( preserveScroll , pageResponse ) as boolean
407
418
preserveState = this . resolvePreserveOption ( preserveState , pageResponse )
408
- if ( preserveState && window . history . state ?. rememberedState && pageResponse . component === this . page . component ) {
409
- pageResponse . rememberedState = window . history . state . rememberedState
419
+ if ( preserveState && this . getHistoryState ( ) ?. rememberedState && pageResponse . component === this . page . component ) {
420
+ pageResponse . rememberedState = this . getHistoryState ( ) . rememberedState
410
421
}
411
422
const requestUrl = url
412
423
const responseUrl = hrefToUrl ( pageResponse . url )
@@ -493,14 +504,45 @@ export class Router {
493
504
} )
494
505
}
495
506
507
+ private _pushState ( page : Page ) : void {
508
+ const uniqueId = this . getStateId ( ) ;
509
+ window . sessionStorage . setItem ( uniqueId , JSON . stringify ( page ) ) ;
510
+ window . history . pushState ( { _id : uniqueId } , '' , page . url ) ;
511
+ }
512
+
513
+ protected getAllStates ( ) {
514
+ return JSON . parse ( window . sessionStorage . getItem ( allStateIdsLocalStorageKey ) ?? '[]' ) ;
515
+ }
516
+
517
+ private getStateId ( ) {
518
+ const newId = `inertia_${ crypto . randomUUID ( ) } ` ;
519
+ window . sessionStorage . setItem ( allStateIdsLocalStorageKey , JSON . stringify ( [ ...this . getAllStates ( ) , newId ] ) ) ;
520
+ return newId ;
521
+ }
522
+
523
+ public clearHistory ( ) : void {
524
+ this . getAllStates ( ) . forEach ( ( id ) => {
525
+ window . sessionStorage . removeItem ( id ) ;
526
+ } ) ;
527
+ window . sessionStorage . removeItem ( allStateIdsLocalStorageKey ) ;
528
+ }
529
+
496
530
protected pushState ( page : Page ) : void {
497
531
this . page = page
498
532
if ( isChromeIOS ) {
499
533
// Defer history.pushState to the next event loop tick to prevent timing conflicts.
500
534
// Ensure any previous history.replaceState completes before pushState is executed.
501
- setTimeout ( ( ) => window . history . pushState ( cloneSerializable ( page ) , '' , page . url ) )
535
+ setTimeout ( ( ) => this . _pushState ( page ) ) ;
502
536
} else {
503
- window . history . pushState ( cloneSerializable ( page ) , '' , page . url )
537
+ this . _pushState ( page ) ;
538
+ }
539
+ }
540
+
541
+ private _replaceState ( page : Page ) : void {
542
+ const currentId = window . history . state ?. _id ?? this . getStateId ( ) ;
543
+ window . sessionStorage . setItem ( currentId , JSON . stringify ( page ) ) ;
544
+ if ( ! window . history . state ?. _id ) {
545
+ window . history . replaceState ( { _id : currentId } , '' , page . url ) ;
504
546
}
505
547
}
506
548
@@ -509,25 +551,29 @@ export class Router {
509
551
if ( isChromeIOS ) {
510
552
// Defer history.replaceState to the next event loop tick to prevent timing conflicts.
511
553
// Ensure any previous history.pushState completes before replaceState is executed.
512
- setTimeout ( ( ) => window . history . replaceState ( cloneSerializable ( page ) , '' , page . url ) )
554
+ setTimeout ( ( ) => this . _replaceState ( page ) )
513
555
} else {
514
- window . history . replaceState ( cloneSerializable ( page ) , '' , page . url )
556
+ this . _replaceState ( page )
515
557
}
516
558
}
517
559
518
560
protected handlePopstateEvent ( event : PopStateEvent ) : void {
519
561
if ( event . state !== null ) {
520
- const page = event . state
521
- const visitId = this . createVisitId ( )
522
- Promise . resolve ( this . resolveComponent ( page . component ) ) . then ( ( component ) => {
523
- if ( visitId === this . visitId ) {
524
- this . page = page
525
- this . swapComponent ( { component, page, preserveState : false } ) . then ( ( ) => {
526
- this . restoreScrollPositions ( )
527
- fireNavigateEvent ( page )
528
- } )
529
- }
530
- } )
562
+ const page = this . getHistoryState ( event . state ?. _id ) ;
563
+ if ( page !== null ) {
564
+ const visitId = this . createVisitId ( )
565
+ Promise . resolve ( this . resolveComponent ( page . component ) ) . then ( ( component ) => {
566
+ if ( visitId === this . visitId ) {
567
+ this . page = page
568
+ this . swapComponent ( { component, page, preserveState : false } ) . then ( ( ) => {
569
+ this . restoreScrollPositions ( )
570
+ fireNavigateEvent ( page )
571
+ } )
572
+ }
573
+ } )
574
+ } else {
575
+ this . reload ( ) ;
576
+ }
531
577
} else {
532
578
const url = hrefToUrl ( this . page . url )
533
579
url . hash = window . location . hash
@@ -592,7 +638,7 @@ export class Router {
592
638
return
593
639
}
594
640
595
- return window . history . state ?. rememberedState ?. [ key ]
641
+ return this . getHistoryState ( ) ?. rememberedState ?. [ key ]
596
642
}
597
643
598
644
public on < TEventName extends GlobalEventNames > (
0 commit comments