@sanity/sdk-react 3.1.0 → 3.2.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/_exports/dashboard-internal.d.ts +2 -0
- package/dist/_exports/dashboard-internal.js +2 -0
- package/dist/_exports/dashboard.d.ts +457 -7
- package/dist/_exports/dashboard.d.ts.map +1 -1
- package/dist/_exports/dashboard.js +623 -2
- package/dist/_exports/dashboard.js.map +1 -1
- package/dist/index.d.ts +90 -87
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -5
- package/dist/index.js.map +1 -1
- package/dist/{useStudioWorkspacesByProjectIdDataset-DxUlukmF.js → useStudioWorkspacesByProjectIdDataset-BZ_Ud887.js} +53 -56
- package/dist/useStudioWorkspacesByProjectIdDataset-BZ_Ud887.js.map +1 -0
- package/package.json +34 -32
- package/src/_exports/dashboard-internal.ts +1 -0
- package/src/_exports/dashboard.test-d.ts +64 -0
- package/src/_exports/dashboard.ts +70 -0
- package/src/_exports/index.ts +1 -1
- package/src/components/auth/AuthBoundary.test.tsx +34 -0
- package/src/components/auth/AuthBoundary.tsx +1 -2
- package/src/context/DashboardTokenRefresh.test.tsx +124 -22
- package/src/context/DashboardTokenRefresh.tsx +31 -15
- package/src/dashboard/createRemoteInstance.test.ts +168 -0
- package/src/dashboard/createRemoteInstance.ts +123 -0
- package/src/dashboard/module.ts +39 -0
- package/src/dashboard/remoteClientState.ts +27 -0
- package/src/dashboard/urlFor.test.ts +246 -0
- package/src/dashboard/urlFor.ts +462 -0
- package/src/hooks/applications/useApplication.test-d.ts +1 -1
- package/src/hooks/dashboard/useApplication.test.tsx +59 -0
- package/src/hooks/dashboard/useApplication.ts +22 -0
- package/src/hooks/dashboard/useApplicationConfig.test.tsx +106 -0
- package/src/hooks/dashboard/useApplicationConfig.ts +45 -0
- package/src/hooks/dashboard/useApplicationConfigs.test.tsx +89 -0
- package/src/hooks/dashboard/useApplicationConfigs.ts +26 -0
- package/src/hooks/dashboard/useApplicationForegroundId.test.tsx +54 -0
- package/src/hooks/dashboard/useApplicationForegroundId.ts +22 -0
- package/src/hooks/dashboard/useApplications.test.tsx +249 -0
- package/src/hooks/dashboard/useApplications.ts +128 -0
- package/src/hooks/dashboard/useEmit.test.tsx +124 -0
- package/src/hooks/dashboard/useEmit.ts +78 -0
- package/src/hooks/dashboard/useNavigate.ts +1 -3
- package/src/hooks/dashboard/useRemoteClient.test.tsx +88 -0
- package/src/hooks/dashboard/useRemoteClient.ts +10 -0
- package/src/hooks/dashboard/useTopic.test.tsx +200 -0
- package/src/hooks/dashboard/useTopic.ts +29 -0
- package/src/hooks/dashboard/useUpdateFavorite.test.tsx +8 -1
- package/src/hooks/document/useApplyDocumentActions.ts +3 -4
- package/src/hooks/document/useCreateDocument.ts +2 -3
- package/src/hooks/document/useDocument.ts +11 -6
- package/src/hooks/document/useEditDocument.ts +5 -5
- package/src/hooks/projection/useDocumentProjection.ts +6 -7
- package/src/hooks/query/useQuery.ts +3 -4
- package/dist/useStudioWorkspacesByProjectIdDataset-DxUlukmF.js.map +0 -1
- package/src/context/dashboardToken.ts +0 -63
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { c } from "react-compiler-runtime";
|
|
2
2
|
import "@sanity/client";
|
|
3
3
|
import { AuthStateType, getAuthState, getDashboardOrganizationId, setAuthToken } from "@sanity/sdk";
|
|
4
|
+
import { getDashboardMessageBus } from "@sanity/sdk/_internal";
|
|
5
|
+
import * as React$1 from "react";
|
|
4
6
|
import { createContext, useContext, useEffect, useRef, useState, useSyncExternalStore } from "react";
|
|
5
7
|
import { SDK_CHANNEL_NAME, SDK_NODE_NAME } from "@sanity/message-protocol";
|
|
6
8
|
import { getNodeState } from "@sanity/sdk/comlink";
|
|
7
|
-
import { filter, firstValueFrom,
|
|
9
|
+
import { defer, filter, firstValueFrom, of } from "rxjs";
|
|
8
10
|
import { jsx } from "react/jsx-runtime";
|
|
9
|
-
import
|
|
11
|
+
import "@sanity/sdk/dashboard";
|
|
12
|
+
import { catchError } from "rxjs/operators";
|
|
10
13
|
const SanityInstanceContext = createContext(null), useSanityInstance = () => {
|
|
11
14
|
let instance = useContext(SanityInstanceContext);
|
|
12
15
|
if (!instance) throw Error("SanityInstance context not found. Please ensure that your component is wrapped in a ResourceProvider or a SanityApp component.");
|
|
@@ -72,7 +75,7 @@ function useWindowConnection(t0) {
|
|
|
72
75
|
let [type, handler] = t4, messageUnsubscribe = node.on(type, handler);
|
|
73
76
|
messageUnsubscribe && messageUnsubscribers.current.push(messageUnsubscribe);
|
|
74
77
|
}), () => {
|
|
75
|
-
messageUnsubscribers.current.forEach(_temp), messageUnsubscribers.current = [];
|
|
78
|
+
messageUnsubscribers.current.forEach(_temp$1), messageUnsubscribers.current = [];
|
|
76
79
|
}), $[4] = node, $[5] = onMessage, $[6] = t3) : t3 = $[6];
|
|
77
80
|
let t4;
|
|
78
81
|
$[7] !== instance || $[8] !== name || $[9] !== node || $[10] !== onMessage ? (t4 = [
|
|
@@ -93,51 +96,34 @@ function useWindowConnection(t0) {
|
|
|
93
96
|
fetch
|
|
94
97
|
}, $[16] = fetch, $[17] = sendMessage, $[18] = t7) : t7 = $[18], t7;
|
|
95
98
|
}
|
|
96
|
-
function _temp(unsubscribe) {
|
|
99
|
+
function _temp$1(unsubscribe) {
|
|
97
100
|
return unsubscribe();
|
|
98
101
|
}
|
|
99
|
-
const
|
|
102
|
+
const MODULE_SLOT_KEY = Symbol.for("sanity.os.module");
|
|
100
103
|
/**
|
|
101
|
-
*
|
|
104
|
+
* Returns the React context that carries the current federation module id.
|
|
102
105
|
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
* @internal
|
|
111
|
-
*/
|
|
112
|
-
function isDashboardEnvironment() {
|
|
113
|
-
return typeof globalThis == "object" && OS_BUS_KEY in globalThis;
|
|
114
|
-
}
|
|
115
|
-
/**
|
|
116
|
-
* Observes the session token issued by the dashboard "OS", tracking the OS auth
|
|
117
|
-
* state over time.
|
|
118
|
-
*
|
|
119
|
-
* Returns `undefined` when the app is not embedded in the dashboard, so the
|
|
120
|
-
* caller uses its normal auth flow. Inside the dashboard, subscribes to the
|
|
121
|
-
* `auth.token` state topic, emitting the current token — or `null` when the OS
|
|
122
|
-
* is signed out — and re-emitting as the OS auth state changes, so sign-in/out
|
|
123
|
-
* propagates instead of being captured once. Any bus error is treated as "no
|
|
124
|
-
* token" (`null`). The token is used in-memory only and never persisted.
|
|
106
|
+
* @remarks
|
|
107
|
+
* This is the slot the CLI-generated wrapper populates with
|
|
108
|
+
* `renderOptions.moduleId`. The context lives in a per-React-copy slot on
|
|
109
|
+
* `globalThis` so the CLI wrapper and the SDK share the same context even
|
|
110
|
+
* across module copies. Each React copy gets its own context, since a context
|
|
111
|
+
* created by one copy is inert in another.
|
|
125
112
|
*
|
|
126
|
-
*
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
}
|
|
131
|
-
/**
|
|
132
|
-
* Asks the dashboard "OS" to reissue the session token, e.g. after its current
|
|
133
|
-
* one was rejected with a 401. Fire-and-forget: the reissued token arrives via
|
|
134
|
-
* the `auth.token` subscription in {@link observeDashboardToken}. No-op outside
|
|
135
|
-
* the dashboard.
|
|
113
|
+
* The slot is keyed on `React.createContext` rather than the `React` namespace
|
|
114
|
+
* object: `import * as React` and `import React from 'react'` can yield
|
|
115
|
+
* different wrapper objects for the same React copy under bundler interop,
|
|
116
|
+
* whereas the `createContext` function is the same reference under both.
|
|
136
117
|
*
|
|
118
|
+
* The provider side lives in the CLI, which must not import the SDK:
|
|
119
|
+
* `packages/@sanity/workbench-cli/src/actions/build/render-remote.ts` in
|
|
120
|
+
* `sanity-io/cli` reconstructs this accessor (same symbol, same key) and
|
|
121
|
+
* wraps `App` in the context's `Provider`. Nothing in this repo exports it.
|
|
137
122
|
* @internal
|
|
138
123
|
*/
|
|
139
|
-
function
|
|
140
|
-
|
|
124
|
+
function getDashboardModuleContext() {
|
|
125
|
+
let globals = globalThis, slot = globals[MODULE_SLOT_KEY] ??= /* @__PURE__ */ new WeakMap(), key = React$1.createContext, context = slot.get(key);
|
|
126
|
+
return context || (context = React$1.createContext(void 0), slot.set(key, context)), context;
|
|
141
127
|
}
|
|
142
128
|
/**
|
|
143
129
|
* Keeps the SDK auth token in sync with the dashboard "OS".
|
|
@@ -150,20 +136,18 @@ function refreshDashboardToken() {
|
|
|
150
136
|
* down; the new token arrives back through the same subscription.
|
|
151
137
|
*/
|
|
152
138
|
function DashboardTokenRefresh(t0) {
|
|
153
|
-
let $ = c(
|
|
154
|
-
$[0]
|
|
155
|
-
let
|
|
156
|
-
if (!token$) return;
|
|
157
|
-
let subscription = token$.subscribe((token) => setAuthToken(instance, token));
|
|
139
|
+
let $ = c(11), { children, messageBus } = t0, instance = useSanityInstance(), authState = useAuthState(), processed401ErrorRef = useRef(null), t1, t2;
|
|
140
|
+
$[0] !== instance || $[1] !== messageBus ? (t1 = () => {
|
|
141
|
+
let subscription = defer(() => messageBus.subscribe("auth.token")).pipe(catchError(_temp)).subscribe((token) => setAuthToken(instance, token));
|
|
158
142
|
return () => subscription.unsubscribe();
|
|
159
|
-
}, t2 = [instance], $[0] = instance, $[1] =
|
|
143
|
+
}, t2 = [instance, messageBus], $[0] = instance, $[1] = messageBus, $[2] = t1, $[3] = t2) : (t1 = $[2], t2 = $[3]), useEffect(t1, t2);
|
|
160
144
|
let t3;
|
|
161
|
-
$[
|
|
145
|
+
$[4] !== authState.error || $[5] !== authState.type || $[6] !== messageBus ? (t3 = () => {
|
|
162
146
|
let has401Error = authState.type === AuthStateType.ERROR && authState.error?.statusCode === 401;
|
|
163
|
-
has401Error && processed401ErrorRef.current !== authState.error ? (processed401ErrorRef.current = authState.error,
|
|
164
|
-
}, $[
|
|
147
|
+
has401Error && processed401ErrorRef.current !== authState.error ? (processed401ErrorRef.current = authState.error, messageBus.emit("auth.token.refresh", void 0).catch(_temp2)) : has401Error || (processed401ErrorRef.current = null);
|
|
148
|
+
}, $[4] = authState.error, $[5] = authState.type, $[6] = messageBus, $[7] = t3) : t3 = $[7];
|
|
165
149
|
let t4;
|
|
166
|
-
return $[
|
|
150
|
+
return $[8] !== authState || $[9] !== messageBus ? (t4 = [authState, messageBus], $[8] = authState, $[9] = messageBus, $[10] = t4) : t4 = $[10], useEffect(t3, t4), children;
|
|
167
151
|
}
|
|
168
152
|
/**
|
|
169
153
|
* Authenticates the SDK with the Sanity Dashboard's session when the app runs
|
|
@@ -204,11 +188,24 @@ function DashboardTokenRefresh(t0) {
|
|
|
204
188
|
*
|
|
205
189
|
* @public
|
|
206
190
|
*/
|
|
191
|
+
function _temp2(error) {
|
|
192
|
+
console.warn("[sanity/sdk] Dashboard token refresh failed:", error);
|
|
193
|
+
}
|
|
194
|
+
function _temp() {
|
|
195
|
+
return of(null);
|
|
196
|
+
}
|
|
207
197
|
const DashboardTokenRefreshProvider = (t0) => {
|
|
208
|
-
let $ = c(
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
198
|
+
let $ = c(7), { children } = t0, instance = useSanityInstance(), t1;
|
|
199
|
+
$[0] === Symbol.for("react.memo_cache_sentinel") ? (t1 = getDashboardModuleContext(), $[0] = t1) : t1 = $[0];
|
|
200
|
+
let moduleId = useContext(t1), t2;
|
|
201
|
+
$[1] !== instance || $[2] !== moduleId ? (t2 = () => getDashboardMessageBus(instance, moduleId), $[1] = instance, $[2] = moduleId, $[3] = t2) : t2 = $[3];
|
|
202
|
+
let [messageBus] = useState(t2);
|
|
203
|
+
if (messageBus) {
|
|
204
|
+
let t3;
|
|
205
|
+
return $[4] !== children || $[5] !== messageBus ? (t3 = /* @__PURE__ */ jsx(DashboardTokenRefresh, {
|
|
206
|
+
messageBus,
|
|
207
|
+
children
|
|
208
|
+
}), $[4] = children, $[5] = messageBus, $[6] = t3) : t3 = $[6], t3;
|
|
212
209
|
}
|
|
213
210
|
return children;
|
|
214
211
|
};
|
|
@@ -312,6 +309,6 @@ function useStudioWorkspacesByProjectIdDataset() {
|
|
|
312
309
|
error
|
|
313
310
|
}, $[5] = error, $[6] = workspacesByProjectIdAndDataset, $[7] = t4) : t4 = $[7], t4;
|
|
314
311
|
}
|
|
315
|
-
export {
|
|
312
|
+
export { useAuthState as a, SanityInstanceContext as c, useWindowConnection as i, useOrganizationId as n, createStateSourceHook as o, DashboardTokenRefreshProvider as r, useSanityInstance as s, useStudioWorkspacesByProjectIdDataset as t };
|
|
316
313
|
|
|
317
|
-
//# sourceMappingURL=useStudioWorkspacesByProjectIdDataset-
|
|
314
|
+
//# sourceMappingURL=useStudioWorkspacesByProjectIdDataset-BZ_Ud887.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useStudioWorkspacesByProjectIdDataset-BZ_Ud887.js","names":["SanityInstance","createContext","SanityInstanceContext","SanityInstance","useContext","SanityInstanceContext","useSanityInstance","instance","Error","SanityConfig","SanityInstance","StateSource","useSyncExternalStore","useSanityInstance","StateSourceFactory","instance","params","TParams","TState","CreateStateSourceHookOptions","getState","shouldSuspend","suspender","Promise","getConfig","createStateSourceHook","options","suspense","undefined","useHook","t0","$","_c","t1","state","subscribe","getCurrent","AuthState","getAuthState","createStateSourceHook","useAuthState","MessageData","NodeInput","SanityInstance","StateSource","FrameMessage","getNodeState","NodeState","WindowMessage","useCallback","useEffect","useRef","filter","firstValueFrom","useSanityInstance","createStateSourceHook","WindowMessageHandler","event","TFrameMessage","UseWindowConnectionOptions","name","connectTo","onMessage","Record","TMessage","WindowConnection","sendMessage","type","TType","data","Extract","fetch","options","signal","AbortSignal","suppressWarnings","responseTimeout","Promise","TResponse","useNodeState","getState","instance","nodeInput","shouldSuspend","getCurrent","undefined","suspender","observable","pipe","Boolean","useWindowConnection","t0","$","_c","t1","node","t2","Symbol","for","messageUnsubscribers","t3","Object","entries","forEach","t4","handler","messageUnsubscribe","on","current","push","_temp","t5","type_0","post","t6","type_1","data_0","fetchOptions","t7","unsubscribe","React","MODULE_SLOT_KEY","Symbol","for","ModuleContext","Context","ModuleSlot","WeakMap","createContext","getDashboardModuleContext","globals","globalThis","slot","key","context","get","undefined","set","ClientError","AuthStateType","setAuthToken","getDashboardMessageBus","MessageBus","React","PropsWithChildren","useContext","useEffect","useRef","useState","defer","of","catchError","getDashboardModuleContext","useAuthState","useSanityInstance","DashboardTokenRefresh","t0","$","_c","children","messageBus","instance","authState","processed401ErrorRef","t1","t2","subscription","subscribe","pipe","_temp","token","unsubscribe","t3","error","type","has401Error","ERROR","statusCode","current","emit","undefined","catch","_temp2","t4","console","warn","DashboardTokenRefreshProvider","FC","Symbol","for","moduleId","getDashboardOrganizationId","useMemo","useSyncExternalStore","useSanityInstance","useOrganizationId","$","_c","instance","t0","subscribe","getCurrent","SDK_CHANNEL_NAME","SDK_NODE_NAME","useEffect","useState","useWindowConnection","DashboardResource","id","name","title","basePath","projectId","dataset","type","userApplicationId","url","WorkspacesByProjectIdDataset","key","StudioWorkspacesResult","workspacesByProjectIdAndDataset","error","useStudioWorkspacesByProjectIdDataset","$","_c","t0","Symbol","for","setWorkspacesByProjectIdAndDataset","setError","t1","connectTo","fetch","t2","t3","fetchWorkspaces","signal","data","undefined","workspaceMap","noProjectIdAndDataset","context","availableResources","forEach","resource","push","const","length","t4","err","Error","controller","AbortController","abort"],"sources":["../src/context/SanityInstanceContext.ts","../src/hooks/context/useSanityInstance.ts","../src/hooks/helpers/createStateSourceHook.tsx","../src/hooks/auth/useAuthState.tsx","../src/hooks/comlink/useWindowConnection.ts","../src/dashboard/module.ts","../src/context/DashboardTokenRefresh.tsx","../src/hooks/dashboard/useOrganizationId.tsx","../src/hooks/dashboard/useStudioWorkspacesByProjectIdDataset.ts"],"sourcesContent":["import {type SanityInstance} from '@sanity/sdk'\nimport {createContext} from 'react'\n\nexport const SanityInstanceContext = createContext<SanityInstance | null>(null)\n","import {type SanityInstance} from '@sanity/sdk'\nimport {useContext} from 'react'\n\nimport {SanityInstanceContext} from '../../context/SanityInstanceContext'\n\n/**\n * Retrieves the current Sanity instance from context\n *\n * @public\n *\n * @category Platform\n * @returns The current Sanity instance\n *\n * @remarks\n * This hook accesses the nearest Sanity instance from the React context.\n * The hook must be used within a component wrapped by a `ResourceProvider` or `SanityApp`.\n *\n * @example Get the current instance\n * ```tsx\n * const instance = useSanityInstance()\n * console.log(instance.config.projectId)\n * ```\n *\n * @throws Error if no SanityInstance is found in context\n */\nexport const useSanityInstance = (): SanityInstance => {\n const instance = useContext(SanityInstanceContext)\n\n if (!instance) {\n throw new Error(\n `SanityInstance context not found. Please ensure that your component is wrapped in a ResourceProvider or a SanityApp component.`,\n )\n }\n\n return instance\n}\n","import {type SanityConfig, type SanityInstance, type StateSource} from '@sanity/sdk'\nimport {useSyncExternalStore} from 'react'\n\nimport {useSanityInstance} from '../context/useSanityInstance'\n\ntype StateSourceFactory<TParams extends unknown[], TState> = (\n instance: SanityInstance,\n ...params: TParams\n) => StateSource<TState>\n\ninterface CreateStateSourceHookOptions<TParams extends unknown[], TState> {\n getState: StateSourceFactory<TParams, TState>\n shouldSuspend?: (instance: SanityInstance, ...params: TParams) => boolean\n suspender?: (instance: SanityInstance, ...params: TParams) => Promise<unknown>\n getConfig?: (...params: TParams) => SanityConfig | undefined\n}\n\nexport function createStateSourceHook<TParams extends unknown[], TState>(\n options: StateSourceFactory<TParams, TState> | CreateStateSourceHookOptions<TParams, TState>,\n): (...params: TParams) => TState {\n const getState = typeof options === 'function' ? options : options.getState\n const suspense = 'shouldSuspend' in options && 'suspender' in options ? options : undefined\n\n function useHook(...params: TParams) {\n const instance = useSanityInstance()\n\n if (suspense?.suspender && suspense?.shouldSuspend?.(instance, ...params)) {\n throw suspense.suspender(instance, ...params)\n }\n\n const state = getState(instance, ...params)\n return useSyncExternalStore(state.subscribe, state.getCurrent)\n }\n\n return useHook\n}\n","import {type AuthState, getAuthState} from '@sanity/sdk'\n\nimport {createStateSourceHook} from '../helpers/createStateSourceHook'\n\n/**\n * @internal\n * A React hook that subscribes to authentication state changes.\n *\n * This hook provides access to the current authentication state type from the Sanity auth store.\n * It automatically re-renders when the authentication state changes.\n *\n * @remarks\n * The hook uses `useSyncExternalStore` to safely subscribe to auth state changes\n * and ensure consistency between server and client rendering.\n *\n * @returns The current authentication state type\n *\n * @example\n * ```tsx\n * function AuthStatus() {\n * const authState = useAuthState()\n * return <div>Current auth state: {authState}</div>\n * }\n * ```\n */\nexport const useAuthState: () => AuthState = createStateSourceHook(getAuthState)\n","import {type MessageData, type NodeInput} from '@sanity/comlink'\nimport {type SanityInstance, type StateSource} from '@sanity/sdk'\nimport {\n type FrameMessage,\n getNodeState,\n type NodeState,\n type WindowMessage,\n} from '@sanity/sdk/comlink'\nimport {useCallback, useEffect, useRef} from 'react'\nimport {filter, firstValueFrom} from 'rxjs'\n\nimport {useSanityInstance} from '../context/useSanityInstance'\nimport {createStateSourceHook} from '../helpers/createStateSourceHook'\n\n/**\n * @internal\n */\nexport type WindowMessageHandler<TFrameMessage extends FrameMessage> = (\n event: TFrameMessage['data'],\n) => TFrameMessage['response']\n\n/**\n * @internal\n */\nexport interface UseWindowConnectionOptions<TMessage extends FrameMessage> {\n name: string\n connectTo: string\n onMessage?: Record<TMessage['type'], WindowMessageHandler<TMessage>>\n}\n\n/**\n * @internal\n */\nexport interface WindowConnection<TMessage extends WindowMessage> {\n sendMessage: <TType extends TMessage['type']>(\n type: TType,\n data?: Extract<TMessage, {type: TType}>['data'],\n ) => void\n fetch: <TResponse>(\n type: string,\n data?: MessageData,\n options?: {\n signal?: AbortSignal\n suppressWarnings?: boolean\n responseTimeout?: number\n },\n ) => Promise<TResponse>\n}\n\nconst useNodeState = createStateSourceHook({\n getState: getNodeState as (\n instance: SanityInstance,\n nodeInput: NodeInput,\n ) => StateSource<NodeState>,\n shouldSuspend: (instance: SanityInstance, nodeInput: NodeInput) =>\n getNodeState(instance, nodeInput).getCurrent() === undefined,\n suspender: (instance: SanityInstance, nodeInput: NodeInput) => {\n return firstValueFrom(getNodeState(instance, nodeInput).observable.pipe(filter(Boolean)))\n },\n})\n\n/**\n * @internal\n * Hook to wrap a Comlink node in a React hook.\n * Our store functionality takes care of the lifecycle of the node,\n * as well as sharing a single node between invocations if they share the same name.\n *\n * Generally not to be used directly, but to be used as a dependency of\n * Comlink-powered hooks like `useStudioWorkspacesByProjectIdDataset`.\n */\nexport function useWindowConnection<\n TWindowMessage extends WindowMessage,\n TFrameMessage extends FrameMessage,\n>({\n name,\n connectTo,\n onMessage,\n}: UseWindowConnectionOptions<TFrameMessage>): WindowConnection<TWindowMessage> {\n const {node} = useNodeState({name, connectTo})\n const messageUnsubscribers = useRef<(() => void)[]>([])\n const instance = useSanityInstance()\n\n useEffect(() => {\n if (onMessage) {\n Object.entries(onMessage).forEach(([type, handler]) => {\n const messageUnsubscribe = node.on(type, handler as WindowMessageHandler<TFrameMessage>)\n if (messageUnsubscribe) {\n messageUnsubscribers.current.push(messageUnsubscribe)\n }\n })\n }\n\n return () => {\n messageUnsubscribers.current.forEach((unsubscribe) => unsubscribe())\n messageUnsubscribers.current = []\n }\n }, [instance, name, onMessage, node])\n\n const sendMessage = useCallback(\n (type: TWindowMessage['type'], data?: Extract<TWindowMessage, {type: typeof type}>['data']) => {\n node.post(type, data)\n },\n [node],\n )\n\n const fetch = useCallback(\n <TResponse>(\n type: string,\n data?: MessageData,\n fetchOptions?: {\n responseTimeout?: number\n signal?: AbortSignal\n suppressWarnings?: boolean\n },\n ): Promise<TResponse> => {\n return node.fetch(type, data, fetchOptions ?? {}) as Promise<TResponse>\n },\n [node],\n )\n return {\n sendMessage,\n fetch,\n }\n}\n","import * as React from 'react'\n\nconst MODULE_SLOT_KEY = Symbol.for('sanity.os.module')\n\ntype ModuleContext = React.Context<string | undefined>\ntype ModuleSlot = WeakMap<typeof React.createContext, ModuleContext>\n\n/**\n * Returns the React context that carries the current federation module id.\n *\n * @remarks\n * This is the slot the CLI-generated wrapper populates with\n * `renderOptions.moduleId`. The context lives in a per-React-copy slot on\n * `globalThis` so the CLI wrapper and the SDK share the same context even\n * across module copies. Each React copy gets its own context, since a context\n * created by one copy is inert in another.\n *\n * The slot is keyed on `React.createContext` rather than the `React` namespace\n * object: `import * as React` and `import React from 'react'` can yield\n * different wrapper objects for the same React copy under bundler interop,\n * whereas the `createContext` function is the same reference under both.\n *\n * The provider side lives in the CLI, which must not import the SDK:\n * `packages/@sanity/workbench-cli/src/actions/build/render-remote.ts` in\n * `sanity-io/cli` reconstructs this accessor (same symbol, same key) and\n * wraps `App` in the context's `Provider`. Nothing in this repo exports it.\n * @internal\n */\nexport function getDashboardModuleContext(): ModuleContext {\n const globals = globalThis as {[MODULE_SLOT_KEY]?: ModuleSlot}\n const slot = (globals[MODULE_SLOT_KEY] ??= new WeakMap())\n const key = React.createContext\n let context = slot.get(key)\n if (!context) {\n context = React.createContext<string | undefined>(undefined)\n slot.set(key, context)\n }\n return context\n}\n","import {type ClientError} from '@sanity/client'\nimport {AuthStateType, setAuthToken} from '@sanity/sdk'\nimport {getDashboardMessageBus} from '@sanity/sdk/_internal'\nimport {type MessageBus} from '@sanity/sdk/dashboard'\nimport React, {type PropsWithChildren, useContext, useEffect, useRef, useState} from 'react'\nimport {defer, of} from 'rxjs'\nimport {catchError} from 'rxjs/operators'\n\nimport {getDashboardModuleContext} from '../dashboard/module'\nimport {useAuthState} from '../hooks/auth/useAuthState'\nimport {useSanityInstance} from '../hooks/context/useSanityInstance'\n\n/**\n * Keeps the SDK auth token in sync with the dashboard \"OS\".\n *\n * When running inside the dashboard the OS owns the session, so we subscribe\n * to its `auth.token` stream and mirror each value into\n * the auth store — a token logs us in, `null` logs us out, and later OS\n * sign-in/out propagates automatically. When a request is rejected with a 401\n * (the token expired), we ask the OS to reissue rather than tearing the session\n * down; the new token arrives back through the same subscription.\n */\nfunction DashboardTokenRefresh({\n children,\n messageBus,\n}: PropsWithChildren<{messageBus: MessageBus}>) {\n const instance = useSanityInstance()\n const authState = useAuthState()\n const processed401ErrorRef = useRef<unknown | null>(null)\n\n useEffect(() => {\n const subscription = defer(() => messageBus.subscribe('auth.token'))\n .pipe(catchError(() => of(null)))\n .subscribe((token) => setAuthToken(instance, token))\n return () => subscription.unsubscribe()\n }, [instance, messageBus])\n\n useEffect(() => {\n const has401Error =\n authState.type === AuthStateType.ERROR && (authState.error as ClientError)?.statusCode === 401\n\n if (has401Error && processed401ErrorRef.current !== authState.error) {\n processed401ErrorRef.current = authState.error\n // Event topics have no replay, so a missing responder or timeout is otherwise dropped silently.\n messageBus.emit('auth.token.refresh', undefined).catch((error) => {\n // eslint-disable-next-line no-console\n console.warn('[sanity/sdk] Dashboard token refresh failed:', error)\n })\n } else if (!has401Error) {\n processed401ErrorRef.current = null\n }\n }, [authState, messageBus])\n\n return children\n}\n\n/**\n * Authenticates the SDK with the Sanity Dashboard's session when the app runs\n * inside the dashboard.\n *\n * The dashboard owns the session there: this provider subscribes to the token\n * the dashboard issues, writes each new value into the SDK's auth store (where\n * SDK hooks read it from), and asks the dashboard for a fresh token when a\n * request fails with a 401. Outside the dashboard it renders children\n * unchanged and the app's normal auth flow applies.\n *\n * @remarks\n * `AuthBoundary` mounts this automatically, so most apps never need it\n * directly. Mount it yourself only when your app runs inside the dashboard\n * without `AuthBoundary` — that is, the app renders its own loading and error\n * UI instead of the SDK's login flow — but still uses SDK hooks such as\n * `useQuery`, which need the dashboard's token in the auth store to\n * authenticate their requests.\n *\n * Mount it once, inside the provider that creates the Sanity instance whose\n * store should receive the token.\n *\n * @example\n * ```tsx\n * import {ResourceProvider} from '@sanity/sdk-react'\n * import {TokenRefreshProvider} from '@sanity/sdk-react/dashboard'\n *\n * function EmbeddedApp() {\n * return (\n * <ResourceProvider fallback={<Loading />}>\n * <TokenRefreshProvider>\n * <App />\n * </TokenRefreshProvider>\n * </ResourceProvider>\n * )\n * }\n * ```\n *\n * @public\n */\nexport const DashboardTokenRefreshProvider: React.FC<PropsWithChildren> = ({children}) => {\n const instance = useSanityInstance()\n const moduleId = useContext(getDashboardModuleContext())\n // The connection is first-caller-wins per instance, and hooks below read it during their\n // render, before any effect here could run. Connecting in the first render pins the module\n // identity before they do; nothing has subscribed to the store yet, so the write is safe.\n // The module id is read once: the CLI wrapper provides it statically above this tree.\n // No retry: the host installs the bus at module evaluation, before any remote renders, and\n // a standalone app has no host to wait for.\n const [messageBus] = useState(() => getDashboardMessageBus(instance, moduleId))\n if (messageBus) {\n return <DashboardTokenRefresh messageBus={messageBus}>{children}</DashboardTokenRefresh>\n }\n\n return children\n}\n","import {getDashboardOrganizationId} from '@sanity/sdk'\nimport {useMemo, useSyncExternalStore} from 'react'\n\nimport {useSanityInstance} from '../context/useSanityInstance'\n\n/**\n * @public\n *\n * A React hook that retrieves the dashboard organization ID that is currently selected in the Sanity Dashboard.\n *\n * @example\n * ```tsx\n * function DashboardComponent() {\n * const orgId = useOrganizationId()\n *\n * if (!orgId) return null\n *\n * return <div>Organization ID: {String(orgId)}</div>\n * }\n * ```\n *\n * @category Dashboard\n * @returns The dashboard organization ID (string | undefined)\n */\nexport function useOrganizationId(): string | undefined {\n const instance = useSanityInstance()\n const {subscribe, getCurrent} = useMemo(() => getDashboardOrganizationId(instance), [instance])\n\n return useSyncExternalStore(subscribe, getCurrent)\n}\n","import {SDK_CHANNEL_NAME, SDK_NODE_NAME} from '@sanity/message-protocol'\nimport {useEffect, useState} from 'react'\n\nimport {useWindowConnection} from '../comlink/useWindowConnection'\n\nexport interface DashboardResource {\n id: string\n name: string\n title: string\n basePath: string\n projectId: string\n dataset: string\n type: string\n userApplicationId: string\n url: string\n}\n\ninterface WorkspacesByProjectIdDataset {\n [key: `${string}:${string}`]: DashboardResource[] // key format: `${projectId}:${dataset}`\n}\n\ninterface StudioWorkspacesResult {\n workspacesByProjectIdAndDataset: WorkspacesByProjectIdDataset\n error: string | null\n}\n\n/**\n * Hook that fetches studio workspaces and organizes them by projectId:dataset\n * @internal\n *\n * @example\n * ```tsx\n * import {useStudioWorkspacesByProjectIdDataset} from '@sanity/sdk-react'\n * import {Card, Code, Button} from '@sanity/ui'\n * import {Suspense} from 'react'\n *\n * function WorkspacesCard() {\n * const {workspacesByProjectIdAndDataset, error} = useStudioWorkspacesByProjectIdDataset()\n * if (error) {\n * return <div>Error: {error}</div>\n * }\n * return (\n * <Card padding={4} radius={2} shadow={1}>\n * <Code language=\"json\">\n * {JSON.stringify(workspacesByProjectIdAndDataset, null, 2)}\n * </Code>\n * </Card>\n * )\n * }\n *\n * // Wrap the component with Suspense since the hook may suspend\n * function DashboardWorkspaces() {\n * return (\n * <Suspense fallback={<Button text=\"Loading...\" disabled />}>\n * <WorkspacesCard />\n * </Suspense>\n * )\n * }\n * ```\n */\nexport function useStudioWorkspacesByProjectIdDataset(): StudioWorkspacesResult {\n const [workspacesByProjectIdAndDataset, setWorkspacesByProjectIdAndDataset] =\n useState<WorkspacesByProjectIdDataset>({})\n const [error, setError] = useState<string | null>(null)\n\n const {fetch} = useWindowConnection({\n name: SDK_NODE_NAME,\n connectTo: SDK_CHANNEL_NAME,\n })\n\n // Once computed, this should probably be in a store and poll for changes\n // However, our stores are currently being refactored\n useEffect(() => {\n if (!fetch) return\n\n async function fetchWorkspaces(signal: AbortSignal) {\n try {\n const data = await fetch<{\n context: {availableResources: Array<DashboardResource>}\n }>('dashboard/v1/context', undefined, {signal})\n\n const workspaceMap: WorkspacesByProjectIdDataset = {}\n const noProjectIdAndDataset: DashboardResource[] = []\n\n data.context.availableResources.forEach((resource) => {\n if (resource.type !== 'studio') return\n if (!resource.projectId || !resource.dataset) {\n noProjectIdAndDataset.push(resource)\n return\n }\n const key = `${resource.projectId}:${resource.dataset}` as const\n if (!workspaceMap[key]) {\n workspaceMap[key] = []\n }\n workspaceMap[key].push(resource)\n })\n\n if (noProjectIdAndDataset.length > 0) {\n workspaceMap['NO_PROJECT_ID:NO_DATASET'] = noProjectIdAndDataset\n }\n\n setWorkspacesByProjectIdAndDataset(workspaceMap)\n setError(null)\n } catch (err: unknown) {\n if (err instanceof Error) {\n if (err.name === 'AbortError') {\n return\n }\n setError('Failed to fetch workspaces')\n }\n }\n }\n\n const controller = new AbortController()\n fetchWorkspaces(controller.signal)\n\n return () => {\n controller.abort()\n }\n }, [fetch])\n\n return {\n workspacesByProjectIdAndDataset,\n error,\n }\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sanity/sdk-react",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.2.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Sanity SDK React toolkit for Content OS",
|
|
6
6
|
"keywords": [
|
|
@@ -35,50 +35,51 @@
|
|
|
35
35
|
"exports": {
|
|
36
36
|
".": "./dist/index.js",
|
|
37
37
|
"./package.json": "./package.json",
|
|
38
|
-
"./dashboard": "./dist/_exports/dashboard.js"
|
|
38
|
+
"./dashboard": "./dist/_exports/dashboard.js",
|
|
39
|
+
"./dashboard/_internal": "./dist/_exports/dashboard-internal.js"
|
|
39
40
|
},
|
|
40
41
|
"publishConfig": {
|
|
41
42
|
"access": "public"
|
|
42
43
|
},
|
|
43
44
|
"dependencies": {
|
|
44
|
-
"@
|
|
45
|
+
"@module-federation/runtime": "^2.9.0",
|
|
46
|
+
"@sanity/client": "^8.6.1",
|
|
45
47
|
"@sanity/message-protocol": "^0.24.0",
|
|
46
|
-
"@sanity/
|
|
47
|
-
"@sanity/
|
|
48
|
-
"groq": "3.88.1-typegen-experimental.0",
|
|
48
|
+
"@sanity/sdk": "3.2.0",
|
|
49
|
+
"@sanity/types": "^6.13.0",
|
|
49
50
|
"react-compiler-runtime": "^1.0.0",
|
|
50
|
-
"react-error-boundary": "^6.1.
|
|
51
|
-
"rxjs": "^7.8.2"
|
|
52
|
-
"xstate": "^5.31.0",
|
|
53
|
-
"@sanity/sdk": "3.1.0"
|
|
51
|
+
"react-error-boundary": "^6.1.5",
|
|
52
|
+
"rxjs": "^7.8.2"
|
|
54
53
|
},
|
|
55
54
|
"devDependencies": {
|
|
55
|
+
"@repo/config-eslint": "0.0.0",
|
|
56
|
+
"@repo/config-test": "0.0.1",
|
|
57
|
+
"@repo/package.bundle": "3.82.0",
|
|
58
|
+
"@repo/package.config": "0.0.1",
|
|
59
|
+
"@repo/tsconfig": "0.0.1",
|
|
56
60
|
"@sanity/browserslist-config": "^1.0.5",
|
|
57
61
|
"@sanity/comlink": "^4.0.3",
|
|
58
|
-
"@sanity/pkg-utils": "^
|
|
59
|
-
"@testing-library/jest-dom": "^
|
|
60
|
-
"@testing-library/react": "^16.3.
|
|
61
|
-
"@types/node": "^24.13.
|
|
62
|
-
"@types/react": "^19.
|
|
63
|
-
"@types/react-dom": "^19.
|
|
64
|
-
"@vitejs/plugin-react": "^6.
|
|
65
|
-
"@vitest/coverage-v8": "^
|
|
62
|
+
"@sanity/pkg-utils": "^13.0.0",
|
|
63
|
+
"@testing-library/jest-dom": "^7.0.1",
|
|
64
|
+
"@testing-library/react": "^16.3.3",
|
|
65
|
+
"@types/node": "^24.13.4",
|
|
66
|
+
"@types/react": "^19.3.0",
|
|
67
|
+
"@types/react-dom": "^19.3.0",
|
|
68
|
+
"@vitejs/plugin-react": "^6.1.1",
|
|
69
|
+
"@vitest/coverage-v8": "^5.0.0",
|
|
66
70
|
"babel-plugin-react-compiler": "^1.0.0",
|
|
67
|
-
"eslint": "^10.
|
|
71
|
+
"eslint": "^10.10.0",
|
|
72
|
+
"groq": "^6.13.2",
|
|
68
73
|
"groq-js": "^2.0.0",
|
|
69
|
-
"jsdom": "^
|
|
70
|
-
"oxfmt": "^0.
|
|
71
|
-
"react": "^19.
|
|
72
|
-
"react-dom": "^19.
|
|
73
|
-
"rolldown": "^1.2.
|
|
74
|
+
"jsdom": "^30.0.1",
|
|
75
|
+
"oxfmt": "^0.67.0",
|
|
76
|
+
"react": "^19.3.0",
|
|
77
|
+
"react-dom": "^19.3.0",
|
|
78
|
+
"rolldown": "^1.2.8",
|
|
74
79
|
"typescript": "^6.0.3",
|
|
75
|
-
"vite": "^8.
|
|
76
|
-
"vitest": "^
|
|
77
|
-
"
|
|
78
|
-
"@repo/config-test": "0.0.1",
|
|
79
|
-
"@repo/package.config": "0.0.1",
|
|
80
|
-
"@repo/config-eslint": "0.0.0",
|
|
81
|
-
"@repo/tsconfig": "0.0.1"
|
|
80
|
+
"vite": "^8.3.0",
|
|
81
|
+
"vitest": "^5.0.0",
|
|
82
|
+
"xstate": "5.32.6"
|
|
82
83
|
},
|
|
83
84
|
"peerDependencies": {
|
|
84
85
|
"react": "^19.2.0",
|
|
@@ -86,7 +87,8 @@
|
|
|
86
87
|
},
|
|
87
88
|
"browserslist": "extends @sanity/browserslist-config",
|
|
88
89
|
"inlinedDependencies": {
|
|
89
|
-
"@sanity/comlink": "4.0.3"
|
|
90
|
+
"@sanity/comlink": "4.0.3",
|
|
91
|
+
"xstate": "5.32.6"
|
|
90
92
|
},
|
|
91
93
|
"scripts": {
|
|
92
94
|
"build": "pkg build --strict --clean --check",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {installMessageBus, resetMessageBus} from '@sanity/sdk/_internal'
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import {expectTypeOf, test} from 'vitest'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
type DashboardTopics,
|
|
5
|
+
type EventTopic,
|
|
6
|
+
type EventTopicDef,
|
|
7
|
+
type MessageBus,
|
|
8
|
+
type MessageBusAbortOptions,
|
|
9
|
+
type MessageBusEmitOptions,
|
|
10
|
+
type MessageBusEmitResult,
|
|
11
|
+
type MessageBusError,
|
|
12
|
+
type MessageBusErrorCode,
|
|
13
|
+
type MessageBusMessage,
|
|
14
|
+
type MessageBusMeta,
|
|
15
|
+
type MessageBusQueryOptions,
|
|
16
|
+
type PayloadOf,
|
|
17
|
+
type ReplyOf,
|
|
18
|
+
type StateTopicDef,
|
|
19
|
+
type TopicName,
|
|
20
|
+
type Topics,
|
|
21
|
+
} from './dashboard'
|
|
22
|
+
import {type installMessageBus, type resetMessageBus} from './dashboard-internal'
|
|
23
|
+
|
|
24
|
+
// Guards issue #1: every `@public` message bus type must be reachable from the
|
|
25
|
+
// `@sanity/sdk-react/dashboard` entrypoint. A missing export fails `ts:check`
|
|
26
|
+
// on the import above.
|
|
27
|
+
test('dashboard entrypoint exposes the message bus public types', () => {
|
|
28
|
+
// EventTopic is exported, so PayloadOf/ReplyOf are usable over event topics.
|
|
29
|
+
expectTypeOf<PayloadOf<'auth.token.refresh'>>().toEqualTypeOf<void>()
|
|
30
|
+
expectTypeOf<ReplyOf<'auth.token.refresh'>>().toEqualTypeOf<string>()
|
|
31
|
+
expectTypeOf<EventTopic>().toExtend<TopicName>()
|
|
32
|
+
|
|
33
|
+
// A consumer can name the constraint to write a generic wrapper over event topics.
|
|
34
|
+
const wrap = <K extends EventTopic>(type: K, payload: PayloadOf<K>): [K, PayloadOf<K>] => [
|
|
35
|
+
type,
|
|
36
|
+
payload,
|
|
37
|
+
]
|
|
38
|
+
expectTypeOf(wrap).toBeFunction()
|
|
39
|
+
|
|
40
|
+
// Topic declarations are reachable for declaration merging.
|
|
41
|
+
expectTypeOf<TopicName>().toEqualTypeOf<keyof Topics>()
|
|
42
|
+
expectTypeOf<keyof DashboardTopics>().toExtend<TopicName>()
|
|
43
|
+
expectTypeOf<StateTopicDef<number>['value']>().toBeNumber()
|
|
44
|
+
expectTypeOf<EventTopicDef<{n: number}, string>['payload']>().toEqualTypeOf<{n: number}>()
|
|
45
|
+
|
|
46
|
+
// The error code can be named when switching on `MessageBusError.code`.
|
|
47
|
+
expectTypeOf<MessageBusError['code']>().toEqualTypeOf<MessageBusErrorCode>()
|
|
48
|
+
|
|
49
|
+
// Event handlers and operation options can be typed explicitly.
|
|
50
|
+
expectTypeOf<MessageBusMessage<{n: number}, string>['payload']>().toEqualTypeOf<{n: number}>()
|
|
51
|
+
expectTypeOf<MessageBusMeta['appId']>().toBeString()
|
|
52
|
+
expectTypeOf<MessageBusAbortOptions['signal']>().toEqualTypeOf<AbortSignal | undefined>()
|
|
53
|
+
expectTypeOf<MessageBusEmitOptions['timeout']>().toEqualTypeOf<number | null | undefined>()
|
|
54
|
+
expectTypeOf<MessageBusQueryOptions['timeout']>().toEqualTypeOf<number | null | undefined>()
|
|
55
|
+
expectTypeOf<MessageBusEmitResult<string>>().toExtend<PromiseLike<string>>()
|
|
56
|
+
expectTypeOf<MessageBus['query']>().toBeFunction()
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
// Guards issue #3: the test-isolation helpers stay on the internal entrypoint,
|
|
60
|
+
// not the public one.
|
|
61
|
+
test('internal entrypoint exposes install and reset', () => {
|
|
62
|
+
expectTypeOf<typeof installMessageBus>().toBeFunction()
|
|
63
|
+
expectTypeOf<typeof resetMessageBus>().toBeFunction()
|
|
64
|
+
})
|
|
@@ -1,12 +1,82 @@
|
|
|
1
1
|
export {DashboardTokenRefreshProvider as TokenRefreshProvider} from '../context/DashboardTokenRefresh'
|
|
2
|
+
export {
|
|
3
|
+
createRemoteInstance,
|
|
4
|
+
type CreateRemoteInstanceOptions,
|
|
5
|
+
type FederationRemote,
|
|
6
|
+
type RemoteInstance,
|
|
7
|
+
} from '../dashboard/createRemoteInstance'
|
|
8
|
+
export {
|
|
9
|
+
type CanvasUrl,
|
|
10
|
+
type CoreApplicationUrl,
|
|
11
|
+
type CreateIntentParameters,
|
|
12
|
+
type DashboardUrl,
|
|
13
|
+
type EditIntentParameters,
|
|
14
|
+
type MediaLibraryUrl,
|
|
15
|
+
type ReleaseIntentParameters,
|
|
16
|
+
type StudioIntentUrl,
|
|
17
|
+
type StudioUrl,
|
|
18
|
+
type StudioWorkspaceUrl,
|
|
19
|
+
UrlBuilder,
|
|
20
|
+
urlFor,
|
|
21
|
+
type Urls,
|
|
22
|
+
} from '../dashboard/urlFor'
|
|
2
23
|
export {
|
|
3
24
|
type AgentResourceContextOptions,
|
|
4
25
|
useAgentResourceContext,
|
|
5
26
|
} from '../hooks/dashboard/useAgentResourceContext'
|
|
27
|
+
export {useApplication} from '../hooks/dashboard/useApplication'
|
|
28
|
+
export {
|
|
29
|
+
type ApplicationConfigSelector,
|
|
30
|
+
useApplicationConfig,
|
|
31
|
+
} from '../hooks/dashboard/useApplicationConfig'
|
|
32
|
+
export {useApplicationConfigs} from '../hooks/dashboard/useApplicationConfigs'
|
|
33
|
+
export {useApplicationForegroundId} from '../hooks/dashboard/useApplicationForegroundId'
|
|
34
|
+
export {
|
|
35
|
+
type DashboardApplication,
|
|
36
|
+
type DashboardView,
|
|
37
|
+
type DashboardWebWorker,
|
|
38
|
+
useApplications,
|
|
39
|
+
} from '../hooks/dashboard/useApplications'
|
|
40
|
+
export {type TopicEmitter, useEmit} from '../hooks/dashboard/useEmit'
|
|
6
41
|
export {useNavigate} from '../hooks/dashboard/useNavigate'
|
|
7
42
|
export {
|
|
8
43
|
type NavigateToStudioResult,
|
|
9
44
|
useNavigateToStudioDocument,
|
|
10
45
|
} from '../hooks/dashboard/useNavigateToStudioDocument'
|
|
11
46
|
export {useOrganizationId} from '../hooks/dashboard/useOrganizationId'
|
|
47
|
+
export {useRemoteClient} from '../hooks/dashboard/useRemoteClient'
|
|
48
|
+
export {useTopic} from '../hooks/dashboard/useTopic'
|
|
12
49
|
export {useWindowTitle} from '../hooks/dashboard/useWindowTitle'
|
|
50
|
+
export type {
|
|
51
|
+
ApplicationConfig,
|
|
52
|
+
ApplicationConfigAppType,
|
|
53
|
+
ConnectMessageBusOptions,
|
|
54
|
+
DashboardTopics,
|
|
55
|
+
EventTopic,
|
|
56
|
+
EventTopicDef,
|
|
57
|
+
MessageBus,
|
|
58
|
+
MessageBusAbortOptions,
|
|
59
|
+
MessageBusClient,
|
|
60
|
+
MessageBusConnection,
|
|
61
|
+
MessageBusEmitOptions,
|
|
62
|
+
MessageBusEmitResult,
|
|
63
|
+
MessageBusErrorCode,
|
|
64
|
+
MessageBusHost,
|
|
65
|
+
MessageBusMessage,
|
|
66
|
+
MessageBusMeta,
|
|
67
|
+
MessageBusQueryOptions,
|
|
68
|
+
MessageBusStateSource,
|
|
69
|
+
NavigationLocation,
|
|
70
|
+
NavigationTarget,
|
|
71
|
+
PayloadOf,
|
|
72
|
+
RemoteModuleRef,
|
|
73
|
+
ReplyOf,
|
|
74
|
+
StateTopic,
|
|
75
|
+
StateTopicDef,
|
|
76
|
+
TopicData,
|
|
77
|
+
TopicName,
|
|
78
|
+
TopicResult,
|
|
79
|
+
Topics,
|
|
80
|
+
ValueOf,
|
|
81
|
+
} from '@sanity/sdk/dashboard'
|
|
82
|
+
export {connectMessageBus, MessageBusError, TopicError} from '@sanity/sdk/dashboard'
|
package/src/_exports/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export * from './sdk-react.ts'
|
|
2
2
|
export * from '@sanity/sdk'
|
|
3
3
|
// React-layer handles shadow core equivalents when imported from @sanity/sdk-react
|
|
4
|
-
export type {DocumentHandle, DocumentTypeHandle, ResourceHandle} from './sdk-react.ts'
|
|
4
|
+
export type {DocumentHandle, DocumentTypeHandle, ResourceHandle, SanityDocument} from './sdk-react.ts'
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import {AuthStateType} from '@sanity/sdk'
|
|
2
|
+
import {installMessageBus} from '@sanity/sdk/_internal'
|
|
2
3
|
import {render, screen, waitFor} from '@testing-library/react'
|
|
3
4
|
import React from 'react'
|
|
4
5
|
import {type FallbackProps} from 'react-error-boundary'
|
|
5
6
|
import {beforeEach, describe, expect, it, type MockInstance, vi} from 'vitest'
|
|
6
7
|
|
|
8
|
+
import {DashboardTokenRefreshProvider} from '../../context/DashboardTokenRefresh'
|
|
7
9
|
import {ResourceProvider} from '../../context/ResourceProvider'
|
|
8
10
|
import {useAuthState} from '../../hooks/auth/useAuthState'
|
|
9
11
|
import {useLoginUrl} from '../../hooks/auth/useLoginUrl'
|
|
@@ -142,6 +144,38 @@ describe('AuthBoundary', () => {
|
|
|
142
144
|
})
|
|
143
145
|
})
|
|
144
146
|
|
|
147
|
+
it('does not redirect when a host bus is installed but the provider has not connected yet', () => {
|
|
148
|
+
// Workbench remotes start LOGGED_OUT (the host mints the token over the bus) and are not
|
|
149
|
+
// in an iframe, so the only thing standing between them and a login redirect is the
|
|
150
|
+
// dashboard check. AuthSwitch's effect runs before its parent provider's, so the check
|
|
151
|
+
// must not depend on the provider having connected first.
|
|
152
|
+
vi.mocked(useAuthState).mockReturnValue({
|
|
153
|
+
type: AuthStateType.LOGGED_OUT,
|
|
154
|
+
isDestroyingSession: false,
|
|
155
|
+
})
|
|
156
|
+
const originalLocation = window.location
|
|
157
|
+
const location = {href: 'http://remote.test/'}
|
|
158
|
+
Object.defineProperty(window, 'location', {value: location, writable: true})
|
|
159
|
+
vi.stubGlobal('__SANITY_APP_ID__', 'remote')
|
|
160
|
+
const globals = globalThis as {[key: symbol]: unknown}
|
|
161
|
+
const busKey = Symbol.for('sanity.os.bus')
|
|
162
|
+
installMessageBus({appId: 'dashboard'})
|
|
163
|
+
try {
|
|
164
|
+
render(
|
|
165
|
+
<ResourceProvider projectId="p" dataset="d" fallback={null}>
|
|
166
|
+
<DashboardTokenRefreshProvider>
|
|
167
|
+
<AuthBoundary projectIds={testProjectIds}>Protected Content</AuthBoundary>
|
|
168
|
+
</DashboardTokenRefreshProvider>
|
|
169
|
+
</ResourceProvider>,
|
|
170
|
+
)
|
|
171
|
+
expect(location.href).toBe('http://remote.test/')
|
|
172
|
+
} finally {
|
|
173
|
+
delete globals[busKey]
|
|
174
|
+
vi.unstubAllGlobals()
|
|
175
|
+
Object.defineProperty(window, 'location', {value: originalLocation, writable: true})
|
|
176
|
+
}
|
|
177
|
+
})
|
|
178
|
+
|
|
145
179
|
it('renders the empty LoginCallback component when authState="logging-in"', () => {
|
|
146
180
|
vi.mocked(useAuthState).mockReturnValue({
|
|
147
181
|
type: AuthStateType.LOGGING_IN,
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import {CorsOriginError} from '@sanity/client'
|
|
2
2
|
import {AuthStateType, getCorsErrorProjectId, isImportError} from '@sanity/sdk'
|
|
3
|
-
import {isStudioConfig} from '@sanity/sdk/_internal'
|
|
3
|
+
import {isDashboardEnvironment, isStudioConfig} from '@sanity/sdk/_internal'
|
|
4
4
|
import {useEffect, useMemo} from 'react'
|
|
5
5
|
import {ErrorBoundary, type FallbackProps} from 'react-error-boundary'
|
|
6
6
|
|
|
7
7
|
import {ComlinkTokenRefreshProvider} from '../../context/ComlinkTokenRefresh'
|
|
8
|
-
import {isDashboardEnvironment} from '../../context/dashboardToken'
|
|
9
8
|
import {DashboardTokenRefreshProvider} from '../../context/DashboardTokenRefresh'
|
|
10
9
|
import {useAuthState} from '../../hooks/auth/useAuthState'
|
|
11
10
|
import {useLoginUrl} from '../../hooks/auth/useLoginUrl'
|