@nuxt/scripts 1.3.2 → 1.3.4
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/devtools-client/200.html +1 -1
- package/dist/devtools-client/404.html +1 -1
- package/dist/devtools-client/_nuxt/{DAubxma0.js → 8ltZ0o6d.js} +1 -1
- package/dist/devtools-client/_nuxt/{CBuBrHyL.js → B8_EBdhg.js} +1 -1
- package/dist/devtools-client/_nuxt/CC0GKROJ.js +1 -0
- package/dist/devtools-client/_nuxt/{DvC3yV2P.js → CMXzQxJZ.js} +1 -1
- package/dist/devtools-client/_nuxt/{xAZ2vgo3.js → CP7c-Js6.js} +4 -4
- package/dist/devtools-client/_nuxt/{DzbSCA_w.js → DTP8MRtW.js} +1 -1
- package/dist/devtools-client/_nuxt/{DgAtAWdT.js → Dkc7iTdR.js} +1 -1
- package/dist/devtools-client/_nuxt/Dl7W1ViV.js +28 -0
- package/dist/devtools-client/_nuxt/{WOtyQdps.js → KxC4Q3ev.js} +1 -1
- package/dist/devtools-client/_nuxt/builds/latest.json +1 -1
- package/dist/devtools-client/_nuxt/builds/meta/c38edf4f-387c-4148-a34a-ca78a5b40869.json +1 -0
- package/dist/devtools-client/_nuxt/{entry.CYUdJFoE.css → entry.9HJPJ6Hp.css} +1 -1
- package/dist/devtools-client/_nuxt/error-404.Bq2JQbJs.css +1 -0
- package/dist/devtools-client/_nuxt/error-500.5E_FOzlc.css +1 -0
- package/dist/devtools-client/_nuxt/{pages.BF1VnccH.css → pages.aU4WirOM.css} +1 -1
- package/dist/devtools-client/docs/index.html +1 -1
- package/dist/devtools-client/first-party/index.html +1 -1
- package/dist/devtools-client/index.html +1 -1
- package/dist/devtools-client/registry/index.html +1 -1
- package/dist/module.d.mts +1 -1
- package/dist/module.d.ts +1 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +263 -47
- package/dist/registry.mjs +2 -2
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMaps.vue +71 -21
- package/dist/runtime/components/GoogleMaps/useGoogleMapsResource.d.ts +2 -1
- package/dist/runtime/components/GoogleMaps/useGoogleMapsResource.js +39 -25
- package/dist/runtime/components/ScriptCarbonAds.vue +11 -3
- package/dist/runtime/components/ScriptCrisp.vue +4 -1
- package/dist/runtime/components/ScriptIntercom.vue +4 -1
- package/dist/runtime/components/ScriptLemonSqueezy.d.vue.ts +13 -13
- package/dist/runtime/components/ScriptLemonSqueezy.vue +30 -6
- package/dist/runtime/components/ScriptLemonSqueezy.vue.d.ts +13 -13
- package/dist/runtime/components/ScriptPayPalButtons.vue +8 -1
- package/dist/runtime/components/ScriptPayPalMessages.vue +6 -0
- package/dist/runtime/components/ScriptStripePricingTable.vue +2 -0
- package/dist/runtime/components/ScriptVimeoPlayer.vue +8 -1
- package/dist/runtime/composables/useScript.js +121 -44
- package/dist/runtime/composables/useScriptEventPage.js +36 -20
- package/dist/runtime/composables/useScriptTriggerIdleTimeout.js +19 -5
- package/dist/runtime/composables/useScriptTriggerInteraction.js +23 -10
- package/dist/runtime/composables/useScriptTriggerServiceWorker.js +21 -18
- package/dist/runtime/devtools-standalone-bridge.client.js +28 -3
- package/dist/runtime/npm-script-proxy.d.ts +7 -0
- package/dist/runtime/npm-script-proxy.js +46 -0
- package/dist/runtime/npm-script-stub.js +4 -5
- package/dist/runtime/registry/posthog.js +11 -3
- package/dist/runtime/registry/speedcurve.js +33 -4
- package/dist/runtime/registry/usercentrics.js +4 -21
- package/dist/runtime/registry/youtube-player.js +16 -5
- package/dist/runtime/server/bluesky-embed.js +15 -6
- package/dist/runtime/server/google-maps-geocode-proxy.js +10 -5
- package/dist/runtime/server/google-static-maps-proxy.js +20 -13
- package/dist/runtime/server/gravatar-proxy.js +48 -11
- package/dist/runtime/server/instagram-embed-asset.js +1 -1
- package/dist/runtime/server/instagram-embed.js +44 -94
- package/dist/runtime/server/proxy-handler.d.ts +2 -1
- package/dist/runtime/server/proxy-handler.js +185 -25
- package/dist/runtime/server/utils/cache-config.d.ts +14 -0
- package/dist/runtime/server/utils/cache-config.js +13 -0
- package/dist/runtime/server/utils/cached-upstream.d.ts +23 -2
- package/dist/runtime/server/utils/cached-upstream.js +212 -15
- package/dist/runtime/server/utils/image-proxy.d.ts +3 -2
- package/dist/runtime/server/utils/image-proxy.js +26 -16
- package/dist/runtime/server/utils/instagram-embed.d.ts +8 -0
- package/dist/runtime/server/utils/instagram-embed.js +155 -0
- package/dist/runtime/server/utils/network-host.d.ts +21 -0
- package/dist/runtime/server/utils/network-host.js +112 -0
- package/dist/runtime/server/utils/privacy.js +55 -19
- package/dist/runtime/server/utils/proxy-query.d.ts +2 -0
- package/dist/runtime/server/utils/proxy-query.js +10 -0
- package/dist/runtime/server/utils/sign.d.ts +1 -1
- package/dist/runtime/server/utils/sign.js +1 -1
- package/dist/runtime/server/utils/withSigning.d.ts +1 -1
- package/dist/runtime/server/utils/withSigning.js +3 -3
- package/dist/runtime/server/vercel-insights-sink.js +1 -1
- package/dist/runtime/server/x-embed.js +8 -4
- package/dist/runtime/unhead-features.d.ts +1 -0
- package/dist/runtime/unhead-features.js +3 -0
- package/dist/runtime/utils/abortable-promise.d.ts +8 -0
- package/dist/runtime/utils/abortable-promise.js +63 -0
- package/dist/runtime/utils/after-next-paint.d.ts +1 -1
- package/dist/runtime/utils/after-next-paint.js +16 -1
- package/dist/runtime/utils/usercentrics-consent.d.ts +10 -0
- package/dist/runtime/utils/usercentrics-consent.js +88 -0
- package/dist/runtime/utils/youtube-readiness.d.ts +7 -0
- package/dist/runtime/utils/youtube-readiness.js +45 -0
- package/dist/runtime/utils.d.ts +3 -1
- package/dist/runtime/utils.js +29 -0
- package/package.json +2 -1
- package/dist/devtools-client/_nuxt/BGGk5RYW.js +0 -1
- package/dist/devtools-client/_nuxt/N_y8tuWA.js +0 -28
- package/dist/devtools-client/_nuxt/builds/meta/e2926f35-ae20-45a0-929d-54d3919623c3.json +0 -1
- package/dist/devtools-client/_nuxt/error-404.CIn5oEzb.css +0 -1
- package/dist/devtools-client/_nuxt/error-500.B1YZl8g8.css +0 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { createError, defineEventHandler, getQuery, setHeader } from "h3";
|
|
1
|
+
import { createError, defineEventHandler, getQuery, setHeader } from "#nuxt-scripts/h3";
|
|
2
2
|
import { createCachedBinaryFetch } from "./cached-upstream.js";
|
|
3
|
+
import { isPublicNetworkHostname } from "./network-host.js";
|
|
3
4
|
import { withSigning } from "./withSigning.js";
|
|
4
5
|
const AMP_RE = /&/g;
|
|
5
6
|
export function createImageProxyHandler(config) {
|
|
@@ -7,12 +8,19 @@ export function createImageProxyHandler(config) {
|
|
|
7
8
|
accept = "image/webp,image/jpeg,image/png,image/*,*/*;q=0.8",
|
|
8
9
|
userAgent,
|
|
9
10
|
cacheMaxAge = 3600,
|
|
10
|
-
|
|
11
|
+
contentTypePrefixes = ["image/"],
|
|
11
12
|
followRedirects = true,
|
|
12
13
|
decodeAmpersands = false,
|
|
13
14
|
cacheName = Array.isArray(config.allowedDomains) ? `nuxt-scripts-img:${config.allowedDomains[0] || "default"}` : "nuxt-scripts-img:custom"
|
|
14
15
|
} = config;
|
|
15
|
-
const
|
|
16
|
+
const domainAllowed = (hostname) => typeof config.allowedDomains === "function" ? config.allowedDomains(hostname) : config.allowedDomains.includes(hostname);
|
|
17
|
+
const normalizedContentTypePrefixes = contentTypePrefixes.map((prefix) => prefix.toLowerCase());
|
|
18
|
+
const contentTypeAllowed = (contentType) => contentType !== "image/svg+xml" && normalizedContentTypePrefixes.some((prefix) => contentType.startsWith(prefix));
|
|
19
|
+
const urlAllowed = (url) => (url.protocol === "http:" || url.protocol === "https:") && !url.username && !url.password && isPublicNetworkHostname(url.hostname) && domainAllowed(url.hostname);
|
|
20
|
+
const cachedFetch = createCachedBinaryFetch(cacheName, cacheMaxAge, {
|
|
21
|
+
allowContentType: contentTypeAllowed,
|
|
22
|
+
allowUrl: urlAllowed
|
|
23
|
+
});
|
|
16
24
|
return withSigning(defineEventHandler(async (event) => {
|
|
17
25
|
const query = getQuery(event);
|
|
18
26
|
let url = query.url;
|
|
@@ -33,17 +41,10 @@ export function createImageProxyHandler(config) {
|
|
|
33
41
|
statusMessage: "Invalid image URL"
|
|
34
42
|
});
|
|
35
43
|
}
|
|
36
|
-
if (parsedUrl
|
|
44
|
+
if (!urlAllowed(parsedUrl)) {
|
|
37
45
|
throw createError({
|
|
38
|
-
statusCode: 400,
|
|
39
|
-
statusMessage: "Invalid URL
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
const domainAllowed = typeof config.allowedDomains === "function" ? config.allowedDomains(parsedUrl.hostname) : config.allowedDomains.includes(parsedUrl.hostname);
|
|
43
|
-
if (!domainAllowed) {
|
|
44
|
-
throw createError({
|
|
45
|
-
statusCode: 403,
|
|
46
|
-
statusMessage: "Domain not allowed"
|
|
46
|
+
statusCode: domainAllowed(parsedUrl.hostname) ? 400 : 403,
|
|
47
|
+
statusMessage: domainAllowed(parsedUrl.hostname) ? "Invalid image URL" : "Domain not allowed"
|
|
47
48
|
});
|
|
48
49
|
}
|
|
49
50
|
const headers = { Accept: accept };
|
|
@@ -52,7 +53,6 @@ export function createImageProxyHandler(config) {
|
|
|
52
53
|
const result = await cachedFetch(url, {
|
|
53
54
|
timeout: 5e3,
|
|
54
55
|
redirect: followRedirects ? "follow" : "manual",
|
|
55
|
-
ignoreResponseError: !followRedirects,
|
|
56
56
|
headers
|
|
57
57
|
}).catch((error) => {
|
|
58
58
|
throw createError({
|
|
@@ -60,14 +60,24 @@ export function createImageProxyHandler(config) {
|
|
|
60
60
|
statusMessage: error.statusMessage || "Failed to fetch image"
|
|
61
61
|
});
|
|
62
62
|
});
|
|
63
|
-
if (
|
|
63
|
+
if (result.status >= 300 && result.status < 400 && result.status !== 304) {
|
|
64
64
|
throw createError({
|
|
65
65
|
statusCode: 403,
|
|
66
66
|
statusMessage: "Redirects not allowed"
|
|
67
67
|
});
|
|
68
68
|
}
|
|
69
|
-
|
|
69
|
+
const responseContentType = result.contentType;
|
|
70
|
+
const upstreamContentType = responseContentType?.split(";", 1)[0]?.trim().toLowerCase();
|
|
71
|
+
if (!responseContentType || !upstreamContentType || !contentTypeAllowed(upstreamContentType)) {
|
|
72
|
+
throw createError({
|
|
73
|
+
statusCode: 415,
|
|
74
|
+
statusMessage: "Unsupported upstream content type"
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
setHeader(event, "Content-Type", responseContentType);
|
|
70
78
|
setHeader(event, "Cache-Control", `public, max-age=${cacheMaxAge}, s-maxage=${cacheMaxAge}`);
|
|
79
|
+
setHeader(event, "Content-Security-Policy", "sandbox; default-src 'none'");
|
|
80
|
+
setHeader(event, "X-Content-Type-Options", "nosniff");
|
|
71
81
|
return result.body;
|
|
72
82
|
}));
|
|
73
83
|
}
|
|
@@ -6,6 +6,14 @@ export declare const STATIC_CDN_RE: RegExp;
|
|
|
6
6
|
export declare const LOOKASIDE_RE: RegExp;
|
|
7
7
|
export declare const INSTAGRAM_IMAGE_HOSTS: string[];
|
|
8
8
|
export declare const INSTAGRAM_ASSET_HOST = "static.cdninstagram.com";
|
|
9
|
+
export declare function isSafeInstagramPostUrl(url: URL): boolean;
|
|
10
|
+
export declare function isSafeInstagramEmbedUrl(url: URL): boolean;
|
|
11
|
+
export declare function isSafeInstagramCssUrl(url: URL): boolean;
|
|
12
|
+
export declare function sanitizeInstagramEmbedHtml(html: string, prefix: string, secret?: string): {
|
|
13
|
+
bodyHtml: string;
|
|
14
|
+
cssUrls: string[];
|
|
15
|
+
};
|
|
16
|
+
export declare function sanitizeInstagramEmbedCss(css: string, scopeSelector: string, prefix: string, secret?: string): string;
|
|
9
17
|
export declare function proxyImageUrl(url: string, prefix?: string, secret?: string): string;
|
|
10
18
|
export declare function proxyAssetUrl(url: string, prefix?: string, secret?: string): string;
|
|
11
19
|
export declare function rewriteUrl(url: string, prefix?: string, secret?: string): string;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ELEMENT_NODE, parse, renderSync, TEXT_NODE, walkSync } from "ultrahtml";
|
|
1
2
|
import { buildProxyUrl } from "./proxy-url.js";
|
|
2
3
|
export const RSRC_RE = /url\(\/rsrc\.php([^)]+)\)/g;
|
|
3
4
|
const SHELL_BODY_RE = /id=["'](?:splash-screen|has-finished-comet-page)["']/;
|
|
@@ -11,11 +12,165 @@ export const STATIC_CDN_RE = /https:\/\/static\.cdninstagram\.com[^"'\s),]+/g;
|
|
|
11
12
|
export const LOOKASIDE_RE = /https:\/\/lookaside\.instagram\.com[^"'\s),]+/g;
|
|
12
13
|
export const INSTAGRAM_IMAGE_HOSTS = ["scontent.cdninstagram.com", "lookaside.instagram.com"];
|
|
13
14
|
export const INSTAGRAM_ASSET_HOST = "static.cdninstagram.com";
|
|
15
|
+
const INSTAGRAM_POST_PATH_RE = /^\/(?:p|reel|tv)\/[^/]+\/?$/;
|
|
16
|
+
const INSTAGRAM_EMBED_PATH_RE = /^\/(?:p|reel|tv)\/[^/]+\/embed\/(?:captioned\/)?$/;
|
|
17
|
+
const BLOCKED_EMBED_TAGS = /* @__PURE__ */ new Set([
|
|
18
|
+
"base",
|
|
19
|
+
"button",
|
|
20
|
+
"embed",
|
|
21
|
+
"form",
|
|
22
|
+
"iframe",
|
|
23
|
+
"input",
|
|
24
|
+
"link",
|
|
25
|
+
"math",
|
|
26
|
+
"meta",
|
|
27
|
+
"noscript",
|
|
28
|
+
"object",
|
|
29
|
+
"option",
|
|
30
|
+
"script",
|
|
31
|
+
"select",
|
|
32
|
+
"style",
|
|
33
|
+
"svg",
|
|
34
|
+
"template",
|
|
35
|
+
"textarea"
|
|
36
|
+
]);
|
|
37
|
+
const EMBED_URL_ATTRS = /* @__PURE__ */ new Set(["action", "formaction", "href", "xlink:href"]);
|
|
38
|
+
const CSS_URL_RE = /url\(([^)]*)\)/gi;
|
|
39
|
+
const CSS_IMAGE_SET_RE = /(?:-webkit-)?image-set\((?:[^()]|\([^)]*\))*\)/gi;
|
|
40
|
+
const DANGEROUS_STYLE_RE = /expression\s*\(|@import|-moz-binding|behavior\s*:/i;
|
|
41
|
+
const SRCSET_SPLIT_RE = /\s+/;
|
|
42
|
+
const MAX_INSTAGRAM_STYLESHEETS = 4;
|
|
14
43
|
const CHARSET_RE = /@charset\s[^;]+;/gi;
|
|
15
44
|
const IMPORT_RE = /@import\s[^;]+;/gi;
|
|
16
45
|
const WHITESPACE_RE = /\s/;
|
|
17
46
|
const AT_RULE_NAME_RE = /@([\w-]+)/;
|
|
18
47
|
const MULTI_SPACE_RE = /\s+/g;
|
|
48
|
+
function isSafeInstagramOrigin(url) {
|
|
49
|
+
return url.protocol === "https:" && !url.username && !url.password && (!url.port || url.port === "443") && (url.hostname === "instagram.com" || url.hostname === "www.instagram.com");
|
|
50
|
+
}
|
|
51
|
+
export function isSafeInstagramPostUrl(url) {
|
|
52
|
+
return isSafeInstagramOrigin(url) && INSTAGRAM_POST_PATH_RE.test(url.pathname);
|
|
53
|
+
}
|
|
54
|
+
export function isSafeInstagramEmbedUrl(url) {
|
|
55
|
+
return isSafeInstagramOrigin(url) && INSTAGRAM_EMBED_PATH_RE.test(url.pathname);
|
|
56
|
+
}
|
|
57
|
+
export function isSafeInstagramCssUrl(url) {
|
|
58
|
+
return url.protocol === "https:" && !url.username && !url.password && (!url.port || url.port === "443") && url.hostname === INSTAGRAM_ASSET_HOST;
|
|
59
|
+
}
|
|
60
|
+
function removeNode(node) {
|
|
61
|
+
node.type = TEXT_NODE;
|
|
62
|
+
node.value = "";
|
|
63
|
+
node.name = void 0;
|
|
64
|
+
node.attributes = {};
|
|
65
|
+
node.children = [];
|
|
66
|
+
}
|
|
67
|
+
function isSafeNavigationUrl(value) {
|
|
68
|
+
if (!/^(?:https?:\/\/|\/|#)/i.test(value))
|
|
69
|
+
return false;
|
|
70
|
+
try {
|
|
71
|
+
const url = new URL(value, "https://www.instagram.com");
|
|
72
|
+
return (url.protocol === "http:" || url.protocol === "https:") && !url.username && !url.password;
|
|
73
|
+
} catch {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function sanitizeCssUrls(css, proxyPrefix) {
|
|
78
|
+
const allowedPrefix = `${proxyPrefix}/embed/instagram-`;
|
|
79
|
+
const withoutUnsafeImageSets = css.replace(CSS_IMAGE_SET_RE, (match) => {
|
|
80
|
+
const values = [...match.matchAll(/(["'])(.*?)\1/g)].map((result) => result[2] || "");
|
|
81
|
+
const safe = !/https?:|data:|blob:|file:|\/\//i.test(match.replaceAll(allowedPrefix, "")) && values.every((value) => value.startsWith(allowedPrefix) || /^image\//i.test(value));
|
|
82
|
+
return safe ? match : "none";
|
|
83
|
+
});
|
|
84
|
+
return withoutUnsafeImageSets.replace(CSS_URL_RE, (match, rawValue) => {
|
|
85
|
+
const trimmed = rawValue.trim();
|
|
86
|
+
const first = trimmed[0];
|
|
87
|
+
const value = (first === '"' || first === "'") && trimmed.at(-1) === first ? trimmed.slice(1, -1).trim() : trimmed;
|
|
88
|
+
return value.startsWith(allowedPrefix) ? match : 'url("")';
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
function rewriteMediaUrl(url, prefix, secret) {
|
|
92
|
+
const rewritten = rewriteUrl(url, prefix, secret);
|
|
93
|
+
return rewritten === url ? void 0 : rewritten;
|
|
94
|
+
}
|
|
95
|
+
export function sanitizeInstagramEmbedHtml(html, prefix, secret) {
|
|
96
|
+
const ast = parse(html);
|
|
97
|
+
const cssUrls = /* @__PURE__ */ new Set();
|
|
98
|
+
walkSync(ast, (node) => {
|
|
99
|
+
if (node.type !== ELEMENT_NODE)
|
|
100
|
+
return;
|
|
101
|
+
const tag = String(node.name).toLowerCase();
|
|
102
|
+
if (tag === "link" && node.attributes.rel?.toLowerCase() === "stylesheet" && node.attributes.href) {
|
|
103
|
+
try {
|
|
104
|
+
const cssUrl = new URL(node.attributes.href);
|
|
105
|
+
if (isSafeInstagramCssUrl(cssUrl) && cssUrls.size < MAX_INSTAGRAM_STYLESHEETS)
|
|
106
|
+
cssUrls.add(cssUrl.toString());
|
|
107
|
+
} catch {
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (BLOCKED_EMBED_TAGS.has(tag)) {
|
|
111
|
+
removeNode(node);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
for (const [name, value] of Object.entries(node.attributes)) {
|
|
115
|
+
const lowerName = name.toLowerCase();
|
|
116
|
+
if (lowerName.startsWith("on") || lowerName === "srcdoc" || lowerName === "nonce") {
|
|
117
|
+
delete node.attributes[name];
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (EMBED_URL_ATTRS.has(lowerName) && !isSafeNavigationUrl(value)) {
|
|
121
|
+
delete node.attributes[name];
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (lowerName === "src" || lowerName === "poster") {
|
|
125
|
+
const rewritten = rewriteMediaUrl(value, prefix, secret);
|
|
126
|
+
if (rewritten)
|
|
127
|
+
node.attributes[name] = rewritten;
|
|
128
|
+
else
|
|
129
|
+
delete node.attributes[name];
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (lowerName === "srcset") {
|
|
133
|
+
const entries = value.split(",").flatMap((entry) => {
|
|
134
|
+
const [url, descriptor, ...rest] = entry.trim().split(SRCSET_SPLIT_RE);
|
|
135
|
+
const rewritten = url ? rewriteMediaUrl(url, prefix, secret) : void 0;
|
|
136
|
+
const safeDescriptor = !descriptor || /^\d+(?:\.\d+)?[wx]$/.test(descriptor);
|
|
137
|
+
return rewritten && safeDescriptor && rest.length === 0 ? [`${rewritten}${descriptor ? ` ${descriptor}` : ""}`] : [];
|
|
138
|
+
});
|
|
139
|
+
if (entries.length)
|
|
140
|
+
node.attributes[name] = entries.join(", ");
|
|
141
|
+
else
|
|
142
|
+
delete node.attributes[name];
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
if (lowerName === "style") {
|
|
146
|
+
const rewritten = rewriteUrlsInText(value, prefix, secret);
|
|
147
|
+
if (DANGEROUS_STYLE_RE.test(rewritten))
|
|
148
|
+
delete node.attributes[name];
|
|
149
|
+
else
|
|
150
|
+
node.attributes[name] = sanitizeCssUrls(rewritten, prefix);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (tag === "a" && node.attributes.target?.toLowerCase() === "_blank")
|
|
154
|
+
node.attributes.rel = "noopener noreferrer";
|
|
155
|
+
});
|
|
156
|
+
let bodyNode;
|
|
157
|
+
walkSync(ast, (node) => {
|
|
158
|
+
if (node.type === ELEMENT_NODE && node.name === "body")
|
|
159
|
+
bodyNode = node;
|
|
160
|
+
});
|
|
161
|
+
return {
|
|
162
|
+
bodyHtml: bodyNode ? bodyNode.children.map((child) => renderSync(child)).join("") : renderSync(ast),
|
|
163
|
+
cssUrls: [...cssUrls]
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
export function sanitizeInstagramEmbedCss(css, scopeSelector, prefix, secret) {
|
|
167
|
+
const rewritten = rewriteUrlsInText(
|
|
168
|
+
css.replace(RSRC_RE, (_match, path) => `url(${proxyAssetUrl(`https://${INSTAGRAM_ASSET_HOST}/rsrc.php${path}`, prefix, secret)})`),
|
|
169
|
+
prefix,
|
|
170
|
+
secret
|
|
171
|
+
);
|
|
172
|
+
return sanitizeCssUrls(scopeCss(rewritten, scopeSelector), prefix).replace(/<\/style/gi, "<\\/style");
|
|
173
|
+
}
|
|
19
174
|
export function proxyImageUrl(url, prefix = "/_scripts", secret) {
|
|
20
175
|
return buildProxyUrl(`${prefix}/embed/instagram-image`, { url: url.replace(AMP_RE, "&") }, secret);
|
|
21
176
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { LookupFunction } from 'node:net';
|
|
2
|
+
interface NetworkAddress {
|
|
3
|
+
address: string;
|
|
4
|
+
family: 4 | 6;
|
|
5
|
+
}
|
|
6
|
+
type ResolveNetworkHostname = (hostname: string, callback: (error: Error | null, addresses: NetworkAddress[]) => void) => void;
|
|
7
|
+
export interface PublicNetworkDispatcher {
|
|
8
|
+
fetch: typeof globalThis.fetch;
|
|
9
|
+
close: () => Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
/** Close a dispatcher without replacing the request or stream error already in flight. */
|
|
12
|
+
export declare function closePublicNetworkDispatcher(dispatcher: PublicNetworkDispatcher | undefined, primaryError?: unknown): Promise<void>;
|
|
13
|
+
/** Reject hostnames that directly address local, private, link-local, or reserved networks. */
|
|
14
|
+
export declare function isPublicNetworkHostname(input: string): boolean;
|
|
15
|
+
/** Resolve once inside the socket connection, reject mixed/private answers, then pin the selected address. */
|
|
16
|
+
export declare function createPublicNetworkLookup(resolveHostname: ResolveNetworkHostname): LookupFunction;
|
|
17
|
+
/** Create a Node fetch dispatcher whose socket lookup validates and pins every DNS answer. */
|
|
18
|
+
export declare function createPublicNetworkDispatcher(resolveHostnameOverride?: ResolveNetworkHostname): Promise<PublicNetworkDispatcher>;
|
|
19
|
+
/** Detect the tagged DNS policy error through fetch/ofetch cause wrappers. */
|
|
20
|
+
export declare function isPrivateNetworkResolutionError(error: unknown): boolean;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { runtime } from "std-env";
|
|
2
|
+
const LOCAL_HOST_SUFFIXES = [
|
|
3
|
+
"home",
|
|
4
|
+
"internal",
|
|
5
|
+
"lan",
|
|
6
|
+
"local",
|
|
7
|
+
"localdomain",
|
|
8
|
+
"localhost"
|
|
9
|
+
];
|
|
10
|
+
export async function closePublicNetworkDispatcher(dispatcher, primaryError) {
|
|
11
|
+
if (!dispatcher)
|
|
12
|
+
return;
|
|
13
|
+
await dispatcher.close().catch((cleanupError) => {
|
|
14
|
+
if (primaryError !== void 0) {
|
|
15
|
+
if (primaryError && typeof primaryError === "object")
|
|
16
|
+
Object.assign(primaryError, { cleanupError });
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
throw cleanupError;
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
function parseIPv4(hostname) {
|
|
23
|
+
const parts = hostname.split(".");
|
|
24
|
+
if (parts.length !== 4 || parts.some((part) => !/^\d{1,3}$/.test(part)))
|
|
25
|
+
return;
|
|
26
|
+
const octets = parts.map(Number);
|
|
27
|
+
return octets.every((octet) => octet >= 0 && octet <= 255) ? octets : void 0;
|
|
28
|
+
}
|
|
29
|
+
function isPublicIPv4([a, b, c]) {
|
|
30
|
+
return a !== 0 && a !== 10 && a !== 127 && !(a === 100 && b >= 64 && b <= 127) && !(a === 169 && b === 254) && !(a === 172 && b >= 16 && b <= 31) && !(a === 192 && b === 0 && c === 0) && !(a === 192 && b === 0 && c === 2) && !(a === 192 && b === 88 && c === 99) && !(a === 192 && b === 168) && !(a === 198 && (b === 18 || b === 19)) && !(a === 198 && b === 51 && c === 100) && !(a === 203 && b === 0 && c === 113) && a < 224;
|
|
31
|
+
}
|
|
32
|
+
function isPublicIPv6(hostname) {
|
|
33
|
+
const groups = hostname.split(":");
|
|
34
|
+
const firstGroup = Number.parseInt(groups[0] || "0", 16);
|
|
35
|
+
if (!Number.isInteger(firstGroup) || firstGroup < 8192 || firstGroup > 16383)
|
|
36
|
+
return false;
|
|
37
|
+
const secondGroup = Number.parseInt(groups[1] || "0", 16);
|
|
38
|
+
return !(firstGroup === 8193 && (secondGroup === 0 || secondGroup === 3512)) && firstGroup !== 8194;
|
|
39
|
+
}
|
|
40
|
+
export function isPublicNetworkHostname(input) {
|
|
41
|
+
const hostname = input.trim().toLowerCase().replace(/^\[|\]$/g, "").split("%", 1)[0].replace(/\.$/, "");
|
|
42
|
+
if (!hostname)
|
|
43
|
+
return false;
|
|
44
|
+
const ipv4 = parseIPv4(hostname);
|
|
45
|
+
if (ipv4)
|
|
46
|
+
return isPublicIPv4(ipv4);
|
|
47
|
+
if (hostname.includes(":"))
|
|
48
|
+
return isPublicIPv6(hostname);
|
|
49
|
+
const labels = hostname.split(".");
|
|
50
|
+
if (labels.length < 2)
|
|
51
|
+
return false;
|
|
52
|
+
const suffix = labels.at(-1);
|
|
53
|
+
return !LOCAL_HOST_SUFFIXES.includes(suffix);
|
|
54
|
+
}
|
|
55
|
+
export function createPublicNetworkLookup(resolveHostname) {
|
|
56
|
+
const networkLookup = (hostname, options, callback) => {
|
|
57
|
+
resolveHostname(hostname, (error, addresses) => {
|
|
58
|
+
if (error) {
|
|
59
|
+
callback(error, "");
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (!addresses.length || addresses.some(({ address }) => !isPublicNetworkHostname(address))) {
|
|
63
|
+
callback(Object.assign(new Error("Upstream hostname resolved to a non-public address"), {
|
|
64
|
+
code: "ERR_NUXT_SCRIPTS_PRIVATE_ADDRESS"
|
|
65
|
+
}), "");
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (options.all) {
|
|
69
|
+
callback(null, addresses);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const requestedFamily = options.family === "IPv4" ? 4 : options.family === "IPv6" ? 6 : options.family === 4 || options.family === 6 ? options.family : void 0;
|
|
73
|
+
const selected = addresses.find(({ family }) => family === requestedFamily) ?? addresses[0];
|
|
74
|
+
callback(null, selected.address, selected.family);
|
|
75
|
+
});
|
|
76
|
+
};
|
|
77
|
+
return networkLookup;
|
|
78
|
+
}
|
|
79
|
+
export async function createPublicNetworkDispatcher(resolveHostnameOverride) {
|
|
80
|
+
if (runtime !== "node")
|
|
81
|
+
return { fetch: globalThis.fetch, close: async () => {
|
|
82
|
+
} };
|
|
83
|
+
const { Agent, fetch } = await import("undici");
|
|
84
|
+
let resolveHostname = resolveHostnameOverride;
|
|
85
|
+
if (!resolveHostname) {
|
|
86
|
+
const { lookup } = await import("node:dns");
|
|
87
|
+
resolveHostname = (hostname, callback) => lookup(hostname, { all: true, verbatim: true }, (error, addresses) => {
|
|
88
|
+
callback(error, addresses);
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
const dispatcher = new Agent({
|
|
92
|
+
connect: {
|
|
93
|
+
lookup: createPublicNetworkLookup(resolveHostname)
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
return {
|
|
97
|
+
fetch: ((input, init) => fetch(input, {
|
|
98
|
+
...init,
|
|
99
|
+
dispatcher
|
|
100
|
+
})),
|
|
101
|
+
close: () => dispatcher.close()
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
export function isPrivateNetworkResolutionError(error) {
|
|
105
|
+
let current = error;
|
|
106
|
+
for (let depth = 0; depth < 5 && current && typeof current === "object"; depth++) {
|
|
107
|
+
if (current.code === "ERR_NUXT_SCRIPTS_PRIVATE_ADDRESS")
|
|
108
|
+
return true;
|
|
109
|
+
current = current.cause;
|
|
110
|
+
}
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
@@ -92,9 +92,30 @@ export const NORMALIZE_PARAMS = {
|
|
|
92
92
|
language: ["ul", "lang", "language", "languages"],
|
|
93
93
|
userAgent: ["ua", "useragent", "user_agent", "client_user_agent", "context.useragent"]
|
|
94
94
|
};
|
|
95
|
+
function expandIPv6(address) {
|
|
96
|
+
const halves = address.split("::");
|
|
97
|
+
if (halves.length > 2)
|
|
98
|
+
return;
|
|
99
|
+
const left = halves[0] ? halves[0].split(":") : [];
|
|
100
|
+
const right = halves[1] ? halves[1].split(":") : [];
|
|
101
|
+
const valid = (part) => /^[\da-f]{1,4}$/i.test(part);
|
|
102
|
+
if (!left.every(valid) || !right.every(valid))
|
|
103
|
+
return;
|
|
104
|
+
if (halves.length === 1)
|
|
105
|
+
return left.length === 8 ? left : void 0;
|
|
106
|
+
const missing = 8 - left.length - right.length;
|
|
107
|
+
if (missing < 1)
|
|
108
|
+
return;
|
|
109
|
+
return [...left, ...Array.from({ length: missing }).fill("0"), ...right];
|
|
110
|
+
}
|
|
95
111
|
export function anonymizeIP(ip) {
|
|
96
112
|
if (ip.includes(":")) {
|
|
97
|
-
|
|
113
|
+
const normalized = ip.split("%", 1)[0] || "";
|
|
114
|
+
const mappedIPv4 = normalized.match(/^::ffff:(\d{1,3}(?:\.\d{1,3}){3})$/i)?.[1];
|
|
115
|
+
if (mappedIPv4)
|
|
116
|
+
return `::ffff:${anonymizeIP(mappedIPv4)}`;
|
|
117
|
+
const expanded = expandIPv6(normalized);
|
|
118
|
+
return expanded ? `${expanded.slice(0, 3).join(":")}::` : normalized;
|
|
98
119
|
}
|
|
99
120
|
const parts = ip.split(".");
|
|
100
121
|
if (parts.length === 4) {
|
|
@@ -239,15 +260,23 @@ function matchesParam(key, params) {
|
|
|
239
260
|
return lk === lp || lk.startsWith(`${lp}[`);
|
|
240
261
|
});
|
|
241
262
|
}
|
|
263
|
+
function mapValue(value, transform) {
|
|
264
|
+
return Array.isArray(value) ? value.map(transform) : transform(value);
|
|
265
|
+
}
|
|
266
|
+
function mapString(value, transform) {
|
|
267
|
+
return mapValue(value, (item) => typeof item === "string" ? transform(item) : item);
|
|
268
|
+
}
|
|
242
269
|
export function stripPayloadFingerprinting(payload, privacy) {
|
|
243
270
|
const p = privacy || FULL_PRIVACY;
|
|
244
|
-
const result =
|
|
245
|
-
let
|
|
271
|
+
const result = /* @__PURE__ */ Object.create(null);
|
|
272
|
+
let deviceClasses = [];
|
|
246
273
|
for (const [key, value] of Object.entries(payload)) {
|
|
247
274
|
if (key.toLowerCase() === "sw") {
|
|
248
|
-
const
|
|
249
|
-
|
|
250
|
-
|
|
275
|
+
const widths = Array.isArray(value) ? value : [value];
|
|
276
|
+
deviceClasses = widths.map((width) => {
|
|
277
|
+
const num = typeof width === "number" ? width : Number(width);
|
|
278
|
+
return Number.isNaN(num) ? void 0 : getDeviceClass(num);
|
|
279
|
+
});
|
|
251
280
|
}
|
|
252
281
|
}
|
|
253
282
|
for (const [key, value] of Object.entries(payload)) {
|
|
@@ -264,12 +293,12 @@ export function stripPayloadFingerprinting(payload, privacy) {
|
|
|
264
293
|
continue;
|
|
265
294
|
}
|
|
266
295
|
const isUserAgentParam = NORMALIZE_PARAMS.userAgent.some((pm) => lowerKey === pm.toLowerCase());
|
|
267
|
-
if (isUserAgentParam
|
|
268
|
-
result[key] = p.userAgent ?
|
|
296
|
+
if (isUserAgentParam) {
|
|
297
|
+
result[key] = p.userAgent ? mapString(value, normalizeUserAgent) : value;
|
|
269
298
|
continue;
|
|
270
299
|
}
|
|
271
|
-
if (matchesParam(key, STRIP_PARAMS.ip)
|
|
272
|
-
result[key] = p.ip ?
|
|
300
|
+
if (matchesParam(key, STRIP_PARAMS.ip)) {
|
|
301
|
+
result[key] = p.ip ? mapString(value, anonymizeIP) : value;
|
|
273
302
|
continue;
|
|
274
303
|
}
|
|
275
304
|
if (matchesParam(key, STRIP_PARAMS.screen)) {
|
|
@@ -279,28 +308,35 @@ export function stripPayloadFingerprinting(payload, privacy) {
|
|
|
279
308
|
}
|
|
280
309
|
if (["sd", "colordepth", "pixelratio"].includes(lowerKey)) {
|
|
281
310
|
result[key] = value;
|
|
282
|
-
} else if (lowerKey === "sh" &&
|
|
283
|
-
const
|
|
284
|
-
|
|
311
|
+
} else if (lowerKey === "sh" && deviceClasses.length > 0) {
|
|
312
|
+
const generalizePairedHeight = (item, index) => {
|
|
313
|
+
const deviceClass = deviceClasses[index] ?? deviceClasses[0];
|
|
314
|
+
if (!deviceClass)
|
|
315
|
+
return generalizeScreen(item, "height");
|
|
316
|
+
const paired = SCREEN_BUCKETS[deviceClass].h;
|
|
317
|
+
return typeof item === "number" ? paired : String(paired);
|
|
318
|
+
};
|
|
319
|
+
result[key] = Array.isArray(value) ? value.map(generalizePairedHeight) : generalizePairedHeight(value, 0);
|
|
285
320
|
} else {
|
|
286
|
-
|
|
321
|
+
const dimension = lowerKey === "sw" ? "width" : lowerKey === "sh" ? "height" : void 0;
|
|
322
|
+
result[key] = mapValue(value, (item) => generalizeScreen(item, dimension));
|
|
287
323
|
}
|
|
288
324
|
continue;
|
|
289
325
|
}
|
|
290
326
|
if (matchesParam(key, STRIP_PARAMS.hardware)) {
|
|
291
|
-
result[key] = p.hardware ?
|
|
327
|
+
result[key] = p.hardware ? mapValue(value, generalizeHardware) : value;
|
|
292
328
|
continue;
|
|
293
329
|
}
|
|
294
330
|
if (matchesParam(key, STRIP_PARAMS.version)) {
|
|
295
|
-
result[key] = p.hardware ?
|
|
331
|
+
result[key] = p.hardware ? mapValue(value, generalizeVersion) : value;
|
|
296
332
|
continue;
|
|
297
333
|
}
|
|
298
334
|
if (matchesParam(key, STRIP_PARAMS.browserVersion)) {
|
|
299
|
-
result[key] = p.hardware ?
|
|
335
|
+
result[key] = p.hardware ? mapValue(value, generalizeBrowserVersions) : value;
|
|
300
336
|
continue;
|
|
301
337
|
}
|
|
302
338
|
if (matchesParam(key, STRIP_PARAMS.location)) {
|
|
303
|
-
result[key] = p.timezone ?
|
|
339
|
+
result[key] = p.timezone ? mapValue(value, generalizeTimezone) : value;
|
|
304
340
|
continue;
|
|
305
341
|
}
|
|
306
342
|
if (matchesParam(key, STRIP_PARAMS.browserData)) {
|
|
@@ -308,7 +344,7 @@ export function stripPayloadFingerprinting(payload, privacy) {
|
|
|
308
344
|
continue;
|
|
309
345
|
}
|
|
310
346
|
if (matchesParam(key, STRIP_PARAMS.deviceInfo)) {
|
|
311
|
-
result[key] = p.hardware ? typeof
|
|
347
|
+
result[key] = p.hardware ? mapValue(value, (item) => typeof item === "string" ? anonymizeDeviceInfo(item) : "") : value;
|
|
312
348
|
continue;
|
|
313
349
|
}
|
|
314
350
|
if (matchesParam(key, STRIP_PARAMS.platform)) {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { PAGE_TOKEN_PARAM, PAGE_TOKEN_TS_PARAM, SIG_PARAM } from "./sign-constants.js";
|
|
2
|
+
const PROXY_AUTH_PARAMS = /* @__PURE__ */ new Set([SIG_PARAM, PAGE_TOKEN_PARAM, PAGE_TOKEN_TS_PARAM]);
|
|
3
|
+
export function stripProxyAuthQuery(query) {
|
|
4
|
+
const upstreamQuery = /* @__PURE__ */ Object.create(null);
|
|
5
|
+
for (const [key, value] of Object.entries(query)) {
|
|
6
|
+
if (!PROXY_AUTH_PARAMS.has(key))
|
|
7
|
+
upstreamQuery[key] = value;
|
|
8
|
+
}
|
|
9
|
+
return upstreamQuery;
|
|
10
|
+
}
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* (a billion guesses gives a ~5% hit rate at 2^64). Longer signatures bloat
|
|
22
22
|
* prerendered HTML for no practical gain.
|
|
23
23
|
*/
|
|
24
|
-
import type { H3Event } from 'h3';
|
|
24
|
+
import type { H3Event } from '#nuxt-scripts/h3';
|
|
25
25
|
import { PAGE_TOKEN_MAX_AGE, PAGE_TOKEN_PARAM, PAGE_TOKEN_TS_PARAM, SIG_LENGTH, SIG_PARAM } from './sign-constants.js';
|
|
26
26
|
export { PAGE_TOKEN_MAX_AGE, PAGE_TOKEN_PARAM, PAGE_TOKEN_TS_PARAM, SIG_LENGTH, SIG_PARAM };
|
|
27
27
|
/**
|
|
@@ -19,5 +19,5 @@
|
|
|
19
19
|
* The outer wrapper runs before any handler logic, so unauthorized requests
|
|
20
20
|
* never reach the upstream fetch and cannot consume API quota.
|
|
21
21
|
*/
|
|
22
|
-
import type { EventHandler, EventHandlerRequest, EventHandlerResponse } from 'h3';
|
|
22
|
+
import type { EventHandler, EventHandlerRequest, EventHandlerResponse } from '#nuxt-scripts/h3';
|
|
23
23
|
export declare function withSigning<Req extends EventHandlerRequest = EventHandlerRequest, Res extends EventHandlerResponse = EventHandlerResponse>(handler: EventHandler<Req, Res>): EventHandler<Req, any>;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { createError, defineEventHandler } from "h3";
|
|
2
|
-
import { useRuntimeConfig } from "
|
|
1
|
+
import { createError, defineEventHandler } from "#nuxt-scripts/h3";
|
|
2
|
+
import { useRuntimeConfig } from "#nuxt-scripts/nitro";
|
|
3
3
|
import { verifyProxyRequest } from "./sign.js";
|
|
4
4
|
export function withSigning(handler) {
|
|
5
5
|
return defineEventHandler(async (event) => {
|
|
6
|
-
const runtimeConfig = useRuntimeConfig(
|
|
6
|
+
const runtimeConfig = useRuntimeConfig();
|
|
7
7
|
const scriptsConfig = runtimeConfig["nuxt-scripts"];
|
|
8
8
|
const secret = scriptsConfig?.proxySecret;
|
|
9
9
|
if (!secret)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { createError, defineEventHandler, getQuery, setHeader } from "h3";
|
|
2
|
-
import { useRuntimeConfig } from "
|
|
3
|
-
import { createCachedJsonFetch } from "./utils/cached-upstream.js";
|
|
1
|
+
import { createError, defineEventHandler, getQuery, setHeader } from "#nuxt-scripts/h3";
|
|
2
|
+
import { useRuntimeConfig } from "#nuxt-scripts/nitro";
|
|
3
|
+
import { createCachedJsonFetch, isSafeHttpsUrl } from "./utils/cached-upstream.js";
|
|
4
4
|
import { rewriteTweetImages } from "./utils/embed-rewriters.js";
|
|
5
5
|
import { withSigning } from "./utils/withSigning.js";
|
|
6
6
|
const TWEET_ID_RE = /^\d+$/;
|
|
@@ -12,6 +12,10 @@ const cachedTweetFetch = createCachedJsonFetch(
|
|
|
12
12
|
(url) => {
|
|
13
13
|
const match = url.match(TWEET_ID_FROM_URL_RE);
|
|
14
14
|
return match?.[1] || url;
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
allowUrl: (url) => isSafeHttpsUrl(url) && url.hostname === "cdn.syndication.twimg.com",
|
|
18
|
+
contentTypePrefixes: ["application/json"]
|
|
15
19
|
}
|
|
16
20
|
);
|
|
17
21
|
export default withSigning(defineEventHandler(async (event) => {
|
|
@@ -43,7 +47,7 @@ export default withSigning(defineEventHandler(async (event) => {
|
|
|
43
47
|
const handlerPath = event.path?.split("?")[0] || "";
|
|
44
48
|
const prefix = handlerPath.replace(EMBED_X_SUFFIX_RE, "") || "/_scripts";
|
|
45
49
|
const imagePath = `${prefix}/embed/x-image`;
|
|
46
|
-
const secret = useRuntimeConfig(
|
|
50
|
+
const secret = useRuntimeConfig()["nuxt-scripts"]?.proxySecret;
|
|
47
51
|
rewriteTweetImages(tweetData, imagePath, secret);
|
|
48
52
|
setHeader(event, "Content-Type", "application/json");
|
|
49
53
|
setHeader(event, "Cache-Control", "public, max-age=600, s-maxage=600");
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function isUnheadSourceLessScriptLoaderEnabled(): boolean;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface AbortablePromiseOptions {
|
|
2
|
+
signal?: AbortSignal;
|
|
3
|
+
abortMessage?: string;
|
|
4
|
+
}
|
|
5
|
+
type PromiseSetup<T> = (resolve: (value: T | PromiseLike<T>) => void, reject: (reason?: unknown) => void) => void | (() => void);
|
|
6
|
+
export declare function createAbortError(message?: string): Error;
|
|
7
|
+
export declare function createAbortablePromise<T>(setup: PromiseSetup<T>, options?: AbortablePromiseOptions): Promise<T>;
|
|
8
|
+
export {};
|