@sanity/sdk 2.19.0 → 2.20.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/_exports/_internal.d.ts +2 -2
- package/dist/_exports/_internal.js +196 -22
- package/dist/_exports/_internal.js.map +1 -1
- package/dist/authStore-CWTAuieY.js +1072 -0
- package/dist/authStore-CWTAuieY.js.map +1 -0
- package/dist/clientStore-CMzJ7XhZ.js +168 -0
- package/dist/clientStore-CMzJ7XhZ.js.map +1 -0
- package/dist/createGroqSearchFilter-B0NdA-EP.js +1222 -0
- package/dist/createGroqSearchFilter-B0NdA-EP.js.map +1 -0
- package/dist/getEnv-76AjZd_f.js +178 -0
- package/dist/getEnv-76AjZd_f.js.map +1 -0
- package/dist/index.d.ts +2 -2105
- package/dist/index.js +4140 -3870
- package/dist/index.js.map +1 -1
- package/dist/rolldown-runtime-vyAXikos.js +9 -0
- package/dist/telemetryManager-w-AMXtmz.js +127 -0
- package/dist/telemetryManager-w-AMXtmz.js.map +1 -0
- package/dist/utils-BMwA8Kg9.d.ts +3334 -0
- package/dist/utils-BMwA8Kg9.d.ts.map +1 -0
- package/dist/version-ByqNiWrK.js +9 -0
- package/dist/version-ByqNiWrK.js.map +1 -0
- package/package.json +12 -20
- package/src/_exports/_internal.ts +1 -0
- package/src/_exports/index.ts +30 -0
- package/src/comments/addonDatasetStore.test.ts +215 -0
- package/src/comments/addonDatasetStore.ts +249 -0
- package/src/comments/buildCommentThreads.test.ts +83 -0
- package/src/comments/buildCommentThreads.ts +54 -0
- package/src/comments/commentActions.test.ts +540 -0
- package/src/comments/commentActions.ts +555 -0
- package/src/comments/commentFieldPath.test.ts +40 -0
- package/src/comments/commentFieldPath.ts +44 -0
- package/src/comments/commentsConstants.ts +80 -0
- package/src/comments/commentsStore.test.ts +419 -0
- package/src/comments/commentsStore.ts +483 -0
- package/src/comments/normalizeComment.test.ts +116 -0
- package/src/comments/normalizeComment.ts +45 -0
- package/src/comments/observeComments.test.ts +193 -0
- package/src/comments/observeComments.ts +113 -0
- package/src/comments/reducers.test.ts +277 -0
- package/src/comments/reducers.ts +316 -0
- package/src/comments/types.ts +249 -0
- package/src/comments/weakenReferences.test.ts +47 -0
- package/src/comments/weakenReferences.ts +29 -0
- package/src/document/documentConstants.ts +8 -0
- package/src/document/documentStore.test.ts +98 -1
- package/src/document/documentStore.ts +36 -5
- package/src/document/patchOperations.test.ts +27 -0
- package/src/document/patchOperations.ts +10 -1
- package/src/document/permissions.test.ts +3 -1
- package/src/document/reducers.test.ts +133 -0
- package/src/document/reducers.ts +18 -3
- package/src/projection/projectionStore.test.ts +0 -8
- package/dist/_chunks-dts/createGroqSearchFilter.d.ts +0 -923
- package/dist/_chunks-dts/createGroqSearchFilter.d.ts.map +0 -1
- package/dist/_chunks-es/_internal.js +0 -134
- package/dist/_chunks-es/_internal.js.map +0 -1
- package/dist/_chunks-es/createGroqSearchFilter.js +0 -1590
- package/dist/_chunks-es/createGroqSearchFilter.js.map +0 -1
- package/dist/_chunks-es/telemetryManager.js +0 -93
- package/dist/_chunks-es/telemetryManager.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
|
@@ -0,0 +1,1072 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-vyAXikos.js";
|
|
2
|
+
import { r as createLogger, t as getEnv } from "./getEnv-76AjZd_f.js";
|
|
3
|
+
import { createClient } from "@sanity/client";
|
|
4
|
+
import { EMPTY, Observable, catchError, defer, distinctUntilChanged, exhaustMap, filter, finalize, fromEvent, map, shareReplay, skip, switchMap, takeWhile, timer } from "rxjs";
|
|
5
|
+
import "@sanity/types";
|
|
6
|
+
import { devtools } from "zustand/middleware";
|
|
7
|
+
import { createStore } from "zustand/vanilla";
|
|
8
|
+
/**
|
|
9
|
+
* @beta
|
|
10
|
+
*/
|
|
11
|
+
function isDatasetResource(resource) {
|
|
12
|
+
return "projectId" in resource && "dataset" in resource;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* @beta
|
|
16
|
+
*/
|
|
17
|
+
function isMediaLibraryResource(resource) {
|
|
18
|
+
return "mediaLibraryId" in resource;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* @beta
|
|
22
|
+
*/
|
|
23
|
+
function isCanvasResource(resource) {
|
|
24
|
+
return "canvasId" in resource;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* @deprecated Use `isDatasetResource` instead.
|
|
28
|
+
* @beta
|
|
29
|
+
*/
|
|
30
|
+
function isDatasetSource(source) {
|
|
31
|
+
return isDatasetResource(source);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* @deprecated Use `isMediaLibraryResource` instead.
|
|
35
|
+
* @beta
|
|
36
|
+
*/
|
|
37
|
+
function isMediaLibrarySource(source) {
|
|
38
|
+
return isMediaLibraryResource(source);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* @deprecated Use `isCanvasResource` instead.
|
|
42
|
+
* @beta
|
|
43
|
+
*/
|
|
44
|
+
function isCanvasSource(source) {
|
|
45
|
+
return isCanvasResource(source);
|
|
46
|
+
}
|
|
47
|
+
const isReleasePerspective = (perspective) => typeof perspective == "object" && !!perspective && "releaseName" in perspective;
|
|
48
|
+
/**
|
|
49
|
+
* Creates a reactive store state container with multiple access patterns
|
|
50
|
+
* @param initialState - Initial state value for the store
|
|
51
|
+
* @param devToolsOptions - Configuration for Zustand devtools integration
|
|
52
|
+
* @returns StoreState instance with get/set/observable interface
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```typescript
|
|
56
|
+
* // Create a simple counter store
|
|
57
|
+
* const counterStore = createStoreState({ count: 0 });
|
|
58
|
+
*
|
|
59
|
+
* // Update state
|
|
60
|
+
* counterStore.set('increment', { count: 1 });
|
|
61
|
+
*
|
|
62
|
+
* // Observe changes
|
|
63
|
+
* counterStore.observable.subscribe(console.log);
|
|
64
|
+
* ```
|
|
65
|
+
*
|
|
66
|
+
* @remarks
|
|
67
|
+
* Uses Zustand for state management under the hood with RxJS for observable interface.
|
|
68
|
+
* Designed to work with both imperative and reactive programming patterns.
|
|
69
|
+
*/
|
|
70
|
+
function createStoreState(initialState, devToolsOptions) {
|
|
71
|
+
let store = createStore()(devtools(() => initialState, devToolsOptions));
|
|
72
|
+
return {
|
|
73
|
+
get: store.getState,
|
|
74
|
+
set: (actionKey, updatedState) => {
|
|
75
|
+
let currentState = store.getState(), nextState = typeof updatedState == "function" ? updatedState(currentState) : updatedState;
|
|
76
|
+
currentState !== nextState && store.setState(nextState, !1, actionKey);
|
|
77
|
+
},
|
|
78
|
+
observable: new Observable((observer) => {
|
|
79
|
+
let emit = () => observer.next(store.getState());
|
|
80
|
+
emit();
|
|
81
|
+
let unsubscribe = store.subscribe(emit);
|
|
82
|
+
return () => unsubscribe();
|
|
83
|
+
})
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Typescript helper function for creating store definitions
|
|
88
|
+
*
|
|
89
|
+
* @param storeDefinition - Configuration object defining the store
|
|
90
|
+
* @returns The finalized store definition
|
|
91
|
+
*/
|
|
92
|
+
function defineStore(storeDefinition) {
|
|
93
|
+
return storeDefinition;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Creates a new instance of a store from a store definition
|
|
97
|
+
*
|
|
98
|
+
* @param instance - The Sanity instance this store will be associated with
|
|
99
|
+
* @param storeDefinition - The store definition containing initial state and initialization logic
|
|
100
|
+
* @returns A store instance with state management and lifecycle methods
|
|
101
|
+
*
|
|
102
|
+
* @remarks
|
|
103
|
+
* The store instance maintains its own state that is scoped to the given Sanity instance.
|
|
104
|
+
* If the store definition includes an initialize function, it will be called during
|
|
105
|
+
* instance creation, and its cleanup function will be called during disposal.
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* ```ts
|
|
109
|
+
* const counterStore = defineStore({
|
|
110
|
+
* name: 'Counter',
|
|
111
|
+
* getInitialState: () => ({ count: 0 }),
|
|
112
|
+
* initialize: ({state}) => {
|
|
113
|
+
* console.log('Counter store initialized')
|
|
114
|
+
* return () => console.log('Counter store disposed')
|
|
115
|
+
* }
|
|
116
|
+
* })
|
|
117
|
+
*
|
|
118
|
+
* const instance = createStoreInstance(sanityInstance, counterStore)
|
|
119
|
+
* // Later when done with the store:
|
|
120
|
+
* instance.dispose()
|
|
121
|
+
* ```
|
|
122
|
+
*/
|
|
123
|
+
function createStoreInstance(instance, key, { name, getInitialState, initialize }) {
|
|
124
|
+
let state = createStoreState(getInitialState(instance, key), {
|
|
125
|
+
enabled: !!getEnv("DEV"),
|
|
126
|
+
name: `${name}-${key.name}`
|
|
127
|
+
}), dispose = initialize?.({
|
|
128
|
+
state,
|
|
129
|
+
instance,
|
|
130
|
+
key
|
|
131
|
+
}), disposed = { current: !1 };
|
|
132
|
+
return {
|
|
133
|
+
state,
|
|
134
|
+
dispose: () => {
|
|
135
|
+
disposed.current || (disposed.current = !0, dispose?.());
|
|
136
|
+
},
|
|
137
|
+
isDisposed: () => disposed.current
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Creates an action binder function that uses the provided key function
|
|
142
|
+
* to determine how store instances are shared between Sanity instances
|
|
143
|
+
*
|
|
144
|
+
* @param keyFn - Function that generates a key from a Sanity config
|
|
145
|
+
* @returns A function that binds store actions to Sanity instances
|
|
146
|
+
*
|
|
147
|
+
* @remarks
|
|
148
|
+
* Action binders determine how store instances are shared across multiple
|
|
149
|
+
* Sanity instances. The key function determines which instances share state.
|
|
150
|
+
*
|
|
151
|
+
* @example
|
|
152
|
+
* ```ts
|
|
153
|
+
* // Create a custom binder that uses a tenant ID for isolation
|
|
154
|
+
* const bindActionByTenant = createActionBinder(config => config.tenantId || 'default')
|
|
155
|
+
*
|
|
156
|
+
* // Use the custom binder with a store definition
|
|
157
|
+
* const getTenantUsers = bindActionByTenant(
|
|
158
|
+
* userStore,
|
|
159
|
+
* ({state}) => state.get().users
|
|
160
|
+
* )
|
|
161
|
+
* ```
|
|
162
|
+
*/
|
|
163
|
+
function createActionBinder(keyFn) {
|
|
164
|
+
let instanceRegistry = /* @__PURE__ */ new Map(), storeRegistry = /* @__PURE__ */ new Map();
|
|
165
|
+
/**
|
|
166
|
+
* Binds a store action to a store definition
|
|
167
|
+
*
|
|
168
|
+
* @param storeDefinition - The store definition
|
|
169
|
+
* @param action - The action to bind
|
|
170
|
+
* @returns A function that executes the action with a Sanity instance
|
|
171
|
+
*/
|
|
172
|
+
return function bindAction(storeDefinition, action) {
|
|
173
|
+
return function boundAction(instance, ...params) {
|
|
174
|
+
let key = keyFn(instance, ...params), compositeKey = storeDefinition.name + (key.name ? `:${key.name}` : ""), instances = instanceRegistry.get(compositeKey);
|
|
175
|
+
instances || (instances = /* @__PURE__ */ new Set(), instanceRegistry.set(compositeKey, instances)), instances.has(instance.instanceId) || (instances.add(instance.instanceId), instance.onDispose(() => {
|
|
176
|
+
instances.delete(instance.instanceId), instances.size === 0 && (storeRegistry.get(compositeKey)?.dispose(), storeRegistry.delete(compositeKey), instanceRegistry.delete(compositeKey));
|
|
177
|
+
}));
|
|
178
|
+
let storeInstance = storeRegistry.get(compositeKey);
|
|
179
|
+
return storeInstance || (storeInstance = createStoreInstance(instance, key, storeDefinition), storeRegistry.set(compositeKey, storeInstance)), action({
|
|
180
|
+
instance,
|
|
181
|
+
state: storeInstance.state,
|
|
182
|
+
key
|
|
183
|
+
}, ...params);
|
|
184
|
+
};
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
const createResourceKey = (instance, resource) => {
|
|
188
|
+
let name, resourceForKey;
|
|
189
|
+
if (resource) {
|
|
190
|
+
if (resourceForKey = resource, isDatasetResource(resource)) name = `${resource.projectId}.${resource.dataset}`;
|
|
191
|
+
else if (isMediaLibraryResource(resource)) name = `media-library:${resource.mediaLibraryId}`;
|
|
192
|
+
else if (isCanvasResource(resource)) name = `canvas:${resource.canvasId}`;
|
|
193
|
+
else throw Error(`Received invalid resource: ${JSON.stringify(resource)}`);
|
|
194
|
+
return {
|
|
195
|
+
name,
|
|
196
|
+
resource: resourceForKey
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
let { projectId, dataset } = instance.config;
|
|
200
|
+
if (!projectId || !dataset) throw Error("This API requires a project ID and dataset configured.");
|
|
201
|
+
return {
|
|
202
|
+
name: `${projectId}.${dataset}`,
|
|
203
|
+
resource: {
|
|
204
|
+
projectId,
|
|
205
|
+
dataset
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
}, bindActionByResource = createActionBinder((instance, { resource }) => createResourceKey(instance, resource)), bindActionByResourceAndPerspective = createActionBinder((instance, options) => {
|
|
209
|
+
let { resource, perspective } = options, utilizedPerspective = perspective ?? instance.config.perspective ?? "drafts", perspectiveKey;
|
|
210
|
+
perspectiveKey = isReleasePerspective(utilizedPerspective) ? utilizedPerspective.releaseName : typeof utilizedPerspective == "string" ? utilizedPerspective : JSON.stringify(utilizedPerspective);
|
|
211
|
+
let sourceKey = createResourceKey(instance, resource);
|
|
212
|
+
return {
|
|
213
|
+
name: `${sourceKey.name}:${perspectiveKey}`,
|
|
214
|
+
resource: sourceKey.resource,
|
|
215
|
+
perspective: utilizedPerspective
|
|
216
|
+
};
|
|
217
|
+
}), bindActionGlobally = createActionBinder((..._rest) => ({ name: "global" }));
|
|
218
|
+
/**
|
|
219
|
+
* Creates a state source action that generates StateSource instances
|
|
220
|
+
*
|
|
221
|
+
* @remarks
|
|
222
|
+
* The returned action can be bound to a store using createActionBinder.
|
|
223
|
+
* When invoked, returns a StateSource that stays synchronized with the store.
|
|
224
|
+
*
|
|
225
|
+
* Key performance features:
|
|
226
|
+
* - Memoizes selector contexts to prevent redundant object creation
|
|
227
|
+
* - Only runs selectors when the underlying state changes
|
|
228
|
+
*
|
|
229
|
+
* For complex data transformations, consider using memoized selectors
|
|
230
|
+
* (like those from Reselect) to prevent expensive recalculations.
|
|
231
|
+
*
|
|
232
|
+
* @example
|
|
233
|
+
* ```ts
|
|
234
|
+
* // Create a simple counter source
|
|
235
|
+
* const getCount = createStateSourceAction(({state}: SelectorContext<CounterState>) => state.count)
|
|
236
|
+
* ```
|
|
237
|
+
*
|
|
238
|
+
* @example
|
|
239
|
+
* ```ts
|
|
240
|
+
* // Create a parameterized source with setup/cleanup
|
|
241
|
+
* const getItem = createStateSourceAction({
|
|
242
|
+
* selector: ({state}, index: number) => state.items[index],
|
|
243
|
+
* onSubscribe: (context, index) => {
|
|
244
|
+
* trackItemSubscription(index)
|
|
245
|
+
* return () => untrackItem(index)
|
|
246
|
+
* }
|
|
247
|
+
* })
|
|
248
|
+
* ```
|
|
249
|
+
*
|
|
250
|
+
* @example
|
|
251
|
+
* ```ts
|
|
252
|
+
* // Binding a state source to a specific store
|
|
253
|
+
* const documentStore = defineStore<DocumentState>({
|
|
254
|
+
* name: 'Documents',
|
|
255
|
+
* getInitialState: () => ({ documents: {} }),
|
|
256
|
+
* // ...
|
|
257
|
+
* })
|
|
258
|
+
*
|
|
259
|
+
* const getDocument = bindActionByDataset(
|
|
260
|
+
* documentStore,
|
|
261
|
+
* createStateSourceAction(({state}, documentId: string) => state.documents[documentId])
|
|
262
|
+
* )
|
|
263
|
+
*
|
|
264
|
+
* // Usage
|
|
265
|
+
* const documentSource = getDocument(sanityInstance, 'doc123')
|
|
266
|
+
* const doc = documentSource.getCurrent()
|
|
267
|
+
* const subscription = documentSource.observable.subscribe(updatedDoc => {
|
|
268
|
+
* console.log('Document changed:', updatedDoc)
|
|
269
|
+
* })
|
|
270
|
+
* ```
|
|
271
|
+
*/
|
|
272
|
+
function createStateSourceAction(options) {
|
|
273
|
+
let selector = typeof options == "function" ? options : options.selector, subscribeHandler = options && "onSubscribe" in options ? options.onSubscribe : void 0, isEqual = options && "isEqual" in options ? options.isEqual ?? Object.is : Object.is, selectorContextCache = /* @__PURE__ */ new WeakMap();
|
|
274
|
+
/**
|
|
275
|
+
* The state source action implementation
|
|
276
|
+
* @param context - Store context providing access to state and instance
|
|
277
|
+
* @param params - Parameters provided when invoking the bound action
|
|
278
|
+
*/
|
|
279
|
+
function stateSourceAction(context, ...params) {
|
|
280
|
+
let { state, instance } = context, getCurrent = (currentState) => {
|
|
281
|
+
if (typeof currentState != "object" || !currentState) throw Error(`Expected store state to be an object but got "${typeof currentState}" instead`);
|
|
282
|
+
let instanceCache = selectorContextCache.get(currentState);
|
|
283
|
+
instanceCache || (instanceCache = /* @__PURE__ */ new WeakMap(), selectorContextCache.set(currentState, instanceCache));
|
|
284
|
+
let selectorContext = instanceCache.get(instance);
|
|
285
|
+
return selectorContext || (selectorContext = {
|
|
286
|
+
state: currentState,
|
|
287
|
+
instance
|
|
288
|
+
}, instanceCache.set(instance, selectorContext)), selector(selectorContext, ...params);
|
|
289
|
+
}, values = state.observable.pipe(map(getCurrent), distinctUntilChanged(isEqual));
|
|
290
|
+
subscribeHandler && (values = withSubscribeHook(values, () => subscribeHandler(context, ...params)));
|
|
291
|
+
let sharedValues = values.pipe(shareReplay({
|
|
292
|
+
bufferSize: 1,
|
|
293
|
+
refCount: !0
|
|
294
|
+
})), subscribe = (onStoreChanged) => {
|
|
295
|
+
let subscription = sharedValues.pipe(skip(1)).subscribe({
|
|
296
|
+
next: () => onStoreChanged?.(),
|
|
297
|
+
error: () => onStoreChanged?.()
|
|
298
|
+
});
|
|
299
|
+
return () => {
|
|
300
|
+
subscription.unsubscribe();
|
|
301
|
+
};
|
|
302
|
+
};
|
|
303
|
+
return {
|
|
304
|
+
getCurrent: () => getCurrent(state.get()),
|
|
305
|
+
subscribe,
|
|
306
|
+
observable: sharedValues
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
return stateSourceAction;
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Creates a new Observable which wraps an existing Observable which will invoke
|
|
313
|
+
* the function when a new subscriber appears.
|
|
314
|
+
*/
|
|
315
|
+
function withSubscribeHook(obs, fn) {
|
|
316
|
+
return defer(() => {
|
|
317
|
+
let cleanup = fn();
|
|
318
|
+
return cleanup ? obs.pipe(finalize(() => cleanup())) : obs;
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Returns the staging API host if the `__SANITY_STAGING__` build-time flag is
|
|
323
|
+
* set to `true` (mirroring how Sanity Studio detects staging builds).
|
|
324
|
+
*
|
|
325
|
+
* @internal
|
|
326
|
+
*/
|
|
327
|
+
function getStagingApiHost() {
|
|
328
|
+
if (typeof __SANITY_STAGING__ < "u" && __SANITY_STAGING__ === !0) return "https://api.sanity.work";
|
|
329
|
+
}
|
|
330
|
+
const loggers = /* @__PURE__ */ new WeakMap();
|
|
331
|
+
/**
|
|
332
|
+
* Returns the auth logger for a Sanity instance, creating it on first use.
|
|
333
|
+
*
|
|
334
|
+
* The logger is cached per instance so repeated action calls (logout,
|
|
335
|
+
* setAuthToken, handleAuthCallback) reuse the same logger instead of
|
|
336
|
+
* rebuilding it. The instance details are nested under `instanceContext`,
|
|
337
|
+
* which is what the logger's formatter reads to render the
|
|
338
|
+
* `[project:x] [dataset:y] [instance:z]` prefix.
|
|
339
|
+
*
|
|
340
|
+
* @internal
|
|
341
|
+
*/
|
|
342
|
+
function getAuthLogger(instance) {
|
|
343
|
+
let logger = loggers.get(instance);
|
|
344
|
+
return logger || (logger = createLogger("auth", { instanceContext: {
|
|
345
|
+
instanceId: instance.instanceId,
|
|
346
|
+
projectId: instance.config.projectId,
|
|
347
|
+
dataset: instance.config.dataset
|
|
348
|
+
} }), loggers.set(instance, logger)), logger;
|
|
349
|
+
}
|
|
350
|
+
const DEFAULT_BASE = "http://localhost", DEFAULT_API_VERSION = "2021-06-07", REQUEST_TAG_PREFIX = "sanity.sdk.auth";
|
|
351
|
+
/**
|
|
352
|
+
* Determines the auth mode from instance config and environment.
|
|
353
|
+
*
|
|
354
|
+
* Priority:
|
|
355
|
+
* 1. `studio` config provided → `'studio'`
|
|
356
|
+
* 2. `studioMode.enabled` in config (deprecated) → `'studio'`
|
|
357
|
+
* 3. Dashboard context detected (`_context` URL param with content) → `'dashboard'`
|
|
358
|
+
* 4. Otherwise → `'standalone'`
|
|
359
|
+
*
|
|
360
|
+
* @internal
|
|
361
|
+
*/
|
|
362
|
+
function resolveAuthMode(config, locationHref) {
|
|
363
|
+
return isStudioConfig(config) ? "studio" : detectDashboardContext(locationHref) ? "dashboard" : "standalone";
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Returns `true` when the config indicates the SDK is running inside a Studio.
|
|
367
|
+
* Checks the new `studio` field first, then falls back to the deprecated
|
|
368
|
+
* `studioMode.enabled` for backwards compatibility.
|
|
369
|
+
*
|
|
370
|
+
* @internal
|
|
371
|
+
*/
|
|
372
|
+
function isStudioConfig(config) {
|
|
373
|
+
return !!config.studio || !!config.studioMode?.enabled;
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Checks whether the given location href contains a `_context` URL parameter
|
|
377
|
+
* with a non-empty JSON object, indicating the SDK is running inside the
|
|
378
|
+
* Sanity Dashboard.
|
|
379
|
+
*
|
|
380
|
+
* @internal
|
|
381
|
+
*/
|
|
382
|
+
function detectDashboardContext(locationHref) {
|
|
383
|
+
try {
|
|
384
|
+
let contextParam = new URL(locationHref, DEFAULT_BASE).searchParams.get("_context");
|
|
385
|
+
if (!contextParam) return !1;
|
|
386
|
+
let parsed = JSON.parse(contextParam);
|
|
387
|
+
return typeof parsed == "object" && !!parsed && !Array.isArray(parsed) && Object.keys(parsed).length > 0;
|
|
388
|
+
} catch (err) {
|
|
389
|
+
return console.error("Failed to parse dashboard context from initial location:", err), !1;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Represents the various states the authentication type can be in.
|
|
394
|
+
*
|
|
395
|
+
* @public
|
|
396
|
+
*/
|
|
397
|
+
let AuthStateType = /* @__PURE__ */ function(AuthStateType) {
|
|
398
|
+
return AuthStateType.LOGGED_IN = "logged-in", AuthStateType.LOGGING_IN = "logging-in", AuthStateType.ERROR = "error", AuthStateType.LOGGED_OUT = "logged-out", AuthStateType;
|
|
399
|
+
}({});
|
|
400
|
+
const REFRESH_INTERVAL = 432e5;
|
|
401
|
+
function createTokenRefreshStream(token, clientFactory, apiHost) {
|
|
402
|
+
return new Observable((subscriber) => {
|
|
403
|
+
let subscription = clientFactory({
|
|
404
|
+
apiVersion: DEFAULT_API_VERSION,
|
|
405
|
+
requestTagPrefix: REQUEST_TAG_PREFIX,
|
|
406
|
+
useProjectHostname: !1,
|
|
407
|
+
useCdn: !1,
|
|
408
|
+
token,
|
|
409
|
+
ignoreBrowserTokenWarning: !0,
|
|
410
|
+
...apiHost && { apiHost }
|
|
411
|
+
}).observable.request({
|
|
412
|
+
uri: "auth/refresh-token",
|
|
413
|
+
method: "POST",
|
|
414
|
+
tag: "refresh-token",
|
|
415
|
+
body: { token }
|
|
416
|
+
}).subscribe(subscriber);
|
|
417
|
+
return () => subscription.unsubscribe();
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
function shouldRefreshToken(lastRefresh) {
|
|
421
|
+
return !lastRefresh || Date.now() - lastRefresh >= REFRESH_INTERVAL;
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Periodically refreshes stamped auth tokens (those containing `-st`).
|
|
425
|
+
*
|
|
426
|
+
* Two triggers cause a refresh while the user stays logged in:
|
|
427
|
+
* - a 12-hour interval timer (only fires while the tab is visible)
|
|
428
|
+
* - the tab becoming visible again after `lastTokenRefresh` has gone stale
|
|
429
|
+
*
|
|
430
|
+
* Non-stamped tokens (e.g. personal access tokens) are never refreshed.
|
|
431
|
+
*
|
|
432
|
+
* @internal
|
|
433
|
+
*/
|
|
434
|
+
const refreshStampedToken = ({ state, instance }) => {
|
|
435
|
+
let logger = getAuthLogger(instance), { clientFactory, apiHost, storageArea, storageKey } = state.get().options;
|
|
436
|
+
return state.observable.pipe(map((storeState) => storeState.authState), filter((authState) => authState.type === "logged-in"), distinctUntilChanged((prev, curr) => prev.token === curr.token), filter((authState) => authState.token.includes("-st")), exhaustMap(() => new Observable((subscriber) => {
|
|
437
|
+
let visibilityHandler = () => {
|
|
438
|
+
let currentState = state.get();
|
|
439
|
+
document.visibilityState === "visible" && currentState.authState.type === "logged-in" && shouldRefreshToken(currentState.authState.lastTokenRefresh) && createTokenRefreshStream(currentState.authState.token, clientFactory, apiHost).subscribe({
|
|
440
|
+
next: (response) => {
|
|
441
|
+
state.set("setRefreshStampedToken", (prev) => ({ authState: prev.authState.type === "logged-in" ? {
|
|
442
|
+
...prev.authState,
|
|
443
|
+
token: response.token,
|
|
444
|
+
lastTokenRefresh: Date.now()
|
|
445
|
+
} : prev.authState })), subscriber.next(response);
|
|
446
|
+
},
|
|
447
|
+
error: (error) => subscriber.error(error)
|
|
448
|
+
});
|
|
449
|
+
}, timerSubscription = timer(REFRESH_INTERVAL, REFRESH_INTERVAL).pipe(filter(() => document.visibilityState === "visible"), switchMap(() => {
|
|
450
|
+
let currentState = state.get().authState;
|
|
451
|
+
if (currentState.type !== "logged-in") throw Error("User logged out before refresh could complete");
|
|
452
|
+
return createTokenRefreshStream(currentState.token, clientFactory, apiHost);
|
|
453
|
+
})).subscribe({
|
|
454
|
+
next: (response) => {
|
|
455
|
+
state.set("setRefreshStampedToken", (prev) => ({ authState: prev.authState.type === "logged-in" ? {
|
|
456
|
+
...prev.authState,
|
|
457
|
+
token: response.token,
|
|
458
|
+
lastTokenRefresh: Date.now()
|
|
459
|
+
} : prev.authState })), subscriber.next(response);
|
|
460
|
+
},
|
|
461
|
+
error: (error) => subscriber.error(error)
|
|
462
|
+
});
|
|
463
|
+
return document.addEventListener("visibilitychange", visibilityHandler), () => {
|
|
464
|
+
document.removeEventListener("visibilitychange", visibilityHandler), timerSubscription.unsubscribe();
|
|
465
|
+
};
|
|
466
|
+
}).pipe(takeWhile(() => state.get().authState.type === "logged-in"), map((response) => ({ token: response.token }))))).subscribe({
|
|
467
|
+
next: (response) => {
|
|
468
|
+
logger.debug("Token refresh completed, updating state"), state.set("setRefreshStampedToken", (prev) => ({ authState: prev.authState.type === "logged-in" ? {
|
|
469
|
+
...prev.authState,
|
|
470
|
+
token: response.token,
|
|
471
|
+
lastTokenRefresh: Date.now()
|
|
472
|
+
} : prev.authState })), storageArea?.setItem(storageKey, JSON.stringify({ token: response.token }));
|
|
473
|
+
},
|
|
474
|
+
error: (error) => {
|
|
475
|
+
logger.error("Token refresh failed", { error }), state.set("setRefreshStampedTokenError", { authState: {
|
|
476
|
+
type: "error",
|
|
477
|
+
error
|
|
478
|
+
} });
|
|
479
|
+
}
|
|
480
|
+
});
|
|
481
|
+
}, subscribeToStateAndFetchCurrentUser = ({ state, instance }, fetchOptions) => {
|
|
482
|
+
let logger = getAuthLogger(instance), { clientFactory, apiHost } = state.get().options, useProjectHostname = fetchOptions?.useProjectHostname ?? isStudioConfig(instance.config), projectId = instance.config.projectId;
|
|
483
|
+
return state.observable.pipe(map(({ authState, options: storeOptions }) => ({
|
|
484
|
+
authState,
|
|
485
|
+
authMethod: storeOptions.authMethod
|
|
486
|
+
})), filter((value) => value.authState.type === "logged-in" && !value.authState.currentUser), map((value) => ({
|
|
487
|
+
token: value.authState.token,
|
|
488
|
+
authMethod: value.authMethod
|
|
489
|
+
})), distinctUntilChanged((prev, curr) => prev.token === curr.token && prev.authMethod === curr.authMethod)).pipe(map(({ token, authMethod }) => clientFactory({
|
|
490
|
+
apiVersion: DEFAULT_API_VERSION,
|
|
491
|
+
requestTagPrefix: REQUEST_TAG_PREFIX,
|
|
492
|
+
token: authMethod === "cookie" ? void 0 : token,
|
|
493
|
+
ignoreBrowserTokenWarning: !0,
|
|
494
|
+
useProjectHostname,
|
|
495
|
+
useCdn: !1,
|
|
496
|
+
...authMethod === "cookie" ? { withCredentials: !0 } : {},
|
|
497
|
+
...useProjectHostname && projectId ? { projectId } : {},
|
|
498
|
+
...apiHost && { apiHost }
|
|
499
|
+
})), switchMap((client) => client.observable.request({
|
|
500
|
+
uri: "/users/me",
|
|
501
|
+
method: "GET",
|
|
502
|
+
tag: "users.get-current"
|
|
503
|
+
}).pipe(
|
|
504
|
+
/**
|
|
505
|
+
* Catch inside switchMap so the outer subscription survives.
|
|
506
|
+
* Without this, a 401 terminates the subscription permanently
|
|
507
|
+
* and subsequent token refreshes via comlink never re-fetch /users/me.
|
|
508
|
+
* @see SDK-1409
|
|
509
|
+
*/
|
|
510
|
+
catchError((error) => (logger.error("Failed to fetch current user", { error }), state.set("setError", { authState: {
|
|
511
|
+
type: "error",
|
|
512
|
+
error
|
|
513
|
+
} }), EMPTY))
|
|
514
|
+
))).subscribe({ next: (currentUser) => {
|
|
515
|
+
logger.info("Current user fetched successfully", { hasEmail: !!currentUser.email }), logger.debug("Current user details", { userId: currentUser.id }), state.set("setCurrentUser", (prev) => ({ authState: prev.authState.type === "logged-in" ? {
|
|
516
|
+
...prev.authState,
|
|
517
|
+
currentUser
|
|
518
|
+
} : prev.authState }));
|
|
519
|
+
} });
|
|
520
|
+
};
|
|
521
|
+
/**
|
|
522
|
+
* Creates a properly initialized {@link LoggedInAuthState}.
|
|
523
|
+
*
|
|
524
|
+
* For stamped tokens (containing `"-st"`), `lastTokenRefresh` is set to
|
|
525
|
+
* `Date.now()` so that the visibility-change handler in
|
|
526
|
+
* {@link refreshStampedToken} does not trigger an unnecessary refresh the
|
|
527
|
+
* first time the tab becomes visible.
|
|
528
|
+
*
|
|
529
|
+
* @param token - The auth token.
|
|
530
|
+
* @param currentUser - The current user, or `null` if not yet fetched.
|
|
531
|
+
* @param existingLastTokenRefresh - An existing timestamp to preserve
|
|
532
|
+
* (e.g. when updating a token while keeping the previous refresh time).
|
|
533
|
+
* @internal
|
|
534
|
+
*/
|
|
535
|
+
function createLoggedInAuthState(token, currentUser, existingLastTokenRefresh) {
|
|
536
|
+
let isStampedToken = token.includes("-st"), lastTokenRefresh = existingLastTokenRefresh ?? (isStampedToken ? Date.now() : void 0);
|
|
537
|
+
return {
|
|
538
|
+
type: "logged-in",
|
|
539
|
+
token,
|
|
540
|
+
currentUser,
|
|
541
|
+
...lastTokenRefresh !== void 0 && { lastTokenRefresh }
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
function getAuthCode(callbackUrl, locationHref) {
|
|
545
|
+
let loc = new URL(locationHref, DEFAULT_BASE), callbackLocation = callbackUrl ? new URL(callbackUrl, DEFAULT_BASE) : void 0, callbackLocationMatches = !callbackLocation || loc.pathname.toLowerCase().startsWith(callbackLocation.pathname.toLowerCase()), authCode = new URLSearchParams(loc.hash.slice(1)).get("sid") || new URLSearchParams(loc.search).get("sid");
|
|
546
|
+
if (!authCode) {
|
|
547
|
+
let contextParam = new URLSearchParams(loc.search).get("_context");
|
|
548
|
+
if (contextParam) try {
|
|
549
|
+
let parsedContext = JSON.parse(contextParam);
|
|
550
|
+
parsedContext && typeof parsedContext == "object" && typeof parsedContext.sid == "string" && parsedContext.sid && (authCode = parsedContext.sid);
|
|
551
|
+
} catch {}
|
|
552
|
+
}
|
|
553
|
+
return authCode && callbackLocationMatches ? authCode : null;
|
|
554
|
+
}
|
|
555
|
+
function getTokenFromLocation(locationHref) {
|
|
556
|
+
let loc = new URL(locationHref);
|
|
557
|
+
return new URLSearchParams(loc.hash.slice(1)).get("token") || null;
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* Attempts to retrieve a token from the configured storage.
|
|
561
|
+
* If invalid or not present, returns null.
|
|
562
|
+
*/
|
|
563
|
+
function getTokenFromStorage(storageArea, storageKey) {
|
|
564
|
+
if (!storageArea) return null;
|
|
565
|
+
let item = storageArea.getItem(storageKey);
|
|
566
|
+
if (item === null) return null;
|
|
567
|
+
try {
|
|
568
|
+
let parsed = JSON.parse(item);
|
|
569
|
+
if (typeof parsed != "object" || !parsed || !("token" in parsed) || typeof parsed.token != "string") throw Error("Invalid stored auth data structure");
|
|
570
|
+
return parsed.token;
|
|
571
|
+
} catch {
|
|
572
|
+
return storageArea.removeItem(storageKey), null;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
/**
|
|
576
|
+
* Creates an observable stream of storage events. If not in a browser environment,
|
|
577
|
+
* returns an EMPTY observable.
|
|
578
|
+
*/
|
|
579
|
+
function getStorageEvents() {
|
|
580
|
+
return typeof window < "u" && typeof window.addEventListener == "function" ? fromEvent(window, "storage") : EMPTY;
|
|
581
|
+
}
|
|
582
|
+
/**
|
|
583
|
+
* Returns a default storage instance (localStorage) if available.
|
|
584
|
+
* If not available or an error occurs, returns undefined.
|
|
585
|
+
*/
|
|
586
|
+
function getDefaultStorage() {
|
|
587
|
+
try {
|
|
588
|
+
return typeof localStorage < "u" && typeof localStorage.getItem == "function" ? localStorage : void 0;
|
|
589
|
+
} catch {
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
/**
|
|
594
|
+
* Returns the default location to use.
|
|
595
|
+
* Tries accessing `location.href`, falls back to a default base if not available or on error.
|
|
596
|
+
*/
|
|
597
|
+
function getDefaultLocation() {
|
|
598
|
+
try {
|
|
599
|
+
return typeof location > "u" ? DEFAULT_BASE : typeof location.href == "string" ? location.href : DEFAULT_BASE;
|
|
600
|
+
} catch {
|
|
601
|
+
return DEFAULT_BASE;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
/**
|
|
605
|
+
* Cleans up the URL by removing the `token` from the hash and the `sid` and `url` search params.
|
|
606
|
+
* @internal
|
|
607
|
+
*/
|
|
608
|
+
function getCleanedUrl(locationUrl) {
|
|
609
|
+
let loc = new URL(locationUrl), rawHash = loc.hash.startsWith("#") ? loc.hash.slice(1) : loc.hash;
|
|
610
|
+
if (rawHash && rawHash.includes("=")) {
|
|
611
|
+
let hashParams = new URLSearchParams(rawHash);
|
|
612
|
+
hashParams.delete("token"), hashParams.delete("withSid");
|
|
613
|
+
let nextHash = hashParams.toString();
|
|
614
|
+
loc.hash = nextHash ? `#${nextHash}` : "";
|
|
615
|
+
}
|
|
616
|
+
return loc.searchParams.delete("sid"), loc.searchParams.delete("url"), loc.toString();
|
|
617
|
+
}
|
|
618
|
+
/** @internal Extracts the structured API error body from a ClientError, if present. */
|
|
619
|
+
function getClientErrorApiBody(error) {
|
|
620
|
+
let body = error.response?.body;
|
|
621
|
+
return body && typeof body == "object" ? body : void 0;
|
|
622
|
+
}
|
|
623
|
+
/** @internal Returns the error type string from an API error body, if available. */
|
|
624
|
+
function getClientErrorApiType(error) {
|
|
625
|
+
let body = getClientErrorApiBody(error);
|
|
626
|
+
return body?.error?.type ?? body?.type;
|
|
627
|
+
}
|
|
628
|
+
/** @internal Returns the error description string from an API error body, if available. */
|
|
629
|
+
function getClientErrorApiDescription(error) {
|
|
630
|
+
let body = getClientErrorApiBody(error);
|
|
631
|
+
return body?.error?.description ?? body?.description;
|
|
632
|
+
}
|
|
633
|
+
/** @internal True if the error represents a projectUserNotFoundError. */
|
|
634
|
+
function isProjectUserNotFoundClientError(error) {
|
|
635
|
+
return getClientErrorApiType(error) === "projectUserNotFoundError";
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* Parses the dashboard context from a location href's `_context` URL parameter.
|
|
639
|
+
* Strips the `sid` property from the parsed context (it's handled separately).
|
|
640
|
+
*
|
|
641
|
+
* @internal
|
|
642
|
+
*/
|
|
643
|
+
function parseDashboardContext(locationHref) {
|
|
644
|
+
try {
|
|
645
|
+
let contextParam = new URL(locationHref, DEFAULT_BASE).searchParams.get("_context");
|
|
646
|
+
if (contextParam) {
|
|
647
|
+
let parsedContext = JSON.parse(contextParam);
|
|
648
|
+
if (parsedContext && typeof parsedContext == "object" && !Array.isArray(parsedContext) && Object.keys(parsedContext).length > 0) return delete parsedContext.sid, parsedContext;
|
|
649
|
+
}
|
|
650
|
+
} catch (err) {
|
|
651
|
+
console.error("Failed to parse dashboard context from initial location:", err);
|
|
652
|
+
}
|
|
653
|
+
return {};
|
|
654
|
+
}
|
|
655
|
+
/**
|
|
656
|
+
* Resolves the initial auth state for Dashboard mode.
|
|
657
|
+
*
|
|
658
|
+
* In dashboard mode the token is provided by the parent frame via Comlink,
|
|
659
|
+
* so the SDK starts in a `LOGGED_OUT` state and waits for the token to arrive.
|
|
660
|
+
* The `_context` URL parameter provides dashboard metadata (orgId, mode, env).
|
|
661
|
+
*
|
|
662
|
+
* A provided token (`auth.token`) or an auth code/callback still takes
|
|
663
|
+
* precedence, even when running inside the dashboard.
|
|
664
|
+
*
|
|
665
|
+
* @internal
|
|
666
|
+
*/
|
|
667
|
+
function getDashboardInitialState(options) {
|
|
668
|
+
let { authConfig, initialLocationHref } = options, providedToken = authConfig.token, callbackUrl = authConfig.callbackUrl, storageKey = "__sanity_auth_token", dashboardContext = parseDashboardContext(initialLocationHref);
|
|
669
|
+
return providedToken ? {
|
|
670
|
+
authState: createLoggedInAuthState(providedToken, null),
|
|
671
|
+
storageKey,
|
|
672
|
+
storageArea: void 0,
|
|
673
|
+
authMethod: void 0,
|
|
674
|
+
dashboardContext
|
|
675
|
+
} : getAuthCode(callbackUrl, initialLocationHref) || getTokenFromLocation(initialLocationHref) ? {
|
|
676
|
+
authState: {
|
|
677
|
+
type: "logging-in",
|
|
678
|
+
isExchangingToken: !1
|
|
679
|
+
},
|
|
680
|
+
storageKey,
|
|
681
|
+
storageArea: void 0,
|
|
682
|
+
authMethod: void 0,
|
|
683
|
+
dashboardContext
|
|
684
|
+
} : {
|
|
685
|
+
authState: {
|
|
686
|
+
type: "logged-out",
|
|
687
|
+
isDestroyingSession: !1
|
|
688
|
+
},
|
|
689
|
+
storageKey,
|
|
690
|
+
storageArea: void 0,
|
|
691
|
+
authMethod: void 0,
|
|
692
|
+
dashboardContext
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* Initialize Dashboard auth subscriptions:
|
|
697
|
+
* - Subscribe to state changes and fetch current user
|
|
698
|
+
* - Start stamped token refresh
|
|
699
|
+
*
|
|
700
|
+
* Note: storage events are NOT subscribed in dashboard mode since
|
|
701
|
+
* the dashboard does not use localStorage for token persistence.
|
|
702
|
+
*
|
|
703
|
+
* @internal
|
|
704
|
+
*/
|
|
705
|
+
function initializeDashboardAuth(context, tokenRefresherRunning) {
|
|
706
|
+
let subscriptions = [], startedRefresher = !1;
|
|
707
|
+
return subscriptions.push(subscribeToStateAndFetchCurrentUser(context, { useProjectHostname: !1 })), tokenRefresherRunning || (startedRefresher = !0, subscriptions.push(refreshStampedToken(context))), {
|
|
708
|
+
dispose: () => {
|
|
709
|
+
for (let subscription of subscriptions) subscription.unsubscribe();
|
|
710
|
+
},
|
|
711
|
+
tokenRefresherStarted: startedRefresher
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
const subscribeToStorageEventsAndSetToken = ({ state }) => {
|
|
715
|
+
let { storageArea, storageKey } = state.get().options;
|
|
716
|
+
return defer(getStorageEvents).pipe(filter((e) => e.storageArea === storageArea && e.key === storageKey), map(() => getTokenFromStorage(storageArea, storageKey)), distinctUntilChanged()).subscribe((token) => {
|
|
717
|
+
state.set("updateTokenFromStorageEvent", { authState: token ? createLoggedInAuthState(token, null) : {
|
|
718
|
+
type: "logged-out",
|
|
719
|
+
isDestroyingSession: !1
|
|
720
|
+
} });
|
|
721
|
+
});
|
|
722
|
+
};
|
|
723
|
+
/**
|
|
724
|
+
* Resolves the initial auth state for Standalone mode.
|
|
725
|
+
*
|
|
726
|
+
* Token discovery order:
|
|
727
|
+
* 1. Provided token (`auth.token` in config)
|
|
728
|
+
* 2. Auth code or token from location (OAuth callback)
|
|
729
|
+
* 3. localStorage: `__sanity_auth_token`
|
|
730
|
+
* 4. Falls back to `LOGGED_OUT`
|
|
731
|
+
*
|
|
732
|
+
* @internal
|
|
733
|
+
*/
|
|
734
|
+
function getStandaloneInitialState(options) {
|
|
735
|
+
let { authConfig, initialLocationHref } = options, providedToken = authConfig.token, callbackUrl = authConfig.callbackUrl, storageKey = "__sanity_auth_token", storageArea = authConfig.storageArea ?? getDefaultStorage();
|
|
736
|
+
if (providedToken) return {
|
|
737
|
+
authState: createLoggedInAuthState(providedToken, null),
|
|
738
|
+
storageKey,
|
|
739
|
+
storageArea,
|
|
740
|
+
authMethod: void 0,
|
|
741
|
+
dashboardContext: {}
|
|
742
|
+
};
|
|
743
|
+
if (getAuthCode(callbackUrl, initialLocationHref) || getTokenFromLocation(initialLocationHref)) return {
|
|
744
|
+
authState: {
|
|
745
|
+
type: "logging-in",
|
|
746
|
+
isExchangingToken: !1
|
|
747
|
+
},
|
|
748
|
+
storageKey,
|
|
749
|
+
storageArea,
|
|
750
|
+
authMethod: void 0,
|
|
751
|
+
dashboardContext: {}
|
|
752
|
+
};
|
|
753
|
+
let token = getTokenFromStorage(storageArea, storageKey);
|
|
754
|
+
return token ? {
|
|
755
|
+
authState: createLoggedInAuthState(token, null),
|
|
756
|
+
storageKey,
|
|
757
|
+
storageArea,
|
|
758
|
+
authMethod: "localstorage",
|
|
759
|
+
dashboardContext: {}
|
|
760
|
+
} : {
|
|
761
|
+
authState: {
|
|
762
|
+
type: "logged-out",
|
|
763
|
+
isDestroyingSession: !1
|
|
764
|
+
},
|
|
765
|
+
storageKey,
|
|
766
|
+
storageArea,
|
|
767
|
+
authMethod: void 0,
|
|
768
|
+
dashboardContext: {}
|
|
769
|
+
};
|
|
770
|
+
}
|
|
771
|
+
/**
|
|
772
|
+
* Initialize Standalone auth subscriptions:
|
|
773
|
+
* - Subscribe to state changes and fetch current user
|
|
774
|
+
* - Subscribe to storage events for token key
|
|
775
|
+
* - Start stamped token refresh
|
|
776
|
+
*
|
|
777
|
+
* @internal
|
|
778
|
+
*/
|
|
779
|
+
function initializeStandaloneAuth(context, tokenRefresherRunning) {
|
|
780
|
+
let subscriptions = [], startedRefresher = !1;
|
|
781
|
+
return subscriptions.push(subscribeToStateAndFetchCurrentUser(context, { useProjectHostname: !1 })), context.state.get().options?.storageArea && subscriptions.push(subscribeToStorageEventsAndSetToken(context)), tokenRefresherRunning || (startedRefresher = !0, subscriptions.push(refreshStampedToken(context))), {
|
|
782
|
+
dispose: () => {
|
|
783
|
+
for (let subscription of subscriptions) subscription.unsubscribe();
|
|
784
|
+
},
|
|
785
|
+
tokenRefresherStarted: startedRefresher
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
/**
|
|
789
|
+
* Attempts to check for cookie auth by making a withCredentials request to the users endpoint.
|
|
790
|
+
* @param projectId - The project ID to check for cookie auth.
|
|
791
|
+
* @param clientFactory - A factory function that creates a Sanity client.
|
|
792
|
+
* @returns True if the user is authenticated, false otherwise.
|
|
793
|
+
* @internal
|
|
794
|
+
*/
|
|
795
|
+
async function checkForCookieAuth(projectId, clientFactory) {
|
|
796
|
+
if (!projectId) return !1;
|
|
797
|
+
try {
|
|
798
|
+
let user = await clientFactory({
|
|
799
|
+
projectId,
|
|
800
|
+
useCdn: !1,
|
|
801
|
+
requestTagPrefix: REQUEST_TAG_PREFIX,
|
|
802
|
+
timeout: 1e4
|
|
803
|
+
}).request({
|
|
804
|
+
uri: "/users/me",
|
|
805
|
+
withCredentials: !0,
|
|
806
|
+
tag: "users.get-current"
|
|
807
|
+
});
|
|
808
|
+
return typeof user == "object" && !!user && typeof user.id == "string";
|
|
809
|
+
} catch {
|
|
810
|
+
return !1;
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
/**
|
|
814
|
+
* Attempts to retrieve a studio token from local storage.
|
|
815
|
+
* @param storageArea - The storage area to retrieve the token from.
|
|
816
|
+
* @param storageKey - The storage key to retrieve the token from.
|
|
817
|
+
* @returns The studio token or null if it does not exist.
|
|
818
|
+
* @internal
|
|
819
|
+
*/
|
|
820
|
+
function getStudioTokenFromLocalStorage(storageArea, storageKey) {
|
|
821
|
+
return !storageArea || !storageKey ? null : getTokenFromStorage(storageArea, storageKey) || null;
|
|
822
|
+
}
|
|
823
|
+
/**
|
|
824
|
+
* Resolves the initial auth state for Studio mode.
|
|
825
|
+
*
|
|
826
|
+
* When a `tokenSource` is provided (via `config.studio.auth.token`), the
|
|
827
|
+
* initial state is `LOGGING_IN` — the actual token arrives asynchronously
|
|
828
|
+
* via the subscription set up in `initializeStudioAuth`.
|
|
829
|
+
*
|
|
830
|
+
* Fallback token discovery order (no tokenSource):
|
|
831
|
+
* 1. Provided token (`auth.token` in config)
|
|
832
|
+
* 2. localStorage: `__studio_auth_token_${projectId}`
|
|
833
|
+
* 3. Falls back to `LOGGED_OUT` (cookie auth is checked async in `initializeStudioAuth`)
|
|
834
|
+
*
|
|
835
|
+
* @internal
|
|
836
|
+
*/
|
|
837
|
+
function getStudioInitialState(options) {
|
|
838
|
+
let { authConfig, projectId, tokenSource } = options, storageArea = authConfig.storageArea ?? getDefaultStorage(), studioStorageKey = `__studio_auth_token_${projectId ?? ""}`;
|
|
839
|
+
if (tokenSource) return {
|
|
840
|
+
authState: {
|
|
841
|
+
type: "logging-in",
|
|
842
|
+
isExchangingToken: !1
|
|
843
|
+
},
|
|
844
|
+
storageKey: studioStorageKey,
|
|
845
|
+
storageArea,
|
|
846
|
+
authMethod: void 0,
|
|
847
|
+
dashboardContext: {}
|
|
848
|
+
};
|
|
849
|
+
let providedToken = authConfig.token, authMethod, token = getStudioTokenFromLocalStorage(storageArea, studioStorageKey);
|
|
850
|
+
return token && (authMethod = "localstorage"), providedToken ? {
|
|
851
|
+
authState: createLoggedInAuthState(providedToken, null),
|
|
852
|
+
storageKey: studioStorageKey,
|
|
853
|
+
storageArea,
|
|
854
|
+
authMethod,
|
|
855
|
+
dashboardContext: {}
|
|
856
|
+
} : token ? {
|
|
857
|
+
authState: createLoggedInAuthState(token, null),
|
|
858
|
+
storageKey: studioStorageKey,
|
|
859
|
+
storageArea,
|
|
860
|
+
authMethod: "localstorage",
|
|
861
|
+
dashboardContext: {}
|
|
862
|
+
} : {
|
|
863
|
+
authState: {
|
|
864
|
+
type: "logged-out",
|
|
865
|
+
isDestroyingSession: !1
|
|
866
|
+
},
|
|
867
|
+
storageKey: studioStorageKey,
|
|
868
|
+
storageArea,
|
|
869
|
+
authMethod: void 0,
|
|
870
|
+
dashboardContext: {}
|
|
871
|
+
};
|
|
872
|
+
}
|
|
873
|
+
/**
|
|
874
|
+
* Initialize Studio auth subscriptions.
|
|
875
|
+
*
|
|
876
|
+
* When a `tokenSource` is available (reactive path), subscribes to it for
|
|
877
|
+
* ongoing token sync. The Studio is the token authority — no independent
|
|
878
|
+
* token refresh or cookie auth probing is needed.
|
|
879
|
+
*
|
|
880
|
+
* Fallback path (no tokenSource):
|
|
881
|
+
* - Subscribe to state changes and fetch current user
|
|
882
|
+
* - Subscribe to storage events for studio token key
|
|
883
|
+
* - Check for cookie auth asynchronously if no token was found
|
|
884
|
+
* - Start stamped token refresh
|
|
885
|
+
*
|
|
886
|
+
* @internal
|
|
887
|
+
*/
|
|
888
|
+
function initializeStudioAuth(context, tokenRefresherRunning) {
|
|
889
|
+
let tokenSource = context.instance.config.studio?.auth?.token;
|
|
890
|
+
return tokenSource ? initializeWithTokenSource(context, tokenSource) : initializeWithFallback(context, tokenRefresherRunning);
|
|
891
|
+
}
|
|
892
|
+
/**
|
|
893
|
+
* Subscribe to a reactive token source from the Studio workspace.
|
|
894
|
+
*
|
|
895
|
+
* When the token source emits a non-null token, the SDK uses it directly.
|
|
896
|
+
* When it emits `null`, the behavior depends on the `authenticated` flag
|
|
897
|
+
* from the Studio's workspace config:
|
|
898
|
+
*
|
|
899
|
+
* - `authenticated: true` — the Studio has already verified the user is
|
|
900
|
+
* logged in (e.g. via cookie auth). The SDK treats the null token as
|
|
901
|
+
* cookie-based auth and stays in the LOGGED_IN state.
|
|
902
|
+
*
|
|
903
|
+
* - `authenticated` absent/false — the user is genuinely not authenticated;
|
|
904
|
+
* transition to LOGGED_OUT.
|
|
905
|
+
*
|
|
906
|
+
* No async cookie probing is needed here because this code path only runs
|
|
907
|
+
* when a Studio provides SDKStudioContext, and the Studio's Workspace type
|
|
908
|
+
* always includes `authenticated`. The async `checkForCookieAuth` fallback
|
|
909
|
+
* remains in `initializeWithFallback` for the non-Studio path.
|
|
910
|
+
*/
|
|
911
|
+
function initializeWithTokenSource(context, tokenSource) {
|
|
912
|
+
let subscriptions = [], studioAuthenticated = context.instance.config.studio?.authenticated === !0;
|
|
913
|
+
subscriptions.push(subscribeToStateAndFetchCurrentUser(context, { useProjectHostname: !0 }));
|
|
914
|
+
let tokenSub = tokenSource.subscribe({ next: (token) => {
|
|
915
|
+
let { state } = context;
|
|
916
|
+
token ? state.set("studioTokenSource", (prev) => ({
|
|
917
|
+
options: {
|
|
918
|
+
...prev.options,
|
|
919
|
+
authMethod: void 0
|
|
920
|
+
},
|
|
921
|
+
authState: createLoggedInAuthState(token, null)
|
|
922
|
+
})) : studioAuthenticated ? state.set("studioTokenSourceCookieAuth", (prev) => ({
|
|
923
|
+
options: {
|
|
924
|
+
...prev.options,
|
|
925
|
+
authMethod: "cookie"
|
|
926
|
+
},
|
|
927
|
+
authState: prev.authState.type === "logged-in" ? prev.authState : createLoggedInAuthState("", null)
|
|
928
|
+
})) : state.set("studioTokenSourceLoggedOut", (prev) => ({
|
|
929
|
+
options: {
|
|
930
|
+
...prev.options,
|
|
931
|
+
authMethod: void 0
|
|
932
|
+
},
|
|
933
|
+
authState: {
|
|
934
|
+
type: "logged-out",
|
|
935
|
+
isDestroyingSession: !1
|
|
936
|
+
}
|
|
937
|
+
}));
|
|
938
|
+
} });
|
|
939
|
+
return {
|
|
940
|
+
dispose: () => {
|
|
941
|
+
tokenSub.unsubscribe();
|
|
942
|
+
for (let subscription of subscriptions) subscription.unsubscribe();
|
|
943
|
+
},
|
|
944
|
+
tokenRefresherStarted: !1
|
|
945
|
+
};
|
|
946
|
+
}
|
|
947
|
+
/**
|
|
948
|
+
* Fallback initialization when no reactive token source is available.
|
|
949
|
+
* Uses localStorage/cookie discovery (existing behavior).
|
|
950
|
+
*/
|
|
951
|
+
function initializeWithFallback(context, tokenRefresherRunning) {
|
|
952
|
+
let subscriptions = [], startedRefresher = !1;
|
|
953
|
+
subscriptions.push(subscribeToStateAndFetchCurrentUser(context, { useProjectHostname: !0 })), context.state.get().options?.storageArea && subscriptions.push(subscribeToStorageEventsAndSetToken(context));
|
|
954
|
+
try {
|
|
955
|
+
let { instance, state } = context;
|
|
956
|
+
if (!(state.get().authState?.type === "logged-in" && state.get().authState.token)) {
|
|
957
|
+
let projectIdValue = instance.config.projectId, clientFactory = state.get().options.clientFactory;
|
|
958
|
+
checkForCookieAuth(projectIdValue, clientFactory).then((isCookieAuthEnabled) => {
|
|
959
|
+
isCookieAuthEnabled && state.set("enableCookieAuth", (prev) => ({
|
|
960
|
+
options: {
|
|
961
|
+
...prev.options,
|
|
962
|
+
authMethod: "cookie"
|
|
963
|
+
},
|
|
964
|
+
authState: prev.authState.type === "logged-in" ? prev.authState : createLoggedInAuthState("", null)
|
|
965
|
+
}));
|
|
966
|
+
});
|
|
967
|
+
}
|
|
968
|
+
} catch {}
|
|
969
|
+
return tokenRefresherRunning || (startedRefresher = !0, subscriptions.push(refreshStampedToken(context))), {
|
|
970
|
+
dispose: () => {
|
|
971
|
+
for (let subscription of subscriptions) subscription.unsubscribe();
|
|
972
|
+
},
|
|
973
|
+
tokenRefresherStarted: startedRefresher
|
|
974
|
+
};
|
|
975
|
+
}
|
|
976
|
+
var authStore_exports = /* @__PURE__ */ __exportAll({
|
|
977
|
+
authStore: () => authStore,
|
|
978
|
+
getAuthMethodState: () => getAuthMethodState,
|
|
979
|
+
getAuthState: () => getAuthState,
|
|
980
|
+
getCurrentUserState: () => getCurrentUserState,
|
|
981
|
+
getDashboardOrganizationId: () => getDashboardOrganizationId,
|
|
982
|
+
getIsInDashboardState: () => getIsInDashboardState,
|
|
983
|
+
getLoginUrlState: () => getLoginUrlState,
|
|
984
|
+
getTokenState: () => getTokenState,
|
|
985
|
+
setAuthToken: () => setAuthToken
|
|
986
|
+
});
|
|
987
|
+
let tokenRefresherRunning = !1;
|
|
988
|
+
const authStore = defineStore({
|
|
989
|
+
name: "Auth",
|
|
990
|
+
getInitialState(instance) {
|
|
991
|
+
let logger = getAuthLogger(instance);
|
|
992
|
+
logger.debug("Initializing auth store", {
|
|
993
|
+
hasProvidedToken: !!instance.config.auth?.token,
|
|
994
|
+
hasCustomProviders: !!(instance.config.auth?.providers && instance.config.auth.providers.length > 0),
|
|
995
|
+
studioMode: instance.config.studioMode?.enabled ?? !1
|
|
996
|
+
});
|
|
997
|
+
let { apiHost: configApiHost, callbackUrl, providers: customProviders, token: providedToken, clientFactory = createClient, initialLocationHref = getDefaultLocation() } = instance.config.auth ?? {}, apiHost = configApiHost ?? getStagingApiHost(), authConfig = instance.config.auth ?? {}, loginDomain = "https://www.sanity.io";
|
|
998
|
+
try {
|
|
999
|
+
apiHost && new URL(apiHost).hostname.endsWith(".sanity.work") && (loginDomain = "https://www.sanity.work");
|
|
1000
|
+
} catch {}
|
|
1001
|
+
let loginUrl = new URL("/login", loginDomain);
|
|
1002
|
+
loginUrl.searchParams.set("origin", getCleanedUrl(initialLocationHref)), loginUrl.searchParams.set("type", "stampedToken"), loginUrl.searchParams.set("withSid", "true");
|
|
1003
|
+
let mode = resolveAuthMode(instance.config, initialLocationHref), strategyOptions = {
|
|
1004
|
+
authConfig,
|
|
1005
|
+
projectId: instance.config.projectId,
|
|
1006
|
+
initialLocationHref,
|
|
1007
|
+
clientFactory,
|
|
1008
|
+
tokenSource: instance.config.studio?.auth?.token
|
|
1009
|
+
}, result;
|
|
1010
|
+
switch (mode) {
|
|
1011
|
+
case "studio":
|
|
1012
|
+
result = getStudioInitialState(strategyOptions);
|
|
1013
|
+
break;
|
|
1014
|
+
case "dashboard":
|
|
1015
|
+
result = getDashboardInitialState(strategyOptions);
|
|
1016
|
+
break;
|
|
1017
|
+
case "standalone": result = getStandaloneInitialState(strategyOptions);
|
|
1018
|
+
}
|
|
1019
|
+
return logger.debug("Auth state initialized", {
|
|
1020
|
+
authStateType: result.authState.type,
|
|
1021
|
+
mode,
|
|
1022
|
+
authMethod: result.authMethod
|
|
1023
|
+
}), {
|
|
1024
|
+
authState: result.authState,
|
|
1025
|
+
dashboardContext: result.dashboardContext,
|
|
1026
|
+
options: {
|
|
1027
|
+
apiHost,
|
|
1028
|
+
loginUrl: loginUrl.toString(),
|
|
1029
|
+
callbackUrl,
|
|
1030
|
+
customProviders,
|
|
1031
|
+
providedToken,
|
|
1032
|
+
clientFactory,
|
|
1033
|
+
initialLocationHref,
|
|
1034
|
+
storageKey: result.storageKey,
|
|
1035
|
+
storageArea: result.storageArea,
|
|
1036
|
+
authMethod: result.authMethod
|
|
1037
|
+
}
|
|
1038
|
+
};
|
|
1039
|
+
},
|
|
1040
|
+
initialize(context) {
|
|
1041
|
+
let logger = getAuthLogger(context.instance), initialLocationHref = context.state.get().options?.initialLocationHref ?? getDefaultLocation(), mode = resolveAuthMode(context.instance.config, initialLocationHref);
|
|
1042
|
+
logger.debug("Setting up auth subscriptions", { mode });
|
|
1043
|
+
let initResult;
|
|
1044
|
+
switch (mode) {
|
|
1045
|
+
case "studio":
|
|
1046
|
+
initResult = initializeStudioAuth(context, tokenRefresherRunning);
|
|
1047
|
+
break;
|
|
1048
|
+
case "dashboard":
|
|
1049
|
+
initResult = initializeDashboardAuth(context, tokenRefresherRunning);
|
|
1050
|
+
break;
|
|
1051
|
+
case "standalone": initResult = initializeStandaloneAuth(context, tokenRefresherRunning);
|
|
1052
|
+
}
|
|
1053
|
+
return initResult.tokenRefresherStarted && (tokenRefresherRunning = !0), () => {
|
|
1054
|
+
logger.debug("Cleaning up auth subscriptions"), initResult.dispose();
|
|
1055
|
+
};
|
|
1056
|
+
}
|
|
1057
|
+
}), getCurrentUserState = bindActionGlobally(authStore, createStateSourceAction(({ state: { authState } }) => authState.type === "logged-in" ? authState.currentUser : null)), getTokenState = bindActionGlobally(authStore, createStateSourceAction(({ state: { authState } }) => authState.type === "logged-in" ? authState.token : null)), getAuthMethodState = bindActionGlobally(authStore, createStateSourceAction(({ state: { options } }) => options.authMethod)), getLoginUrlState = bindActionGlobally(authStore, createStateSourceAction(({ state: { options } }) => options.loginUrl)), getAuthState = bindActionGlobally(authStore, createStateSourceAction(({ state: { authState } }) => authState)), getDashboardOrganizationId = bindActionGlobally(authStore, createStateSourceAction(({ state: { dashboardContext } }) => dashboardContext?.orgId)), getIsInDashboardState = bindActionGlobally(authStore, createStateSourceAction(({ state: { dashboardContext } }) => !!dashboardContext && Object.keys(dashboardContext).length > 0)), setAuthToken = bindActionGlobally(authStore, ({ state, instance }, token) => {
|
|
1058
|
+
let logger = getAuthLogger(instance), currentAuthState = state.get().authState;
|
|
1059
|
+
if (token) {
|
|
1060
|
+
if (currentAuthState.type !== "logged-in" || currentAuthState.token !== token) {
|
|
1061
|
+
logger.info("Setting auth token");
|
|
1062
|
+
let currentUser = currentAuthState.type === "logged-in" ? currentAuthState.currentUser : null, preservedLastTokenRefresh = currentAuthState.type === "logged-in" ? currentAuthState.lastTokenRefresh : void 0;
|
|
1063
|
+
state.set("setToken", { authState: createLoggedInAuthState(token, currentUser, preservedLastTokenRefresh) });
|
|
1064
|
+
}
|
|
1065
|
+
} else currentAuthState.type !== "logged-out" && (logger.info("Clearing auth token"), state.set("setToken", { authState: {
|
|
1066
|
+
type: "logged-out",
|
|
1067
|
+
isDestroyingSession: !1
|
|
1068
|
+
} }));
|
|
1069
|
+
});
|
|
1070
|
+
export { defineStore as A, REQUEST_TAG_PREFIX as C, bindActionByResource as D, createStateSourceAction as E, isDatasetSource as F, isMediaLibraryResource as I, isMediaLibrarySource as L, isCanvasResource as M, isCanvasSource as N, bindActionByResourceAndPerspective as O, isDatasetResource as P, DEFAULT_API_VERSION as S, getStagingApiHost as T, getDefaultLocation as _, getCurrentUserState as a, AuthStateType as b, getLoginUrlState as c, createLoggedInAuthState as d, getAuthCode as f, getClientErrorApiType as g, getClientErrorApiDescription as h, getAuthState as i, isReleasePerspective as j, bindActionGlobally as k, getTokenState as l, getClientErrorApiBody as m, authStore_exports as n, getDashboardOrganizationId as o, getCleanedUrl as p, getAuthMethodState as r, getIsInDashboardState as s, authStore as t, setAuthToken as u, getTokenFromLocation as v, getAuthLogger as w, isStudioConfig as x, isProjectUserNotFoundClientError as y };
|
|
1071
|
+
|
|
1072
|
+
//# sourceMappingURL=authStore-CWTAuieY.js.map
|