@sanity/sdk 0.0.0-chore-react-18-compat.1 → 0.0.0-chore-react-18-compat.3

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.
Files changed (134) hide show
  1. package/dist/index.d.ts +441 -322
  2. package/dist/index.js +1685 -1481
  3. package/dist/index.js.map +1 -1
  4. package/package.json +13 -15
  5. package/src/_exports/index.ts +32 -30
  6. package/src/auth/authStore.test.ts +149 -104
  7. package/src/auth/authStore.ts +51 -100
  8. package/src/auth/handleAuthCallback.test.ts +67 -34
  9. package/src/auth/handleAuthCallback.ts +8 -7
  10. package/src/auth/logout.test.ts +61 -29
  11. package/src/auth/logout.ts +26 -28
  12. package/src/auth/refreshStampedToken.test.ts +197 -91
  13. package/src/auth/refreshStampedToken.ts +170 -59
  14. package/src/auth/subscribeToStateAndFetchCurrentUser.test.ts +5 -5
  15. package/src/auth/subscribeToStateAndFetchCurrentUser.ts +45 -47
  16. package/src/auth/subscribeToStorageEventsAndSetToken.test.ts +4 -5
  17. package/src/auth/subscribeToStorageEventsAndSetToken.ts +22 -24
  18. package/src/client/clientStore.test.ts +131 -67
  19. package/src/client/clientStore.ts +117 -116
  20. package/src/comlink/controller/actions/destroyController.test.ts +38 -13
  21. package/src/comlink/controller/actions/destroyController.ts +11 -15
  22. package/src/comlink/controller/actions/getOrCreateChannel.test.ts +56 -27
  23. package/src/comlink/controller/actions/getOrCreateChannel.ts +37 -35
  24. package/src/comlink/controller/actions/getOrCreateController.test.ts +27 -16
  25. package/src/comlink/controller/actions/getOrCreateController.ts +23 -22
  26. package/src/comlink/controller/actions/releaseChannel.test.ts +37 -13
  27. package/src/comlink/controller/actions/releaseChannel.ts +22 -21
  28. package/src/comlink/controller/comlinkControllerStore.test.ts +65 -36
  29. package/src/comlink/controller/comlinkControllerStore.ts +44 -5
  30. package/src/comlink/node/actions/getOrCreateNode.test.ts +31 -15
  31. package/src/comlink/node/actions/getOrCreateNode.ts +30 -29
  32. package/src/comlink/node/actions/releaseNode.test.ts +75 -55
  33. package/src/comlink/node/actions/releaseNode.ts +19 -21
  34. package/src/comlink/node/comlinkNodeStore.test.ts +6 -11
  35. package/src/comlink/node/comlinkNodeStore.ts +22 -5
  36. package/src/config/authConfig.ts +79 -0
  37. package/src/config/sanityConfig.ts +48 -0
  38. package/src/datasets/datasets.test.ts +2 -2
  39. package/src/datasets/datasets.ts +18 -5
  40. package/src/document/actions.test.ts +22 -10
  41. package/src/document/actions.ts +44 -56
  42. package/src/document/applyDocumentActions.test.ts +96 -36
  43. package/src/document/applyDocumentActions.ts +140 -99
  44. package/src/document/documentStore.test.ts +103 -155
  45. package/src/document/documentStore.ts +247 -238
  46. package/src/document/listen.ts +56 -55
  47. package/src/document/patchOperations.ts +0 -43
  48. package/src/document/permissions.test.ts +25 -12
  49. package/src/document/permissions.ts +11 -4
  50. package/src/document/processActions.test.ts +41 -8
  51. package/src/document/reducers.test.ts +87 -16
  52. package/src/document/reducers.ts +2 -2
  53. package/src/document/sharedListener.test.ts +34 -16
  54. package/src/document/sharedListener.ts +33 -11
  55. package/src/preview/getPreviewState.test.ts +40 -39
  56. package/src/preview/getPreviewState.ts +68 -56
  57. package/src/preview/previewConstants.ts +43 -0
  58. package/src/preview/previewQuery.test.ts +1 -1
  59. package/src/preview/previewQuery.ts +4 -5
  60. package/src/preview/previewStore.test.ts +13 -58
  61. package/src/preview/previewStore.ts +7 -21
  62. package/src/preview/resolvePreview.test.ts +33 -104
  63. package/src/preview/resolvePreview.ts +11 -21
  64. package/src/preview/subscribeToStateAndFetchBatches.test.ts +96 -97
  65. package/src/preview/subscribeToStateAndFetchBatches.ts +85 -81
  66. package/src/preview/util.ts +1 -0
  67. package/src/project/project.test.ts +3 -3
  68. package/src/project/project.ts +28 -5
  69. package/src/projection/getProjectionState.test.ts +188 -72
  70. package/src/projection/getProjectionState.ts +92 -62
  71. package/src/projection/projectionQuery.test.ts +114 -12
  72. package/src/projection/projectionQuery.ts +75 -32
  73. package/src/projection/projectionStore.test.ts +13 -51
  74. package/src/projection/projectionStore.ts +6 -43
  75. package/src/projection/resolveProjection.test.ts +32 -127
  76. package/src/projection/resolveProjection.ts +16 -28
  77. package/src/projection/subscribeToStateAndFetchBatches.test.ts +203 -116
  78. package/src/projection/subscribeToStateAndFetchBatches.ts +140 -85
  79. package/src/projection/types.ts +50 -0
  80. package/src/projection/util.ts +3 -1
  81. package/src/projects/projects.test.ts +13 -4
  82. package/src/projects/projects.ts +6 -1
  83. package/src/query/queryStore.test.ts +10 -47
  84. package/src/query/queryStore.ts +151 -133
  85. package/src/query/queryStoreConstants.ts +2 -0
  86. package/src/store/createActionBinder.test.ts +153 -0
  87. package/src/store/createActionBinder.ts +176 -0
  88. package/src/store/createSanityInstance.test.ts +84 -0
  89. package/src/store/createSanityInstance.ts +124 -0
  90. package/src/store/createStateSourceAction.test.ts +196 -0
  91. package/src/store/createStateSourceAction.ts +260 -0
  92. package/src/store/createStoreInstance.test.ts +81 -0
  93. package/src/store/createStoreInstance.ts +80 -0
  94. package/src/store/createStoreState.test.ts +85 -0
  95. package/src/store/createStoreState.ts +92 -0
  96. package/src/store/defineStore.test.ts +18 -0
  97. package/src/store/defineStore.ts +81 -0
  98. package/src/users/reducers.test.ts +318 -0
  99. package/src/users/reducers.ts +88 -0
  100. package/src/users/types.ts +46 -4
  101. package/src/users/usersConstants.ts +4 -0
  102. package/src/users/usersStore.test.ts +350 -223
  103. package/src/users/usersStore.ts +285 -149
  104. package/src/utils/createFetcherStore.test.ts +6 -7
  105. package/src/utils/createFetcherStore.ts +150 -153
  106. package/src/utils/createGroqSearchFilter.test.ts +75 -0
  107. package/src/utils/createGroqSearchFilter.ts +85 -0
  108. package/src/{common/util.test.ts → utils/hashString.test.ts} +1 -1
  109. package/dist/index.cjs +0 -4888
  110. package/dist/index.cjs.map +0 -1
  111. package/dist/index.d.cts +0 -2121
  112. package/src/auth/fetchLoginUrls.test.ts +0 -163
  113. package/src/auth/fetchLoginUrls.ts +0 -74
  114. package/src/common/createLiveEventSubscriber.test.ts +0 -121
  115. package/src/common/createLiveEventSubscriber.ts +0 -55
  116. package/src/common/types.ts +0 -4
  117. package/src/instance/identity.test.ts +0 -46
  118. package/src/instance/identity.ts +0 -29
  119. package/src/instance/sanityInstance.test.ts +0 -77
  120. package/src/instance/sanityInstance.ts +0 -57
  121. package/src/instance/types.ts +0 -37
  122. package/src/preview/getPreviewProjection.ts +0 -45
  123. package/src/resources/README.md +0 -370
  124. package/src/resources/createAction.test.ts +0 -101
  125. package/src/resources/createAction.ts +0 -44
  126. package/src/resources/createResource.test.ts +0 -112
  127. package/src/resources/createResource.ts +0 -102
  128. package/src/resources/createStateSourceAction.test.ts +0 -114
  129. package/src/resources/createStateSourceAction.ts +0 -83
  130. package/src/resources/createStore.test.ts +0 -67
  131. package/src/resources/createStore.ts +0 -46
  132. package/src/store/createStore.test.ts +0 -108
  133. package/src/store/createStore.ts +0 -106
  134. /package/src/{common/util.ts → utils/hashString.ts} +0 -0
@@ -2,8 +2,10 @@ import {type ClientConfig, createClient, type SanityClient} from '@sanity/client
2
2
  import {type CurrentUser} from '@sanity/types'
3
3
  import {type Subscription} from 'rxjs'
4
4
 
5
- import {createResource} from '../resources/createResource'
6
- import {createStateSourceAction} from '../resources/createStateSourceAction'
5
+ import {type AuthConfig, type AuthProvider} from '../config/authConfig'
6
+ import {bindActionGlobally} from '../store/createActionBinder'
7
+ import {createStateSourceAction} from '../store/createStateSourceAction'
8
+ import {defineStore} from '../store/defineStore'
7
9
  import {AuthStateType} from './authStateType'
8
10
  import {refreshStampedToken} from './refreshStampedToken'
9
11
  import {subscribeToStateAndFetchCurrentUser} from './subscribeToStateAndFetchCurrentUser'
@@ -45,84 +47,6 @@ export type LoggingInAuthState = {type: AuthStateType.LOGGING_IN; isExchangingTo
45
47
  */
46
48
  export type ErrorAuthState = {type: AuthStateType.ERROR; error: unknown}
47
49
 
48
- /**
49
- * Configuration for an authentication provider
50
- * @public
51
- */
52
- export interface AuthProvider {
53
- /**
54
- * Unique identifier for the auth provider (e.g., 'google', 'github')
55
- */
56
- name: string
57
-
58
- /**
59
- * Display name for the auth provider in the UI
60
- */
61
- title: string
62
-
63
- /**
64
- * Complete authentication URL including callback and token parameters
65
- */
66
- url: string
67
-
68
- /**
69
- * Optional URL for direct sign-up flow
70
- */
71
- signUpUrl?: string
72
- }
73
-
74
- /**
75
- * Configuration options for creating an auth store.
76
- *
77
- * @public
78
- */
79
- export interface AuthConfig {
80
- /**
81
- * The initial location href to use when handling auth callbacks.
82
- * Defaults to the current window location if available.
83
- */
84
- initialLocationHref?: string
85
-
86
- /**
87
- * Factory function to create a SanityClient instance.
88
- * Defaults to the standard Sanity client factory if not provided.
89
- */
90
- clientFactory?: (config: ClientConfig) => SanityClient
91
-
92
- /**
93
- * Custom authentication providers to use instead of or in addition to the default ones.
94
- * Can be an array of providers or a function that takes the default providers and returns
95
- * a modified array or a Promise resolving to one.
96
- */
97
- providers?: AuthProvider[] | ((prev: AuthProvider[]) => AuthProvider[] | Promise<AuthProvider[]>)
98
-
99
- /**
100
- * The API hostname for requests. Usually leave this undefined, but it can be set
101
- * if using a custom domain or CNAME for the API endpoint.
102
- */
103
- apiHost?: string
104
-
105
- /**
106
- * Storage implementation to persist authentication state.
107
- * Defaults to `localStorage` if available.
108
- */
109
- storageArea?: Storage
110
-
111
- /**
112
- * A callback URL for your application.
113
- * If none is provided, the auth API will redirect back to the current location (`location.href`).
114
- * When handling callbacks, this URL's pathname is checked to ensure it matches the callback.
115
- */
116
- callbackUrl?: string
117
-
118
- /**
119
- * A static authentication token to use instead of handling the OAuth flow.
120
- * When provided, the auth store will remain in a logged-in state with this token,
121
- * ignoring any storage or callback handling.
122
- */
123
- token?: string
124
- }
125
-
126
50
  /**
127
51
  * Represents the various states the authentication can be in.
128
52
  *
@@ -149,13 +73,14 @@ export interface AuthStoreState {
149
73
  storageKey: string
150
74
  storageArea: Storage | undefined
151
75
  apiHost: string | undefined
76
+ loginUrl: string
152
77
  callbackUrl: string | undefined
153
78
  providedToken: string | undefined
154
79
  }
155
80
  dashboardContext?: DashboardContext
156
81
  }
157
82
 
158
- export const authStore = createResource<AuthStoreState>({
83
+ export const authStore = defineStore<AuthStoreState>({
159
84
  name: 'Auth',
160
85
  getInitialState(instance) {
161
86
  const {
@@ -170,6 +95,20 @@ export const authStore = createResource<AuthStoreState>({
170
95
 
171
96
  const storageKey = `__sanity_auth_token`
172
97
 
98
+ // This login URL will only be used for local development
99
+ let loginDomain = 'https://www.sanity.io'
100
+ try {
101
+ if (apiHost && new URL(apiHost).hostname.endsWith('.sanity.work')) {
102
+ loginDomain = 'https://www.sanity.work'
103
+ }
104
+ } catch {
105
+ /* empty */
106
+ }
107
+ const loginUrl = new URL('/login', loginDomain)
108
+ loginUrl.searchParams.set('origin', initialLocationHref)
109
+ loginUrl.searchParams.set('type', 'stampedToken') // Token must be stamped to have an sid passed back
110
+ loginUrl.searchParams.set('withSid', 'true')
111
+
173
112
  let authState: AuthState
174
113
 
175
114
  const token = getTokenFromStorage(storageArea, storageKey)
@@ -188,6 +127,7 @@ export const authStore = createResource<AuthStoreState>({
188
127
  authState,
189
128
  options: {
190
129
  apiHost,
130
+ loginUrl: loginUrl.toString(),
191
131
  callbackUrl,
192
132
  customProviders,
193
133
  providedToken,
@@ -198,22 +138,23 @@ export const authStore = createResource<AuthStoreState>({
198
138
  },
199
139
  }
200
140
  },
201
- initialize() {
202
- const stateSubscription = subscribeToStateAndFetchCurrentUser(this)
203
- let storageEventsSubscription: Subscription | undefined
204
- if (this.state.get().options?.storageArea) {
205
- storageEventsSubscription = subscribeToStorageEventsAndSetToken(this)
141
+ initialize(context) {
142
+ const subscriptions: Subscription[] = []
143
+ subscriptions.push(subscribeToStateAndFetchCurrentUser(context))
144
+
145
+ if (context.state.get().options?.storageArea) {
146
+ subscriptions.push(subscribeToStorageEventsAndSetToken(context))
206
147
  }
207
- let refreshStampedTokenSubscription: Subscription | undefined
148
+
208
149
  if (!tokenRefresherRunning) {
209
150
  tokenRefresherRunning = true
210
- refreshStampedTokenSubscription = refreshStampedToken(this)
151
+ subscriptions.push(refreshStampedToken(context))
211
152
  }
212
153
 
213
154
  return () => {
214
- stateSubscription.unsubscribe()
215
- storageEventsSubscription?.unsubscribe()
216
- refreshStampedTokenSubscription?.unsubscribe()
155
+ for (const subscription of subscriptions) {
156
+ subscription.unsubscribe()
157
+ }
217
158
  }
218
159
  },
219
160
  })
@@ -221,33 +162,43 @@ export const authStore = createResource<AuthStoreState>({
221
162
  /**
222
163
  * @public
223
164
  */
224
- export const getCurrentUserState = createStateSourceAction(authStore, ({authState}) =>
225
- authState.type === AuthStateType.LOGGED_IN ? authState.currentUser : null,
165
+ export const getCurrentUserState = bindActionGlobally(
166
+ authStore,
167
+ createStateSourceAction(({state: {authState}}) =>
168
+ authState.type === AuthStateType.LOGGED_IN ? authState.currentUser : null,
169
+ ),
226
170
  )
227
171
 
228
172
  /**
229
173
  * @public
230
174
  */
231
- export const getTokenState = createStateSourceAction(authStore, ({authState}) =>
232
- authState.type === AuthStateType.LOGGED_IN ? authState.token : null,
175
+ export const getTokenState = bindActionGlobally(
176
+ authStore,
177
+ createStateSourceAction(({state: {authState}}) =>
178
+ authState.type === AuthStateType.LOGGED_IN ? authState.token : null,
179
+ ),
233
180
  )
181
+
234
182
  /**
235
183
  * @public
236
184
  */
237
- export const getLoginUrlsState = createStateSourceAction(
185
+ export const getLoginUrlState = bindActionGlobally(
238
186
  authStore,
239
- ({providers}) => providers ?? null,
187
+ createStateSourceAction(({state: {options}}) => options.loginUrl),
240
188
  )
241
189
 
242
190
  /**
243
191
  * @public
244
192
  */
245
- export const getAuthState = createStateSourceAction(authStore, ({authState}) => authState)
193
+ export const getAuthState = bindActionGlobally(
194
+ authStore,
195
+ createStateSourceAction(({state: {authState}}) => authState),
196
+ )
246
197
 
247
198
  /**
248
199
  * @public
249
200
  */
250
- export const getDashboardOrganizationId = createStateSourceAction(
201
+ export const getDashboardOrganizationId = bindActionGlobally(
251
202
  authStore,
252
- ({dashboardContext}) => dashboardContext?.orgId,
203
+ createStateSourceAction(({state: {dashboardContext}}) => dashboardContext?.orgId),
253
204
  )
@@ -1,10 +1,12 @@
1
+ import {NEVER} from 'rxjs'
1
2
  import {beforeEach, describe, it} from 'vitest'
2
3
 
3
- import {createSanityInstance} from '../instance/sanityInstance'
4
- import {createResourceState} from '../resources/createResource'
4
+ import {createSanityInstance, type SanityInstance} from '../store/createSanityInstance'
5
5
  import {AuthStateType} from './authStateType'
6
- import {authStore} from './authStore'
6
+ import {getAuthState} from './authStore'
7
7
  import {handleAuthCallback} from './handleAuthCallback'
8
+ import {subscribeToStateAndFetchCurrentUser} from './subscribeToStateAndFetchCurrentUser'
9
+ import {subscribeToStorageEventsAndSetToken} from './subscribeToStorageEventsAndSetToken'
8
10
  import {getAuthCode, getTokenFromStorage} from './utils'
9
11
 
10
12
  vi.mock('./utils', async (importOriginal) => {
@@ -12,20 +14,31 @@ vi.mock('./utils', async (importOriginal) => {
12
14
  return {...original, getTokenFromStorage: vi.fn(), getAuthCode: vi.fn()}
13
15
  })
14
16
 
15
- describe('handleAuthCallback', () => {
17
+ vi.mock('./subscribeToStateAndFetchCurrentUser')
18
+ vi.mock('./subscribeToStorageEventsAndSetToken')
19
+
20
+ let instance: SanityInstance | undefined
21
+
22
+ describe('handleCallback', () => {
16
23
  beforeEach(() => {
17
24
  vi.clearAllMocks()
25
+ vi.mocked(subscribeToStateAndFetchCurrentUser).mockImplementation(() => NEVER.subscribe())
26
+ vi.mocked(subscribeToStorageEventsAndSetToken).mockImplementation(() => NEVER.subscribe())
27
+ })
28
+
29
+ afterEach(() => {
30
+ instance?.dispose()
18
31
  })
19
32
 
20
33
  it('trades the auth code for a token, sets the state to logged in, and sets the token in storage', async () => {
21
- const mockRequest = vi.fn().mockResolvedValue({token: 'new-token'})
22
- const clientFactory = vi.fn().mockReturnValue({request: mockRequest})
34
+ const request = vi.fn().mockResolvedValue({token: 'new-token'})
35
+ const clientFactory = vi.fn().mockReturnValue({request})
23
36
  const setItem = vi.fn()
24
37
  vi.mocked(getTokenFromStorage).mockReturnValue(null)
25
38
  const authCode = 'auth-code'
26
39
  vi.mocked(getAuthCode).mockReturnValue(authCode)
27
40
 
28
- const instance = createSanityInstance({
41
+ instance = createSanityInstance({
29
42
  projectId: 'p',
30
43
  dataset: 'd',
31
44
  auth: {
@@ -34,15 +47,16 @@ describe('handleAuthCallback', () => {
34
47
  },
35
48
  })
36
49
 
37
- const state = createResourceState(authStore.getInitialState(instance))
38
- expect(state.get()).toMatchObject({authState: {isExchangingToken: false}})
50
+ const authState = getAuthState(instance)
51
+
52
+ expect(authState.getCurrent()).toMatchObject({isExchangingToken: false})
39
53
 
40
54
  const resultPromise = handleAuthCallback(
41
- {instance, state},
55
+ instance,
42
56
  'https://example.com/callback?foo=bar#withSid=code',
43
57
  )
44
58
 
45
- expect(state.get()).toMatchObject({authState: {isExchangingToken: true}})
59
+ expect(authState.getCurrent()).toMatchObject({isExchangingToken: true})
46
60
  const result = await resultPromise
47
61
 
48
62
  expect(result).toBe('https://example.com/callback?foo=bar')
@@ -51,13 +65,13 @@ describe('handleAuthCallback', () => {
51
65
  requestTagPrefix: 'sanity.sdk.auth',
52
66
  useProjectHostname: false,
53
67
  })
54
- expect(mockRequest).toHaveBeenLastCalledWith({
68
+ expect(request).toHaveBeenLastCalledWith({
55
69
  method: 'GET',
56
70
  query: {sid: authCode},
57
71
  tag: 'fetch-token',
58
72
  uri: '/auth/fetch',
59
73
  })
60
- expect(setItem).toHaveBeenCalledWith(state.get().options.storageKey, '{"token":"new-token"}')
74
+ expect(setItem).toHaveBeenCalledWith('__sanity_auth_token', '{"token":"new-token"}')
61
75
  })
62
76
 
63
77
  it('returns early if there is a provided token', async () => {
@@ -68,7 +82,7 @@ describe('handleAuthCallback', () => {
68
82
  const authCode = 'auth-code'
69
83
  vi.mocked(getAuthCode).mockReturnValue(authCode)
70
84
 
71
- const instance = createSanityInstance({
85
+ instance = createSanityInstance({
72
86
  projectId: 'p',
73
87
  dataset: 'd',
74
88
  auth: {
@@ -78,9 +92,8 @@ describe('handleAuthCallback', () => {
78
92
  },
79
93
  })
80
94
 
81
- const state = createResourceState(authStore.getInitialState(instance))
82
95
  const result = await handleAuthCallback(
83
- {instance, state},
96
+ instance,
84
97
  'https://example.com/callback?foo=bar#withSid=code',
85
98
  )
86
99
 
@@ -90,37 +103,58 @@ describe('handleAuthCallback', () => {
90
103
  })
91
104
 
92
105
  it('returns early if already exchanging the the token', async () => {
93
- const clientFactory = vi.fn()
106
+ let resolveRequest!: (value: {token: string; label: string}) => void
107
+ const requestPromise = new Promise<{token: string; label: string}>((resolve) => {
108
+ resolveRequest = resolve
109
+ })
110
+ vi.mocked(getAuthCode).mockReturnValue('code')
111
+ const request = vi.fn().mockReturnValue(requestPromise)
112
+ const clientFactory = vi.fn().mockReturnValue({request})
94
113
  const setItem = vi.fn()
95
114
 
96
- const instance = createSanityInstance({
115
+ instance = createSanityInstance({
97
116
  projectId: 'p',
98
117
  dataset: 'd',
99
118
  auth: {
100
119
  clientFactory,
101
- storageArea: {setItem} as unknown as Storage,
120
+ storageArea: {setItem: setItem as Storage['setItem']} as Storage,
102
121
  },
103
122
  })
104
123
 
105
- const state = createResourceState(authStore.getInitialState(instance))
106
- state.set('setAlreadyExchanging', {
107
- authState: {type: AuthStateType.LOGGING_IN, isExchangingToken: true},
124
+ const authState = getAuthState(instance)
125
+ expect(authState.getCurrent()).toMatchObject({type: AuthStateType.LOGGING_IN})
126
+
127
+ const locationHref = 'https://example.com/callback?foo=bar#withSid=code'
128
+ const originalResultPromise = handleAuthCallback(instance, locationHref)
129
+
130
+ expect(authState.getCurrent()).toMatchObject({
131
+ type: AuthStateType.LOGGING_IN,
132
+ isExchangingToken: true,
108
133
  })
109
- const result = await handleAuthCallback(
110
- {instance, state},
111
- 'https://example.com/callback?foo=bar#withSid=code',
112
- )
113
134
 
114
- expect(result).toBe(false)
135
+ // ensures mock calls are reset to zero
136
+ clientFactory.mockClear()
137
+ setItem.mockClear()
138
+
139
+ // notice how this resolves first
140
+ const earlyResult = await handleAuthCallback(instance, locationHref)
141
+ expect(earlyResult).toBe(false)
142
+
115
143
  expect(clientFactory).not.toHaveBeenCalled()
116
144
  expect(setItem).not.toHaveBeenCalled()
145
+
146
+ // this will resolve
147
+ resolveRequest({token: 'token', label: 'label'})
148
+ expect(await originalResultPromise).toBe('https://example.com/callback?foo=bar')
149
+
150
+ // expect(result).toBe(false)
117
151
  })
118
152
 
119
153
  it('returns early if there is no auth code present', async () => {
120
154
  const clientFactory = vi.fn()
121
155
  const setItem = vi.fn()
122
156
 
123
- const instance = createSanityInstance({
157
+ instance = createSanityInstance({
124
158
  projectId: 'p',
125
159
  dataset: 'd',
126
160
  auth: {
@@ -130,9 +164,8 @@ describe('handleAuthCallback', () => {
130
164
  })
131
165
  vi.mocked(getAuthCode).mockReturnValue(null)
132
166
 
133
- const state = createResourceState(authStore.getInitialState(instance))
134
167
  const result = await handleAuthCallback(
135
- {instance, state},
168
+ instance,
136
169
  'https://example.com/callback?foo=bar#withSid=code',
137
170
  )
138
171
 
@@ -150,7 +183,7 @@ describe('handleAuthCallback', () => {
150
183
  const authCode = 'auth-code'
151
184
  vi.mocked(getAuthCode).mockReturnValue(authCode)
152
185
 
153
- const instance = createSanityInstance({
186
+ instance = createSanityInstance({
154
187
  projectId: 'p',
155
188
  dataset: 'd',
156
189
  auth: {
@@ -159,14 +192,14 @@ describe('handleAuthCallback', () => {
159
192
  },
160
193
  })
161
194
 
162
- const state = createResourceState(authStore.getInitialState(instance))
195
+ const authState = getAuthState(instance)
163
196
  const result = await handleAuthCallback(
164
- {instance, state},
197
+ instance,
165
198
  'https://example.com/callback?foo=bar#withSid=code',
166
199
  )
167
200
 
168
201
  expect(result).toBe(false)
169
- expect(state.get()).toMatchObject({authState: {type: AuthStateType.ERROR, error}})
202
+ expect(authState.getCurrent()).toMatchObject({type: AuthStateType.ERROR, error})
170
203
 
171
204
  expect(clientFactory).toHaveBeenCalledWith({
172
205
  apiVersion: '2021-06-07',
@@ -1,4 +1,4 @@
1
- import {createAction} from '../resources/createAction'
1
+ import {bindActionGlobally} from '../store/createActionBinder'
2
2
  import {DEFAULT_API_VERSION, REQUEST_TAG_PREFIX} from './authConstants'
3
3
  import {AuthStateType} from './authStateType'
4
4
  import {authStore, type AuthStoreState, type DashboardContext} from './authStore'
@@ -7,11 +7,12 @@ import {getAuthCode, getDefaultLocation} from './utils'
7
7
  /**
8
8
  * @public
9
9
  */
10
- export const handleAuthCallback = createAction(authStore, ({state}) => {
11
- const {providedToken, callbackUrl, clientFactory, apiHost, storageArea, storageKey} =
12
- state.get().options
10
+ export const handleAuthCallback = bindActionGlobally(
11
+ authStore,
12
+ async ({state}, locationHref: string = getDefaultLocation()) => {
13
+ const {providedToken, callbackUrl, clientFactory, apiHost, storageArea, storageKey} =
14
+ state.get().options
13
15
 
14
- return async function (locationHref: string = getDefaultLocation()) {
15
16
  // If a token is provided, no need to handle callback
16
17
  if (providedToken) return false
17
18
 
@@ -69,5 +70,5 @@ export const handleAuthCallback = createAction(authStore, ({state}) => {
69
70
  state.set('exchangeSessionForTokenError', {authState: {type: AuthStateType.ERROR, error}})
70
71
  return false
71
72
  }
72
- }
73
- })
73
+ },
74
+ )
@@ -1,10 +1,12 @@
1
+ import {NEVER} from 'rxjs'
1
2
  import {describe, it} from 'vitest'
2
3
 
3
- import {createSanityInstance} from '../instance/sanityInstance'
4
- import {createResourceState} from '../resources/createResource'
4
+ import {createSanityInstance, type SanityInstance} from '../store/createSanityInstance'
5
5
  import {AuthStateType} from './authStateType'
6
- import {authStore} from './authStore'
6
+ import {getAuthState} from './authStore'
7
7
  import {logout} from './logout'
8
+ import {subscribeToStateAndFetchCurrentUser} from './subscribeToStateAndFetchCurrentUser'
9
+ import {subscribeToStorageEventsAndSetToken} from './subscribeToStorageEventsAndSetToken'
8
10
  import {getTokenFromStorage} from './utils'
9
11
 
10
12
  vi.mock('./utils', async (importOriginal) => {
@@ -12,28 +14,43 @@ vi.mock('./utils', async (importOriginal) => {
12
14
  return {...original, getTokenFromStorage: vi.fn()}
13
15
  })
14
16
 
17
+ vi.mock('./subscribeToStateAndFetchCurrentUser')
18
+ vi.mock('./subscribeToStorageEventsAndSetToken')
19
+
20
+ let instance: SanityInstance | undefined
21
+
22
+ beforeEach(() => {
23
+ vi.mocked(subscribeToStateAndFetchCurrentUser).mockImplementation(() => NEVER.subscribe())
24
+ vi.mocked(subscribeToStorageEventsAndSetToken).mockImplementation(() => NEVER.subscribe())
25
+ })
26
+
27
+ afterEach(() => {
28
+ instance?.dispose()
29
+ })
30
+
15
31
  describe('logout', () => {
16
32
  it("calls '/auth/logout', sets the state to logged out, and removes any storage items", async () => {
17
33
  vi.mocked(getTokenFromStorage).mockReturnValue('token')
18
34
  const mockRequest = vi.fn().mockResolvedValue(undefined)
19
- const mockClient = {request: mockRequest}
20
- const clientFactory = vi.fn().mockReturnValue(mockClient)
21
- const removeItem = vi.fn()
22
- const instance = createSanityInstance({
35
+ const clientFactory = vi.fn().mockReturnValue({request: mockRequest})
36
+ const removeItem = vi.fn() as Storage['removeItem']
37
+
38
+ instance = createSanityInstance({
23
39
  projectId: 'p',
24
40
  dataset: 'd',
25
41
  auth: {
26
42
  clientFactory,
27
- storageArea: {removeItem} as unknown as Storage,
43
+ storageArea: {removeItem} as Storage,
28
44
  },
29
45
  })
30
46
 
31
- const state = createResourceState(authStore.getInitialState(instance))
47
+ const authState = getAuthState(instance)
48
+ expect(authState.getCurrent()).toMatchObject({type: AuthStateType.LOGGED_IN})
32
49
 
33
- const logoutPromise = logout({state, instance})
34
- expect(state.get()).toMatchObject({authState: {isDestroyingSession: true}})
50
+ const logoutPromise = logout(instance)
51
+ expect(authState.getCurrent()).toMatchObject({isDestroyingSession: true})
35
52
  await logoutPromise
36
- expect(state.get()).toMatchObject({authState: {isDestroyingSession: false}})
53
+ expect(authState.getCurrent()).toMatchObject({isDestroyingSession: false})
37
54
 
38
55
  expect(clientFactory).toHaveBeenCalledWith({
39
56
  apiVersion: '2021-06-07',
@@ -42,50 +59,65 @@ describe('logout', () => {
42
59
  useProjectHostname: false,
43
60
  })
44
61
  expect(mockRequest).toHaveBeenCalledWith({method: 'POST', uri: '/auth/logout'})
45
- expect(removeItem).toHaveBeenCalledWith(state.get().options.storageKey)
62
+ expect(removeItem).toHaveBeenCalledWith('__sanity_auth_token')
46
63
  })
47
64
 
48
65
  it('returns early if there is a provided token', async () => {
49
66
  const clientFactory = vi.fn()
50
- const removeItem = vi.fn()
51
- const instance = createSanityInstance({
67
+ const removeItem = vi.fn() as Storage['removeItem']
68
+ instance = createSanityInstance({
52
69
  projectId: 'p',
53
70
  dataset: 'd',
54
71
  auth: {
55
72
  token: 'provided-token',
56
73
  clientFactory,
57
- storageArea: {removeItem} as unknown as Storage,
74
+ storageArea: {removeItem} as Storage,
58
75
  },
59
76
  })
60
77
 
61
- const state = createResourceState(authStore.getInitialState(instance))
62
-
63
- await logout({state, instance})
78
+ await logout(instance)
64
79
 
65
80
  expect(clientFactory).not.toHaveBeenCalled()
66
81
  expect(removeItem).not.toHaveBeenCalled()
67
82
  })
68
83
 
69
84
  it('returns early if the session is already destroying', async () => {
70
- const clientFactory = vi.fn()
71
- const removeItem = vi.fn()
72
- const instance = createSanityInstance({
85
+ let resolveRequest!: () => void
86
+ const requestPromise = new Promise<void>((resolve) => {
87
+ resolveRequest = resolve
88
+ })
89
+ const request = vi.fn().mockReturnValue(requestPromise)
90
+ const clientFactory = vi.fn().mockReturnValue({request})
91
+ const removeItem = vi.fn() as Storage['removeItem']
92
+ vi.mocked(getTokenFromStorage).mockReturnValue('token')
93
+
94
+ instance = createSanityInstance({
73
95
  projectId: 'p',
74
96
  dataset: 'd',
75
97
  auth: {
76
- token: 'provided-token',
77
98
  clientFactory,
78
- storageArea: {removeItem} as unknown as Storage,
99
+ storageArea: {removeItem} as Storage,
79
100
  },
80
101
  })
81
102
 
82
- const state = createResourceState(authStore.getInitialState(instance))
83
- state.set('setAlreadyDestroying', {
84
- authState: {type: AuthStateType.LOGGED_OUT, isDestroyingSession: true},
85
- })
103
+ const authState = getAuthState(instance)
104
+ expect(authState.getCurrent()).toMatchObject({type: AuthStateType.LOGGED_IN})
105
+
106
+ const originalLogout = logout(instance)
107
+ expect(authState.getCurrent()).toMatchObject({isDestroyingSession: true})
108
+
109
+ // reset counts
110
+ clientFactory.mockClear()
111
+ vi.mocked(removeItem).mockClear()
112
+
113
+ await logout(instance) // should early return
86
114
 
87
- await logout({state, instance})
88
115
  expect(clientFactory).not.toHaveBeenCalled()
89
116
  expect(removeItem).not.toHaveBeenCalled()
117
+
118
+ resolveRequest()
119
+
120
+ await originalLogout
121
+ expect(removeItem).toHaveBeenCalledOnce()
90
122
  })
91
123
  })