@sanity/preview-url-secret
Advanced tools
Comparing version 1.6.20-canary.1 to 1.6.20-canary.3
@@ -1,24 +0,1 @@ | ||
const schemaType = "sanity.previewUrlSecret"; | ||
const schemaIdPrefix = "sanity-preview-url-secret"; | ||
const apiVersion = "2023-11-09"; | ||
const urlSearchParamPreviewSecret = "sanity-preview-secret"; | ||
const urlSearchParamPreviewPathname = "sanity-preview-pathname"; | ||
const isDev = process.env.NODE_ENV === "development"; | ||
const SECRET_TTL = 60 * 60; | ||
const fetchSecretQuery = ( | ||
/* groq */ | ||
`*[_type == "${schemaType}" && secret == $secret && dateTime(_updatedAt) > dateTime(now()) - ${SECRET_TTL}][0]{ | ||
_id, | ||
_updatedAt, | ||
secret, | ||
studioUrl, | ||
}` | ||
); | ||
const deleteExpiredSecretsQuery = ( | ||
/* groq */ | ||
`*[_type == "${schemaType}" && dateTime(_updatedAt) <= dateTime(now()) - ${SECRET_TTL}]` | ||
); | ||
const tag = "sanity.preview-url-secret"; | ||
export { SECRET_TTL, apiVersion, deleteExpiredSecretsQuery, fetchSecretQuery, isDev, schemaIdPrefix, schemaType, tag, urlSearchParamPreviewPathname, urlSearchParamPreviewSecret }; | ||
//# sourceMappingURL=constants.js.map | ||
const e="sanity.previewUrlSecret",t="sanity-preview-url-secret",s="2023-11-09",a="sanity-preview-secret",r="sanity-preview-pathname",i="development"===process.env.NODE_ENV,n=3600,d=`*[_type == "${e}" && secret == $secret && dateTime(_updatedAt) > dateTime(now()) - 3600][0]{\n _id,\n _updatedAt,\n secret,\n studioUrl,\n}`,p=`*[_type == "${e}" && dateTime(_updatedAt) <= dateTime(now()) - 3600]`,c="sanity.preview-url-secret";export{n as S,s as a,r as b,e as c,p as d,d as f,i,t as s,c as t,a as u};//# sourceMappingURL=constants.js.map |
@@ -1,33 +0,1 @@ | ||
import { uuid } from '@sanity/uuid'; | ||
import { apiVersion, SECRET_TTL, schemaIdPrefix, schemaType, tag, deleteExpiredSecretsQuery } from './_chunks-es/constants.js'; | ||
function generateUrlSecret() { | ||
if (typeof crypto !== "undefined") { | ||
const array = new Uint8Array(16); | ||
crypto.getRandomValues(array); | ||
let key = ""; | ||
for (let i = 0; i < array.length; i++) { | ||
key += array[i].toString(16).padStart(2, "0"); | ||
} | ||
key = btoa(key).replace(/\+/g, "-").replace(/\//g, "_").replace(/[=]+$/, ""); | ||
return key; | ||
} | ||
return Math.random().toString(36).slice(2); | ||
} | ||
async function createPreviewSecret(_client, source, studioUrl, userId, id = uuid()) { | ||
const client = _client.withConfig({ apiVersion }); | ||
try { | ||
const expiresAt = new Date(Date.now() + 1e3 * SECRET_TTL); | ||
const _id = `${schemaIdPrefix}.${id}`; | ||
const newSecret = generateUrlSecret(); | ||
const patch = client.patch(_id).set({ secret: newSecret, source, studioUrl, userId }); | ||
await client.transaction().createOrReplace({ _id, _type: schemaType }).patch(patch).commit({ tag }); | ||
return { secret: newSecret, expiresAt }; | ||
} finally { | ||
await client.delete({ query: deleteExpiredSecretsQuery }); | ||
} | ||
} | ||
export { createPreviewSecret }; | ||
//# sourceMappingURL=create-secret.js.map | ||
import{uuid as t}from"@sanity/uuid";import{a as e,S as a,s as r,c as n,t as s,d as o}from"./_chunks-es/constants.js";async function c(c,i,p,u,l=t()){const y=c.withConfig({apiVersion:e});try{const t=new Date(Date.now()+1e3*a),e=`${r}.${l}`,o=function(){if(typeof crypto<"u"){const t=new Uint8Array(16);crypto.getRandomValues(t);let e="";for(let a=0;a<t.length;a++)e+=t[a].toString(16).padStart(2,"0");return e=btoa(e).replace(/\+/g,"-").replace(/\//g,"_").replace(/[=]+$/,""),e}return Math.random().toString(36).slice(2)}(),c=y.patch(e).set({secret:o,source:i,studioUrl:p,userId:u});return await y.transaction().createOrReplace({_id:e,_type:n}).patch(c).commit({tag:s}),{secret:o,expiresAt:t}}finally{await y.delete({query:o})}}export{c as createPreviewSecret};//# sourceMappingURL=create-secret.js.map |
@@ -1,49 +0,1 @@ | ||
import { urlSearchParamPreviewSecret, urlSearchParamPreviewPathname } from './_chunks-es/constants.js'; | ||
function definePreviewUrl(options) { | ||
const { | ||
draftMode, | ||
previewMode, | ||
origin = typeof location === "undefined" ? "https://localhost" : location.origin | ||
} = options; | ||
const enableUrl = previewMode?.enable || draftMode?.enable; | ||
let { preview = "/" } = options; | ||
const productionUrl = new URL(preview, origin); | ||
const enablePreviewModeUrl = enableUrl ? new URL(enableUrl, origin) : void 0; | ||
return async (context) => { | ||
try { | ||
if (context.previewSearchParam) { | ||
const restoredUrl = new URL(context.previewSearchParam, productionUrl); | ||
if (restoredUrl.origin === productionUrl.origin) { | ||
preview = `${restoredUrl.pathname}${restoredUrl.search}`; | ||
} | ||
} else if (context.referrer) { | ||
const referrerUrl = new URL(context.referrer); | ||
if (referrerUrl.origin === productionUrl.origin) { | ||
preview = `${referrerUrl.pathname}${referrerUrl.search}`; | ||
} | ||
} | ||
} catch { | ||
} | ||
if (typeof location !== "undefined" && location.origin === productionUrl.origin && context.studioBasePath && (preview.startsWith(`${context.studioBasePath}/`) || preview === context.studioBasePath)) { | ||
preview = options.preview || "/"; | ||
} | ||
const previewUrl = new URL(preview, productionUrl); | ||
if (enablePreviewModeUrl) { | ||
const enablePreviewModeRequestUrl = new URL(enablePreviewModeUrl); | ||
const { searchParams } = enablePreviewModeRequestUrl; | ||
searchParams.set(urlSearchParamPreviewSecret, context.previewUrlSecret); | ||
if (previewUrl.pathname !== enablePreviewModeRequestUrl.pathname) { | ||
searchParams.set( | ||
urlSearchParamPreviewPathname, | ||
`${previewUrl.pathname}${previewUrl.search}` | ||
); | ||
} | ||
return enablePreviewModeRequestUrl.toString(); | ||
} | ||
return previewUrl.toString(); | ||
}; | ||
} | ||
export { definePreviewUrl }; | ||
//# sourceMappingURL=define-preview-url.js.map | ||
import{u as e,b as t}from"./_chunks-es/constants.js";function r(r){const{draftMode:a,previewMode:n,origin:o=(typeof location>"u"?"https://localhost":location.origin)}=r,i=n?.enable||a?.enable;let{preview:s="/"}=r;const c=new URL(s,o),h=i?new URL(i,o):void 0;return async a=>{try{if(a.previewSearchParam){const e=new URL(a.previewSearchParam,c);e.origin===c.origin&&(s=`${e.pathname}${e.search}`)}else if(a.referrer){const e=new URL(a.referrer);e.origin===c.origin&&(s=`${e.pathname}${e.search}`)}}catch{}typeof location<"u"&&location.origin===c.origin&&a.studioBasePath&&(s.startsWith(`${a.studioBasePath}/`)||s===a.studioBasePath)&&(s=r.preview||"/");const n=new URL(s,c);if(h){const r=new URL(h),{searchParams:o}=r;return o.set(e,a.previewUrlSecret),n.pathname!==r.pathname&&o.set(t,`${n.pathname}${n.search}`),r.toString()}return n.toString()}}export{r as definePreviewUrl};//# sourceMappingURL=define-preview-url.js.map |
@@ -1,11 +0,1 @@ | ||
import { urlSearchParamPreviewPathname } from './_chunks-es/constants.js'; | ||
function getRedirectTo(url) { | ||
if (url.searchParams.has(urlSearchParamPreviewPathname)) { | ||
return new URL(url.searchParams.get(urlSearchParamPreviewPathname), url.origin); | ||
} | ||
return url; | ||
} | ||
export { getRedirectTo }; | ||
//# sourceMappingURL=get-redirect-to.js.map | ||
import{b as s}from"./_chunks-es/constants.js";function r(r){return r.searchParams.has(s)?new URL(r.searchParams.get(s),r.origin):r}export{r as getRedirectTo};//# sourceMappingURL=get-redirect-to.js.map |
@@ -1,98 +0,1 @@ | ||
import { apiVersion, urlSearchParamPreviewSecret, urlSearchParamPreviewPathname, fetchSecretQuery, tag, isDev } from './_chunks-es/constants.js'; | ||
function createClientWithConfig(client) { | ||
if (!client) { | ||
throw new TypeError("`client` is required"); | ||
} | ||
if (!client.config().token) { | ||
throw new TypeError("`client` must have a `token` specified"); | ||
} | ||
return client.withConfig({ | ||
// Userland might be using an API version that's too old to use perspectives | ||
apiVersion, | ||
// We can't use the CDN, the secret is typically validated right after it's created | ||
useCdn: false, | ||
// The documents that hold secrets are never drafts | ||
perspective: "published", | ||
// Don't waste time returning a source map, we don't need it | ||
resultSourceMap: false, | ||
// @ts-expect-error - If stega is enabled, make sure it's disabled | ||
stega: false | ||
}); | ||
} | ||
function parsePreviewUrl(unsafeUrl) { | ||
const url = new URL(unsafeUrl, "http://localhost"); | ||
const secret = url.searchParams.get(urlSearchParamPreviewSecret); | ||
if (!secret) { | ||
throw new Error("Missing secret"); | ||
} | ||
let redirectTo = void 0; | ||
const unsafeRedirectTo = url.searchParams.get(urlSearchParamPreviewPathname); | ||
if (unsafeRedirectTo) { | ||
const { pathname, search, hash } = new URL(unsafeRedirectTo, "http://localhost"); | ||
redirectTo = `${pathname}${search}${hash}`; | ||
} | ||
return { secret, redirectTo }; | ||
} | ||
async function validateSecret(client, secret, disableCacheNoStore) { | ||
if (typeof EdgeRuntime !== "undefined") { | ||
await new Promise((resolve) => setTimeout(resolve, 300)); | ||
} | ||
if (!secret || !secret.trim()) { | ||
return { isValid: false, studioUrl: null }; | ||
} | ||
const result = await client.fetch( | ||
fetchSecretQuery, | ||
{ secret }, | ||
{ | ||
tag, | ||
// In CloudFlare Workers we can't pass the cache header | ||
...!disableCacheNoStore ? { cache: "no-store" } : void 0 | ||
} | ||
); | ||
if (!result?._id || !result?._updatedAt || !result?.secret) { | ||
return { isValid: false, studioUrl: null }; | ||
} | ||
return { isValid: secret === result.secret, studioUrl: result.studioUrl }; | ||
} | ||
async function validatePreviewUrl(_client, previewUrl, disableCacheNoStore = globalThis.navigator?.userAgent === "Cloudflare-Workers") { | ||
const client = createClientWithConfig(_client); | ||
let parsedPreviewUrl; | ||
try { | ||
parsedPreviewUrl = parsePreviewUrl(previewUrl); | ||
} catch (error) { | ||
if (isDev) { | ||
console.error("Failed to parse preview URL", error, { | ||
previewUrl, | ||
client | ||
}); | ||
} | ||
return { isValid: false }; | ||
} | ||
const { isValid, studioUrl } = await validateSecret( | ||
client, | ||
parsedPreviewUrl.secret, | ||
disableCacheNoStore | ||
); | ||
const redirectTo = isValid ? parsedPreviewUrl.redirectTo : void 0; | ||
let studioOrigin; | ||
if (isValid) { | ||
try { | ||
studioOrigin = new URL(studioUrl).origin; | ||
} catch (error) { | ||
if (isDev) { | ||
console.error("Failed to parse studioUrl", error, { | ||
previewUrl, | ||
studioUrl | ||
}); | ||
} | ||
} | ||
} | ||
return { isValid, redirectTo, studioOrigin }; | ||
} | ||
export { urlSearchParamPreviewPathname, urlSearchParamPreviewSecret, validatePreviewUrl }; | ||
//# sourceMappingURL=index.js.map | ||
import{a as e,u as t,b as r,f as i,t as s,i as o}from"./_chunks-es/constants.js";async function n(n,a,c="Cloudflare-Workers"===globalThis.navigator?.userAgent){const l=function(t){if(!t)throw new TypeError("`client` is required");if(!t.config().token)throw new TypeError("`client` must have a `token` specified");return t.withConfig({apiVersion:e,useCdn:!1,perspective:"published",resultSourceMap:!1,stega:!1})}(n);let u;try{u=function(e){const i=new URL(e,"http://localhost"),s=i.searchParams.get(t);if(!s)throw new Error("Missing secret");let o;const n=i.searchParams.get(r);if(n){const{pathname:e,search:t,hash:r}=new URL(n,"http://localhost");o=`${e}${t}${r}`}return{secret:s,redirectTo:o}}(a)}catch(e){return o&&console.error("Failed to parse preview URL",e,{previewUrl:a,client:l}),{isValid:!1}}const{isValid:d,studioUrl:h}=await async function(e,t,r){if(typeof EdgeRuntime<"u"&&await new Promise((e=>setTimeout(e,300))),!t||!t.trim())return{isValid:!1,studioUrl:null};const o=await e.fetch(i,{secret:t},{tag:s,...r?void 0:{cache:"no-store"}});return o?._id&&o?._updatedAt&&o?.secret?{isValid:t===o.secret,studioUrl:o.studioUrl}:{isValid:!1,studioUrl:null}}(l,u.secret,c),p=d?u.redirectTo:void 0;let f;if(d)try{f=new URL(h).origin}catch(e){o&&console.error("Failed to parse studioUrl",e,{previewUrl:a,studioUrl:h})}return{isValid:d,redirectTo:p,studioOrigin:f}}export{r as urlSearchParamPreviewPathname,t as urlSearchParamPreviewSecret,n as validatePreviewUrl};//# sourceMappingURL=index.js.map |
@@ -1,84 +0,1 @@ | ||
import { defineType, definePlugin } from 'sanity'; | ||
import { schemaType, SECRET_TTL } from './_chunks-es/constants.js'; | ||
import { LockIcon, CloseCircleIcon, CheckmarkCircleIcon } from '@sanity/icons'; | ||
const debugUrlSecretsType = defineType({ | ||
type: "document", | ||
icon: LockIcon, | ||
name: schemaType, | ||
title: "@sanity/preview-url-secret", | ||
readOnly: true, | ||
fields: [ | ||
{ | ||
type: "string", | ||
name: "secret", | ||
title: "Secret" | ||
}, | ||
{ | ||
type: "string", | ||
name: "source", | ||
title: "Source Tool" | ||
}, | ||
{ | ||
type: "string", | ||
name: "studioUrl", | ||
title: "Studio URL" | ||
}, | ||
{ | ||
type: "string", | ||
name: "userId", | ||
title: "Sanity User ID" | ||
} | ||
], | ||
preview: { | ||
select: { | ||
source: "source", | ||
studioUrl: "studioUrl", | ||
updatedAt: "_updatedAt" | ||
}, | ||
prepare(data) { | ||
const url = data.studioUrl ? new URL(data.studioUrl, location.origin) : void 0; | ||
const updatedAt = new Date(data.updatedAt).getTime(); | ||
const expiresAt = new Date(updatedAt + 1e3 * SECRET_TTL); | ||
const expired = expiresAt < /* @__PURE__ */ new Date(); | ||
const icon = expired ? CloseCircleIcon : CheckmarkCircleIcon; | ||
return { | ||
title: url ? `${url.host}${url.pathname}` : data.source, | ||
subtitle: expired ? "Expired" : `Expires in ${Math.round((expiresAt.getTime() - Date.now()) / (1e3 * 60))} minutes`, | ||
media: icon | ||
}; | ||
} | ||
} | ||
}); | ||
const debugSecrets = definePlugin(() => { | ||
return { | ||
name: "sanity-plugin-debug-secrets", | ||
schema: { | ||
types: [debugUrlSecretsType] | ||
}, | ||
document: { | ||
actions: (prev, context) => { | ||
if (context.schemaType !== schemaType) { | ||
return prev; | ||
} | ||
return prev.filter(({ action }) => action === "delete"); | ||
}, | ||
inspectors: (prev, context) => { | ||
if (context.documentType !== schemaType) { | ||
return prev; | ||
} | ||
return []; | ||
}, | ||
unstable_fieldActions: (prev, context) => { | ||
if (context.schemaType.name !== schemaType) { | ||
return prev; | ||
} | ||
return []; | ||
} | ||
} | ||
}; | ||
}); | ||
export { debugSecrets }; | ||
//# sourceMappingURL=sanity-plugin-debug-secrets.js.map | ||
import{defineType as e,definePlugin as t}from"sanity";import{c as i,S as s}from"./_chunks-es/constants.js";import{LockIcon as n,CloseCircleIcon as r,CheckmarkCircleIcon as o}from"@sanity/icons";const a=e({type:"document",icon:n,name:i,title:"@sanity/preview-url-secret",readOnly:!0,fields:[{type:"string",name:"secret",title:"Secret"},{type:"string",name:"source",title:"Source Tool"},{type:"string",name:"studioUrl",title:"Studio URL"},{type:"string",name:"userId",title:"Sanity User ID"}],preview:{select:{source:"source",studioUrl:"studioUrl",updatedAt:"_updatedAt"},prepare(e){const t=e.studioUrl?new URL(e.studioUrl,location.origin):void 0,i=new Date(e.updatedAt).getTime(),n=new Date(i+1e3*s),a=n<new Date,c=a?r:o;return{title:t?`${t.host}${t.pathname}`:e.source,subtitle:a?"Expired":`Expires in ${Math.round((n.getTime()-Date.now())/6e4)} minutes`,media:c}}}}),c=t((()=>({name:"sanity-plugin-debug-secrets",schema:{types:[a]},document:{actions:(e,t)=>t.schemaType!==i?e:e.filter((({action:e})=>"delete"===e)),inspectors:(e,t)=>t.documentType!==i?e:[],unstable_fieldActions:(e,t)=>t.schemaType.name!==i?e:[]}})));export{c as debugSecrets};//# sourceMappingURL=sanity-plugin-debug-secrets.js.map |
@@ -1,20 +0,1 @@ | ||
import { urlSearchParamPreviewPathname, urlSearchParamPreviewSecret } from './_chunks-es/constants.js'; | ||
function withoutSecretSearchParams(url) { | ||
const newUrl = new URL(url); | ||
newUrl.searchParams.delete(urlSearchParamPreviewPathname); | ||
newUrl.searchParams.delete(urlSearchParamPreviewSecret); | ||
return newUrl; | ||
} | ||
function hasSecretSearchParams(url) { | ||
return url.searchParams.has(urlSearchParamPreviewSecret); | ||
} | ||
function setSecretSearchParams(url, secret, redirectTo) { | ||
const newUrl = new URL(url); | ||
newUrl.searchParams.set(urlSearchParamPreviewSecret, secret); | ||
newUrl.searchParams.set(urlSearchParamPreviewPathname, redirectTo); | ||
return newUrl; | ||
} | ||
export { hasSecretSearchParams, setSecretSearchParams, withoutSecretSearchParams }; | ||
//# sourceMappingURL=without-secret-search-params.js.map | ||
import{b as s,u as e}from"./_chunks-es/constants.js";function a(a){const r=new URL(a);return r.searchParams.delete(s),r.searchParams.delete(e),r}function r(s){return s.searchParams.has(e)}function n(a,r,n){const t=new URL(a);return t.searchParams.set(e,r),t.searchParams.set(s,n),t}export{r as hasSecretSearchParams,n as setSecretSearchParams,a as withoutSecretSearchParams};//# sourceMappingURL=without-secret-search-params.js.map |
{ | ||
"name": "@sanity/preview-url-secret", | ||
"version": "1.6.20-canary.1", | ||
"version": "1.6.20-canary.3", | ||
"homepage": "https://github.com/sanity-io/visual-editing/tree/main/packages/preview-url-secret#readme", | ||
@@ -5,0 +5,0 @@ "bugs": { |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 2 instances in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
121006
803
6