sanity-plugin-iframe-pane 2.3.1 → 2.3.2-canary.2

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 (40) hide show
  1. package/README.md +15 -5
  2. package/lib/_chunks/is-valid-secret-57fea7e5.js +35 -0
  3. package/lib/_chunks/is-valid-secret-57fea7e5.js.map +1 -0
  4. package/lib/_chunks/is-valid-secret-7b704c76.cjs +41 -0
  5. package/lib/_chunks/is-valid-secret-7b704c76.cjs.map +1 -0
  6. package/lib/_chunks/types-107599a1.js +34 -0
  7. package/lib/_chunks/types-107599a1.js.map +1 -0
  8. package/lib/_chunks/types-4a29b6ac.cjs +38 -0
  9. package/lib/_chunks/types-4a29b6ac.cjs.map +1 -0
  10. package/lib/index.cjs +462 -154
  11. package/lib/index.cjs.js +3 -1
  12. package/lib/index.cjs.map +1 -1
  13. package/lib/index.d.ts +38 -11
  14. package/lib/index.js +462 -158
  15. package/lib/index.js.map +1 -1
  16. package/lib/is-valid-secret.cjs +8 -0
  17. package/lib/is-valid-secret.cjs.js +4 -0
  18. package/lib/is-valid-secret.cjs.map +1 -0
  19. package/lib/is-valid-secret.d.ts +41 -0
  20. package/lib/is-valid-secret.js +2 -0
  21. package/lib/is-valid-secret.js.map +1 -0
  22. package/lib/preview-url.cjs +77 -0
  23. package/lib/preview-url.cjs.js +4 -0
  24. package/lib/preview-url.cjs.map +1 -0
  25. package/lib/preview-url.d.ts +17 -0
  26. package/lib/preview-url.js +72 -0
  27. package/lib/preview-url.js.map +1 -0
  28. package/package.json +34 -10
  29. package/src/DisplayUrl.tsx +21 -0
  30. package/src/GetUrlSecret.tsx +80 -0
  31. package/src/Iframe.tsx +272 -158
  32. package/src/Toolbar.tsx +153 -0
  33. package/src/defineUrlResolver.tsx +31 -0
  34. package/src/index.ts +3 -5
  35. package/src/is-valid-secret.ts +2 -0
  36. package/src/isValidSecret.tsx +67 -0
  37. package/src/preview-url.ts +2 -0
  38. package/src/previewUrl.ts +62 -0
  39. package/src/types.ts +17 -0
  40. package/src/utils.ts +45 -0
@@ -0,0 +1,2 @@
1
+ export type * from './isValidSecret'
2
+ export {isValidSecret} from './isValidSecret'
@@ -0,0 +1,67 @@
1
+ import {name} from '../package.json'
2
+
3
+ export type UrlSecretId = `${string}.${string}`
4
+
5
+ // updated within the hour, if it's older it'll create a new secret or return null
6
+ export const SECRET_TTL = 60 * 60
7
+
8
+ export const fetchSecretQuery = /* groq */ `*[_id == $id && dateTime(_updatedAt) > dateTime(now()) - ${SECRET_TTL}][0]{secret, _updatedAt}`
9
+ export type FetchSecretResponse = {
10
+ secret: string | null
11
+ _updatedAt: string | null
12
+ } | null
13
+
14
+ export const tag = name
15
+
16
+ export const apiVersion = '2023-08-08'
17
+
18
+ export type SanityClientLike = {
19
+ config(): {token?: string}
20
+ withConfig(config: {apiVersion?: string; useCdn?: boolean; perspective: 'raw'}): SanityClientLike
21
+ fetch<
22
+ R,
23
+ Q = {
24
+ [key: string]: any
25
+ },
26
+ >(
27
+ query: string,
28
+ params: Q,
29
+ options: {tag?: string},
30
+ ): Promise<R>
31
+ }
32
+ export async function isValidSecret(
33
+ client: SanityClientLike,
34
+ urlSecretId: UrlSecretId,
35
+ urlSecret: string,
36
+ ): Promise<boolean> {
37
+ if (!urlSecret) {
38
+ throw new TypeError('`urlSecret` is required')
39
+ }
40
+ if (!urlSecretId) {
41
+ throw new TypeError('`urlSecretId` is required')
42
+ }
43
+ if (!urlSecretId.includes('.')) {
44
+ throw new TypeError(
45
+ `\`urlSecretId\` must have a dot prefix, \`${urlSecretId}\` is not secure, add a prefix, for example \`preview.${urlSecretId}\` `,
46
+ )
47
+ }
48
+ if (!client) {
49
+ throw new TypeError('`client` is required')
50
+ }
51
+ if (!client.config().token) {
52
+ throw new TypeError('`client` must have a `token` specified')
53
+ }
54
+
55
+ const customClient = client.withConfig({
56
+ apiVersion,
57
+ useCdn: false,
58
+ perspective: 'raw',
59
+ })
60
+ const data = await customClient.fetch<FetchSecretResponse>(
61
+ fetchSecretQuery,
62
+ {id: urlSecretId},
63
+ {tag},
64
+ )
65
+
66
+ return data?.secret === urlSecret
67
+ }
@@ -0,0 +1,2 @@
1
+ export type * from './previewUrl'
2
+ export {previewUrl} from './previewUrl'
@@ -0,0 +1,62 @@
1
+ /**
2
+ * This plugin sets up the "Open preview (CTRL + ALT + O)" in the dropdown menu that hosts
3
+ * other actions like "Review changes" and "Inspect"
4
+ */
5
+
6
+ import {definePlugin} from 'sanity'
7
+
8
+ import {defineUrlResolver, DefineUrlResolverOptions} from './defineUrlResolver'
9
+ import {
10
+ apiVersion,
11
+ fetchSecretQuery,
12
+ FetchSecretResponse,
13
+ tag,
14
+ type UrlSecretId,
15
+ } from './isValidSecret'
16
+ import {MissingSlug} from './types'
17
+ import {patchUrlSecret} from './utils'
18
+
19
+ export type {DefineUrlResolverOptions, UrlSecretId}
20
+
21
+ export interface ProductionUrlOptions extends DefineUrlResolverOptions {
22
+ matchTypes?: string[]
23
+ urlSecretId?: UrlSecretId
24
+ }
25
+
26
+ export const previewUrl = definePlugin<ProductionUrlOptions>(
27
+ ({urlSecretId, base, matchTypes, requiresSlug}) => {
28
+ if (!base) {
29
+ throw new TypeError('`base` is required')
30
+ }
31
+
32
+ const urlResolver = defineUrlResolver({base, requiresSlug})
33
+ return {
34
+ name: 'previewUrl',
35
+ document: {
36
+ productionUrl: async (prev, {document, getClient}) => {
37
+ if (matchTypes?.length && !matchTypes.includes(document._type)) {
38
+ return prev
39
+ }
40
+
41
+ let urlSecret: string | null = null
42
+ if (urlSecretId) {
43
+ const client = getClient({apiVersion})
44
+ const data = await client.fetch<FetchSecretResponse>(
45
+ fetchSecretQuery,
46
+ {id: urlSecretId},
47
+ {tag},
48
+ )
49
+ urlSecret = data?.secret ? data.secret : await patchUrlSecret(client, urlSecretId)
50
+ }
51
+
52
+ const url = urlResolver(document, urlSecret)
53
+ if (url) {
54
+ return url === MissingSlug ? prev : url
55
+ }
56
+
57
+ return prev
58
+ },
59
+ },
60
+ }
61
+ },
62
+ )
package/src/types.ts ADDED
@@ -0,0 +1,17 @@
1
+ export const MissingSlug = Symbol('MissingSlug')
2
+
3
+ export type UrlState = string | typeof MissingSlug
4
+
5
+ export type IframeSizeKey = keyof SizeProps
6
+
7
+ export type Size = 'desktop' | 'mobile'
8
+
9
+ export type SizeProps = {
10
+ // eslint-disable-next-line no-unused-vars
11
+ [key in Size]: {
12
+ width: string | number
13
+ height: string | number
14
+ }
15
+ }
16
+
17
+ export type SetError = (error: unknown) => void
package/src/utils.ts ADDED
@@ -0,0 +1,45 @@
1
+ import type {SanityClient} from 'sanity'
2
+
3
+ import {SECRET_TTL, tag, UrlSecretId} from './isValidSecret'
4
+
5
+ export function getExpiresAt(_updatedAt: Date) {
6
+ return new Date(_updatedAt.getTime() + 1000 * SECRET_TTL)
7
+ }
8
+
9
+ function generateUrlSecret() {
10
+ // Try using WebCrypto if available
11
+ if (typeof crypto !== 'undefined') {
12
+ // Generate a random array of 16 bytes
13
+ const array = new Uint8Array(16)
14
+ crypto.getRandomValues(array)
15
+
16
+ // Convert the array to a URL-safe string
17
+ let key = ''
18
+ for (let i = 0; i < array.length; i++) {
19
+ // Convert each byte to a 2-digit hexadecimal number
20
+ key += array[i].toString(16).padStart(2, '0')
21
+ }
22
+
23
+ // Replace '+' and '/' from base64url to '-' and '_'
24
+ key = btoa(key).replace(/\+/g, '-').replace(/\//g, '_').replace(/[=]+$/, '')
25
+
26
+ return key
27
+ }
28
+ // If not fallback to Math.random
29
+ return Math.random().toString(36).slice(2)
30
+ }
31
+
32
+ export async function patchUrlSecret(
33
+ client: SanityClient,
34
+ urlSecretId: UrlSecretId,
35
+ signal?: AbortSignal,
36
+ ): Promise<string> {
37
+ const newSecret = generateUrlSecret()
38
+ const patch = client.patch(urlSecretId).set({secret: newSecret})
39
+ await client
40
+ .transaction()
41
+ .createIfNotExists({_id: urlSecretId, _type: urlSecretId})
42
+ .patch(patch)
43
+ .commit({tag, signal})
44
+ return newSecret
45
+ }