@@ -73,11 +73,8 @@ export function create_client({ target, base, trailing_slash }) {
73
73
/** @type {Array<((url: URL) => boolean)> } */
74
74
const invalidated = [ ] ;
75
75
76
- /** @type {{id: string | null, promise: Promise<import('./types').NavigationResult | undefined> | null} } */
77
- const load_cache = {
78
- id : null ,
79
- promise : null
80
- } ;
76
+ /** @type {{id: string, promise: Promise<import('./types').NavigationResult | undefined>} | null } */
77
+ let load_cache = null ;
81
78
82
79
const callbacks = {
83
80
/** @type {Array<(navigation: import('types').Navigation & { cancel: () => void }) => void> } */
@@ -91,18 +88,13 @@ export function create_client({ target, base, trailing_slash }) {
91
88
let current = {
92
89
branch : [ ] ,
93
90
error : null ,
94
- session_id : 0 ,
95
91
// @ts -ignore - we need the initial value to be null
96
92
url : null
97
93
} ;
98
94
99
95
let started = false ;
100
96
let autoscroll = true ;
101
97
let updating = false ;
102
- let session_id = 1 ;
103
-
104
- /** @type {Promise<void> | null } */
105
- let invalidating = null ;
106
98
let force_invalidation = false ;
107
99
108
100
/** @type {import('svelte').SvelteComponent } */
@@ -140,31 +132,38 @@ export function create_client({ target, base, trailing_slash }) {
140
132
/** @type {{} } */
141
133
let token ;
142
134
143
- function invalidate ( ) {
144
- if ( ! invalidating ) {
145
- const url = new URL ( location . href ) ;
146
-
147
- invalidating = Promise . resolve ( ) . then ( async ( ) => {
148
- const intent = get_navigation_intent ( url , true ) ;
149
- await update ( intent , url , [ ] ) ;
150
-
151
- invalidating = null ;
152
- force_invalidation = false ;
153
- } ) ;
154
- }
155
-
156
- return invalidating ;
135
+ /** @type {Promise<void> | null } */
136
+ let pending_invalidate ;
137
+
138
+ async function invalidate ( ) {
139
+ // Accept all invalidations as they come, don't swallow any while another invalidation
140
+ // is running because subsequent invalidations may make earlier ones outdated,
141
+ // but batch multiple synchronous invalidations.
142
+ pending_invalidate = pending_invalidate || Promise . resolve ( ) ;
143
+ await pending_invalidate ;
144
+ pending_invalidate = null ;
145
+
146
+ const url = new URL ( location . href ) ;
147
+ const intent = get_navigation_intent ( url , true ) ;
148
+ // Clear prefetch, it might be affected by the invalidation.
149
+ // Also solves an edge case where a prefetch is triggered, the navigation for it
150
+ // was then triggered and is still running while the invalidation kicks in,
151
+ // at which point the invalidation should take over and "win".
152
+ load_cache = null ;
153
+ await update ( intent , url , [ ] ) ;
157
154
}
158
155
159
156
/**
160
157
* @param {string | URL } url
161
158
* @param {{ noscroll?: boolean; replaceState?: boolean; keepfocus?: boolean; state?: any } } opts
162
159
* @param {string[] } redirect_chain
160
+ * @param {{} } [nav_token]
163
161
*/
164
162
async function goto (
165
163
url ,
166
164
{ noscroll = false , replaceState = false , keepfocus = false , state = { } } ,
167
- redirect_chain
165
+ redirect_chain ,
166
+ nav_token
168
167
) {
169
168
if ( typeof url === 'string' ) {
170
169
url = new URL ( url , get_base_uri ( document ) ) ;
@@ -179,6 +178,7 @@ export function create_client({ target, base, trailing_slash }) {
179
178
state,
180
179
replaceState
181
180
} ,
181
+ nav_token,
182
182
accepted : ( ) => { } ,
183
183
blocked : ( ) => { } ,
184
184
type : 'goto'
@@ -193,8 +193,7 @@ export function create_client({ target, base, trailing_slash }) {
193
193
throw new Error ( 'Attempted to prefetch a URL that does not belong to this app' ) ;
194
194
}
195
195
196
- load_cache . promise = load_route ( intent ) ;
197
- load_cache . id = intent . id ;
196
+ load_cache = { id : intent . id , promise : load_route ( intent ) } ;
198
197
199
198
return load_cache . promise ;
200
199
}
@@ -205,10 +204,11 @@ export function create_client({ target, base, trailing_slash }) {
205
204
* @param {URL } url
206
205
* @param {string[] } redirect_chain
207
206
* @param {{hash?: string, scroll: { x: number, y: number } | null, keepfocus: boolean, details: { replaceState: boolean, state: any } | null} } [opts]
207
+ * @param {{} } [nav_token] To distinguish between different navigation events and determine the latest. Needed for example for redirects to keep the original token
208
208
* @param {() => void } [callback]
209
209
*/
210
- async function update ( intent , url , redirect_chain , opts , callback ) {
211
- const current_token = ( token = { } ) ;
210
+ async function update ( intent , url , redirect_chain , opts , nav_token = { } , callback ) {
211
+ token = nav_token ;
212
212
let navigation_result = intent && ( await load_route ( intent ) ) ;
213
213
214
214
if (
@@ -239,9 +239,7 @@ export function create_client({ target, base, trailing_slash }) {
239
239
url = intent ?. url || url ;
240
240
241
241
// abort if user navigated during update
242
- if ( token !== current_token ) return false ;
243
-
244
- invalidated . length = 0 ;
242
+ if ( token !== nav_token ) return false ;
245
243
246
244
if ( navigation_result . type === 'redirect' ) {
247
245
if ( redirect_chain . length > 10 || redirect_chain . includes ( url . pathname ) ) {
@@ -252,7 +250,12 @@ export function create_client({ target, base, trailing_slash }) {
252
250
routeId : null
253
251
} ) ;
254
252
} else {
255
- goto ( new URL ( navigation_result . location , url ) . href , { } , [ ...redirect_chain , url . pathname ] ) ;
253
+ goto (
254
+ new URL ( navigation_result . location , url ) . href ,
255
+ { } ,
256
+ [ ...redirect_chain , url . pathname ] ,
257
+ nav_token
258
+ ) ;
256
259
return false ;
257
260
}
258
261
} else if ( navigation_result . props ?. page ?. status >= 400 ) {
@@ -262,6 +265,11 @@ export function create_client({ target, base, trailing_slash }) {
262
265
}
263
266
}
264
267
268
+ // reset invalidation only after a finished navigation. If there are redirects or
269
+ // additional invalidations, they should get the same invalidation treatment
270
+ invalidated . length = 0 ;
271
+ force_invalidation = false ;
272
+
265
273
updating = true ;
266
274
267
275
if ( opts && opts . details ) {
@@ -271,6 +279,9 @@ export function create_client({ target, base, trailing_slash }) {
271
279
history [ details . replaceState ? 'replaceState' : 'pushState' ] ( details . state , '' , url ) ;
272
280
}
273
281
282
+ // reset prefetch synchronously after the history state has been set to avoid race conditions
283
+ load_cache = null ;
284
+
274
285
if ( started ) {
275
286
current = navigation_result . state ;
276
287
@@ -334,8 +345,6 @@ export function create_client({ target, base, trailing_slash }) {
334
345
await tick ( ) ;
335
346
}
336
347
337
- load_cache . promise = null ;
338
- load_cache . id = null ;
339
348
autoscroll = true ;
340
349
341
350
if ( navigation_result . props . page ) {
@@ -410,8 +419,7 @@ export function create_client({ target, base, trailing_slash }) {
410
419
params,
411
420
branch,
412
421
error,
413
- route,
414
- session_id
422
+ route
415
423
} ,
416
424
props : {
417
425
components : filtered . map ( ( branch_node ) => branch_node . node . component )
@@ -683,7 +691,7 @@ export function create_client({ target, base, trailing_slash }) {
683
691
* @returns {Promise<import('./types').NavigationResult | undefined> }
684
692
*/
685
693
async function load_route ( { id, invalidating, url, params, route } ) {
686
- if ( load_cache . id === id && load_cache . promise ) {
694
+ if ( load_cache ? .id === id ) {
687
695
return load_cache . promise ;
688
696
}
689
697
@@ -981,6 +989,7 @@ export function create_client({ target, base, trailing_slash }) {
981
989
* } | null;
982
990
* type: import('types').NavigationType;
983
991
* delta?: number;
992
+ * nav_token?: {};
984
993
* accepted: () => void;
985
994
* blocked: () => void;
986
995
* }} opts
@@ -993,6 +1002,7 @@ export function create_client({ target, base, trailing_slash }) {
993
1002
details,
994
1003
type,
995
1004
delta,
1005
+ nav_token,
996
1006
accepted,
997
1007
blocked
998
1008
} ) {
@@ -1050,6 +1060,7 @@ export function create_client({ target, base, trailing_slash }) {
1050
1060
keepfocus,
1051
1061
details
1052
1062
} ,
1063
+ nav_token ,
1053
1064
( ) => {
1054
1065
callbacks . after_navigate . forEach ( ( fn ) => fn ( navigation ) ) ;
1055
1066
stores . navigating . set ( null ) ;
0 commit comments