@sanity/sdk 3.0.0-rc.0 → 3.0.0-rc.1
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/_chunks-dts/createGroqSearchFilter.d.ts +1147 -0
- package/dist/_chunks-dts/createGroqSearchFilter.d.ts.map +1 -0
- package/dist/_chunks-es/_internal.js +134 -0
- package/dist/_chunks-es/_internal.js.map +1 -0
- package/dist/_chunks-es/createGroqSearchFilter.js +1864 -0
- package/dist/_chunks-es/createGroqSearchFilter.js.map +1 -0
- package/dist/_chunks-es/telemetryManager.js +93 -0
- package/dist/_chunks-es/telemetryManager.js.map +1 -0
- package/dist/_exports/_internal.d.ts +95 -0
- package/dist/_exports/_internal.d.ts.map +1 -0
- package/dist/_exports/_internal.js +24 -0
- package/dist/_exports/_internal.js.map +1 -0
- package/dist/index.d.ts +1079 -1076
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1967 -2302
- package/dist/index.js.map +1 -1
- package/package.json +48 -47
- package/src/_exports/_internal.ts +26 -0
- package/src/_exports/index.ts +120 -11
- package/src/access/checkPermissions.test.ts +55 -0
- package/src/access/checkPermissions.ts +54 -0
- package/src/agent/agentActions.test.ts +16 -60
- package/src/agent/agentActions.ts +25 -38
- package/src/applications/applications.test-d.ts +83 -0
- package/src/applications/applications.test.ts +130 -0
- package/src/applications/applications.ts +364 -0
- package/src/applications/userApplications.test.ts +126 -0
- package/src/applications/userApplications.ts +261 -0
- package/src/auth/authLogger.ts +30 -0
- package/src/auth/authMode.test.ts +25 -0
- package/src/auth/authMode.ts +6 -3
- package/src/auth/authStore.test.ts +194 -13
- package/src/auth/authStore.ts +59 -26
- package/src/auth/getOrganizationVerificationState.test.ts +18 -17
- package/src/auth/getOrganizationVerificationState.ts +4 -4
- package/src/auth/handleAuthCallback.test.ts +35 -1
- package/src/auth/handleAuthCallback.ts +25 -6
- package/src/auth/logout.test.ts +75 -2
- package/src/auth/logout.ts +23 -4
- package/src/auth/refreshStampedToken.test.ts +219 -469
- package/src/auth/refreshStampedToken.ts +79 -195
- package/src/auth/studioAuth.ts +1 -1
- package/src/auth/studioModeAuth.ts +2 -1
- package/src/auth/subscribeToStateAndFetchCurrentUser.test.ts +146 -13
- package/src/auth/subscribeToStateAndFetchCurrentUser.ts +37 -6
- package/src/auth/subscribeToStorageEventsAndSetToken.test.ts +2 -0
- package/src/client/clientStore.test.ts +62 -37
- package/src/client/clientStore.ts +45 -75
- package/src/client/liveEvents.test.ts +151 -0
- package/src/client/liveEvents.ts +107 -0
- package/src/comlink/controller/actions/destroyController.test.ts +4 -1
- package/src/comlink/controller/actions/getOrCreateChannel.test.ts +4 -1
- package/src/comlink/controller/actions/getOrCreateChannel.ts +2 -2
- package/src/comlink/controller/actions/getOrCreateController.test.ts +4 -1
- package/src/comlink/controller/actions/releaseChannel.test.ts +1 -1
- package/src/comlink/controller/comlinkControllerStore.test.ts +4 -1
- package/src/comlink/node/actions/getOrCreateNode.test.ts +9 -3
- package/src/comlink/node/actions/getOrCreateNode.ts +2 -2
- package/src/comlink/node/actions/releaseNode.test.ts +7 -4
- package/src/comlink/node/comlinkNodeStore.test.ts +2 -2
- package/src/comlink/node/getNodeState.test.ts +1 -1
- package/src/comlink/node/getNodeState.ts +2 -1
- package/src/config/__tests__/handles.test.ts +18 -12
- package/src/config/handles.ts +25 -7
- package/src/config/sanityConfig.ts +109 -79
- package/src/datasets/datasets.test.ts +2 -2
- package/src/datasets/datasets.ts +10 -4
- package/src/document/actions.test.ts +115 -33
- package/src/document/actions.ts +212 -15
- package/src/document/applyDocumentActions.test.ts +38 -12
- package/src/document/applyDocumentActions.ts +30 -9
- package/src/document/documentConstants.ts +7 -0
- package/src/document/documentStore.concurrency.test.ts +1306 -0
- package/src/document/documentStore.test.ts +673 -278
- package/src/document/documentStore.ts +162 -71
- package/src/document/events.test.ts +57 -2
- package/src/document/events.ts +82 -26
- package/src/document/listen.test.ts +56 -0
- package/src/document/listen.ts +74 -21
- package/src/document/listenerEventOperators.test.ts +311 -0
- package/src/document/listenerEventOperators.ts +217 -0
- package/src/document/patchOperations.test.ts +49 -2
- package/src/document/patchOperations.ts +50 -0
- package/src/document/permissions.test.ts +105 -60
- package/src/document/permissions.ts +44 -19
- package/src/document/processActions/create.ts +138 -0
- package/src/document/processActions/delete.ts +100 -0
- package/src/document/processActions/discard.ts +63 -0
- package/src/document/processActions/edit.ts +154 -0
- package/src/document/processActions/processActions.ts +218 -0
- package/src/document/processActions/publish.ts +120 -0
- package/src/document/processActions/releaseArchive.ts +77 -0
- package/src/document/processActions/releaseCreate.ts +59 -0
- package/src/document/processActions/releaseDelete.ts +65 -0
- package/src/document/processActions/releaseEdit.ts +37 -0
- package/src/document/processActions/releasePublish.ts +45 -0
- package/src/document/processActions/releaseSchedule.ts +87 -0
- package/src/document/processActions/releaseUtil.ts +31 -0
- package/src/document/processActions/shared.ts +308 -0
- package/src/document/processActions/unpublish.ts +85 -0
- package/src/document/processActions.test.ts +1129 -121
- package/src/document/reducers.test.ts +639 -64
- package/src/document/reducers.ts +187 -48
- package/src/document/resourceRules.test.ts +178 -0
- package/src/document/resourceRules.ts +117 -0
- package/src/document/sharedListener.test.ts +16 -14
- package/src/document/sharedListener.ts +24 -8
- package/src/document/util.ts +14 -0
- package/src/favorites/favorites.test.ts +5 -6
- package/src/installations/installations.test-d.ts +68 -0
- package/src/installations/installations.test.ts +62 -0
- package/src/installations/installations.ts +202 -0
- package/src/organization/organization.test-d.ts +36 -0
- package/src/organization/organization.test.ts +138 -0
- package/src/organization/organization.ts +138 -0
- package/src/organizations/organizations.test-d.ts +56 -0
- package/src/organizations/organizations.test.ts +150 -0
- package/src/organizations/organizations.ts +107 -0
- package/src/presence/bifurTransport.test.ts +46 -6
- package/src/presence/bifurTransport.ts +19 -2
- package/src/presence/presenceStore.test.ts +94 -23
- package/src/presence/presenceStore.ts +85 -29
- package/src/preview/getPreviewState.test.ts +137 -0
- package/src/preview/getPreviewState.ts +69 -0
- package/src/preview/previewProjectionUtils.test.ts +9 -22
- package/src/preview/previewProjectionUtils.ts +7 -3
- package/src/preview/resolvePreview.test.ts +64 -0
- package/src/preview/resolvePreview.ts +43 -0
- package/src/preview/types.ts +10 -0
- package/src/project/project.test-d.ts +37 -0
- package/src/project/project.test.ts +157 -12
- package/src/project/project.ts +138 -25
- package/src/projection/getProjectionState.test.ts +18 -20
- package/src/projection/getProjectionState.ts +23 -3
- package/src/projection/projectionQuery.ts +5 -5
- package/src/projection/projectionStore.test.ts +1 -1
- package/src/projection/resolveProjection.test.ts +1 -2
- package/src/projection/resolveProjection.ts +16 -0
- package/src/projection/subscribeToStateAndFetchBatches.test.ts +1 -1
- package/src/projection/subscribeToStateAndFetchBatches.ts +32 -23
- package/src/projection/types.ts +8 -0
- package/src/projects/projects.test-d.ts +32 -0
- package/src/projects/projects.test.ts +108 -42
- package/src/projects/projects.ts +63 -27
- package/src/query/queryStore.test.ts +259 -123
- package/src/query/queryStore.ts +78 -63
- package/src/query/reducers.test.ts +0 -42
- package/src/query/reducers.ts +2 -18
- package/src/releases/getPerspectiveState.test.ts +17 -23
- package/src/releases/getPerspectiveState.ts +8 -20
- package/src/releases/observeReleases.test.ts +127 -0
- package/src/releases/observeReleases.ts +102 -0
- package/src/releases/releasesStore.test.ts +100 -53
- package/src/releases/releasesStore.ts +69 -44
- package/src/releases/utils/sortReleases.test.ts +38 -2
- package/src/releases/utils/sortReleases.ts +15 -10
- package/src/store/createActionBinder.test.ts +87 -90
- package/src/store/createActionBinder.ts +79 -25
- package/src/store/createSanityInstance.test.ts +83 -13
- package/src/store/createSanityInstance.ts +77 -2
- package/src/store/createStateSourceAction.test.ts +64 -2
- package/src/store/createStateSourceAction.ts +39 -44
- package/src/store/createStoreInstance.test.ts +4 -2
- package/src/store/fetcherStore.test-d.ts +121 -0
- package/src/store/fetcherStore.test.ts +299 -0
- package/src/store/fetcherStore.ts +801 -0
- package/src/telemetry/environment.test.ts +119 -0
- package/src/telemetry/environment.ts +92 -0
- package/src/telemetry/events.ts +42 -0
- package/src/telemetry/initTelemetry.test.ts +432 -0
- package/src/telemetry/initTelemetry.ts +218 -0
- package/src/telemetry/telemetryManager.test.ts +327 -0
- package/src/telemetry/telemetryManager.ts +199 -0
- package/src/users/reducers.test.ts +6 -1
- package/src/users/reducers.ts +5 -6
- package/src/users/types.ts +4 -4
- package/src/users/usersStore.test.ts +16 -12
- package/src/users/usersStore.ts +5 -1
- package/src/utils/buildQuery.test.ts +14 -0
- package/src/utils/buildQuery.ts +17 -0
- package/src/utils/createFetcherStore.test.ts +7 -5
- package/src/utils/createFetcherStore.ts +8 -5
- package/src/utils/getStagingApiHost.test.ts +21 -0
- package/src/utils/getStagingApiHost.ts +14 -0
- package/src/utils/ids.test.ts +1 -29
- package/src/utils/ids.ts +0 -10
- package/src/utils/includeShape.ts +36 -0
- package/src/utils/isImportError.test.ts +72 -0
- package/src/utils/isImportError.ts +34 -0
- package/src/utils/logger.test.ts +12 -0
- package/src/utils/logger.ts +8 -3
- package/src/utils/object.test.ts +95 -0
- package/src/utils/object.ts +142 -0
- package/src/utils/setCleanupTimeout.ts +37 -0
- package/src/document/processActions.ts +0 -707
- package/src/utils/defineIntent.test.ts +0 -477
- package/src/utils/defineIntent.ts +0 -244
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {type Observable} from 'rxjs'
|
|
1
|
+
import {map, type Observable} from 'rxjs'
|
|
2
2
|
import {TestScheduler} from 'rxjs/testing'
|
|
3
3
|
import {beforeEach, describe, expect, it, vi} from 'vitest'
|
|
4
4
|
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
compareProjectOrganization,
|
|
7
7
|
type OrgVerificationResult,
|
|
8
8
|
} from '../project/organizationVerification'
|
|
9
|
-
import {
|
|
9
|
+
import {project} from '../project/project'
|
|
10
10
|
import {type SanityInstance} from '../store/createSanityInstance'
|
|
11
11
|
import {type StateSource} from '../store/createStateSourceAction'
|
|
12
12
|
import {getDashboardOrganizationId} from './dashboardUtils'
|
|
@@ -17,7 +17,7 @@ vi.mock('./dashboardUtils', () => ({
|
|
|
17
17
|
getDashboardOrganizationId: vi.fn(),
|
|
18
18
|
}))
|
|
19
19
|
vi.mock('../project/project', () => ({
|
|
20
|
-
|
|
20
|
+
project: {getState: vi.fn()},
|
|
21
21
|
}))
|
|
22
22
|
// Mock the comparison function to check its inputs
|
|
23
23
|
vi.mock('../project/organizationVerification', async (importOriginal) => {
|
|
@@ -32,10 +32,10 @@ vi.mock('../project/organizationVerification', async (importOriginal) => {
|
|
|
32
32
|
describe('observeOrganizationVerificationState', () => {
|
|
33
33
|
let testScheduler: TestScheduler
|
|
34
34
|
|
|
35
|
-
// Mock instance
|
|
35
|
+
// Mock instance (only config.projectId is used)
|
|
36
36
|
const mockInstance = {
|
|
37
|
-
config: {
|
|
38
|
-
} as
|
|
37
|
+
config: {projectId: 'proj-1', dataset: 'd'},
|
|
38
|
+
} as SanityInstance
|
|
39
39
|
|
|
40
40
|
beforeEach(() => {
|
|
41
41
|
testScheduler = new TestScheduler((actual, expected) => {
|
|
@@ -54,11 +54,11 @@ describe('observeOrganizationVerificationState', () => {
|
|
|
54
54
|
} as any) // Cast to any to bypass strict type checking in mock
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
// Helper to mock
|
|
57
|
+
// Helper to mock the project fetcher's state; wraps plain values in the snapshot envelope
|
|
58
58
|
const mockProjectOrgId = (observable: Observable<{organizationId: string | null} | null>) => {
|
|
59
|
-
vi.mocked(
|
|
59
|
+
vi.mocked(project.getState).mockReturnValue({
|
|
60
60
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
61
|
-
observable: observable
|
|
61
|
+
observable: observable.pipe(map((value) => ({data: value ?? undefined}))) as any,
|
|
62
62
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
63
63
|
} as StateSource<any>)
|
|
64
64
|
}
|
|
@@ -80,17 +80,18 @@ describe('observeOrganizationVerificationState', () => {
|
|
|
80
80
|
const expectedValues = {a: {error: null}}
|
|
81
81
|
|
|
82
82
|
const result$ = observeOrganizationVerificationState(mockInstance, [
|
|
83
|
-
mockInstance.config.
|
|
83
|
+
mockInstance.config.projectId!,
|
|
84
84
|
])
|
|
85
85
|
expectObservable(result$).toBe(expectedMarble, expectedValues)
|
|
86
86
|
})
|
|
87
87
|
expect(compareProjectOrganization).not.toHaveBeenCalled()
|
|
88
88
|
})
|
|
89
89
|
|
|
90
|
-
it('should emit {error: null} if instance
|
|
90
|
+
it('should emit {error: null} if instance.config.projectId is missing', () => {
|
|
91
91
|
const instanceWithoutProjectId = {
|
|
92
|
-
config: {
|
|
93
|
-
|
|
92
|
+
config: {projectId: undefined, dataset: 'd'},
|
|
93
|
+
// Add other required properties if SanityInstance type needs them
|
|
94
|
+
} as SanityInstance
|
|
94
95
|
|
|
95
96
|
testScheduler.run(({hot, expectObservable}) => {
|
|
96
97
|
// Dashboard org ID doesn't matter much here, but provide one
|
|
@@ -109,7 +110,7 @@ describe('observeOrganizationVerificationState', () => {
|
|
|
109
110
|
expectObservable(result$).toBe(expectedMarble, expectedValues)
|
|
110
111
|
})
|
|
111
112
|
// No project fetch or comparison should occur
|
|
112
|
-
expect(
|
|
113
|
+
expect(project.getState).not.toHaveBeenCalled()
|
|
113
114
|
expect(compareProjectOrganization).not.toHaveBeenCalled()
|
|
114
115
|
})
|
|
115
116
|
|
|
@@ -138,7 +139,7 @@ describe('observeOrganizationVerificationState', () => {
|
|
|
138
139
|
const expectedValues = {r: {error: null}} // Expect null error
|
|
139
140
|
|
|
140
141
|
const result$ = observeOrganizationVerificationState(mockInstance, [
|
|
141
|
-
mockInstance.config.
|
|
142
|
+
mockInstance.config.projectId!,
|
|
142
143
|
])
|
|
143
144
|
expectObservable(result$).toBe(expectedMarble, expectedValues)
|
|
144
145
|
})
|
|
@@ -162,7 +163,7 @@ describe('observeOrganizationVerificationState', () => {
|
|
|
162
163
|
const expectedValues = {r: comparisonResult}
|
|
163
164
|
|
|
164
165
|
const result$ = observeOrganizationVerificationState(mockInstance, [
|
|
165
|
-
mockInstance.config.
|
|
166
|
+
mockInstance.config.projectId!,
|
|
166
167
|
])
|
|
167
168
|
expectObservable(result$).toBe(expectedMarble, expectedValues)
|
|
168
169
|
})
|
|
@@ -186,7 +187,7 @@ describe('observeOrganizationVerificationState', () => {
|
|
|
186
187
|
const expectedValues = {r: comparisonResult}
|
|
187
188
|
|
|
188
189
|
const result$ = observeOrganizationVerificationState(mockInstance, [
|
|
189
|
-
mockInstance.config.
|
|
190
|
+
mockInstance.config.projectId!,
|
|
190
191
|
])
|
|
191
192
|
expectObservable(result$).toBe(expectedMarble, expectedValues)
|
|
192
193
|
})
|
|
@@ -4,14 +4,14 @@ import {
|
|
|
4
4
|
compareProjectOrganization,
|
|
5
5
|
type OrgVerificationResult,
|
|
6
6
|
} from '../project/organizationVerification'
|
|
7
|
-
import {
|
|
7
|
+
import {project} from '../project/project'
|
|
8
8
|
import {type SanityInstance} from '../store/createSanityInstance'
|
|
9
9
|
import {getDashboardOrganizationId} from './dashboardUtils'
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Creates an observable that emits the organization verification state for a given instance.
|
|
13
13
|
* It combines the dashboard organization ID (from auth context) with the
|
|
14
|
-
* project's actual organization ID (fetched via
|
|
14
|
+
* project's actual organization ID (fetched via the project fetcher) and compares them.
|
|
15
15
|
* @public
|
|
16
16
|
*/
|
|
17
17
|
export function observeOrganizationVerificationState(
|
|
@@ -24,8 +24,8 @@ export function observeOrganizationVerificationState(
|
|
|
24
24
|
|
|
25
25
|
// Create observables for each project's org ID
|
|
26
26
|
const projectOrgIdObservables = projectIds.map((id) =>
|
|
27
|
-
|
|
28
|
-
map((
|
|
27
|
+
project.getState(instance, {projectId: id}).observable.pipe(
|
|
28
|
+
map((snapshot) => ({projectId: id, orgId: snapshot.data?.organizationId ?? null})),
|
|
29
29
|
// Ensure we only proceed if the orgId is loaded, distinct prevents unnecessary checks
|
|
30
30
|
distinctUntilChanged((prev, curr) => prev.orgId === curr.orgId),
|
|
31
31
|
),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {NEVER} from 'rxjs'
|
|
2
|
-
import {beforeEach, describe, it} from 'vitest'
|
|
2
|
+
import {beforeEach, describe, expect, it, vi} from 'vitest'
|
|
3
3
|
|
|
4
4
|
import {createSanityInstance, type SanityInstance} from '../store/createSanityInstance'
|
|
5
5
|
import {AuthStateType} from './authStateType'
|
|
@@ -22,6 +22,25 @@ vi.mock('./utils', async (importOriginal) => {
|
|
|
22
22
|
vi.mock('./subscribeToStateAndFetchCurrentUser')
|
|
23
23
|
vi.mock('./subscribeToStorageEventsAndSetToken')
|
|
24
24
|
|
|
25
|
+
// Mock logger to prevent actual logging during tests
|
|
26
|
+
vi.mock('../utils/logger', async (importOriginal) => {
|
|
27
|
+
const original = await importOriginal<typeof import('../utils/logger')>()
|
|
28
|
+
return {
|
|
29
|
+
...original,
|
|
30
|
+
createLogger: vi.fn(() => ({
|
|
31
|
+
info: vi.fn(),
|
|
32
|
+
debug: vi.fn(),
|
|
33
|
+
warn: vi.fn(),
|
|
34
|
+
error: vi.fn(),
|
|
35
|
+
trace: vi.fn(),
|
|
36
|
+
})),
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
// Import createLogger after mocking
|
|
41
|
+
// eslint-disable-next-line import-x/first
|
|
42
|
+
import {createLogger} from '../utils/logger'
|
|
43
|
+
|
|
25
44
|
let instance: SanityInstance | undefined
|
|
26
45
|
|
|
27
46
|
describe('handleCallback', () => {
|
|
@@ -44,6 +63,8 @@ describe('handleCallback', () => {
|
|
|
44
63
|
vi.mocked(getAuthCode).mockReturnValue(authCode)
|
|
45
64
|
|
|
46
65
|
instance = createSanityInstance({
|
|
66
|
+
projectId: 'p',
|
|
67
|
+
dataset: 'd',
|
|
47
68
|
auth: {
|
|
48
69
|
clientFactory,
|
|
49
70
|
storageArea: {setItem} as unknown as Storage,
|
|
@@ -87,6 +108,8 @@ describe('handleCallback', () => {
|
|
|
87
108
|
vi.mocked(getAuthCode).mockReturnValue(authCode)
|
|
88
109
|
|
|
89
110
|
instance = createSanityInstance({
|
|
111
|
+
projectId: 'p',
|
|
112
|
+
dataset: 'd',
|
|
90
113
|
auth: {
|
|
91
114
|
clientFactory,
|
|
92
115
|
storageArea: {setItem} as unknown as Storage,
|
|
@@ -115,6 +138,8 @@ describe('handleCallback', () => {
|
|
|
115
138
|
const setItem = vi.fn()
|
|
116
139
|
|
|
117
140
|
instance = createSanityInstance({
|
|
141
|
+
projectId: 'p',
|
|
142
|
+
dataset: 'd',
|
|
118
143
|
auth: {
|
|
119
144
|
clientFactory,
|
|
120
145
|
storageArea: {setItem: setItem as Storage['setItem']} as Storage,
|
|
@@ -155,6 +180,8 @@ describe('handleCallback', () => {
|
|
|
155
180
|
const setItem = vi.fn()
|
|
156
181
|
|
|
157
182
|
instance = createSanityInstance({
|
|
183
|
+
projectId: 'p',
|
|
184
|
+
dataset: 'd',
|
|
158
185
|
auth: {
|
|
159
186
|
clientFactory,
|
|
160
187
|
storageArea: {setItem} as unknown as Storage,
|
|
@@ -182,6 +209,8 @@ describe('handleCallback', () => {
|
|
|
182
209
|
vi.mocked(getAuthCode).mockReturnValue(authCode)
|
|
183
210
|
|
|
184
211
|
instance = createSanityInstance({
|
|
212
|
+
projectId: 'p',
|
|
213
|
+
dataset: 'd',
|
|
185
214
|
auth: {
|
|
186
215
|
clientFactory,
|
|
187
216
|
storageArea: {setItem} as unknown as Storage,
|
|
@@ -219,6 +248,8 @@ describe('handleCallback', () => {
|
|
|
219
248
|
vi.mocked(getTokenFromLocation).mockReturnValue('hash-token')
|
|
220
249
|
|
|
221
250
|
instance = createSanityInstance({
|
|
251
|
+
projectId: 'p',
|
|
252
|
+
dataset: 'd',
|
|
222
253
|
auth: {
|
|
223
254
|
clientFactory,
|
|
224
255
|
storageArea: {setItem} as unknown as Storage,
|
|
@@ -242,5 +273,8 @@ describe('handleCallback', () => {
|
|
|
242
273
|
})
|
|
243
274
|
expect(clientFactory).not.toHaveBeenCalled()
|
|
244
275
|
expect(setItem).not.toHaveBeenCalled()
|
|
276
|
+
|
|
277
|
+
// Verify logger was called
|
|
278
|
+
expect(createLogger).toHaveBeenCalled()
|
|
245
279
|
})
|
|
246
280
|
})
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {bindActionGlobally} from '../store/createActionBinder'
|
|
2
2
|
import {DEFAULT_API_VERSION, REQUEST_TAG_PREFIX} from './authConstants'
|
|
3
|
+
import {getAuthLogger} from './authLogger'
|
|
3
4
|
import {AuthStateType} from './authStateType'
|
|
4
5
|
import {authStore, type AuthStoreState, type DashboardContext} from './authStore'
|
|
5
6
|
import {
|
|
@@ -15,16 +16,24 @@ import {
|
|
|
15
16
|
*/
|
|
16
17
|
export const handleAuthCallback = bindActionGlobally(
|
|
17
18
|
authStore,
|
|
18
|
-
async ({state}, locationHref: string = getDefaultLocation()) => {
|
|
19
|
+
async ({state, instance}, locationHref: string = getDefaultLocation()) => {
|
|
20
|
+
const logger = getAuthLogger(instance)
|
|
21
|
+
|
|
19
22
|
const {providedToken, callbackUrl, clientFactory, apiHost, storageArea, storageKey} =
|
|
20
23
|
state.get().options
|
|
21
24
|
|
|
22
25
|
// If a token is provided, no need to handle callback
|
|
23
|
-
if (providedToken)
|
|
26
|
+
if (providedToken) {
|
|
27
|
+
logger.debug('Skipping auth callback - token already provided')
|
|
28
|
+
return false
|
|
29
|
+
}
|
|
24
30
|
|
|
25
31
|
// Don't handle the callback if already in flight.
|
|
26
32
|
const {authState} = state.get()
|
|
27
|
-
if (authState.type === AuthStateType.LOGGING_IN && authState.isExchangingToken)
|
|
33
|
+
if (authState.type === AuthStateType.LOGGING_IN && authState.isExchangingToken) {
|
|
34
|
+
logger.debug('Skipping auth callback - token exchange already in progress')
|
|
35
|
+
return false
|
|
36
|
+
}
|
|
28
37
|
|
|
29
38
|
// Prepare the cleaned-up URL early. It will be returned on both success and error if an authCode/token was processed.
|
|
30
39
|
const cleanedUrl = getCleanedUrl(locationHref)
|
|
@@ -32,6 +41,7 @@ export const handleAuthCallback = bindActionGlobally(
|
|
|
32
41
|
// Check if there is a token in the is in the Dashboard iframe url hash
|
|
33
42
|
const tokenFromUrl = getTokenFromLocation(locationHref)
|
|
34
43
|
if (tokenFromUrl) {
|
|
44
|
+
logger.info('Auth token found in URL, logging in')
|
|
35
45
|
state.set('setTokenFromUrl', {
|
|
36
46
|
authState: createLoggedInAuthState(tokenFromUrl, null),
|
|
37
47
|
})
|
|
@@ -40,7 +50,10 @@ export const handleAuthCallback = bindActionGlobally(
|
|
|
40
50
|
|
|
41
51
|
// If there is no matching `authCode` then we can't handle the callback
|
|
42
52
|
const authCode = getAuthCode(callbackUrl, locationHref)
|
|
43
|
-
if (!authCode)
|
|
53
|
+
if (!authCode) {
|
|
54
|
+
logger.debug('No auth code found in callback URL')
|
|
55
|
+
return false
|
|
56
|
+
}
|
|
44
57
|
|
|
45
58
|
// Get the SanityOS dashboard context from the url
|
|
46
59
|
const parsedUrl = new URL(locationHref)
|
|
@@ -52,15 +65,18 @@ export const handleAuthCallback = bindActionGlobally(
|
|
|
52
65
|
if (parsedContext && typeof parsedContext === 'object') {
|
|
53
66
|
delete parsedContext.sid
|
|
54
67
|
dashboardContext = parsedContext
|
|
68
|
+
logger.debug('Dashboard context parsed from callback URL', {
|
|
69
|
+
hasDashboardContext: true,
|
|
70
|
+
})
|
|
55
71
|
}
|
|
56
72
|
}
|
|
57
73
|
} catch (err) {
|
|
58
74
|
// If JSON parsing fails, use empty context
|
|
59
|
-
|
|
60
|
-
console.error('Failed to parse dashboard context:', err)
|
|
75
|
+
logger.warn('Failed to parse dashboard context from callback URL', {error: err})
|
|
61
76
|
}
|
|
62
77
|
|
|
63
78
|
// Otherwise, start the exchange
|
|
79
|
+
logger.info('Exchanging auth code for token')
|
|
64
80
|
state.set('exchangeSessionForToken', {
|
|
65
81
|
authState: {type: AuthStateType.LOGGING_IN, isExchangingToken: true},
|
|
66
82
|
dashboardContext,
|
|
@@ -75,6 +91,7 @@ export const handleAuthCallback = bindActionGlobally(
|
|
|
75
91
|
...(apiHost && {apiHost}),
|
|
76
92
|
})
|
|
77
93
|
|
|
94
|
+
logger.debug('Fetching token from auth endpoint')
|
|
78
95
|
const {token} = await client.request<{token: string; label: string}>({
|
|
79
96
|
method: 'GET',
|
|
80
97
|
uri: '/auth/fetch',
|
|
@@ -82,11 +99,13 @@ export const handleAuthCallback = bindActionGlobally(
|
|
|
82
99
|
tag: 'fetch-token',
|
|
83
100
|
})
|
|
84
101
|
|
|
102
|
+
logger.info('Auth token obtained successfully, user logged in')
|
|
85
103
|
storageArea?.setItem(storageKey, JSON.stringify({token}))
|
|
86
104
|
state.set('setToken', {authState: createLoggedInAuthState(token, null)})
|
|
87
105
|
|
|
88
106
|
return cleanedUrl
|
|
89
107
|
} catch (error) {
|
|
108
|
+
logger.error('Failed to exchange auth code for token', {error})
|
|
90
109
|
state.set('exchangeSessionForTokenError', {authState: {type: AuthStateType.ERROR, error}})
|
|
91
110
|
return cleanedUrl
|
|
92
111
|
}
|
package/src/auth/logout.test.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {NEVER} from 'rxjs'
|
|
2
|
-
import {describe, it} from 'vitest'
|
|
2
|
+
import {describe, expect, it, vi} from 'vitest'
|
|
3
3
|
|
|
4
4
|
import {createSanityInstance, type SanityInstance} from '../store/createSanityInstance'
|
|
5
5
|
import {AuthStateType} from './authStateType'
|
|
@@ -17,9 +17,25 @@ vi.mock('./utils', async (importOriginal) => {
|
|
|
17
17
|
vi.mock('./subscribeToStateAndFetchCurrentUser')
|
|
18
18
|
vi.mock('./subscribeToStorageEventsAndSetToken')
|
|
19
19
|
|
|
20
|
+
// Mock logger to prevent actual logging during tests
|
|
21
|
+
vi.mock('../utils/logger', async (importOriginal) => {
|
|
22
|
+
const original = await importOriginal<typeof import('../utils/logger')>()
|
|
23
|
+
return {
|
|
24
|
+
...original,
|
|
25
|
+
createLogger: vi.fn(() => ({
|
|
26
|
+
info: vi.fn(),
|
|
27
|
+
debug: vi.fn(),
|
|
28
|
+
warn: vi.fn(),
|
|
29
|
+
error: vi.fn(),
|
|
30
|
+
trace: vi.fn(),
|
|
31
|
+
})),
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
|
|
20
35
|
let instance: SanityInstance | undefined
|
|
21
36
|
|
|
22
37
|
beforeEach(() => {
|
|
38
|
+
vi.clearAllMocks()
|
|
23
39
|
vi.mocked(subscribeToStateAndFetchCurrentUser).mockImplementation(() => NEVER.subscribe())
|
|
24
40
|
vi.mocked(subscribeToStorageEventsAndSetToken).mockImplementation(() => NEVER.subscribe())
|
|
25
41
|
})
|
|
@@ -36,6 +52,8 @@ describe('logout', () => {
|
|
|
36
52
|
const removeItem = vi.fn() as Storage['removeItem']
|
|
37
53
|
|
|
38
54
|
instance = createSanityInstance({
|
|
55
|
+
projectId: 'p',
|
|
56
|
+
dataset: 'd',
|
|
39
57
|
auth: {
|
|
40
58
|
clientFactory,
|
|
41
59
|
storageArea: {removeItem} as Storage,
|
|
@@ -57,7 +75,7 @@ describe('logout', () => {
|
|
|
57
75
|
useProjectHostname: false,
|
|
58
76
|
useCdn: false,
|
|
59
77
|
})
|
|
60
|
-
expect(mockRequest).toHaveBeenCalledWith({method: 'POST', uri: '/auth/logout'})
|
|
78
|
+
expect(mockRequest).toHaveBeenCalledWith({method: 'POST', uri: '/auth/logout', tag: 'logout'})
|
|
61
79
|
expect(removeItem).toHaveBeenCalledWith('__sanity_auth_token')
|
|
62
80
|
})
|
|
63
81
|
|
|
@@ -65,6 +83,8 @@ describe('logout', () => {
|
|
|
65
83
|
const clientFactory = vi.fn()
|
|
66
84
|
const removeItem = vi.fn() as Storage['removeItem']
|
|
67
85
|
instance = createSanityInstance({
|
|
86
|
+
projectId: 'p',
|
|
87
|
+
dataset: 'd',
|
|
68
88
|
auth: {
|
|
69
89
|
token: 'provided-token',
|
|
70
90
|
clientFactory,
|
|
@@ -89,6 +109,8 @@ describe('logout', () => {
|
|
|
89
109
|
vi.mocked(getTokenFromStorage).mockReturnValue('token')
|
|
90
110
|
|
|
91
111
|
instance = createSanityInstance({
|
|
112
|
+
projectId: 'p',
|
|
113
|
+
dataset: 'd',
|
|
92
114
|
auth: {
|
|
93
115
|
clientFactory,
|
|
94
116
|
storageArea: {removeItem} as Storage,
|
|
@@ -115,4 +137,55 @@ describe('logout', () => {
|
|
|
115
137
|
await originalLogout
|
|
116
138
|
expect(removeItem).toHaveBeenCalledTimes(2)
|
|
117
139
|
})
|
|
140
|
+
|
|
141
|
+
it('handles logout when already logged out', async () => {
|
|
142
|
+
vi.mocked(getTokenFromStorage).mockReturnValue(null)
|
|
143
|
+
const mockRequest = vi.fn()
|
|
144
|
+
const clientFactory = vi.fn().mockReturnValue({request: mockRequest})
|
|
145
|
+
const removeItem = vi.fn() as Storage['removeItem']
|
|
146
|
+
|
|
147
|
+
instance = createSanityInstance({
|
|
148
|
+
projectId: 'p',
|
|
149
|
+
dataset: 'd',
|
|
150
|
+
auth: {
|
|
151
|
+
clientFactory,
|
|
152
|
+
storageArea: {removeItem} as Storage,
|
|
153
|
+
},
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
const authState = getAuthState(instance)
|
|
157
|
+
expect(authState.getCurrent()).toMatchObject({type: AuthStateType.LOGGED_OUT})
|
|
158
|
+
|
|
159
|
+
await logout(instance)
|
|
160
|
+
|
|
161
|
+
// Should not make API call when already logged out
|
|
162
|
+
expect(clientFactory).not.toHaveBeenCalled()
|
|
163
|
+
expect(mockRequest).not.toHaveBeenCalled()
|
|
164
|
+
|
|
165
|
+
// Should still clean up storage
|
|
166
|
+
expect(removeItem).toHaveBeenCalledWith('__sanity_auth_token')
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
it('cleans up storage even if logout request fails', async () => {
|
|
170
|
+
vi.mocked(getTokenFromStorage).mockReturnValue('token')
|
|
171
|
+
const error = new Error('Logout request failed')
|
|
172
|
+
const mockRequest = vi.fn().mockRejectedValue(error)
|
|
173
|
+
const clientFactory = vi.fn().mockReturnValue({request: mockRequest})
|
|
174
|
+
const removeItem = vi.fn() as Storage['removeItem']
|
|
175
|
+
|
|
176
|
+
instance = createSanityInstance({
|
|
177
|
+
projectId: 'p',
|
|
178
|
+
dataset: 'd',
|
|
179
|
+
auth: {
|
|
180
|
+
clientFactory,
|
|
181
|
+
storageArea: {removeItem} as Storage,
|
|
182
|
+
},
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
// logout rejects when the request fails, matching the pre-logging behavior
|
|
186
|
+
await expect(logout(instance)).rejects.toThrow('Logout request failed')
|
|
187
|
+
|
|
188
|
+
// Should still clean up storage even on error
|
|
189
|
+
expect(removeItem).toHaveBeenCalledWith('__sanity_auth_token')
|
|
190
|
+
})
|
|
118
191
|
})
|
package/src/auth/logout.ts
CHANGED
|
@@ -1,25 +1,35 @@
|
|
|
1
1
|
import {bindActionGlobally} from '../store/createActionBinder'
|
|
2
2
|
import {DEFAULT_API_VERSION, REQUEST_TAG_PREFIX} from './authConstants'
|
|
3
|
+
import {getAuthLogger} from './authLogger'
|
|
3
4
|
import {AuthStateType} from './authStateType'
|
|
4
5
|
import {authStore} from './authStore'
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* @public
|
|
8
9
|
*/
|
|
9
|
-
export const logout = bindActionGlobally(authStore, async ({state}) => {
|
|
10
|
+
export const logout = bindActionGlobally(authStore, async ({state, instance}) => {
|
|
11
|
+
const logger = getAuthLogger(instance)
|
|
12
|
+
|
|
10
13
|
const {clientFactory, apiHost, providedToken, storageArea, storageKey} = state.get().options
|
|
11
14
|
|
|
12
15
|
// If a token is statically provided, logout does nothing
|
|
13
|
-
if (providedToken)
|
|
16
|
+
if (providedToken) {
|
|
17
|
+
logger.debug('Skipping logout - token is statically provided')
|
|
18
|
+
return
|
|
19
|
+
}
|
|
14
20
|
|
|
15
21
|
const {authState} = state.get()
|
|
16
22
|
|
|
17
23
|
// If we already have an inflight request, no-op
|
|
18
|
-
if (authState.type === AuthStateType.LOGGED_OUT && authState.isDestroyingSession)
|
|
24
|
+
if (authState.type === AuthStateType.LOGGED_OUT && authState.isDestroyingSession) {
|
|
25
|
+
logger.debug('Skipping logout - already in progress')
|
|
26
|
+
return
|
|
27
|
+
}
|
|
19
28
|
const token = authState.type === AuthStateType.LOGGED_IN && authState.token
|
|
20
29
|
|
|
21
30
|
try {
|
|
22
31
|
if (token) {
|
|
32
|
+
logger.info('Logging out user')
|
|
23
33
|
state.set('loggingOut', {
|
|
24
34
|
authState: {type: AuthStateType.LOGGED_OUT, isDestroyingSession: true},
|
|
25
35
|
})
|
|
@@ -33,9 +43,18 @@ export const logout = bindActionGlobally(authStore, async ({state}) => {
|
|
|
33
43
|
useCdn: false,
|
|
34
44
|
})
|
|
35
45
|
|
|
36
|
-
|
|
46
|
+
logger.debug('Calling logout endpoint')
|
|
47
|
+
await client.request<void>({uri: '/auth/logout', method: 'POST', tag: 'logout'})
|
|
48
|
+
} else {
|
|
49
|
+
logger.debug('No token to logout - already logged out')
|
|
37
50
|
}
|
|
51
|
+
} catch (error) {
|
|
52
|
+
// Re-throw to preserve the existing contract: logout rejects when the
|
|
53
|
+
// request fails. Local state is still cleared in the finally block.
|
|
54
|
+
logger.error('Logout request failed', {error})
|
|
55
|
+
throw error
|
|
38
56
|
} finally {
|
|
57
|
+
logger.info('User logged out, clearing stored tokens')
|
|
39
58
|
state.set('logoutSuccess', {
|
|
40
59
|
authState: {type: AuthStateType.LOGGED_OUT, isDestroyingSession: false},
|
|
41
60
|
})
|