sanity 3.83.1-canary.23 → 3.84.1-next.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.
Files changed (63) hide show
  1. package/lib/_chunks-cjs/PostMessageSchema.js.map +1 -1
  2. package/lib/_chunks-cjs/PostMessageTelemetry.js +1 -1
  3. package/lib/_chunks-cjs/PostMessageTelemetry.js.map +1 -1
  4. package/lib/_chunks-cjs/PresentationToolGrantsCheck.js +429 -1417
  5. package/lib/_chunks-cjs/PresentationToolGrantsCheck.js.map +1 -1
  6. package/lib/_chunks-cjs/presentation.js +70 -22
  7. package/lib/_chunks-cjs/presentation.js.map +1 -1
  8. package/lib/_chunks-cjs/resources6.js +0 -8
  9. package/lib/_chunks-cjs/resources6.js.map +1 -1
  10. package/lib/_chunks-cjs/version.js +1 -1
  11. package/lib/_chunks-es/PostMessageSchema.mjs.map +1 -1
  12. package/lib/_chunks-es/PostMessageTelemetry.mjs +1 -1
  13. package/lib/_chunks-es/PostMessageTelemetry.mjs.map +1 -1
  14. package/lib/_chunks-es/PresentationToolGrantsCheck.mjs +437 -1406
  15. package/lib/_chunks-es/PresentationToolGrantsCheck.mjs.map +1 -1
  16. package/lib/_chunks-es/presentation.mjs +73 -24
  17. package/lib/_chunks-es/presentation.mjs.map +1 -1
  18. package/lib/_chunks-es/resources6.mjs +0 -8
  19. package/lib/_chunks-es/resources6.mjs.map +1 -1
  20. package/lib/_chunks-es/version.mjs +1 -1
  21. package/lib/_singletons.d.mts +61 -2889
  22. package/lib/_singletons.d.ts +61 -2889
  23. package/lib/presentation.d.mts +57 -2887
  24. package/lib/presentation.d.ts +57 -2887
  25. package/lib/presentation.js +4 -0
  26. package/lib/presentation.js.map +1 -1
  27. package/lib/presentation.mjs +5 -1
  28. package/package.json +15 -16
  29. package/src/presentation/PostMessageTelemetry.tsx +1 -1
  30. package/src/presentation/PresentationTool.tsx +76 -85
  31. package/src/presentation/PresentationToolGrantsCheck.tsx +75 -39
  32. package/src/presentation/document/LocationsBanner.tsx +13 -52
  33. package/src/presentation/i18n/resources.ts +0 -10
  34. package/src/presentation/index.ts +13 -0
  35. package/src/presentation/{util → lib}/parse.ts +1 -2
  36. package/src/presentation/overlays/schema/PostMessageSchema.tsx +0 -1
  37. package/src/presentation/panels/usePanelsStorage.ts +1 -1
  38. package/src/presentation/preview/IFrame.tsx +35 -83
  39. package/src/presentation/preview/Preview.tsx +56 -172
  40. package/src/presentation/preview/PreviewHeader.tsx +10 -43
  41. package/src/presentation/preview/PreviewLocationInput.tsx +24 -48
  42. package/src/presentation/preview/SharePreviewMenu.tsx +2 -2
  43. package/src/presentation/reducers/presentationReducer.ts +134 -0
  44. package/src/presentation/types.ts +7 -139
  45. package/src/presentation/useMainDocument.ts +12 -4
  46. package/src/presentation/useParams.ts +3 -2
  47. package/src/presentation/usePreviewUrl.ts +133 -0
  48. package/src/presentation/actors/create-preview-secret.ts +0 -19
  49. package/src/presentation/actors/read-shared-secret.ts +0 -18
  50. package/src/presentation/actors/resolve-allow-patterns.ts +0 -55
  51. package/src/presentation/actors/resolve-initial-url.ts +0 -65
  52. package/src/presentation/actors/resolve-preview-mode-url.ts +0 -72
  53. package/src/presentation/actors/resolve-preview-mode.ts +0 -66
  54. package/src/presentation/actors/resolve-url-from-preview-search-param.ts +0 -29
  55. package/src/presentation/machines/presentation-machine.ts +0 -101
  56. package/src/presentation/machines/preview-url.ts +0 -568
  57. package/src/presentation/useAllowPatterns.ts +0 -12
  58. package/src/presentation/useId.ts +0 -7
  59. package/src/presentation/usePresentationPerspective.ts +0 -14
  60. package/src/presentation/usePreviewUrlActorRef.ts +0 -96
  61. package/src/presentation/useReportInvalidPreviewSearchParam.tsx +0 -43
  62. package/src/presentation/useTargetOrigin.ts +0 -11
  63. /package/src/presentation/{util → lib}/debounce.ts +0 -0
@@ -1,6 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: !0 });
3
3
  var presentation = require("./_chunks-cjs/presentation.js");
4
+ exports.ACTION_IFRAME_LOADED = presentation.ACTION_IFRAME_LOADED;
5
+ exports.ACTION_IFRAME_REFRESH = presentation.ACTION_IFRAME_REFRESH;
6
+ exports.ACTION_IFRAME_RELOAD = presentation.ACTION_IFRAME_RELOAD;
7
+ exports.ACTION_VISUAL_EDITING_OVERLAYS_TOGGLE = presentation.ACTION_VISUAL_EDITING_OVERLAYS_TOGGLE;
4
8
  exports.defineDocuments = presentation.defineDocuments;
5
9
  exports.defineLocations = presentation.defineLocations;
6
10
  exports.presentationTool = presentation.presentationTool;
@@ -1 +1 @@
1
- {"version":3,"file":"presentation.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;"}
1
+ {"version":3,"file":"presentation.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;"}
@@ -1,5 +1,9 @@
1
- import { defineDocuments, defineLocations, presentationTool, usePresentationNavigate, usePresentationParams, useSharedState } from "./_chunks-es/presentation.mjs";
1
+ import { ACTION_IFRAME_LOADED, ACTION_IFRAME_REFRESH, ACTION_IFRAME_RELOAD, ACTION_VISUAL_EDITING_OVERLAYS_TOGGLE, defineDocuments, defineLocations, presentationTool, usePresentationNavigate, usePresentationParams, useSharedState } from "./_chunks-es/presentation.mjs";
2
2
  export {
3
+ ACTION_IFRAME_LOADED,
4
+ ACTION_IFRAME_REFRESH,
5
+ ACTION_IFRAME_RELOAD,
6
+ ACTION_VISUAL_EDITING_OVERLAYS_TOGGLE,
3
7
  defineDocuments,
4
8
  defineLocations,
5
9
  presentationTool,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sanity",
3
- "version": "3.83.1-canary.23+2bb4c143a9",
3
+ "version": "3.84.1-next.0",
4
4
  "description": "Sanity is a real-time content infrastructure with a scalable, hosted backend featuring a Graph Oriented Query Language (GROQ), asset pipelines and fast edge caches",
5
5
  "keywords": [
6
6
  "sanity",
@@ -156,11 +156,11 @@
156
156
  "@rexxars/react-json-inspector": "^9.0.1",
157
157
  "@sanity/asset-utils": "^2.0.6",
158
158
  "@sanity/bifur-client": "^0.4.1",
159
- "@sanity/cli": "3.83.1-canary.23+2bb4c143a9",
159
+ "@sanity/cli": "3.84.1-next.0",
160
160
  "@sanity/client": "^6.28.4",
161
161
  "@sanity/color": "^3.0.0",
162
162
  "@sanity/comlink": "^3.0.1",
163
- "@sanity/diff": "3.83.1-canary.23+2bb4c143a9",
163
+ "@sanity/diff": "3.84.1-next.0",
164
164
  "@sanity/diff-match-patch": "^3.1.1",
165
165
  "@sanity/diff-patch": "^5.0.0",
166
166
  "@sanity/eventsource": "^5.0.0",
@@ -170,15 +170,15 @@
170
170
  "@sanity/import": "^3.38.2",
171
171
  "@sanity/insert-menu": "^1.1.9",
172
172
  "@sanity/logos": "^2.1.13",
173
- "@sanity/migrate": "3.83.1-canary.23+2bb4c143a9",
174
- "@sanity/mutator": "3.83.1-canary.23+2bb4c143a9",
173
+ "@sanity/migrate": "3.84.1-next.0",
174
+ "@sanity/mutator": "3.84.1-next.0",
175
175
  "@sanity/presentation-comlink": "^1.0.15",
176
176
  "@sanity/preview-url-secret": "^2.1.7",
177
- "@sanity/schema": "3.83.1-canary.23+2bb4c143a9",
177
+ "@sanity/schema": "3.84.1-next.0",
178
178
  "@sanity/telemetry": "^0.8.0",
179
- "@sanity/types": "3.83.1-canary.23+2bb4c143a9",
179
+ "@sanity/types": "3.84.1-next.0",
180
180
  "@sanity/ui": "^2.15.13",
181
- "@sanity/util": "3.83.1-canary.23+2bb4c143a9",
181
+ "@sanity/util": "3.84.1-next.0",
182
182
  "@sanity/uuid": "^3.0.2",
183
183
  "@sentry/react": "^8.33.0",
184
184
  "@tanstack/react-table": "^8.16.0",
@@ -189,7 +189,6 @@
189
189
  "@types/tar-stream": "^3.1.3",
190
190
  "@types/use-sync-external-store": "^1.5.0",
191
191
  "@vitejs/plugin-react": "^4.3.4",
192
- "@xstate/react": "^5.0.3",
193
192
  "archiver": "^7.0.0",
194
193
  "arrify": "^2.0.1",
195
194
  "async-mutex": "^0.4.1",
@@ -260,25 +259,25 @@
260
259
  "semver": "^7.3.5",
261
260
  "shallow-equals": "^1.0.0",
262
261
  "speakingurl": "^14.0.1",
262
+ "suspend-react": "0.1.3",
263
263
  "tar-fs": "^2.1.1",
264
264
  "tar-stream": "^3.1.7",
265
- "urlpattern-polyfill": "10.0.0",
266
265
  "use-device-pixel-ratio": "^1.1.0",
267
266
  "use-effect-event": "^1.0.2",
268
267
  "use-hot-module-reload": "^2.0.0",
269
268
  "use-sync-external-store": "^1.5.0",
270
269
  "uuid": "^11.0.5",
270
+ "valibot": "0.31.1",
271
271
  "vite": "^6.0.11",
272
- "xstate": "^5.19.2",
273
272
  "yargs": "^17.3.0"
274
273
  },
275
274
  "devDependencies": {
276
275
  "@playwright/experimental-ct-react": "1.51.1",
277
276
  "@playwright/test": "1.51.1",
278
- "@repo/dev-aliases": "3.83.0",
279
- "@repo/package.config": "3.83.0",
280
- "@repo/test-config": "3.83.0",
281
- "@sanity/codegen": "3.83.1-canary.23+2bb4c143a9",
277
+ "@repo/dev-aliases": "3.84.1-next.0",
278
+ "@repo/package.config": "3.84.1-next.0",
279
+ "@repo/test-config": "3.84.1-next.0",
280
+ "@sanity/codegen": "3.84.1-next.0",
282
281
  "@sanity/generate-help-url": "^3.0.0",
283
282
  "@sanity/pkg-utils": "6.13.4",
284
283
  "@sanity/tsdoc": "1.0.169",
@@ -325,5 +324,5 @@
325
324
  "engines": {
326
325
  "node": ">=18"
327
326
  },
328
- "gitHead": "2bb4c143a953cacedc1c687cda33a0fe9fb1a007"
327
+ "gitHead": "09b868ccb5b76181daf2495b124005cb36072796"
329
328
  }
@@ -13,7 +13,7 @@ const PostMessageTelemetry: FC<PostMessageTelemetryProps> = (props) => {
13
13
  const telemetry = useTelemetry()
14
14
 
15
15
  useEffect(() => {
16
- return comlink.on('visual-editing/telemetry-log', (message) => {
16
+ return comlink.on('visual-editing/telemetry-log', async (message) => {
17
17
  const {event, data} = message
18
18
 
19
19
  // SANITY_STUDIO_DEBUG_TELEMETRY ensures noop/in-browser logging for telemetry events
@@ -17,8 +17,7 @@ import {
17
17
  urlSearchParamVercelSetBypassCookie,
18
18
  } from '@sanity/preview-url-secret/constants'
19
19
  import {BoundaryElementProvider, Flex} from '@sanity/ui'
20
- import {useActorRef, useSelector} from '@xstate/react'
21
- import {lazy, Suspense, useCallback, useEffect, useMemo, useRef, useState} from 'react'
20
+ import {lazy, Suspense, useCallback, useEffect, useMemo, useReducer, useRef, useState} from 'react'
22
21
  import {
23
22
  type CommentIntentGetter,
24
23
  COMMENTS_INSPECTOR_NAME,
@@ -26,6 +25,7 @@ import {
26
25
  type SanityDocument,
27
26
  type Tool,
28
27
  useDataset,
28
+ usePerspective,
29
29
  useProjectId,
30
30
  useUnique,
31
31
  useWorkspace,
@@ -36,8 +36,7 @@ import {useEffectEvent} from 'use-effect-event'
36
36
 
37
37
  import {DEFAULT_TOOL_NAME, EDIT_INTENT_MODE} from './constants'
38
38
  import PostMessageFeatures from './features/PostMessageFeatures'
39
- import {presentationMachine} from './machines/presentation-machine'
40
- import {type PreviewUrlRef} from './machines/preview-url'
39
+ import {debounce} from './lib/debounce'
41
40
  import {SharedStateProvider} from './overlays/SharedStateProvider'
42
41
  import {Panel} from './panels/Panel'
43
42
  import {Panels} from './panels/Panels'
@@ -47,24 +46,29 @@ import {usePresentationNavigator} from './PresentationNavigator'
47
46
  import {PresentationParamsProvider} from './PresentationParamsProvider'
48
47
  import {PresentationProvider} from './PresentationProvider'
49
48
  import {Preview} from './preview/Preview'
49
+ import {
50
+ ACTION_IFRAME_LOADED,
51
+ ACTION_IFRAME_REFRESH,
52
+ ACTION_VISUAL_EDITING_OVERLAYS_TOGGLE,
53
+ presentationReducer,
54
+ presentationReducerInit,
55
+ } from './reducers/presentationReducer'
50
56
  import {
51
57
  type FrameState,
52
58
  type PresentationNavigate,
59
+ type PresentationPerspective,
53
60
  type PresentationPluginOptions,
54
61
  type PresentationStateParams,
55
62
  type PresentationViewport,
56
63
  type StructureDocumentPaneParams,
57
64
  type VisualEditingConnection,
58
65
  } from './types'
59
- import {useAllowPatterns} from './useAllowPatterns'
60
66
  import {useDocumentsOnPage} from './useDocumentsOnPage'
61
67
  import {useMainDocument} from './useMainDocument'
62
68
  import {useParams} from './useParams'
63
69
  import {usePopups} from './usePopups'
64
- import {usePresentationPerspective} from './usePresentationPerspective'
70
+ import {usePreviewUrl} from './usePreviewUrl'
65
71
  import {useStatus} from './useStatus'
66
- import {useTargetOrigin} from './useTargetOrigin'
67
- import {debounce} from './util/debounce'
68
72
 
69
73
  const LiveQueries = lazy(() => import('./loader/LiveQueries'))
70
74
  const PostMessageDocuments = lazy(() => import('./overlays/PostMessageDocuments'))
@@ -80,25 +84,20 @@ const Container = styled(Flex)`
80
84
 
81
85
  export default function PresentationTool(props: {
82
86
  tool: Tool<PresentationPluginOptions>
87
+ canCreateUrlPreviewSecrets: boolean
83
88
  canToggleSharePreviewAccess: boolean
84
89
  canUseSharedPreviewAccess: boolean
85
90
  vercelProtectionBypass: string | null
86
- initialPreviewUrl: URL
87
- previewUrlRef: PreviewUrlRef
88
91
  }): React.JSX.Element {
89
92
  const {
93
+ canCreateUrlPreviewSecrets,
90
94
  canToggleSharePreviewAccess,
91
95
  canUseSharedPreviewAccess,
92
96
  tool,
93
97
  vercelProtectionBypass,
94
- initialPreviewUrl,
95
- previewUrlRef,
96
98
  } = props
97
-
98
- const allowOrigins = useAllowPatterns(previewUrlRef)
99
- const targetOrigin = useTargetOrigin(previewUrlRef)
100
-
101
99
  const components = tool.options?.components
100
+ const _previewUrl = tool.options?.previewUrl
102
101
  const name = tool.name || DEFAULT_TOOL_NAME
103
102
  const {unstable_navigator, unstable_header} = components || {}
104
103
 
@@ -106,12 +105,39 @@ export default function PresentationTool(props: {
106
105
  state: PresentationStateParams
107
106
  }
108
107
  const routerSearchParams = useUnique(Object.fromEntries(routerState._searchParams || []))
109
- const perspective = usePresentationPerspective()
110
-
111
- const canSharePreviewAccess = useSelector(
112
- previewUrlRef,
113
- (state) => state.context.previewMode?.shareAccess !== false,
108
+ const {perspectiveStack, selectedPerspectiveName = 'drafts', selectedReleaseId} = usePerspective()
109
+ const perspective = (
110
+ selectedReleaseId ? perspectiveStack : selectedPerspectiveName
111
+ ) as PresentationPerspective
112
+
113
+ const initialPreviewUrl = usePreviewUrl(
114
+ _previewUrl || '/',
115
+ name,
116
+ perspective,
117
+ routerSearchParams.preview || null,
118
+ canCreateUrlPreviewSecrets,
114
119
  )
120
+ const canSharePreviewAccess = useMemo<boolean>(() => {
121
+ if (
122
+ _previewUrl &&
123
+ typeof _previewUrl === 'object' &&
124
+ 'draftMode' in _previewUrl &&
125
+ _previewUrl.draftMode
126
+ ) {
127
+ // eslint-disable-next-line no-console
128
+ console.warn('previewUrl.draftMode is deprecated, use previewUrl.previewMode instead')
129
+ return _previewUrl.draftMode.shareAccess !== false
130
+ }
131
+ if (
132
+ _previewUrl &&
133
+ typeof _previewUrl === 'object' &&
134
+ 'previewMode' in _previewUrl &&
135
+ _previewUrl.previewMode
136
+ ) {
137
+ return _previewUrl.previewMode.shareAccess !== false
138
+ }
139
+ return false
140
+ }, [_previewUrl])
115
141
 
116
142
  const [devMode] = useState(() => {
117
143
  const option = tool.options?.devMode
@@ -122,6 +148,10 @@ export default function PresentationTool(props: {
122
148
  return typeof window !== 'undefined' && window.location.hostname === 'localhost'
123
149
  })
124
150
 
151
+ const targetOrigin = useMemo(() => {
152
+ return initialPreviewUrl.origin
153
+ }, [initialPreviewUrl.origin])
154
+
125
155
  const iframeRef = useRef<HTMLIFrameElement>(null)
126
156
 
127
157
  const [controller, setController] = useState<Controller>()
@@ -151,7 +181,7 @@ export default function PresentationTool(props: {
151
181
  // Most navigation events should be debounced, so use this unless explicitly needed
152
182
  const navigate = useMemo(() => debounce<PresentationNavigate>(_navigate, 50), [_navigate])
153
183
 
154
- const presentationRef = useActorRef(presentationMachine)
184
+ const [state, dispatch] = useReducer(presentationReducer, {}, presentationReducerInit)
155
185
 
156
186
  const viewport = useMemo(() => (params.viewport ? 'mobile' : 'desktop'), [params.viewport])
157
187
 
@@ -165,7 +195,7 @@ export default function PresentationTool(props: {
165
195
  navigate: _navigate,
166
196
  navigationHistory,
167
197
  path: params.preview,
168
- targetOrigin,
198
+ previewUrl: tool.options?.previewUrl,
169
199
  resolvers: tool.options?.resolve?.mainDocuments,
170
200
  })
171
201
 
@@ -175,7 +205,7 @@ export default function PresentationTool(props: {
175
205
 
176
206
  const {open: handleOpenPopup} = usePopups(controller)
177
207
 
178
- const isLoading = useSelector(presentationRef, (state) => state.matches('loading'))
208
+ const isLoading = state.iframe.status === 'loading'
179
209
 
180
210
  useEffect(() => {
181
211
  const target = iframeRef.current?.contentWindow
@@ -220,19 +250,7 @@ export default function PresentationTool(props: {
220
250
  })
221
251
 
222
252
  comlink.on('visual-editing/navigate', (data) => {
223
- const {title} = data
224
- let url = data.url
225
- /**
226
- * The URL is relative, we need to resolve it to an absolute URL
227
- */
228
- if (!url.startsWith('http')) {
229
- try {
230
- url = new URL(url, targetOrigin).toString()
231
- } catch {
232
- // ignore
233
- }
234
- }
235
-
253
+ const {title, url} = data
236
254
  if (frameStateRef.current.url !== url) {
237
255
  try {
238
256
  // Handle bypass params being forwarded to the final URL
@@ -256,7 +274,10 @@ export default function PresentationTool(props: {
256
274
  })
257
275
 
258
276
  comlink.on('visual-editing/toggle', (data) => {
259
- presentationRef.send({type: 'toggle visual editing overlays', enabled: data.enabled})
277
+ dispatch({
278
+ type: ACTION_VISUAL_EDITING_OVERLAYS_TOGGLE,
279
+ enabled: data.enabled,
280
+ })
260
281
  })
261
282
 
262
283
  comlink.on('visual-editing/documents', (data) => {
@@ -273,12 +294,12 @@ export default function PresentationTool(props: {
273
294
  if (data.source === 'manual') {
274
295
  clearTimeout(refreshRef.current)
275
296
  } else if (data.source === 'mutation') {
276
- presentationRef.send({type: 'iframe refresh'})
297
+ dispatch({type: ACTION_IFRAME_REFRESH})
277
298
  }
278
299
  })
279
300
 
280
301
  comlink.on('visual-editing/refreshed', () => {
281
- presentationRef.send({type: 'iframe loaded'})
302
+ dispatch({type: ACTION_IFRAME_LOADED})
282
303
  })
283
304
 
284
305
  comlink.onStatus(setOverlaysConnection)
@@ -289,7 +310,7 @@ export default function PresentationTool(props: {
289
310
  stop()
290
311
  setVisualEditingComlink(null)
291
312
  }
292
- }, [controller, presentationRef, setDocumentsOnPage, setOverlaysConnection, targetOrigin])
313
+ }, [controller, setDocumentsOnPage, setOverlaysConnection, targetOrigin])
293
314
 
294
315
  useEffect(() => {
295
316
  if (!controller) return undefined
@@ -322,7 +343,7 @@ export default function PresentationTool(props: {
322
343
 
323
344
  const handleFocusPath = useCallback(
324
345
  (nextPath: Path) => {
325
- // Don't need to explicitly set the id here because it was either already set via postMessage or is the same if navigating in the document pane
346
+ // Dont need to explicitly set the id here because it was either already set via postMessage or is the same if navigating in the document pane
326
347
  navigate({path: studioPath.toString(nextPath)}, {}, true)
327
348
  },
328
349
  [navigate],
@@ -330,20 +351,13 @@ export default function PresentationTool(props: {
330
351
 
331
352
  const handlePreviewPath = useCallback(
332
353
  (nextPath: string) => {
333
- const url = new URL(nextPath, targetOrigin)
334
- const preview = url.toString()
335
- if (params.preview === preview) {
336
- return
337
- }
338
- if (Array.isArray(allowOrigins)) {
339
- if (allowOrigins.some((pattern) => pattern.test(preview))) {
340
- navigate({}, {preview})
341
- }
342
- } else if (url.origin === targetOrigin) {
354
+ const url = new URL(nextPath, initialPreviewUrl.origin)
355
+ const preview = url.pathname + url.search
356
+ if (url.origin === initialPreviewUrl.origin && preview !== params.preview) {
343
357
  navigate({}, {preview})
344
358
  }
345
359
  },
346
- [targetOrigin, params.preview, allowOrigins, navigate],
360
+ [initialPreviewUrl, params, navigate],
347
361
  )
348
362
 
349
363
  const handleStructureParams = useCallback(
@@ -369,32 +383,12 @@ export default function PresentationTool(props: {
369
383
  params.preview &&
370
384
  frameStateRef.current.url !== params.preview
371
385
  ) {
372
- try {
373
- const frameOrigin = new URL(frameStateRef.current.url, targetOrigin).origin
374
- const previewOrigin = new URL(params.preview, targetOrigin).origin
375
- if (frameOrigin !== previewOrigin) {
376
- return
377
- }
378
- } catch {
379
- // ignore
380
- }
381
-
382
386
  frameStateRef.current.url = params.preview
383
- if (overlaysConnection === 'connected') {
384
- /**
385
- * Translate the possibly absolute params url back to a relative URL
386
- */
387
- let url = params.preview
388
- if (url.startsWith('http')) {
389
- try {
390
- const newUrl = new URL(params.preview, targetOrigin)
391
- url = newUrl.pathname + newUrl.search + newUrl.hash
392
- } catch {
393
- // ignore
394
- }
395
- }
387
+ if (overlaysConnection !== 'connected' && iframeRef.current) {
388
+ iframeRef.current.src = `${targetOrigin}${params.preview}`
389
+ } else {
396
390
  visualEditingComlink?.post('presentation/navigate', {
397
- url,
391
+ url: params.preview,
398
392
  type: 'replace',
399
393
  })
400
394
  }
@@ -442,7 +436,7 @@ export default function PresentationTool(props: {
442
436
  const refreshRef = useRef<number>(undefined)
443
437
  const handleRefresh = useCallback(
444
438
  (fallback: () => void) => {
445
- presentationRef.send({type: 'iframe refresh'})
439
+ dispatch({type: ACTION_IFRAME_REFRESH})
446
440
  if (visualEditingComlink) {
447
441
  // We only wait 300ms for the iframe to ack the refresh request before running the fallback logic
448
442
  refreshRef.current = window.setTimeout(fallback, 300)
@@ -455,7 +449,7 @@ export default function PresentationTool(props: {
455
449
  }
456
450
  fallback()
457
451
  },
458
- [loadersConnection, presentationRef, previewKitConnection, visualEditingComlink],
452
+ [loadersConnection, previewKitConnection, visualEditingComlink],
459
453
  )
460
454
 
461
455
  const workspace = useWorkspace()
@@ -517,13 +511,14 @@ export default function PresentationTool(props: {
517
511
  <Flex direction="column" flex={1} height="fill" ref={setBoundaryElement}>
518
512
  <BoundaryElementProvider element={boundaryElement}>
519
513
  <Preview
520
- // @TODO move closer to the <iframe> element itself to allow for more precise handling of when to reload the iframe and when to reconnect when the target origin changes
521
514
  // Make sure the iframe is unmounted if the targetOrigin has changed
522
515
  key={targetOrigin}
523
516
  canSharePreviewAccess={canSharePreviewAccess}
524
517
  canToggleSharePreviewAccess={canToggleSharePreviewAccess}
525
518
  canUseSharedPreviewAccess={canUseSharedPreviewAccess}
519
+ dispatch={dispatch}
526
520
  header={unstable_header}
521
+ iframe={state.iframe}
527
522
  initialUrl={initialPreviewUrl}
528
523
  loadersConnection={loadersConnection}
529
524
  navigatorEnabled={navigatorEnabled}
@@ -539,9 +534,8 @@ export default function PresentationTool(props: {
539
534
  toggleNavigator={toggleNavigator}
540
535
  toggleOverlay={toggleOverlay}
541
536
  viewport={viewport}
537
+ visualEditing={state.visualEditing}
542
538
  vercelProtectionBypass={vercelProtectionBypass}
543
- presentationRef={presentationRef}
544
- previewUrlRef={previewUrlRef}
545
539
  />
546
540
  </BoundaryElementProvider>
547
541
  </Flex>
@@ -606,12 +600,10 @@ export default function PresentationTool(props: {
606
600
  )
607
601
  }
608
602
 
609
- // @TODO reconcile with core utils
610
603
  function isAltKey(event: KeyboardEvent): boolean {
611
604
  return event.key === 'Alt'
612
605
  }
613
606
 
614
- // @TODO reconcile with core utils
615
607
  const IS_MAC =
616
608
  typeof window != 'undefined' && /Mac|iPod|iPhone|iPad/.test(window.navigator.platform)
617
609
  const MODIFIERS: Record<string, 'altKey' | 'ctrlKey' | 'metaKey' | 'shiftKey'> = {
@@ -620,7 +612,6 @@ const MODIFIERS: Record<string, 'altKey' | 'ctrlKey' | 'metaKey' | 'shiftKey'> =
620
612
  mod: IS_MAC ? 'metaKey' : 'ctrlKey',
621
613
  shift: 'shiftKey',
622
614
  }
623
- // @TODO reconcile with core utils
624
615
  function isHotkey(keys: string[], event: KeyboardEvent): boolean {
625
616
  return keys.every((key) => {
626
617
  if (MODIFIERS[key]) {
@@ -1,67 +1,103 @@
1
- import {useSelector} from '@xstate/react'
2
- import {type Tool} from 'sanity'
1
+ import {
2
+ schemaIdSingleton,
3
+ schemaType,
4
+ schemaTypeSingleton,
5
+ } from '@sanity/preview-url-secret/constants'
6
+ import {useToast} from '@sanity/ui'
7
+ import {uuid} from '@sanity/uuid'
8
+ import {useEffect, useState} from 'react'
9
+ import {type PermissionCheckResult, type Tool, useGrantsStore, useTranslation} from 'sanity'
3
10
 
11
+ import {presentationLocaleNamespace} from './i18n'
4
12
  import {PresentationSpinner} from './PresentationSpinner'
5
13
  import PresentationTool from './PresentationTool'
6
14
  import {type PresentationPluginOptions} from './types'
7
- import {usePreviewUrlActorRef} from './usePreviewUrlActorRef'
8
- import {useReportInvalidPreviewSearchParam} from './useReportInvalidPreviewSearchParam'
9
15
  import {useVercelBypassSecret} from './useVercelBypassSecret'
10
16
 
11
- export default function PresentationToolGrantsCheck({
12
- tool,
13
- }: {
17
+ export default function PresentationToolGrantsCheck(props: {
14
18
  tool: Tool<PresentationPluginOptions>
15
19
  }): React.JSX.Element {
16
- const previewUrlRef = usePreviewUrlActorRef(tool.options?.previewUrl, tool.options?.allowOrigins)
17
- useReportInvalidPreviewSearchParam(previewUrlRef)
20
+ const {t} = useTranslation(presentationLocaleNamespace)
21
+ const {previewUrl} = props.tool.options ?? {}
22
+ const {push: pushToast} = useToast()
23
+ const willGeneratePreviewUrlSecret =
24
+ typeof previewUrl === 'object' || typeof previewUrl === 'function'
25
+ const grantsStore = useGrantsStore()
26
+ const [previewAccessSharingCreatePermission, setCreateAccessSharingPermission] =
27
+ useState<PermissionCheckResult | null>(null)
28
+ const [previewAccessSharingUpdatePermission, setUpdateAccessSharingPermission] =
29
+ useState<PermissionCheckResult | null>(null)
30
+ const [previewAccessSharingReadPermission, setReadAccessSharingPermission] =
31
+ useState<PermissionCheckResult | null>(null)
32
+ const [previewUrlSecretPermission, setPreviewUrlSecretPermission] =
33
+ useState<PermissionCheckResult | null>(null)
34
+
35
+ useEffect(() => {
36
+ if (!willGeneratePreviewUrlSecret) return undefined
37
+
38
+ const previewCreateAccessSharingPermissionSubscription = grantsStore
39
+ .checkDocumentPermission('create', {_id: schemaIdSingleton, _type: schemaTypeSingleton})
40
+ .subscribe(setCreateAccessSharingPermission)
41
+ const previewUpdateAccessSharingPermissionSubscription = grantsStore
42
+ .checkDocumentPermission('update', {_id: schemaIdSingleton, _type: schemaTypeSingleton})
43
+ .subscribe(setUpdateAccessSharingPermission)
44
+ const previewReadAccessSharingPermissionSubscription = grantsStore
45
+ .checkDocumentPermission('read', {_id: schemaIdSingleton, _type: schemaTypeSingleton})
46
+ .subscribe(setReadAccessSharingPermission)
47
+ const previewUrlSecretPermissionSubscription = grantsStore
48
+ .checkDocumentPermission('create', {_id: `drafts.${uuid()}`, _type: schemaType})
49
+ .subscribe(setPreviewUrlSecretPermission)
50
+
51
+ return () => {
52
+ previewCreateAccessSharingPermissionSubscription.unsubscribe()
53
+ previewUpdateAccessSharingPermissionSubscription.unsubscribe()
54
+ previewReadAccessSharingPermissionSubscription.unsubscribe()
55
+ previewUrlSecretPermissionSubscription.unsubscribe()
56
+ }
57
+ }, [grantsStore, willGeneratePreviewUrlSecret])
58
+
59
+ const canCreateUrlPreviewSecrets = previewUrlSecretPermission?.granted
60
+
61
+ useEffect(() => {
62
+ if (!willGeneratePreviewUrlSecret || canCreateUrlPreviewSecrets !== false) return undefined
63
+ const raf = requestAnimationFrame(() =>
64
+ pushToast({
65
+ closable: true,
66
+ status: 'error',
67
+ duration: 30_000,
68
+ title: t('preview-url-secret.missing-grants'),
69
+ }),
70
+ )
71
+ return () => cancelAnimationFrame(raf)
72
+ }, [canCreateUrlPreviewSecrets, pushToast, t, willGeneratePreviewUrlSecret])
18
73
 
19
- const previewAccessSharingCreatePermission = useSelector(
20
- previewUrlRef,
21
- (state) => state.context.previewAccessSharingCreatePermission,
22
- )
23
- const previewAccessSharingUpdatePermission = useSelector(
24
- previewUrlRef,
25
- (state) => state.context.previewAccessSharingUpdatePermission,
26
- )
27
- const previewAccessSharingReadPermission = useSelector(
28
- previewUrlRef,
29
- (state) => state.context.previewAccessSharingReadPermission,
30
- )
31
- const previewUrlSecretPermission = useSelector(
32
- previewUrlRef,
33
- (state) => state.context.previewUrlSecretPermission,
34
- )
35
- const url = useSelector(previewUrlRef, (state) => state.context.previewUrl)
36
- // @TODO the vercel protection bypass can be moved to the iframe level
37
74
  const [vercelProtectionBypass, vercelProtectionBypassReadyState] = useVercelBypassSecret()
38
75
 
39
76
  if (
40
- !url ||
41
77
  vercelProtectionBypassReadyState === 'loading' ||
42
- !previewAccessSharingCreatePermission ||
43
- typeof previewAccessSharingCreatePermission.granted === 'undefined' ||
44
- !previewAccessSharingUpdatePermission ||
45
- typeof previewAccessSharingUpdatePermission.granted === 'undefined' ||
46
- !previewUrlSecretPermission ||
47
- !previewAccessSharingReadPermission ||
48
- typeof previewAccessSharingReadPermission.granted === 'undefined' ||
49
- typeof previewUrlSecretPermission.granted === 'undefined'
78
+ (willGeneratePreviewUrlSecret &&
79
+ (!previewAccessSharingCreatePermission ||
80
+ typeof previewAccessSharingCreatePermission.granted === 'undefined' ||
81
+ !previewAccessSharingUpdatePermission ||
82
+ typeof previewAccessSharingUpdatePermission.granted === 'undefined' ||
83
+ !previewUrlSecretPermission ||
84
+ !previewAccessSharingReadPermission ||
85
+ typeof previewAccessSharingReadPermission.granted === 'undefined' ||
86
+ typeof previewUrlSecretPermission.granted === 'undefined'))
50
87
  ) {
51
88
  return <PresentationSpinner />
52
89
  }
53
90
 
54
91
  return (
55
92
  <PresentationTool
56
- tool={tool}
57
- initialPreviewUrl={url}
93
+ {...props}
58
94
  vercelProtectionBypass={vercelProtectionBypass}
95
+ canCreateUrlPreviewSecrets={canCreateUrlPreviewSecrets === true}
59
96
  canToggleSharePreviewAccess={
60
97
  previewAccessSharingCreatePermission?.granted === true &&
61
98
  previewAccessSharingUpdatePermission?.granted === true
62
99
  }
63
100
  canUseSharedPreviewAccess={previewAccessSharingReadPermission?.granted === true}
64
- previewUrlRef={previewUrlRef}
65
101
  />
66
102
  )
67
103
  }