@sanity/sdk 2.15.0 → 2.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,18 +1,11 @@
1
- // NOTE: vi.mock REMOVED
2
-
3
1
  import {of, Subscription, throwError} from 'rxjs'
4
- import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest' // Removed Mock type
2
+ import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest'
5
3
 
6
4
  import {createSanityInstance} from '../store/createSanityInstance'
7
5
  import {createStoreState} from '../store/createStoreState'
8
6
  import {AuthStateType} from './authStateType'
9
7
  import {type AuthState, authStore} from './authStore'
10
- import {
11
- getLastRefreshTime,
12
- getNextRefreshDelay,
13
- refreshStampedToken,
14
- setLastRefreshTime,
15
- } from './refreshStampedToken'
8
+ import {refreshStampedToken} from './refreshStampedToken'
16
9
  import {createLoggedInAuthState} from './utils'
17
10
 
18
11
  // Mock logger to prevent actual logging during tests
@@ -30,23 +23,16 @@ vi.mock('../utils/logger', async (importOriginal) => {
30
23
  }
31
24
  })
32
25
 
33
- // Type definitions for Web Locks (can be kept if needed for context)
34
- // ... (Lock, LockOptions, LockGrantedCallback types)
35
-
36
26
  describe('refreshStampedToken', () => {
37
27
  let mockStorage: Storage
38
- let originalNavigator: typeof navigator // Restored
39
28
  let originalDocument: Document
40
29
  let subscriptions: Subscription[]
41
- // mockLocksRequest removed
42
30
 
43
31
  beforeEach(() => {
44
32
  subscriptions = []
45
33
  vi.clearAllMocks()
46
34
  vi.useFakeTimers()
47
35
 
48
- originalNavigator = global.navigator // Restore original navigator setup
49
-
50
36
  // Mock document for visibility API
51
37
  originalDocument = global.document
52
38
  Object.defineProperty(global, 'document', {
@@ -67,49 +53,10 @@ describe('refreshStampedToken', () => {
67
53
  key: vi.fn(),
68
54
  length: 0,
69
55
  }
70
- // Restore a basic, functional mock for navigator.locks
71
- // This mock *will* run the callback, including the infinite loop
72
- // if not handled carefully in tests.
73
- const mockLocks = {
74
- request: vi.fn(
75
- async (
76
- _name: string,
77
- _options: LockOptions | LockGrantedCallback<unknown>,
78
- callback?: LockGrantedCallback<unknown>,
79
- ) => {
80
- const actualCallback = typeof _options === 'function' ? _options : callback
81
- if (!actualCallback) return false
82
- const mockLock: Lock = {name: 'mock-lock', mode: 'exclusive'}
83
- try {
84
- await new Promise((resolve) => setTimeout(resolve, 0))
85
- // CAUTION: This executes the callback provided by acquireTokenRefreshLock
86
- // which contains the infinite loop. Tests need to avoid triggering
87
- // timers indefinitely if this runs.
88
- await actualCallback(mockLock)
89
- // This return is unlikely to be hit if callback loops
90
- return true
91
- } catch (error) {
92
- // eslint-disable-next-line no-console
93
- console.error('Mock lock request failed:', error)
94
- return false
95
- }
96
- },
97
- ),
98
- query: vi.fn(async () => ({held: [], pending: []})),
99
- }
100
- Object.defineProperty(global, 'navigator', {
101
- value: {locks: mockLocks},
102
- writable: true,
103
- })
104
56
  })
105
57
 
106
58
  afterEach(async () => {
107
59
  subscriptions.forEach((sub) => sub.unsubscribe())
108
- // Restore original navigator
109
- Object.defineProperty(global, 'navigator', {
110
- value: originalNavigator,
111
- writable: true,
112
- })
113
60
  // Restore original document
114
61
  Object.defineProperty(global, 'document', {
115
62
  value: originalDocument,
@@ -125,353 +72,222 @@ describe('refreshStampedToken', () => {
125
72
  vi.useRealTimers()
126
73
  })
127
74
 
128
- describe('dashboard context', () => {
129
- it('refreshes the token immediately without using locks', async () => {
130
- // Test setup remains similar, using fake timers
131
- const mockClient = {
132
- observable: {request: vi.fn(() => of({token: 'sk-refreshed-token-st123'}))},
133
- }
134
- const mockClientFactory = vi.fn().mockReturnValue(mockClient)
135
- const instance = createSanityInstance({
136
- projectId: 'p',
137
- dataset: 'd',
138
- auth: {clientFactory: mockClientFactory, storageArea: mockStorage},
139
- })
140
- const initialState = authStore.getInitialState(instance, null)
141
- initialState.authState = {
142
- type: AuthStateType.LOGGED_IN,
143
- token: 'sk-initial-token-st123',
144
- currentUser: null,
145
- }
146
- initialState.dashboardContext = {mode: 'test'}
147
- const state = createStoreState(initialState)
148
-
149
- const subscription = refreshStampedToken({state, instance, key: null})
150
- subscriptions.push(subscription)
151
-
152
- await vi.advanceTimersToNextTimerAsync()
153
-
154
- const finalAuthStateDash = state.get().authState
155
- expect(finalAuthStateDash.type).toBe(AuthStateType.LOGGED_IN)
156
- // Ensure token was updated
157
- if (finalAuthStateDash.type === AuthStateType.LOGGED_IN) {
158
- expect(finalAuthStateDash.token).toBe('sk-refreshed-token-st123')
159
- }
160
- // Verify navigator.locks.request was NOT called
161
- const locksRequest = navigator.locks.request as ReturnType<typeof vi.fn>
162
- expect(locksRequest).not.toHaveBeenCalled()
75
+ it('refreshes the token when the interval timer fires', async () => {
76
+ const mockClient = {
77
+ observable: {request: vi.fn(() => of({token: 'sk-refreshed-token-st123'}))},
78
+ }
79
+ const mockClientFactory = vi.fn().mockReturnValue(mockClient)
80
+ const instance = createSanityInstance({
81
+ projectId: 'p',
82
+ dataset: 'd',
83
+ auth: {clientFactory: mockClientFactory, storageArea: mockStorage},
163
84
  })
85
+ const initialState = authStore.getInitialState(instance, null)
86
+ initialState.authState = {
87
+ type: AuthStateType.LOGGED_IN,
88
+ token: 'sk-initial-token-st123',
89
+ currentUser: null,
90
+ }
91
+ const state = createStoreState(initialState)
92
+
93
+ const subscription = refreshStampedToken({state, instance, key: null})
94
+ subscriptions.push(subscription)
164
95
 
165
- it('does not refresh on visibility change when lastTokenRefresh is recent', async () => {
166
- const mockClient = {
167
- observable: {request: vi.fn(() => of({token: 'sk-refreshed-token-st123'}))},
168
- }
169
- const mockClientFactory = vi.fn().mockReturnValue(mockClient)
170
- const instance = createSanityInstance({
171
- auth: {clientFactory: mockClientFactory, storageArea: mockStorage},
172
- })
173
- const initialState = authStore.getInitialState(instance, null)
174
- initialState.authState = createLoggedInAuthState('sk-initial-token-st123', null)
175
- initialState.dashboardContext = {mode: 'test'}
176
- const state = createStoreState(initialState)
177
-
178
- const subscription = refreshStampedToken({state, instance, key: null})
179
- subscriptions.push(subscription)
180
-
181
- const addEventListenerMock = global.document.addEventListener as ReturnType<typeof vi.fn>
182
- expect(addEventListenerMock).toHaveBeenCalledWith('visibilitychange', expect.any(Function))
183
- const visibilityHandler = addEventListenerMock.mock.calls[0][1] as () => void
184
-
185
- Object.defineProperty(global.document, 'visibilityState', {
186
- value: 'visible',
187
- writable: true,
188
- configurable: true,
189
- })
190
-
191
- visibilityHandler()
192
- await vi.advanceTimersByTimeAsync(100)
193
-
194
- expect(mockClient.observable.request).not.toHaveBeenCalled()
195
- const finalAuthState = state.get().authState
196
- if (finalAuthState.type === AuthStateType.LOGGED_IN) {
197
- expect(finalAuthState.token).toBe('sk-initial-token-st123')
198
- }
96
+ await vi.advanceTimersToNextTimerAsync()
97
+
98
+ const finalAuthState = state.get().authState
99
+ expect(finalAuthState.type).toBe(AuthStateType.LOGGED_IN)
100
+ if (finalAuthState.type === AuthStateType.LOGGED_IN) {
101
+ expect(finalAuthState.token).toBe('sk-refreshed-token-st123')
102
+ }
103
+ })
104
+
105
+ it('writes the refreshed token to storage', async () => {
106
+ const mockClient = {
107
+ observable: {request: vi.fn(() => of({token: 'sk-refreshed-token-st123'}))},
108
+ }
109
+ const mockClientFactory = vi.fn().mockReturnValue(mockClient)
110
+ const instance = createSanityInstance({
111
+ projectId: 'p',
112
+ dataset: 'd',
113
+ auth: {clientFactory: mockClientFactory, storageArea: mockStorage},
199
114
  })
115
+ const initialState = authStore.getInitialState(instance, null)
116
+ initialState.authState = {
117
+ type: AuthStateType.LOGGED_IN,
118
+ token: 'sk-initial-token-st123',
119
+ currentUser: null,
120
+ }
121
+ const state = createStoreState(initialState)
122
+
123
+ const subscription = refreshStampedToken({state, instance, key: null})
124
+ subscriptions.push(subscription)
200
125
 
201
- it('refreshes on visibility change when lastTokenRefresh is stale', async () => {
202
- const REFRESH_INTERVAL = 12 * 60 * 60 * 1000
203
- const mockClient = {
204
- observable: {request: vi.fn(() => of({token: 'sk-refreshed-token-st123'}))},
205
- }
206
- const mockClientFactory = vi.fn().mockReturnValue(mockClient)
207
- const instance = createSanityInstance({
208
- auth: {clientFactory: mockClientFactory, storageArea: mockStorage},
209
- })
210
- const initialState = authStore.getInitialState(instance, null)
211
- const staleTimestamp = Date.now() - REFRESH_INTERVAL - 1000
212
- initialState.authState = {
213
- type: AuthStateType.LOGGED_IN,
214
- token: 'sk-initial-token-st123',
215
- currentUser: null,
216
- lastTokenRefresh: staleTimestamp,
217
- }
218
- initialState.dashboardContext = {mode: 'test'}
219
- const state = createStoreState(initialState)
220
-
221
- const subscription = refreshStampedToken({state, instance, key: null})
222
- subscriptions.push(subscription)
223
-
224
- const addEventListenerMock = global.document.addEventListener as ReturnType<typeof vi.fn>
225
- expect(addEventListenerMock).toHaveBeenCalledWith('visibilitychange', expect.any(Function))
226
- const visibilityHandler = addEventListenerMock.mock.calls[0][1] as () => void
227
-
228
- Object.defineProperty(global.document, 'visibilityState', {
229
- value: 'visible',
230
- writable: true,
231
- configurable: true,
232
- })
233
-
234
- visibilityHandler()
235
- await vi.advanceTimersToNextTimerAsync()
236
-
237
- expect(mockClient.observable.request).toHaveBeenCalled()
238
- const finalAuthState = state.get().authState
239
- if (finalAuthState.type === AuthStateType.LOGGED_IN) {
240
- expect(finalAuthState.token).toBe('sk-refreshed-token-st123')
241
- expect(finalAuthState.lastTokenRefresh).toBeGreaterThan(staleTimestamp)
242
- }
126
+ await vi.advanceTimersToNextTimerAsync()
127
+
128
+ expect(mockClient.observable.request).toHaveBeenCalled()
129
+ expect(mockStorage.setItem).toHaveBeenCalledWith(
130
+ initialState.options.storageKey,
131
+ JSON.stringify({token: 'sk-refreshed-token-st123'}),
132
+ )
133
+ })
134
+
135
+ it('does not refresh on visibility change when lastTokenRefresh is recent', async () => {
136
+ const mockClient = {
137
+ observable: {request: vi.fn(() => of({token: 'sk-refreshed-token-st123'}))},
138
+ }
139
+ const mockClientFactory = vi.fn().mockReturnValue(mockClient)
140
+ const instance = createSanityInstance({
141
+ auth: {clientFactory: mockClientFactory, storageArea: mockStorage},
142
+ })
143
+ const initialState = authStore.getInitialState(instance, null)
144
+ initialState.authState = createLoggedInAuthState('sk-initial-token-st123', null)
145
+ const state = createStoreState(initialState)
146
+
147
+ const subscription = refreshStampedToken({state, instance, key: null})
148
+ subscriptions.push(subscription)
149
+
150
+ const addEventListenerMock = global.document.addEventListener as ReturnType<typeof vi.fn>
151
+ expect(addEventListenerMock).toHaveBeenCalledWith('visibilitychange', expect.any(Function))
152
+ const visibilityHandler = addEventListenerMock.mock.calls[0][1] as () => void
153
+
154
+ Object.defineProperty(global.document, 'visibilityState', {
155
+ value: 'visible',
156
+ writable: true,
157
+ configurable: true,
243
158
  })
244
159
 
245
- it('refreshes on visibility change when lastTokenRefresh is undefined (pre-fix behavior)', async () => {
246
- const mockClient = {
247
- observable: {request: vi.fn(() => of({token: 'sk-refreshed-token-st123'}))},
248
- }
249
- const mockClientFactory = vi.fn().mockReturnValue(mockClient)
250
- const instance = createSanityInstance({
251
- auth: {clientFactory: mockClientFactory, storageArea: mockStorage},
252
- })
253
- const initialState = authStore.getInitialState(instance, null)
254
- initialState.authState = {
255
- type: AuthStateType.LOGGED_IN,
256
- token: 'sk-initial-token-st123',
257
- currentUser: null,
258
- // lastTokenRefresh intentionally omitted to demonstrate the old bug
259
- }
260
- initialState.dashboardContext = {mode: 'test'}
261
- const state = createStoreState(initialState)
262
-
263
- const subscription = refreshStampedToken({state, instance, key: null})
264
- subscriptions.push(subscription)
265
-
266
- const addEventListenerMock = global.document.addEventListener as ReturnType<typeof vi.fn>
267
- const visibilityHandler = addEventListenerMock.mock.calls[0][1] as () => void
268
-
269
- Object.defineProperty(global.document, 'visibilityState', {
270
- value: 'visible',
271
- writable: true,
272
- configurable: true,
273
- })
274
-
275
- visibilityHandler()
276
- await vi.advanceTimersToNextTimerAsync()
277
-
278
- // Without lastTokenRefresh, shouldRefreshToken returns true — this is the bug
279
- expect(mockClient.observable.request).toHaveBeenCalled()
160
+ visibilityHandler()
161
+ await vi.advanceTimersByTimeAsync(100)
162
+
163
+ expect(mockClient.observable.request).not.toHaveBeenCalled()
164
+ const finalAuthState = state.get().authState
165
+ if (finalAuthState.type === AuthStateType.LOGGED_IN) {
166
+ expect(finalAuthState.token).toBe('sk-initial-token-st123')
167
+ }
168
+ })
169
+
170
+ it('refreshes on visibility change when lastTokenRefresh is stale', async () => {
171
+ const REFRESH_INTERVAL = 12 * 60 * 60 * 1000
172
+ const mockClient = {
173
+ observable: {request: vi.fn(() => of({token: 'sk-refreshed-token-st123'}))},
174
+ }
175
+ const mockClientFactory = vi.fn().mockReturnValue(mockClient)
176
+ const instance = createSanityInstance({
177
+ auth: {clientFactory: mockClientFactory, storageArea: mockStorage},
280
178
  })
179
+ const initialState = authStore.getInitialState(instance, null)
180
+ const staleTimestamp = Date.now() - REFRESH_INTERVAL - 1000
181
+ initialState.authState = {
182
+ type: AuthStateType.LOGGED_IN,
183
+ token: 'sk-initial-token-st123',
184
+ currentUser: null,
185
+ lastTokenRefresh: staleTimestamp,
186
+ }
187
+ const state = createStoreState(initialState)
281
188
 
282
- it('does not refresh when tab is not visible', async () => {
283
- // Set visibility to hidden
284
- Object.defineProperty(global, 'document', {
285
- value: {
286
- visibilityState: 'hidden',
287
- addEventListener: vi.fn(),
288
- removeEventListener: vi.fn(),
289
- },
290
- writable: true,
291
- configurable: true,
292
- })
293
-
294
- const mockClient = {
295
- observable: {request: vi.fn(() => of({token: 'sk-refreshed-token-st123'}))},
296
- }
297
- const mockClientFactory = vi.fn().mockReturnValue(mockClient)
298
- const instance = createSanityInstance({
299
- projectId: 'p',
300
- dataset: 'd',
301
- auth: {clientFactory: mockClientFactory, storageArea: mockStorage},
302
- })
303
- const initialState = authStore.getInitialState(instance, null)
304
- initialState.authState = {
305
- type: AuthStateType.LOGGED_IN,
306
- token: 'sk-initial-token-st123',
307
- currentUser: null,
308
- }
309
- initialState.dashboardContext = {mode: 'test'}
310
- const state = createStoreState(initialState)
311
-
312
- const subscription = refreshStampedToken({state, instance, key: null})
313
- subscriptions.push(subscription)
314
-
315
- await vi.advanceTimersToNextTimerAsync()
316
-
317
- // Verify that no refresh occurred
318
- expect(mockClient.observable.request).not.toHaveBeenCalled()
319
- const finalAuthState = state.get().authState
320
- if (finalAuthState.type === AuthStateType.LOGGED_IN) {
321
- expect(finalAuthState.token).toBe('sk-initial-token-st123')
322
- }
189
+ const subscription = refreshStampedToken({state, instance, key: null})
190
+ subscriptions.push(subscription)
191
+
192
+ const addEventListenerMock = global.document.addEventListener as ReturnType<typeof vi.fn>
193
+ expect(addEventListenerMock).toHaveBeenCalledWith('visibilitychange', expect.any(Function))
194
+ const visibilityHandler = addEventListenerMock.mock.calls[0][1] as () => void
195
+
196
+ Object.defineProperty(global.document, 'visibilityState', {
197
+ value: 'visible',
198
+ writable: true,
199
+ configurable: true,
323
200
  })
201
+
202
+ visibilityHandler()
203
+ await vi.advanceTimersToNextTimerAsync()
204
+
205
+ expect(mockClient.observable.request).toHaveBeenCalled()
206
+ const finalAuthState = state.get().authState
207
+ if (finalAuthState.type === AuthStateType.LOGGED_IN) {
208
+ expect(finalAuthState.token).toBe('sk-refreshed-token-st123')
209
+ expect(finalAuthState.lastTokenRefresh).toBeGreaterThan(staleTimestamp)
210
+ }
324
211
  })
325
212
 
326
- describe('non-dashboard context', () => {
327
- // Test is simplified: just ensure it runs without error
328
- it('attempts token refresh coordination when not in dashboard context', async () => {
329
- // Fake timers enabled via beforeEach
330
- const mockClient = {observable: {request: vi.fn()}}
331
- const mockClientFactory = vi.fn().mockReturnValue(mockClient)
332
- const instance = createSanityInstance({
333
- projectId: 'p',
334
- dataset: 'd',
335
- auth: {clientFactory: mockClientFactory, storageArea: mockStorage},
336
- })
337
- const initialState = authStore.getInitialState(instance, null)
338
- initialState.authState = {
339
- type: AuthStateType.LOGGED_IN,
340
- token: 'sk-initial-token-st123',
341
- currentUser: null,
342
- }
343
- const state = createStoreState(initialState)
344
-
345
- let subscription: Subscription | undefined
346
- // We expect this NOT to throw, but accept we can't easily test the lock call or outcome
347
- expect(() => {
348
- subscription = refreshStampedToken({state, instance, key: null})
349
- subscriptions.push(subscription!)
350
- }).not.toThrow()
351
-
352
- // DO NOT advance timers or yield here - focus on immediate observable logic
353
- // We cannot reliably test that failingLocksRequest is called due to async/timer issues,
354
- // but we *can* test the consequence of it resolving to false.
355
-
356
- // VERIFY THE OUTCOME:
357
- // Check client request was NOT made (because filter(hasLock => hasLock) receives false)
358
- expect(mockClient.observable.request).not.toHaveBeenCalled()
359
- // Check state remains unchanged
360
- const finalAuthState = state.get().authState
361
- expect(finalAuthState.type).toBe(AuthStateType.LOGGED_IN)
362
- if (finalAuthState.type === AuthStateType.LOGGED_IN) {
363
- expect(finalAuthState.token).toBe('sk-initial-token-st123')
364
- }
213
+ it('refreshes on visibility change when lastTokenRefresh is undefined (pre-fix behavior)', async () => {
214
+ const mockClient = {
215
+ observable: {request: vi.fn(() => of({token: 'sk-refreshed-token-st123'}))},
216
+ }
217
+ const mockClientFactory = vi.fn().mockReturnValue(mockClient)
218
+ const instance = createSanityInstance({
219
+ auth: {clientFactory: mockClientFactory, storageArea: mockStorage},
365
220
  })
221
+ const initialState = authStore.getInitialState(instance, null)
222
+ initialState.authState = {
223
+ type: AuthStateType.LOGGED_IN,
224
+ token: 'sk-initial-token-st123',
225
+ currentUser: null,
226
+ // lastTokenRefresh intentionally omitted to demonstrate the old bug
227
+ }
228
+ const state = createStoreState(initialState)
229
+
230
+ const subscription = refreshStampedToken({state, instance, key: null})
231
+ subscriptions.push(subscription)
232
+
233
+ const addEventListenerMock = global.document.addEventListener as ReturnType<typeof vi.fn>
234
+ const visibilityHandler = addEventListenerMock.mock.calls[0][1] as () => void
366
235
 
367
- it('skips refresh if lock request returns false', async () => {
368
- // Fake timers enabled via beforeEach
369
- // Mock navigator.locks.request LOCALLY for this test to return false
370
- const originalLocks = navigator.locks
371
- // Use mockResolvedValue: the from(acquireTokenRefreshLock(...)) expects a Promise
372
- const failingLocksRequest = vi.fn().mockResolvedValue(false)
373
- Object.defineProperty(global, 'navigator', {
374
- value: {locks: {...originalLocks, request: failingLocksRequest}},
375
- writable: true,
376
- })
377
-
378
- try {
379
- const mockClient = {
380
- observable: {request: vi.fn(() => of({token: 'sk-refreshed-token-st123'}))},
381
- }
382
- const mockClientFactory = vi.fn().mockReturnValue(mockClient)
383
- const instance = createSanityInstance({
384
- projectId: 'p',
385
- dataset: 'd',
386
- auth: {clientFactory: mockClientFactory, storageArea: mockStorage},
387
- })
388
- const initialState = authStore.getInitialState(instance, null)
389
- initialState.authState = {
390
- type: AuthStateType.LOGGED_IN,
391
- token: 'sk-initial-token-st123',
392
- currentUser: null,
393
- }
394
- const state = createStoreState(initialState)
395
-
396
- const subscription = refreshStampedToken({state, instance, key: null})
397
- subscriptions.push(subscription)
398
-
399
- // DO NOT advance timers or yield here - focus on immediate observable logic
400
- // We cannot reliably test that failingLocksRequest is called due to async/timer issues,
401
- // but we *can* test the consequence of it resolving to false.
402
-
403
- // VERIFY THE OUTCOME:
404
- // Check client request was NOT made (because filter(hasLock => hasLock) receives false)
405
- expect(mockClient.observable.request).not.toHaveBeenCalled()
406
- // Check state remains unchanged
407
- const finalAuthState = state.get().authState
408
- expect(finalAuthState.type).toBe(AuthStateType.LOGGED_IN)
409
- if (finalAuthState.type === AuthStateType.LOGGED_IN) {
410
- expect(finalAuthState.token).toBe('sk-initial-token-st123')
411
- }
412
- } finally {
413
- // Restore original navigator.locks
414
- Object.defineProperty(global, 'navigator', {value: {locks: originalLocks}, writable: true})
415
- }
236
+ Object.defineProperty(global.document, 'visibilityState', {
237
+ value: 'visible',
238
+ writable: true,
239
+ configurable: true,
416
240
  })
241
+
242
+ visibilityHandler()
243
+ await vi.advanceTimersToNextTimerAsync()
244
+
245
+ // Without lastTokenRefresh, shouldRefreshToken returns true — this is the bug
246
+ expect(mockClient.observable.request).toHaveBeenCalled()
417
247
  })
418
248
 
419
- describe('unsupported environments', () => {
420
- it('falls back to immediate refresh if Web Locks API is not supported', async () => {
421
- // Temporarily remove navigator.locks for this test
422
- const originalLocks = navigator.locks
423
- Object.defineProperty(global.navigator, 'locks', {
424
- value: undefined,
425
- writable: true,
426
- })
427
-
428
- try {
429
- const mockClient = {
430
- observable: {request: vi.fn(() => of({token: 'sk-refreshed-immediately-st123'}))},
431
- }
432
- const mockClientFactory = vi.fn().mockReturnValue(mockClient)
433
- const instance = createSanityInstance({
434
- projectId: 'p',
435
- dataset: 'd',
436
- auth: {clientFactory: mockClientFactory, storageArea: mockStorage},
437
- })
438
- const initialState = authStore.getInitialState(instance, null)
439
- initialState.authState = {
440
- type: AuthStateType.LOGGED_IN,
441
- token: 'sk-initial-token-st123',
442
- currentUser: null,
443
- }
444
- const state = createStoreState(initialState)
445
-
446
- const subscription = refreshStampedToken({state, instance, key: null})
447
- subscriptions.push(subscription)
448
-
449
- // Advance timers to allow the async `performRefresh` to execute
450
- await vi.advanceTimersToNextTimerAsync()
451
-
452
- // Verify the refresh was performed and state was updated
453
- expect(mockClient.observable.request).toHaveBeenCalled()
454
- const finalAuthState = state.get().authState
455
- expect(finalAuthState.type).toBe(AuthStateType.LOGGED_IN)
456
- if (finalAuthState.type === AuthStateType.LOGGED_IN) {
457
- expect(finalAuthState.token).toBe('sk-refreshed-immediately-st123')
458
- }
459
- // Verify token was set in storage
460
- expect(mockStorage.setItem).toHaveBeenCalledWith(
461
- initialState.options.storageKey,
462
- JSON.stringify({token: 'sk-refreshed-immediately-st123'}),
463
- )
464
- } finally {
465
- // Restore navigator.locks
466
- Object.defineProperty(global.navigator, 'locks', {
467
- value: originalLocks,
468
- writable: true,
469
- })
470
- }
249
+ it('does not refresh when tab is not visible', async () => {
250
+ // Set visibility to hidden
251
+ Object.defineProperty(global, 'document', {
252
+ value: {
253
+ visibilityState: 'hidden',
254
+ addEventListener: vi.fn(),
255
+ removeEventListener: vi.fn(),
256
+ },
257
+ writable: true,
258
+ configurable: true,
259
+ })
260
+
261
+ const mockClient = {
262
+ observable: {request: vi.fn(() => of({token: 'sk-refreshed-token-st123'}))},
263
+ }
264
+ const mockClientFactory = vi.fn().mockReturnValue(mockClient)
265
+ const instance = createSanityInstance({
266
+ projectId: 'p',
267
+ dataset: 'd',
268
+ auth: {clientFactory: mockClientFactory, storageArea: mockStorage},
471
269
  })
270
+ const initialState = authStore.getInitialState(instance, null)
271
+ initialState.authState = {
272
+ type: AuthStateType.LOGGED_IN,
273
+ token: 'sk-initial-token-st123',
274
+ currentUser: null,
275
+ }
276
+ const state = createStoreState(initialState)
277
+
278
+ const subscription = refreshStampedToken({state, instance, key: null})
279
+ subscriptions.push(subscription)
280
+
281
+ await vi.advanceTimersToNextTimerAsync()
282
+
283
+ // Verify that no refresh occurred
284
+ expect(mockClient.observable.request).not.toHaveBeenCalled()
285
+ const finalAuthState = state.get().authState
286
+ if (finalAuthState.type === AuthStateType.LOGGED_IN) {
287
+ expect(finalAuthState.token).toBe('sk-initial-token-st123')
288
+ }
472
289
  })
473
290
 
474
- // Restore other tests to their simpler form
475
291
  it('sets an error state when token refresh fails', async () => {
476
292
  const error = new Error('Refresh failed')
477
293
  const mockClient = {observable: {request: vi.fn(() => throwError(() => error))}}
@@ -487,7 +303,6 @@ describe('refreshStampedToken', () => {
487
303
  token: 'sk-initial-token-st123',
488
304
  currentUser: null,
489
305
  }
490
- initialState.dashboardContext = {mode: 'test'}
491
306
  const state = createStoreState(initialState)
492
307
 
493
308
  const subscription = refreshStampedToken({state, instance, key: null})
@@ -523,8 +338,6 @@ describe('refreshStampedToken', () => {
523
338
  await vi.advanceTimersByTimeAsync(0)
524
339
 
525
340
  expect(mockClientFactory).not.toHaveBeenCalled()
526
- const locksRequest = navigator.locks.request as ReturnType<typeof vi.fn>
527
- expect(locksRequest).not.toHaveBeenCalled()
528
341
  expect(state.get().authState.type).toBe(AuthStateType.LOGGED_OUT)
529
342
  })
530
343
 
@@ -550,8 +363,6 @@ describe('refreshStampedToken', () => {
550
363
  await vi.advanceTimersByTimeAsync(0)
551
364
 
552
365
  expect(mockClient.observable.request).not.toHaveBeenCalled()
553
- const locksRequest = navigator.locks.request as ReturnType<typeof vi.fn>
554
- expect(locksRequest).not.toHaveBeenCalled()
555
366
  // Add type guard before accessing token property
556
367
  const finalAuthState = state.get().authState
557
368
  expect(finalAuthState.type).toBe(AuthStateType.LOGGED_IN)
@@ -560,92 +371,3 @@ describe('refreshStampedToken', () => {
560
371
  }
561
372
  })
562
373
  })
563
-
564
- describe('time-based refresh helpers', () => {
565
- let mockStorage: Storage
566
- const storageKey = 'my-test-key'
567
-
568
- beforeEach(() => {
569
- mockStorage = {
570
- getItem: vi.fn(),
571
- setItem: vi.fn(),
572
- removeItem: vi.fn(),
573
- clear: vi.fn(),
574
- key: vi.fn(),
575
- length: 0,
576
- }
577
- vi.useFakeTimers()
578
- })
579
-
580
- afterEach(() => {
581
- vi.useRealTimers()
582
- })
583
-
584
- describe('getLastRefreshTime', () => {
585
- it('returns 0 if storage is undefined', () => {
586
- expect(getLastRefreshTime(undefined, storageKey)).toBe(0)
587
- })
588
-
589
- it('returns 0 if item is not in storage', () => {
590
- vi.spyOn(mockStorage, 'getItem').mockReturnValue(null)
591
- expect(getLastRefreshTime(mockStorage, storageKey)).toBe(0)
592
- expect(mockStorage.getItem).toHaveBeenCalledWith(`${storageKey}_last_refresh`)
593
- })
594
-
595
- it('returns the parsed timestamp from storage', () => {
596
- const now = Date.now()
597
- vi.spyOn(mockStorage, 'getItem').mockReturnValue(now.toString())
598
- expect(getLastRefreshTime(mockStorage, storageKey)).toBe(now)
599
- })
600
-
601
- it('returns 0 if stored data is malformed', () => {
602
- vi.spyOn(mockStorage, 'getItem').mockReturnValue('not a number')
603
- expect(getLastRefreshTime(mockStorage, storageKey)).toBe(0)
604
- })
605
-
606
- it('returns 0 on storage access error', () => {
607
- vi.spyOn(mockStorage, 'getItem').mockImplementation(() => {
608
- throw new Error('Storage access failed')
609
- })
610
- expect(getLastRefreshTime(mockStorage, storageKey)).toBe(0)
611
- })
612
- })
613
-
614
- describe('setLastRefreshTime', () => {
615
- it('sets the current timestamp in storage', () => {
616
- const now = Date.now()
617
- setLastRefreshTime(mockStorage, storageKey)
618
- expect(mockStorage.setItem).toHaveBeenCalledWith(`${storageKey}_last_refresh`, now.toString())
619
- })
620
-
621
- it('does not throw on storage access error', () => {
622
- vi.spyOn(mockStorage, 'setItem').mockImplementation(() => {
623
- throw new Error('Storage access failed')
624
- })
625
- expect(() => setLastRefreshTime(mockStorage, storageKey)).not.toThrow()
626
- })
627
- })
628
-
629
- describe('getNextRefreshDelay', () => {
630
- const REFRESH_INTERVAL = 12 * 60 * 60 * 1000
631
-
632
- it('returns 0 if last refresh time is not available', () => {
633
- vi.spyOn(mockStorage, 'getItem').mockReturnValue(null)
634
- expect(getNextRefreshDelay(mockStorage, storageKey)).toBe(0)
635
- })
636
-
637
- it('returns the remaining time until the next refresh', () => {
638
- const lastRefresh = Date.now() - 10000 // 10 seconds ago
639
- vi.spyOn(mockStorage, 'getItem').mockReturnValue(lastRefresh.toString())
640
-
641
- const delay = getNextRefreshDelay(mockStorage, storageKey)
642
- expect(delay).toBeCloseTo(REFRESH_INTERVAL - 10000, -2)
643
- })
644
-
645
- it('returns 0 if the refresh interval has passed', () => {
646
- const lastRefresh = Date.now() - REFRESH_INTERVAL - 5000 // 5 seconds past due
647
- vi.spyOn(mockStorage, 'getItem').mockReturnValue(lastRefresh.toString())
648
- expect(getNextRefreshDelay(mockStorage, storageKey)).toBe(0)
649
- })
650
- })
651
- })