@sanity/preview-url-secret
Advanced tools
Comparing version 1.0.3-canary.1 to 1.1.0
# Changelog | ||
## [1.1.0](https://github.com/sanity-io/visual-editing/compare/preview-url-secret-v1.0.2...preview-url-secret-v1.1.0) (2023-11-16) | ||
### Features | ||
* add `getRedirectTo` utility ([03591b5](https://github.com/sanity-io/visual-editing/commit/03591b50d60675d3d1a0eed1b66c7e528a63a1b7)) | ||
* return when the secret expires ([8f2c1ce](https://github.com/sanity-io/visual-editing/commit/8f2c1ceefcce73728488b2a3db73cbbee21cf34f)) | ||
## [1.0.2](https://github.com/sanity-io/visual-editing/compare/preview-url-secret-v1.0.1...preview-url-secret-v1.0.2) (2023-11-16) | ||
@@ -4,0 +12,0 @@ |
@@ -1,47 +0,1 @@ | ||
import { uuid } from '@sanity/uuid'; | ||
import { apiVersion, SECRET_TTL, schemaIdPrefix, tag, schemaType, deleteExpiredSecretsQuery } from './_chunks/constants-iLpfgSLF.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,t as n,c as o,d as s}from"./_chunks/constants-BZ2GbOYw.js";async function c(c,i,p,u,l=t()){const d=c.withConfig({apiVersion:e});try{const t=new Date(Date.now()+1e3*a),e=`${r}.${l}`,s=function(){if("undefined"!=typeof crypto){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=d.patch(e).set({secret:s,source:i,studioUrl:p,userId:u});return await d.transaction().createOrReplace({_id:e,_type:o}).patch(c).commit({tag:n}),{secret:s,expiresAt:t}}finally{await d.delete({query:s})}}export{c as createPreviewSecret};//# sourceMappingURL=create-secret.js.map |
@@ -1,27 +0,1 @@ | ||
import { urlSearchParamPreviewSecret, urlSearchParamPreviewPathname } from './_chunks/constants-iLpfgSLF.js'; | ||
function definePreviewUrl(options) { | ||
const { | ||
draftMode, | ||
origin, | ||
preview = "/" | ||
} = options; | ||
const productionUrl = new URL(preview, origin); | ||
const enableDraftModeUrl = draftMode.enable ? new URL(draftMode.enable, origin) : void 0; | ||
return async context => { | ||
const previewUrl = new URL(context.previewSearchParam || preview, productionUrl); | ||
if (enableDraftModeUrl) { | ||
const enableDraftModeRequestUrl = new URL(enableDraftModeUrl); | ||
const { | ||
searchParams | ||
} = enableDraftModeRequestUrl; | ||
searchParams.set(urlSearchParamPreviewSecret, context.previewUrlSecret); | ||
if (previewUrl.pathname !== enableDraftModeRequestUrl.pathname) { | ||
searchParams.set(urlSearchParamPreviewPathname, previewUrl.pathname); | ||
} | ||
return enableDraftModeRequestUrl.toString(); | ||
} | ||
return previewUrl.toString(); | ||
}; | ||
} | ||
export { definePreviewUrl }; | ||
//# sourceMappingURL=define-preview-url.js.map | ||
import{u as e,b as n}from"./_chunks/constants-BZ2GbOYw.js";function r(r){const{draftMode:t,origin:a,preview:s="/"}=r,o=new URL(s,a),i=t.enable?new URL(t.enable,a):void 0;return async r=>{const t=new URL(r.previewSearchParam||s,o);if(i){const a=new URL(i),{searchParams:s}=a;return s.set(e,r.previewUrlSecret),t.pathname!==a.pathname&&s.set(n,t.pathname),a.toString()}return t.toString()}}export{r as definePreviewUrl};//# sourceMappingURL=define-preview-url.js.map |
@@ -1,9 +0,1 @@ | ||
import { urlSearchParamPreviewPathname } from './_chunks/constants-iLpfgSLF.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 r}from"./_chunks/constants-BZ2GbOYw.js";function s(s){return s.searchParams.has(r)?new URL(s.searchParams.get(r),s.origin):s}export{s as getRedirectTo};//# sourceMappingURL=get-redirect-to.js.map |
@@ -1,85 +0,1 @@ | ||
import { apiVersion, urlSearchParamPreviewSecret, urlSearchParamPreviewPathname, fetchSecretQuery, tag, isDev } from './_chunks/constants-iLpfgSLF.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 rigth 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 | ||
} = new URL(unsafeRedirectTo, "http://localhost"); | ||
redirectTo = `${pathname}${search}`; | ||
} | ||
return { | ||
secret, | ||
redirectTo | ||
}; | ||
} | ||
async function validateSecret(client, secret) { | ||
if (typeof EdgeRuntime !== "undefined") { | ||
await new Promise(resolve => setTimeout(resolve, 300)); | ||
} | ||
if (!secret || !secret.trim()) { | ||
return false; | ||
} | ||
const result = await client.fetch(fetchSecretQuery, { | ||
secret | ||
}, { | ||
tag, | ||
// @ts-expect-error -- the `cache` option is valid, but not in the types when NextJS typings aren't installed | ||
cache: "no-store" | ||
}); | ||
if (!result?._id || !result?._updatedAt || !result?.secret) { | ||
return false; | ||
} | ||
return secret === result.secret; | ||
} | ||
async function validatePreviewUrl(_client, previewUrl) { | ||
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 = await validateSecret(client, parsedPreviewUrl.secret); | ||
const redirectTo = isValid ? parsedPreviewUrl.redirectTo : void 0; | ||
return { | ||
isValid, | ||
redirectTo | ||
}; | ||
} | ||
export { urlSearchParamPreviewPathname, urlSearchParamPreviewSecret, validatePreviewUrl }; | ||
//# sourceMappingURL=index.js.map | ||
import{a as e,u as t,b as r,f as s,t as i,i as n}from"./_chunks/constants-BZ2GbOYw.js";async function o(o,c){const a=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})}(o);let u;try{u=function(e){const s=new URL(e,"http://localhost"),i=s.searchParams.get(t);if(!i)throw new Error("Missing secret");let n;const o=s.searchParams.get(r);if(o){const{pathname:e,search:t}=new URL(o,"http://localhost");n=`${e}${t}`}return{secret:i,redirectTo:n}}(c)}catch(e){return n&&console.error("Failed to parse preview URL",e,{previewUrl:c,client:a}),{isValid:!1}}const h=await async function(e,t){if("undefined"!=typeof EdgeRuntime&&await new Promise((e=>setTimeout(e,300))),!t||!t.trim())return!1;const r=await e.fetch(s,{secret:t},{tag:i,cache:"no-store"});return!!(r?._id&&r?._updatedAt&&r?.secret)&&t===r.secret}(a,u.secret);return{isValid:h,redirectTo:h?u.redirectTo:void 0}}export{r as urlSearchParamPreviewPathname,t as urlSearchParamPreviewSecret,o as validatePreviewUrl};//# sourceMappingURL=index.js.map |
@@ -1,9 +0,1 @@ | ||
import { urlSearchParamPreviewPathname, urlSearchParamPreviewSecret } from './_chunks/constants-iLpfgSLF.js'; | ||
function withoutSecretSearchParams(url) { | ||
const newUrl = new URL(url); | ||
newUrl.searchParams.delete(urlSearchParamPreviewPathname); | ||
newUrl.searchParams.delete(urlSearchParamPreviewSecret); | ||
return newUrl; | ||
} | ||
export { withoutSecretSearchParams }; | ||
//# sourceMappingURL=without-secret-search-params.js.map | ||
import{b as e,u as s}from"./_chunks/constants-BZ2GbOYw.js";function a(a){const r=new URL(a);return r.searchParams.delete(e),r.searchParams.delete(s),r}export{a as withoutSecretSearchParams};//# sourceMappingURL=without-secret-search-params.js.map |
{ | ||
"name": "@sanity/preview-url-secret", | ||
"version": "1.0.3-canary.1", | ||
"version": "1.1.0", | ||
"homepage": "https://github.com/sanity-io/visual-editing/tree/main/packages/preview-url-secret#readme", | ||
@@ -99,9 +99,2 @@ "bugs": { | ||
], | ||
"scripts": { | ||
"prebuild": "rimraf dist", | ||
"build": "pkg build --strict && pkg --strict", | ||
"lint": "eslint .", | ||
"prepublishOnly": "pnpm build", | ||
"watch": "pkg watch --strict" | ||
}, | ||
"browserslist": [ | ||
@@ -170,3 +163,2 @@ "> 0.2% and last 2 versions and supports es6-module and supports es6-module-dynamic-import and not dead and not IE 11", | ||
"@typescript-eslint/parser": "^6.11.0", | ||
"channels": "workspace:*", | ||
"eslint": "^8.53.0", | ||
@@ -176,3 +168,4 @@ "eslint-config-prettier": "^9.0.0", | ||
"eslint-plugin-simple-import-sort": "^10.0.0", | ||
"typescript": "^5.2.2" | ||
"typescript": "^5.2.2", | ||
"channels": "0.0.0" | ||
}, | ||
@@ -187,3 +180,9 @@ "peerDependencies": { | ||
"access": "public" | ||
}, | ||
"scripts": { | ||
"prebuild": "rimraf dist", | ||
"build": "pkg build --strict && pkg --strict", | ||
"lint": "eslint .", | ||
"watch": "pkg watch --strict" | ||
} | ||
} | ||
} |
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
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
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 3 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
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
38
57131
198
4
1