@sanity/sdk 2.6.0 → 2.7.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.
- package/dist/index.d.ts +124 -13
- package/dist/index.js +468 -243
- package/dist/index.js.map +1 -1
- package/package.json +5 -4
- package/src/_exports/index.ts +3 -0
- package/src/auth/authMode.test.ts +56 -0
- package/src/auth/authMode.ts +71 -0
- package/src/auth/authStore.test.ts +85 -4
- package/src/auth/authStore.ts +63 -125
- package/src/auth/authStrategy.ts +39 -0
- package/src/auth/dashboardAuth.ts +132 -0
- package/src/auth/standaloneAuth.ts +109 -0
- package/src/auth/studioAuth.ts +217 -0
- package/src/auth/studioModeAuth.test.ts +43 -1
- package/src/auth/studioModeAuth.ts +10 -1
- package/src/auth/subscribeToStateAndFetchCurrentUser.ts +21 -6
- package/src/config/sanityConfig.ts +48 -7
- package/src/projection/getProjectionState.ts +6 -5
- package/src/projection/projectionQuery.test.ts +38 -55
- package/src/projection/projectionQuery.ts +27 -31
- package/src/projection/projectionStore.test.ts +4 -4
- package/src/projection/projectionStore.ts +3 -2
- package/src/projection/resolveProjection.ts +2 -2
- package/src/projection/statusQuery.test.ts +35 -0
- package/src/projection/statusQuery.ts +71 -0
- package/src/projection/subscribeToStateAndFetchBatches.test.ts +63 -50
- package/src/projection/subscribeToStateAndFetchBatches.ts +106 -27
- package/src/projection/types.ts +12 -0
- package/src/projection/util.ts +0 -1
- package/src/query/queryStore.test.ts +64 -0
- package/src/query/queryStore.ts +30 -10
- package/src/releases/getPerspectiveState.test.ts +17 -14
- package/src/releases/getPerspectiveState.ts +58 -38
- package/src/releases/releasesStore.test.ts +59 -61
- package/src/releases/releasesStore.ts +21 -35
- package/src/releases/utils/isReleasePerspective.ts +7 -0
- package/src/store/createActionBinder.test.ts +211 -1
- package/src/store/createActionBinder.ts +95 -17
- package/src/store/createSanityInstance.ts +3 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sanity/sdk",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.7.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Sanity SDK",
|
|
6
6
|
"keywords": [
|
|
@@ -44,10 +44,11 @@
|
|
|
44
44
|
"prettier": "@sanity/prettier-config",
|
|
45
45
|
"dependencies": {
|
|
46
46
|
"@sanity/bifur-client": "^0.4.1",
|
|
47
|
-
"@sanity/client": "^7.
|
|
47
|
+
"@sanity/client": "^7.14.1",
|
|
48
48
|
"@sanity/comlink": "^3.0.4",
|
|
49
49
|
"@sanity/diff-match-patch": "^3.2.0",
|
|
50
50
|
"@sanity/diff-patch": "^6.0.0",
|
|
51
|
+
"@sanity/id-utils": "^1.0.0",
|
|
51
52
|
"@sanity/json-match": "^1.0.5",
|
|
52
53
|
"@sanity/message-protocol": "^0.18.0",
|
|
53
54
|
"@sanity/mutate": "^0.12.4",
|
|
@@ -72,10 +73,10 @@
|
|
|
72
73
|
"vite": "^6.3.4",
|
|
73
74
|
"vitest": "^3.2.4",
|
|
74
75
|
"@repo/config-eslint": "0.0.0",
|
|
76
|
+
"@repo/config-test": "0.0.1",
|
|
75
77
|
"@repo/package.bundle": "3.82.0",
|
|
76
|
-
"@repo/tsconfig": "0.0.1",
|
|
77
78
|
"@repo/package.config": "0.0.1",
|
|
78
|
-
"@repo/
|
|
79
|
+
"@repo/tsconfig": "0.0.1"
|
|
79
80
|
},
|
|
80
81
|
"engines": {
|
|
81
82
|
"node": ">=20.19"
|
package/src/_exports/index.ts
CHANGED
|
@@ -24,6 +24,7 @@ export {
|
|
|
24
24
|
agentTransform,
|
|
25
25
|
agentTranslate,
|
|
26
26
|
} from '../agent/agentActions'
|
|
27
|
+
export {isStudioConfig} from '../auth/authMode'
|
|
27
28
|
export {AuthStateType} from '../auth/authStateType'
|
|
28
29
|
export {
|
|
29
30
|
type AuthState,
|
|
@@ -99,6 +100,8 @@ export {
|
|
|
99
100
|
type ProjectHandle,
|
|
100
101
|
type ReleasePerspective,
|
|
101
102
|
type SanityConfig,
|
|
103
|
+
type StudioConfig,
|
|
104
|
+
type TokenSource,
|
|
102
105
|
} from '../config/sanityConfig'
|
|
103
106
|
export {getDatasetsState, resolveDatasets} from '../datasets/datasets'
|
|
104
107
|
export {
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import {describe, expect, it} from 'vitest'
|
|
2
|
+
|
|
3
|
+
import {type SanityConfig} from '../config/sanityConfig'
|
|
4
|
+
import {isStudioConfig, resolveAuthMode} from './authMode'
|
|
5
|
+
|
|
6
|
+
describe('resolveAuthMode', () => {
|
|
7
|
+
it('returns "studio" when studio config is provided', () => {
|
|
8
|
+
const config: SanityConfig = {studio: {}}
|
|
9
|
+
expect(resolveAuthMode(config, 'https://example.com')).toBe('studio')
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
it('returns "studio" when deprecated studioMode.enabled is true', () => {
|
|
13
|
+
const config: SanityConfig = {studioMode: {enabled: true}}
|
|
14
|
+
expect(resolveAuthMode(config, 'https://example.com')).toBe('studio')
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
it('returns "dashboard" when _context URL param has a non-empty JSON object', () => {
|
|
18
|
+
const context = encodeURIComponent(JSON.stringify({orgId: '123'}))
|
|
19
|
+
const href = `https://example.com?_context=${context}`
|
|
20
|
+
expect(resolveAuthMode({}, href)).toBe('dashboard')
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('returns "standalone" by default', () => {
|
|
24
|
+
expect(resolveAuthMode({}, 'https://example.com')).toBe('standalone')
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('prefers studio config over studioMode', () => {
|
|
28
|
+
const config: SanityConfig = {
|
|
29
|
+
studio: {},
|
|
30
|
+
studioMode: {enabled: true},
|
|
31
|
+
}
|
|
32
|
+
expect(resolveAuthMode(config, 'https://example.com')).toBe('studio')
|
|
33
|
+
})
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
describe('isStudioConfig', () => {
|
|
37
|
+
it('returns true when studio config is provided', () => {
|
|
38
|
+
expect(isStudioConfig({studio: {}})).toBe(true)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('returns true when deprecated studioMode.enabled is true', () => {
|
|
42
|
+
expect(isStudioConfig({studioMode: {enabled: true}})).toBe(true)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('returns false when studioMode.enabled is false', () => {
|
|
46
|
+
expect(isStudioConfig({studioMode: {enabled: false}})).toBe(false)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('returns false for empty config', () => {
|
|
50
|
+
expect(isStudioConfig({})).toBe(false)
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('returns true when both studio and studioMode are present', () => {
|
|
54
|
+
expect(isStudioConfig({studio: {}, studioMode: {enabled: true}})).toBe(true)
|
|
55
|
+
})
|
|
56
|
+
})
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import {type SanityConfig} from '../config/sanityConfig'
|
|
2
|
+
import {DEFAULT_BASE} from './authConstants'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The runtime auth mode determines which authentication strategy the SDK uses.
|
|
6
|
+
*
|
|
7
|
+
* - `studio` — Running inside Sanity Studio. Token is discovered from
|
|
8
|
+
* Studio's localStorage entry or via cookie auth.
|
|
9
|
+
* - `dashboard` — Running inside the Sanity Dashboard iframe. Token is
|
|
10
|
+
* provided by the parent frame via Comlink.
|
|
11
|
+
* - `standalone` — Running as an independent app. Token comes from
|
|
12
|
+
* localStorage or the OAuth login flow.
|
|
13
|
+
*
|
|
14
|
+
* @internal
|
|
15
|
+
*/
|
|
16
|
+
type AuthMode = 'studio' | 'dashboard' | 'standalone'
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Determines the auth mode from instance config and environment.
|
|
20
|
+
*
|
|
21
|
+
* Priority:
|
|
22
|
+
* 1. `studio` config provided → `'studio'`
|
|
23
|
+
* 2. `studioMode.enabled` in config (deprecated) → `'studio'`
|
|
24
|
+
* 3. Dashboard context detected (`_context` URL param with content) → `'dashboard'`
|
|
25
|
+
* 4. Otherwise → `'standalone'`
|
|
26
|
+
*
|
|
27
|
+
* @internal
|
|
28
|
+
*/
|
|
29
|
+
export function resolveAuthMode(config: SanityConfig, locationHref: string): AuthMode {
|
|
30
|
+
if (isStudioConfig(config)) return 'studio'
|
|
31
|
+
if (detectDashboardContext(locationHref)) return 'dashboard'
|
|
32
|
+
return 'standalone'
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Returns `true` when the config indicates the SDK is running inside a Studio.
|
|
37
|
+
* Checks the new `studio` field first, then falls back to the deprecated
|
|
38
|
+
* `studioMode.enabled` for backwards compatibility.
|
|
39
|
+
*
|
|
40
|
+
* @internal
|
|
41
|
+
*/
|
|
42
|
+
export function isStudioConfig(config: SanityConfig): boolean {
|
|
43
|
+
return !!config.studio || !!config.studioMode?.enabled
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Checks whether the given location href contains a `_context` URL parameter
|
|
48
|
+
* with a non-empty JSON object, indicating the SDK is running inside the
|
|
49
|
+
* Sanity Dashboard.
|
|
50
|
+
*
|
|
51
|
+
* @internal
|
|
52
|
+
*/
|
|
53
|
+
function detectDashboardContext(locationHref: string): boolean {
|
|
54
|
+
try {
|
|
55
|
+
const parsedUrl = new URL(locationHref, DEFAULT_BASE)
|
|
56
|
+
const contextParam = parsedUrl.searchParams.get('_context')
|
|
57
|
+
if (!contextParam) return false
|
|
58
|
+
|
|
59
|
+
const parsed: unknown = JSON.parse(contextParam)
|
|
60
|
+
return (
|
|
61
|
+
typeof parsed === 'object' &&
|
|
62
|
+
parsed !== null &&
|
|
63
|
+
!Array.isArray(parsed) &&
|
|
64
|
+
Object.keys(parsed as Record<string, unknown>).length > 0
|
|
65
|
+
)
|
|
66
|
+
} catch (err) {
|
|
67
|
+
// eslint-disable-next-line no-console
|
|
68
|
+
console.error('Failed to parse dashboard context from initial location:', err)
|
|
69
|
+
return false
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -153,6 +153,34 @@ describe('authStore', () => {
|
|
|
153
153
|
errorSpy.mockRestore()
|
|
154
154
|
})
|
|
155
155
|
|
|
156
|
+
it('rejects array _context and falls back to standalone mode', () => {
|
|
157
|
+
const initialLocationHref = `https://example.com/?_context=${encodeURIComponent(JSON.stringify(['a', 'b']))}`
|
|
158
|
+
instance = createSanityInstance({
|
|
159
|
+
projectId: 'p',
|
|
160
|
+
dataset: 'd',
|
|
161
|
+
auth: {initialLocationHref},
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
const {dashboardContext, authState} = authStore.getInitialState(instance, null)
|
|
165
|
+
// Array should not be treated as dashboard context — falls through to standalone
|
|
166
|
+
expect(dashboardContext).toStrictEqual({})
|
|
167
|
+
expect(authState.type).toBe(AuthStateType.LOGGED_OUT)
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
it('rejects empty object _context and falls back to standalone mode', () => {
|
|
171
|
+
const initialLocationHref = `https://example.com/?_context=${encodeURIComponent(JSON.stringify({}))}`
|
|
172
|
+
instance = createSanityInstance({
|
|
173
|
+
projectId: 'p',
|
|
174
|
+
dataset: 'd',
|
|
175
|
+
auth: {initialLocationHref},
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
const {dashboardContext, authState} = authStore.getInitialState(instance, null)
|
|
179
|
+
// Empty object not treated as dashboard — falls through to standalone
|
|
180
|
+
expect(dashboardContext).toStrictEqual({})
|
|
181
|
+
expect(authState.type).toBe(AuthStateType.LOGGED_OUT)
|
|
182
|
+
})
|
|
183
|
+
|
|
156
184
|
it('sets to logged in if provided token is present (even in dashboard)', () => {
|
|
157
185
|
const token = 'provided-token'
|
|
158
186
|
const context = {mode: 'dashboard'}
|
|
@@ -234,7 +262,7 @@ describe('authStore', () => {
|
|
|
234
262
|
expect(dashboardContext).toStrictEqual({})
|
|
235
263
|
})
|
|
236
264
|
|
|
237
|
-
it('sets to logged in using studio token when studio
|
|
265
|
+
it('sets to logged in using studio token when studio config is provided and token exists', () => {
|
|
238
266
|
const studioToken = 'studio-token'
|
|
239
267
|
const projectId = 'studio-project'
|
|
240
268
|
const studioStorageKey = `__studio_auth_token_${projectId}`
|
|
@@ -248,7 +276,7 @@ describe('authStore', () => {
|
|
|
248
276
|
instance = createSanityInstance({
|
|
249
277
|
projectId,
|
|
250
278
|
dataset: 'd',
|
|
251
|
-
|
|
279
|
+
studio: {},
|
|
252
280
|
auth: {storageArea: mockStorage}, // Provide mock storage
|
|
253
281
|
})
|
|
254
282
|
|
|
@@ -258,7 +286,7 @@ describe('authStore', () => {
|
|
|
258
286
|
expect(options.authMethod).toBe('localstorage')
|
|
259
287
|
})
|
|
260
288
|
|
|
261
|
-
it('checks for cookie auth during initialize when studio
|
|
289
|
+
it('checks for cookie auth during initialize when studio config is provided and no studio token exists', () => {
|
|
262
290
|
const projectId = 'studio-project'
|
|
263
291
|
const studioStorageKey = `__studio_auth_token_${projectId}`
|
|
264
292
|
const mockStorage = {
|
|
@@ -272,7 +300,7 @@ describe('authStore', () => {
|
|
|
272
300
|
instance = createSanityInstance({
|
|
273
301
|
projectId,
|
|
274
302
|
dataset: 'd',
|
|
275
|
-
|
|
303
|
+
studio: {},
|
|
276
304
|
auth: {storageArea: mockStorage},
|
|
277
305
|
})
|
|
278
306
|
|
|
@@ -287,6 +315,59 @@ describe('authStore', () => {
|
|
|
287
315
|
expect(checkForCookieAuth).toHaveBeenCalledWith(projectId, expect.any(Function))
|
|
288
316
|
})
|
|
289
317
|
|
|
318
|
+
it('starts in LOGGING_IN state when studio config with tokenSource is provided', () => {
|
|
319
|
+
const mockTokenSource = {
|
|
320
|
+
subscribe: vi.fn(() => ({unsubscribe: vi.fn()})),
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
instance = createSanityInstance({
|
|
324
|
+
projectId: 'studio-project',
|
|
325
|
+
dataset: 'production',
|
|
326
|
+
studio: {
|
|
327
|
+
auth: {token: mockTokenSource},
|
|
328
|
+
},
|
|
329
|
+
})
|
|
330
|
+
|
|
331
|
+
const {authState} = authStore.getInitialState(instance, null)
|
|
332
|
+
expect(authState.type).toBe(AuthStateType.LOGGING_IN)
|
|
333
|
+
// Should not try localStorage or cookie auth
|
|
334
|
+
expect(getStudioTokenFromLocalStorage).not.toHaveBeenCalled()
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
it('resolves to studio mode when studio config is provided (without studioMode flag)', () => {
|
|
338
|
+
instance = createSanityInstance({
|
|
339
|
+
projectId: 'studio-project',
|
|
340
|
+
dataset: 'production',
|
|
341
|
+
studio: {},
|
|
342
|
+
})
|
|
343
|
+
|
|
344
|
+
const {authState} = authStore.getInitialState(instance, null)
|
|
345
|
+
// Without tokenSource, falls back to localStorage discovery like studioMode
|
|
346
|
+
expect(authState.type).toBe(AuthStateType.LOGGED_OUT)
|
|
347
|
+
expect(getStudioTokenFromLocalStorage).toHaveBeenCalled()
|
|
348
|
+
})
|
|
349
|
+
|
|
350
|
+
it('subscribes to tokenSource during initialize when studio config is provided', () => {
|
|
351
|
+
const mockUnsubscribe = vi.fn()
|
|
352
|
+
const mockSubscribe = vi.fn(() => ({unsubscribe: mockUnsubscribe}))
|
|
353
|
+
const mockTokenSource = {subscribe: mockSubscribe}
|
|
354
|
+
|
|
355
|
+
instance = createSanityInstance({
|
|
356
|
+
projectId: 'studio-project',
|
|
357
|
+
dataset: 'production',
|
|
358
|
+
studio: {
|
|
359
|
+
auth: {token: mockTokenSource},
|
|
360
|
+
},
|
|
361
|
+
})
|
|
362
|
+
|
|
363
|
+
// Trigger store creation + initialize
|
|
364
|
+
getAuthState(instance)
|
|
365
|
+
|
|
366
|
+
expect(mockSubscribe).toHaveBeenCalledWith({next: expect.any(Function)})
|
|
367
|
+
// Should NOT start cookie auth or storage event subscriptions
|
|
368
|
+
expect(checkForCookieAuth).not.toHaveBeenCalled()
|
|
369
|
+
})
|
|
370
|
+
|
|
290
371
|
it('falls back to default auth (storage token) when studio mode is disabled', () => {
|
|
291
372
|
const storageToken = 'regular-storage-token'
|
|
292
373
|
vi.mocked(getTokenFromStorage).mockReturnValue(storageToken)
|
package/src/auth/authStore.ts
CHANGED
|
@@ -1,24 +1,21 @@
|
|
|
1
1
|
import {type ClientConfig, createClient, type SanityClient} from '@sanity/client'
|
|
2
2
|
import {type CurrentUser} from '@sanity/types'
|
|
3
|
-
import {type Subscription} from 'rxjs'
|
|
4
3
|
|
|
5
4
|
import {type AuthConfig, type AuthProvider} from '../config/authConfig'
|
|
6
5
|
import {bindActionGlobally} from '../store/createActionBinder'
|
|
7
6
|
import {createStateSourceAction} from '../store/createStateSourceAction'
|
|
8
7
|
import {defineStore} from '../store/defineStore'
|
|
8
|
+
import {resolveAuthMode} from './authMode'
|
|
9
9
|
import {AuthStateType} from './authStateType'
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
getTokenFromLocation,
|
|
20
|
-
getTokenFromStorage,
|
|
21
|
-
} from './utils'
|
|
10
|
+
import {type AuthStrategyOptions} from './authStrategy'
|
|
11
|
+
import {getDashboardInitialState, initializeDashboardAuth} from './dashboardAuth'
|
|
12
|
+
import {getStandaloneInitialState, initializeStandaloneAuth} from './standaloneAuth'
|
|
13
|
+
import {getStudioInitialState, initializeStudioAuth} from './studioAuth'
|
|
14
|
+
import {getCleanedUrl, getDefaultLocation} from './utils'
|
|
15
|
+
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
// Public types
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
22
19
|
|
|
23
20
|
/**
|
|
24
21
|
* Represents the various states the authentication can be in.
|
|
@@ -96,8 +93,13 @@ export interface AuthStoreState {
|
|
|
96
93
|
dashboardContext?: DashboardContext
|
|
97
94
|
}
|
|
98
95
|
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
// Store definition — thin orchestrator
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
|
|
99
100
|
export const authStore = defineStore<AuthStoreState>({
|
|
100
101
|
name: 'Auth',
|
|
102
|
+
|
|
101
103
|
getInitialState(instance) {
|
|
102
104
|
const {
|
|
103
105
|
apiHost,
|
|
@@ -107,12 +109,11 @@ export const authStore = defineStore<AuthStoreState>({
|
|
|
107
109
|
clientFactory = createClient,
|
|
108
110
|
initialLocationHref = getDefaultLocation(),
|
|
109
111
|
} = instance.config.auth ?? {}
|
|
110
|
-
let storageArea = instance.config.auth?.storageArea
|
|
111
112
|
|
|
112
|
-
|
|
113
|
-
const studioModeEnabled = instance.config.studioMode?.enabled
|
|
113
|
+
const authConfig = instance.config.auth ?? {}
|
|
114
114
|
|
|
115
|
-
//
|
|
115
|
+
// Build login URL (used by standalone mode, but always computed for the
|
|
116
|
+
// public `getLoginUrlState` accessor)
|
|
116
117
|
let loginDomain = 'https://www.sanity.io'
|
|
117
118
|
try {
|
|
118
119
|
if (apiHost && new URL(apiHost).hostname.endsWith('.sanity.work')) {
|
|
@@ -126,80 +127,33 @@ export const authStore = defineStore<AuthStoreState>({
|
|
|
126
127
|
loginUrl.searchParams.set('type', 'stampedToken') // Token must be stamped to have an sid passed back
|
|
127
128
|
loginUrl.searchParams.set('withSid', 'true')
|
|
128
129
|
|
|
129
|
-
//
|
|
130
|
-
|
|
131
|
-
let isInDashboard = false
|
|
132
|
-
try {
|
|
133
|
-
const parsedUrl = new URL(initialLocationHref)
|
|
134
|
-
const contextParam = parsedUrl.searchParams.get('_context')
|
|
135
|
-
if (contextParam) {
|
|
136
|
-
const parsedContext = JSON.parse(contextParam)
|
|
137
|
-
|
|
138
|
-
// Consider it in dashboard if context is present and an object
|
|
139
|
-
if (
|
|
140
|
-
parsedContext &&
|
|
141
|
-
typeof parsedContext === 'object' &&
|
|
142
|
-
Object.keys(parsedContext).length > 0
|
|
143
|
-
) {
|
|
144
|
-
// Explicitly remove the 'sid' property from the parsed object *before* assigning
|
|
145
|
-
delete parsedContext.sid
|
|
146
|
-
|
|
147
|
-
// Now assign the potentially modified object to dashboardContext
|
|
148
|
-
dashboardContext = parsedContext
|
|
149
|
-
isInDashboard = true
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
} catch (err) {
|
|
153
|
-
// eslint-disable-next-line no-console
|
|
154
|
-
console.error('Failed to parse dashboard context from initial location:', err)
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
if (!isInDashboard || studioModeEnabled) {
|
|
158
|
-
// If not in dashboard, use the storage area from the config
|
|
159
|
-
// If studio mode is enabled, use the local storage area (default)
|
|
160
|
-
storageArea = storageArea ?? getDefaultStorage()
|
|
161
|
-
}
|
|
130
|
+
// Resolve auth mode and delegate to the appropriate strategy
|
|
131
|
+
const mode = resolveAuthMode(instance.config, initialLocationHref)
|
|
162
132
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
token = getStudioTokenFromLocalStorage(storageArea, studioStorageKey)
|
|
170
|
-
if (token) {
|
|
171
|
-
authMethod = 'localstorage'
|
|
172
|
-
}
|
|
173
|
-
} else {
|
|
174
|
-
token = getTokenFromStorage(storageArea, storageKey)
|
|
175
|
-
if (token) {
|
|
176
|
-
authMethod = 'localstorage'
|
|
177
|
-
}
|
|
133
|
+
const strategyOptions: AuthStrategyOptions = {
|
|
134
|
+
authConfig,
|
|
135
|
+
projectId: instance.config.projectId,
|
|
136
|
+
initialLocationHref,
|
|
137
|
+
clientFactory,
|
|
138
|
+
tokenSource: instance.config.studio?.auth?.token,
|
|
178
139
|
}
|
|
179
140
|
|
|
180
|
-
let
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
} else if (token && !isInDashboard && !studioModeEnabled) {
|
|
192
|
-
// Only use token from storage if NOT running in dashboard and studio mode is not enabled
|
|
193
|
-
authState = {type: AuthStateType.LOGGED_IN, token, currentUser: null}
|
|
194
|
-
} else {
|
|
195
|
-
// Default to logged out if no provided token, not handling callback,
|
|
196
|
-
// or if token exists but we ARE in dashboard mode.
|
|
197
|
-
authState = {type: AuthStateType.LOGGED_OUT, isDestroyingSession: false}
|
|
141
|
+
let result
|
|
142
|
+
switch (mode) {
|
|
143
|
+
case 'studio':
|
|
144
|
+
result = getStudioInitialState(strategyOptions)
|
|
145
|
+
break
|
|
146
|
+
case 'dashboard':
|
|
147
|
+
result = getDashboardInitialState(strategyOptions)
|
|
148
|
+
break
|
|
149
|
+
case 'standalone':
|
|
150
|
+
result = getStandaloneInitialState(strategyOptions)
|
|
151
|
+
break
|
|
198
152
|
}
|
|
199
153
|
|
|
200
154
|
return {
|
|
201
|
-
authState,
|
|
202
|
-
dashboardContext,
|
|
155
|
+
authState: result.authState,
|
|
156
|
+
dashboardContext: result.dashboardContext,
|
|
203
157
|
options: {
|
|
204
158
|
apiHost,
|
|
205
159
|
loginUrl: loginUrl.toString(),
|
|
@@ -208,59 +162,43 @@ export const authStore = defineStore<AuthStoreState>({
|
|
|
208
162
|
providedToken,
|
|
209
163
|
clientFactory,
|
|
210
164
|
initialLocationHref,
|
|
211
|
-
storageKey,
|
|
212
|
-
storageArea,
|
|
213
|
-
authMethod,
|
|
165
|
+
storageKey: result.storageKey,
|
|
166
|
+
storageArea: result.storageArea,
|
|
167
|
+
authMethod: result.authMethod,
|
|
214
168
|
},
|
|
215
169
|
}
|
|
216
170
|
},
|
|
171
|
+
|
|
217
172
|
initialize(context) {
|
|
218
|
-
const
|
|
219
|
-
|
|
220
|
-
const
|
|
221
|
-
if (storageArea) {
|
|
222
|
-
subscriptions.push(subscribeToStorageEventsAndSetToken(context))
|
|
223
|
-
}
|
|
173
|
+
const initialLocationHref =
|
|
174
|
+
context.state.get().options?.initialLocationHref ?? getDefaultLocation()
|
|
175
|
+
const mode = resolveAuthMode(context.instance.config, initialLocationHref)
|
|
224
176
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
checkForCookieAuth(projectId, clientFactory).then((isCookieAuthEnabled) => {
|
|
237
|
-
if (!isCookieAuthEnabled) return
|
|
238
|
-
state.set('enableCookieAuth', (prev) => ({
|
|
239
|
-
options: {...prev.options, authMethod: 'cookie'},
|
|
240
|
-
authState:
|
|
241
|
-
prev.authState.type === AuthStateType.LOGGED_IN
|
|
242
|
-
? prev.authState
|
|
243
|
-
: {type: AuthStateType.LOGGED_IN, token: '', currentUser: null},
|
|
244
|
-
}))
|
|
245
|
-
})
|
|
246
|
-
}
|
|
247
|
-
} catch {
|
|
248
|
-
// best-effort cookie detection
|
|
177
|
+
let initResult
|
|
178
|
+
switch (mode) {
|
|
179
|
+
case 'studio':
|
|
180
|
+
initResult = initializeStudioAuth(context, tokenRefresherRunning)
|
|
181
|
+
break
|
|
182
|
+
case 'dashboard':
|
|
183
|
+
initResult = initializeDashboardAuth(context, tokenRefresherRunning)
|
|
184
|
+
break
|
|
185
|
+
case 'standalone':
|
|
186
|
+
initResult = initializeStandaloneAuth(context, tokenRefresherRunning)
|
|
187
|
+
break
|
|
249
188
|
}
|
|
250
189
|
|
|
251
|
-
if (
|
|
190
|
+
if (initResult.tokenRefresherStarted) {
|
|
252
191
|
tokenRefresherRunning = true
|
|
253
|
-
subscriptions.push(refreshStampedToken(context))
|
|
254
192
|
}
|
|
255
193
|
|
|
256
|
-
return
|
|
257
|
-
for (const subscription of subscriptions) {
|
|
258
|
-
subscription.unsubscribe()
|
|
259
|
-
}
|
|
260
|
-
}
|
|
194
|
+
return initResult.dispose
|
|
261
195
|
},
|
|
262
196
|
})
|
|
263
197
|
|
|
198
|
+
// ---------------------------------------------------------------------------
|
|
199
|
+
// Public bound actions
|
|
200
|
+
// ---------------------------------------------------------------------------
|
|
201
|
+
|
|
264
202
|
/**
|
|
265
203
|
* @public
|
|
266
204
|
*/
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import {type ClientConfig, type SanityClient} from '@sanity/client'
|
|
2
|
+
|
|
3
|
+
import {type AuthConfig} from '../config/authConfig'
|
|
4
|
+
import {type TokenSource} from '../config/sanityConfig'
|
|
5
|
+
import {type AuthMethodOptions, type AuthState, type DashboardContext} from './authStore'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The result returned by each auth strategy's `getInitialState` function.
|
|
9
|
+
* Provides the auth store with the initial auth state and the options needed
|
|
10
|
+
* to run the store.
|
|
11
|
+
*
|
|
12
|
+
* @internal
|
|
13
|
+
*/
|
|
14
|
+
export interface AuthStrategyResult {
|
|
15
|
+
authState: AuthState
|
|
16
|
+
storageKey: string
|
|
17
|
+
storageArea: Storage | undefined
|
|
18
|
+
authMethod: AuthMethodOptions
|
|
19
|
+
dashboardContext: DashboardContext
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Shared options that every auth strategy receives.
|
|
24
|
+
* Extracted from the instance config once and passed through.
|
|
25
|
+
*
|
|
26
|
+
* @internal
|
|
27
|
+
*/
|
|
28
|
+
export interface AuthStrategyOptions {
|
|
29
|
+
authConfig: AuthConfig
|
|
30
|
+
projectId: string | undefined
|
|
31
|
+
initialLocationHref: string
|
|
32
|
+
clientFactory: (config: ClientConfig) => SanityClient
|
|
33
|
+
/**
|
|
34
|
+
* Reactive token source from a Studio workspace. When provided, the studio
|
|
35
|
+
* auth strategy subscribes to it for ongoing token sync instead of
|
|
36
|
+
* discovering tokens from localStorage or cookies.
|
|
37
|
+
*/
|
|
38
|
+
tokenSource?: TokenSource
|
|
39
|
+
}
|