@nuxt/scripts 1.0.0-rc.1 → 1.0.0-rc.10
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/bin/cli.mjs +2 -0
- package/dist/cli.d.mts +2 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.mjs +50 -0
- package/dist/devtools-client/200.html +1 -1
- package/dist/devtools-client/404.html +1 -1
- package/dist/devtools-client/_nuxt/{ajngqPCs.js → BgPDxVUn.js} +1 -1
- package/dist/devtools-client/_nuxt/{DKL6PHO3.js → BmlapxLP.js} +1 -1
- package/dist/devtools-client/_nuxt/CM2vefXI.js +188 -0
- package/dist/devtools-client/_nuxt/{CfOsp0mU.js → DAF5Qk9P.js} +1 -1
- package/dist/devtools-client/_nuxt/{B3kN3DAy.js → Dx6HhVmj.js} +1 -1
- package/dist/devtools-client/_nuxt/{dlaR8P-P.js → S8LiR9M1.js} +1 -1
- package/dist/devtools-client/_nuxt/builds/latest.json +1 -1
- package/dist/devtools-client/_nuxt/builds/meta/5458a3f2-af35-479c-8852-bf6f92fed611.json +1 -0
- package/dist/devtools-client/_nuxt/{entry.BwpOBArY.css → entry.BKkVrcJj.css} +1 -1
- package/dist/devtools-client/_nuxt/error-404.d44aGwWI.css +1 -0
- package/dist/devtools-client/_nuxt/error-500.NthMfIEt.css +1 -0
- package/dist/devtools-client/_nuxt/index.DZD1lwyI.css +1 -0
- package/dist/devtools-client/_nuxt/vBkR1GJq.js +1 -0
- 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 +66 -2
- package/dist/module.d.ts +66 -2
- package/dist/module.json +1 -1
- package/dist/module.mjs +144 -28
- package/dist/registry.d.mts +1 -0
- package/dist/registry.d.ts +1 -0
- package/dist/registry.mjs +14 -14
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMaps.d.vue.ts +73 -97
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMaps.vue +81 -58
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMaps.vue.d.ts +73 -97
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsInfoWindow.d.vue.ts +2 -3
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsInfoWindow.vue +1 -0
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsInfoWindow.vue.d.ts +2 -3
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsMarker.d.vue.ts +2 -3
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsMarker.vue +2 -1
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsMarker.vue.d.ts +2 -3
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsMarkerClusterer.d.vue.ts +10 -43
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsMarkerClusterer.vue +3 -1
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsMarkerClusterer.vue.d.ts +10 -43
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsOverlayView.d.vue.ts +50 -30
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsOverlayView.vue +145 -104
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsOverlayView.vue.d.ts +50 -30
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsStaticMap.vue +7 -2
- package/dist/runtime/components/GoogleMaps/types.d.ts +42 -0
- package/dist/runtime/components/GoogleMaps/types.js +1 -0
- package/dist/runtime/components/GoogleMaps/useGoogleMapsResource.d.ts +50 -0
- package/dist/runtime/components/GoogleMaps/useGoogleMapsResource.js +76 -1
- package/dist/runtime/components/ScriptBlueskyEmbed.d.vue.ts +10 -12
- package/dist/runtime/components/ScriptBlueskyEmbed.vue +13 -10
- package/dist/runtime/components/ScriptBlueskyEmbed.vue.d.ts +10 -12
- package/dist/runtime/components/ScriptCarbonAds.d.vue.ts +4 -7
- package/dist/runtime/components/ScriptCarbonAds.vue +1 -0
- package/dist/runtime/components/ScriptCarbonAds.vue.d.ts +4 -7
- package/dist/runtime/components/ScriptCrisp.d.vue.ts +7 -11
- package/dist/runtime/components/ScriptCrisp.vue +1 -0
- package/dist/runtime/components/ScriptCrisp.vue.d.ts +7 -11
- package/dist/runtime/components/ScriptGoogleAdsense.d.vue.ts +4 -7
- package/dist/runtime/components/ScriptGoogleAdsense.vue +1 -0
- package/dist/runtime/components/ScriptGoogleAdsense.vue.d.ts +4 -7
- package/dist/runtime/components/ScriptInstagramEmbed.d.vue.ts +11 -13
- package/dist/runtime/components/ScriptInstagramEmbed.vue +4 -1
- package/dist/runtime/components/ScriptInstagramEmbed.vue.d.ts +11 -13
- package/dist/runtime/components/ScriptIntercom.d.vue.ts +7 -11
- package/dist/runtime/components/ScriptIntercom.vue +1 -0
- package/dist/runtime/components/ScriptIntercom.vue.d.ts +7 -11
- package/dist/runtime/components/ScriptLemonSqueezy.d.vue.ts +2 -3
- package/dist/runtime/components/ScriptLemonSqueezy.vue +1 -0
- package/dist/runtime/components/ScriptLemonSqueezy.vue.d.ts +2 -3
- package/dist/runtime/components/ScriptPayPalButtons.d.vue.ts +8 -13
- package/dist/runtime/components/ScriptPayPalButtons.vue +1 -0
- package/dist/runtime/components/ScriptPayPalButtons.vue.d.ts +8 -13
- package/dist/runtime/components/ScriptPayPalMessages.d.vue.ts +8 -13
- package/dist/runtime/components/ScriptPayPalMessages.vue +1 -0
- package/dist/runtime/components/ScriptPayPalMessages.vue.d.ts +8 -13
- package/dist/runtime/components/ScriptStripePricingTable.d.vue.ts +5 -9
- package/dist/runtime/components/ScriptStripePricingTable.vue +1 -0
- package/dist/runtime/components/ScriptStripePricingTable.vue.d.ts +5 -9
- package/dist/runtime/components/ScriptVimeoPlayer.d.vue.ts +8 -11
- package/dist/runtime/components/ScriptVimeoPlayer.vue +1 -0
- package/dist/runtime/components/ScriptVimeoPlayer.vue.d.ts +8 -11
- package/dist/runtime/components/ScriptXEmbed.d.vue.ts +10 -12
- package/dist/runtime/components/ScriptXEmbed.vue +12 -9
- package/dist/runtime/components/ScriptXEmbed.vue.d.ts +10 -12
- package/dist/runtime/components/ScriptYouTubePlayer.d.vue.ts +8 -13
- package/dist/runtime/components/ScriptYouTubePlayer.vue +1 -0
- package/dist/runtime/components/ScriptYouTubePlayer.vue.d.ts +8 -13
- package/dist/runtime/composables/useScript.js +17 -6
- package/dist/runtime/composables/useScriptProxyToken.d.ts +12 -0
- package/dist/runtime/composables/useScriptProxyToken.js +4 -0
- package/dist/runtime/composables/useScriptProxyUrl.d.ts +12 -0
- package/dist/runtime/composables/useScriptProxyUrl.js +27 -0
- package/dist/runtime/plugins/proxy-token.server.d.ts +10 -0
- package/dist/runtime/plugins/proxy-token.server.js +17 -0
- package/dist/runtime/registry/bing-uet.d.ts +189 -11
- package/dist/runtime/registry/bing-uet.js +16 -2
- package/dist/runtime/registry/bluesky-embed.d.ts +0 -4
- package/dist/runtime/registry/bluesky-embed.js +0 -4
- package/dist/runtime/registry/clarity.d.ts +6 -2
- package/dist/runtime/registry/clarity.js +12 -1
- package/dist/runtime/registry/google-analytics.d.ts +6 -2
- package/dist/runtime/registry/google-analytics.js +12 -1
- package/dist/runtime/registry/google-tag-manager.d.ts +6 -2
- package/dist/runtime/registry/google-tag-manager.js +10 -1
- package/dist/runtime/registry/gravatar.js +10 -13
- package/dist/runtime/registry/matomo-analytics.d.ts +9 -3
- package/dist/runtime/registry/matomo-analytics.js +28 -1
- package/dist/runtime/registry/meta-pixel.d.ts +8 -2
- package/dist/runtime/registry/meta-pixel.js +10 -1
- package/dist/runtime/registry/mixpanel-analytics.d.ts +12 -2
- package/dist/runtime/registry/mixpanel-analytics.js +16 -4
- package/dist/runtime/registry/posthog.d.ts +8 -2
- package/dist/runtime/registry/posthog.js +15 -4
- package/dist/runtime/registry/schemas.d.ts +65 -0
- package/dist/runtime/registry/schemas.js +75 -8
- package/dist/runtime/registry/tiktok-pixel.d.ts +16 -2
- package/dist/runtime/registry/tiktok-pixel.js +22 -1
- package/dist/runtime/registry/x-embed.d.ts +0 -4
- package/dist/runtime/registry/x-embed.js +0 -4
- package/dist/runtime/server/bluesky-embed-image.d.ts +1 -1
- package/dist/runtime/server/bluesky-embed.d.ts +1 -15
- package/dist/runtime/server/bluesky-embed.js +25 -6
- package/dist/runtime/server/google-maps-geocode-proxy.js +12 -8
- package/dist/runtime/server/google-static-maps-proxy.d.ts +1 -1
- package/dist/runtime/server/google-static-maps-proxy.js +17 -11
- package/dist/runtime/server/gravatar-proxy.d.ts +1 -1
- package/dist/runtime/server/gravatar-proxy.js +10 -10
- package/dist/runtime/server/instagram-embed-asset.d.ts +1 -1
- package/dist/runtime/server/instagram-embed-image.d.ts +1 -1
- package/dist/runtime/server/instagram-embed.d.ts +1 -16
- package/dist/runtime/server/instagram-embed.js +26 -125
- package/dist/runtime/server/proxy-handler.js +1 -2
- package/dist/runtime/server/utils/cached-upstream.d.ts +55 -0
- package/dist/runtime/server/utils/cached-upstream.js +65 -0
- package/dist/runtime/server/utils/embed-rewriters.d.ts +19 -0
- package/dist/runtime/server/utils/embed-rewriters.js +41 -0
- package/dist/runtime/server/utils/image-proxy.d.ts +3 -1
- package/dist/runtime/server/utils/image-proxy.js +11 -8
- package/dist/runtime/server/utils/instagram-embed.d.ts +16 -0
- package/dist/runtime/server/utils/instagram-embed.js +153 -0
- package/dist/runtime/server/utils/proxy-url.d.ts +9 -0
- package/dist/runtime/server/utils/proxy-url.js +21 -0
- package/dist/runtime/server/utils/sign-constants.d.ts +16 -0
- package/dist/runtime/server/utils/sign-constants.js +5 -0
- package/dist/runtime/server/utils/sign.d.ts +101 -0
- package/dist/runtime/server/utils/sign.js +91 -0
- package/dist/runtime/server/utils/withSigning.d.ts +23 -0
- package/dist/runtime/server/utils/withSigning.js +19 -0
- package/dist/runtime/server/x-embed-image.d.ts +1 -1
- package/dist/runtime/server/x-embed.js +23 -4
- package/dist/runtime/types.d.ts +41 -6
- package/dist/runtime/types.js +1 -0
- package/dist/stats.mjs +298 -338
- package/dist/types-source.mjs +537 -164
- package/dist/types.d.mts +2 -2
- package/package.json +10 -6
- package/dist/devtools-client/_nuxt/C8jhSQ8l.js +0 -1
- package/dist/devtools-client/_nuxt/CJD6wrkT.js +0 -188
- package/dist/devtools-client/_nuxt/builds/meta/b800a0be-5cab-4ea6-89e3-dd3a85690a73.json +0 -1
- package/dist/devtools-client/_nuxt/error-404.CvOVjXeC.css +0 -1
- package/dist/devtools-client/_nuxt/error-500.BIm53nmx.css +0 -1
- package/dist/devtools-client/_nuxt/index.CA-OpSj0.css +0 -1
|
@@ -1,18 +1,3 @@
|
|
|
1
|
-
export
|
|
2
|
-
export declare const AMP_RE: RegExp;
|
|
3
|
-
export declare const SCONTENT_RE: RegExp;
|
|
4
|
-
export declare const STATIC_CDN_RE: RegExp;
|
|
5
|
-
export declare const LOOKASIDE_RE: RegExp;
|
|
6
|
-
export declare const INSTAGRAM_IMAGE_HOSTS: string[];
|
|
7
|
-
export declare const INSTAGRAM_ASSET_HOST = "static.cdninstagram.com";
|
|
8
|
-
export declare function proxyImageUrl(url: string, prefix?: string): string;
|
|
9
|
-
export declare function proxyAssetUrl(url: string, prefix?: string): string;
|
|
10
|
-
export declare function rewriteUrl(url: string, prefix?: string): string;
|
|
11
|
-
export declare function rewriteUrlsInText(text: string, prefix?: string): string;
|
|
12
|
-
/**
|
|
13
|
-
* Scope CSS rules under a parent selector and strip global/page-level rules.
|
|
14
|
-
* Removes :root, html, body selectors and @charset/@import at-rules.
|
|
15
|
-
*/
|
|
16
|
-
export declare function scopeCss(css: string, scopeSelector: string): string;
|
|
1
|
+
export { proxyAssetUrl, proxyImageUrl, rewriteUrl, rewriteUrlsInText, scopeCss } from './utils/instagram-embed.js';
|
|
17
2
|
declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<string>>;
|
|
18
3
|
export default _default;
|
|
@@ -1,40 +1,22 @@
|
|
|
1
1
|
import { createError, defineEventHandler, getQuery, setHeader } from "h3";
|
|
2
|
-
import {
|
|
2
|
+
import { useRuntimeConfig } from "nitropack/runtime";
|
|
3
3
|
import { ELEMENT_NODE, parse, renderSync, TEXT_NODE, walkSync } from "ultrahtml";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
export
|
|
8
|
-
export const LOOKASIDE_RE = /https:\/\/lookaside\.instagram\.com[^"'\s),]+/g;
|
|
9
|
-
export const INSTAGRAM_IMAGE_HOSTS = ["scontent.cdninstagram.com", "lookaside.instagram.com"];
|
|
10
|
-
export const INSTAGRAM_ASSET_HOST = "static.cdninstagram.com";
|
|
11
|
-
const CHARSET_RE = /@charset\s[^;]+;/gi;
|
|
12
|
-
const IMPORT_RE = /@import\s[^;]+;/gi;
|
|
13
|
-
const WHITESPACE_RE = /\s/;
|
|
4
|
+
import { createCachedJsonFetch } from "./utils/cached-upstream.js";
|
|
5
|
+
import { proxyAssetUrl, rewriteUrl, rewriteUrlsInText, RSRC_RE, scopeCss } from "./utils/instagram-embed.js";
|
|
6
|
+
import { withSigning } from "./utils/withSigning.js";
|
|
7
|
+
export { proxyAssetUrl, proxyImageUrl, rewriteUrl, rewriteUrlsInText, scopeCss } from "./utils/instagram-embed.js";
|
|
14
8
|
const EMBED_INSTAGRAM_SUFFIX_RE = /\/embed\/instagram$/;
|
|
15
|
-
const AT_RULE_NAME_RE = /@([\w-]+)/;
|
|
16
|
-
const MULTI_SPACE_RE = /\s+/g;
|
|
17
9
|
const SRCSET_SPLIT_RE = /\s+/;
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
return proxyAssetUrl(url, prefix);
|
|
29
|
-
if (INSTAGRAM_IMAGE_HOSTS.some((h) => parsed.hostname === h || parsed.hostname.endsWith(`.cdninstagram.com`)))
|
|
30
|
-
return proxyImageUrl(url, prefix);
|
|
31
|
-
} catch {
|
|
32
|
-
}
|
|
33
|
-
return url;
|
|
34
|
-
}
|
|
35
|
-
export function rewriteUrlsInText(text, prefix = "/_scripts") {
|
|
36
|
-
return text.replace(SCONTENT_RE, (m) => proxyImageUrl(m, prefix)).replace(STATIC_CDN_RE, (m) => proxyAssetUrl(m, prefix)).replace(LOOKASIDE_RE, (m) => proxyImageUrl(m, prefix));
|
|
37
|
-
}
|
|
10
|
+
const cachedEmbedFetch = createCachedJsonFetch(
|
|
11
|
+
"nuxt-scripts-instagram-embed",
|
|
12
|
+
600,
|
|
13
|
+
(url) => url
|
|
14
|
+
);
|
|
15
|
+
const cachedCssFetch = createCachedJsonFetch(
|
|
16
|
+
"nuxt-scripts-instagram-css",
|
|
17
|
+
86400,
|
|
18
|
+
(url) => url
|
|
19
|
+
);
|
|
38
20
|
function removeNode(node) {
|
|
39
21
|
node.type = TEXT_NODE;
|
|
40
22
|
node.value = "";
|
|
@@ -42,91 +24,10 @@ function removeNode(node) {
|
|
|
42
24
|
node.attributes = {};
|
|
43
25
|
node.children = [];
|
|
44
26
|
}
|
|
45
|
-
export
|
|
46
|
-
let result = css.replace(CHARSET_RE, "");
|
|
47
|
-
result = result.replace(IMPORT_RE, "");
|
|
48
|
-
return processRules(result, scopeSelector);
|
|
49
|
-
}
|
|
50
|
-
function processRules(css, scopeSelector) {
|
|
51
|
-
const output = [];
|
|
52
|
-
let i = 0;
|
|
53
|
-
while (i < css.length) {
|
|
54
|
-
while (i < css.length && WHITESPACE_RE.test(css[i])) i++;
|
|
55
|
-
if (i >= css.length)
|
|
56
|
-
break;
|
|
57
|
-
if (css[i] === "@") {
|
|
58
|
-
const atRule = extractAtRule(css, i);
|
|
59
|
-
if (atRule) {
|
|
60
|
-
const atName = atRule.content.match(AT_RULE_NAME_RE)?.[1]?.toLowerCase();
|
|
61
|
-
if (atName === "media" || atName === "supports" || atName === "layer") {
|
|
62
|
-
const braceStart = atRule.content.indexOf("{");
|
|
63
|
-
const innerCss = atRule.content.slice(braceStart + 1, -1);
|
|
64
|
-
const scopedInner = processRules(innerCss, scopeSelector);
|
|
65
|
-
output.push(`${atRule.content.slice(0, braceStart + 1) + scopedInner}}`);
|
|
66
|
-
} else if (atName === "keyframes" || atName === "-webkit-keyframes" || atName === "font-face") {
|
|
67
|
-
output.push(atRule.content);
|
|
68
|
-
}
|
|
69
|
-
i = atRule.end;
|
|
70
|
-
continue;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
const bracePos = css.indexOf("{", i);
|
|
74
|
-
if (bracePos === -1)
|
|
75
|
-
break;
|
|
76
|
-
const selector = css.slice(i, bracePos).trim();
|
|
77
|
-
const block = extractBlock(css, bracePos);
|
|
78
|
-
if (!block)
|
|
79
|
-
break;
|
|
80
|
-
i = block.end;
|
|
81
|
-
if (!selector)
|
|
82
|
-
continue;
|
|
83
|
-
const selectors = selector.split(",").map((s) => s.trim());
|
|
84
|
-
const filteredSelectors = selectors.filter((s) => {
|
|
85
|
-
const normalized = s.replace(MULTI_SPACE_RE, " ").trim().toLowerCase();
|
|
86
|
-
return normalized !== ":root" && normalized !== "html" && normalized !== "body" && !normalized.startsWith(":root ") && !normalized.startsWith("html ") && !normalized.startsWith("body ") && normalized !== "html, body";
|
|
87
|
-
});
|
|
88
|
-
if (filteredSelectors.length === 0)
|
|
89
|
-
continue;
|
|
90
|
-
const scopedSelectors = filteredSelectors.map((s) => {
|
|
91
|
-
return `${scopeSelector} ${s}`;
|
|
92
|
-
});
|
|
93
|
-
output.push(`${scopedSelectors.join(", ")} ${block.content}`);
|
|
94
|
-
}
|
|
95
|
-
return output.join("\n");
|
|
96
|
-
}
|
|
97
|
-
function extractAtRule(css, start) {
|
|
98
|
-
const bracePos = css.indexOf("{", start);
|
|
99
|
-
const semiPos = css.indexOf(";", start);
|
|
100
|
-
if (semiPos !== -1 && (bracePos === -1 || semiPos < bracePos)) {
|
|
101
|
-
return { content: css.slice(start, semiPos + 1), end: semiPos + 1 };
|
|
102
|
-
}
|
|
103
|
-
if (bracePos === -1)
|
|
104
|
-
return null;
|
|
105
|
-
const block = extractBlock(css, bracePos);
|
|
106
|
-
if (!block)
|
|
107
|
-
return null;
|
|
108
|
-
return {
|
|
109
|
-
content: css.slice(start, bracePos) + block.content,
|
|
110
|
-
end: block.end
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
function extractBlock(css, openBrace) {
|
|
114
|
-
let depth = 0;
|
|
115
|
-
for (let j = openBrace; j < css.length; j++) {
|
|
116
|
-
if (css[j] === "{") {
|
|
117
|
-
depth++;
|
|
118
|
-
} else if (css[j] === "}") {
|
|
119
|
-
depth--;
|
|
120
|
-
if (depth === 0) {
|
|
121
|
-
return { content: css.slice(openBrace, j + 1), end: j + 1 };
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
return null;
|
|
126
|
-
}
|
|
127
|
-
export default defineEventHandler(async (event) => {
|
|
27
|
+
export default withSigning(defineEventHandler(async (event) => {
|
|
128
28
|
const handlerPath = event.path?.split("?")[0] || "";
|
|
129
29
|
const prefix = handlerPath.replace(EMBED_INSTAGRAM_SUFFIX_RE, "") || "/_scripts";
|
|
30
|
+
const secret = useRuntimeConfig(event)["nuxt-scripts"]?.proxySecret;
|
|
130
31
|
const query = getQuery(event);
|
|
131
32
|
const postUrl = query.url;
|
|
132
33
|
const captions = query.captions === "true";
|
|
@@ -154,7 +55,7 @@ export default defineEventHandler(async (event) => {
|
|
|
154
55
|
const pathname = parsedUrl.pathname.endsWith("/") ? parsedUrl.pathname : `${parsedUrl.pathname}/`;
|
|
155
56
|
const cleanUrl = parsedUrl.origin + pathname;
|
|
156
57
|
const embedUrl = `${cleanUrl}embed/${captions ? "captioned/" : ""}`;
|
|
157
|
-
const html = await
|
|
58
|
+
const html = await cachedEmbedFetch(embedUrl, {
|
|
158
59
|
headers: {
|
|
159
60
|
"Accept": "text/html",
|
|
160
61
|
"User-Agent": "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
|
|
@@ -181,22 +82,22 @@ export default defineEventHandler(async (event) => {
|
|
|
181
82
|
}
|
|
182
83
|
for (const attr of ["src", "poster"]) {
|
|
183
84
|
if (node.attributes[attr])
|
|
184
|
-
node.attributes[attr] = rewriteUrl(node.attributes[attr], prefix);
|
|
85
|
+
node.attributes[attr] = rewriteUrl(node.attributes[attr], prefix, secret);
|
|
185
86
|
}
|
|
186
87
|
if (node.attributes.srcset) {
|
|
187
88
|
node.attributes.srcset = node.attributes.srcset.split(",").map((entry) => {
|
|
188
89
|
const parts = entry.trim().split(SRCSET_SPLIT_RE);
|
|
189
90
|
const url = parts[0];
|
|
190
91
|
const descriptor = parts.slice(1).join(" ");
|
|
191
|
-
return url ? `${rewriteUrl(url, prefix)}${descriptor ? ` ${descriptor}` : ""}` : entry;
|
|
92
|
+
return url ? `${rewriteUrl(url, prefix, secret)}${descriptor ? ` ${descriptor}` : ""}` : entry;
|
|
192
93
|
}).join(", ");
|
|
193
94
|
}
|
|
194
95
|
if (node.attributes.style)
|
|
195
|
-
node.attributes.style = rewriteUrlsInText(node.attributes.style, prefix);
|
|
96
|
+
node.attributes.style = rewriteUrlsInText(node.attributes.style, prefix, secret);
|
|
196
97
|
});
|
|
197
98
|
walkSync(ast, (node) => {
|
|
198
99
|
if (node.type === TEXT_NODE && node.value)
|
|
199
|
-
node.value = rewriteUrlsInText(node.value, prefix);
|
|
100
|
+
node.value = rewriteUrlsInText(node.value, prefix, secret);
|
|
200
101
|
});
|
|
201
102
|
let bodyNode = null;
|
|
202
103
|
walkSync(ast, (node) => {
|
|
@@ -206,7 +107,7 @@ export default defineEventHandler(async (event) => {
|
|
|
206
107
|
const bodyHtml = bodyNode ? bodyNode.children.map((child) => renderSync(child)).join("") : renderSync(ast);
|
|
207
108
|
const cssContents = await Promise.all(
|
|
208
109
|
cssUrls.map(
|
|
209
|
-
(url) =>
|
|
110
|
+
(url) => cachedCssFetch(url, {
|
|
210
111
|
headers: { Accept: "text/css" }
|
|
211
112
|
}).catch(() => "")
|
|
212
113
|
)
|
|
@@ -214,9 +115,9 @@ export default defineEventHandler(async (event) => {
|
|
|
214
115
|
let combinedCss = cssContents.join("\n");
|
|
215
116
|
combinedCss = combinedCss.replace(
|
|
216
117
|
RSRC_RE,
|
|
217
|
-
(_m, path) => `url(${
|
|
118
|
+
(_m, path) => `url(${proxyAssetUrl(`https://static.cdninstagram.com/rsrc.php${path}`, prefix, secret)})`
|
|
218
119
|
);
|
|
219
|
-
combinedCss = rewriteUrlsInText(combinedCss, prefix);
|
|
120
|
+
combinedCss = rewriteUrlsInText(combinedCss, prefix, secret);
|
|
220
121
|
combinedCss = scopeCss(combinedCss, ".instagram-embed-root");
|
|
221
122
|
const baseStyles = `
|
|
222
123
|
.instagram-embed-root { background: white; max-width: 540px; width: calc(100% - 2px); border-radius: 3px; border: 1px solid rgb(219, 219, 219); display: block; margin: 0px 0px 12px; min-width: 326px; padding: 0px; }
|
|
@@ -229,4 +130,4 @@ ${combinedCss}</style>${bodyHtml}</div>`;
|
|
|
229
130
|
setHeader(event, "Content-Type", "text/html");
|
|
230
131
|
setHeader(event, "Cache-Control", "public, max-age=600, s-maxage=600");
|
|
231
132
|
return result;
|
|
232
|
-
});
|
|
133
|
+
}));
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { useRuntimeConfig } from "#imports";
|
|
2
1
|
import { createError, defineEventHandler, getHeaders, getQuery, getRequestIP, getRequestWebStream, readBody, setResponseHeader } from "h3";
|
|
3
|
-
import { useNitroApp } from "nitropack/runtime";
|
|
2
|
+
import { useNitroApp, useRuntimeConfig } from "nitropack/runtime";
|
|
4
3
|
import {
|
|
5
4
|
anonymizeIP,
|
|
6
5
|
mergePrivacy,
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Buffer } from 'node:buffer';
|
|
2
|
+
/**
|
|
3
|
+
* Server-side caches for upstream proxy fetches.
|
|
4
|
+
*
|
|
5
|
+
* ## Why
|
|
6
|
+
*
|
|
7
|
+
* Proxy URLs arriving from the client carry per-request auth artefacts (`sig`,
|
|
8
|
+
* `_pt`, `_ts`) that change across renders. CDNs key on full URL so each
|
|
9
|
+
* rotation produces a unique edge cache entry and upstream origins take the hit
|
|
10
|
+
* on every render. Caching the *upstream response* here — keyed on the inner
|
|
11
|
+
* resource URL (or normalized param set) — dedupes those fetches across every
|
|
12
|
+
* request that resolves to the same upstream, regardless of how the caller
|
|
13
|
+
* authenticated.
|
|
14
|
+
*
|
|
15
|
+
* Safe because `withSigning` runs before any cache path: unsigned requests 403
|
|
16
|
+
* before they can do a cache lookup. Cache stores hold only responses produced
|
|
17
|
+
* from legitimately-authenticated requests.
|
|
18
|
+
*
|
|
19
|
+
* ## Binary payloads
|
|
20
|
+
*
|
|
21
|
+
* Image/blob responses are stored as base64 strings so they round-trip cleanly
|
|
22
|
+
* through every unstorage driver (memory, filesystem, redis, cloudflare kv).
|
|
23
|
+
* The 33% size overhead is tolerable; the alternative is relying on each driver
|
|
24
|
+
* to preserve Buffer/ArrayBuffer which is not universal.
|
|
25
|
+
*/
|
|
26
|
+
export interface CachedBinaryResponse {
|
|
27
|
+
base64: string;
|
|
28
|
+
contentType: string | null;
|
|
29
|
+
}
|
|
30
|
+
export interface CachedBinaryFetchOptions {
|
|
31
|
+
headers?: Record<string, string>;
|
|
32
|
+
timeout?: number;
|
|
33
|
+
redirect?: 'follow' | 'manual';
|
|
34
|
+
ignoreResponseError?: boolean;
|
|
35
|
+
}
|
|
36
|
+
export interface CachedBinaryResult extends CachedBinaryResponse {
|
|
37
|
+
body: Buffer;
|
|
38
|
+
status: number;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Cache upstream binary/image fetches. Returns a helper that restores the
|
|
42
|
+
* response body as a Buffer so the handler can pipe it straight to the client.
|
|
43
|
+
*/
|
|
44
|
+
export declare function createCachedBinaryFetch(name: string, maxAge: number): (url: string, opts?: CachedBinaryFetchOptions) => Promise<CachedBinaryResult>;
|
|
45
|
+
/**
|
|
46
|
+
* Cache upstream JSON/text fetches. `getKey` is caller-controlled so handlers
|
|
47
|
+
* can normalize on whichever inner params identify the resource (tweet ID,
|
|
48
|
+
* post URL, query hash).
|
|
49
|
+
*/
|
|
50
|
+
export declare function createCachedJsonFetch<T>(name: string, maxAge: number, getKey: (url: string, opts?: {
|
|
51
|
+
headers?: Record<string, string>;
|
|
52
|
+
}) => string): (url: string, opts?: {
|
|
53
|
+
headers?: Record<string, string>;
|
|
54
|
+
timeout?: number;
|
|
55
|
+
}) => Promise<T>;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
2
|
+
import { defineCachedFunction } from "nitropack/runtime";
|
|
3
|
+
import { $fetch } from "ofetch";
|
|
4
|
+
export function createCachedBinaryFetch(name, maxAge) {
|
|
5
|
+
const cached = defineCachedFunction(
|
|
6
|
+
async (url, opts) => {
|
|
7
|
+
const response = await $fetch.raw(url, {
|
|
8
|
+
responseType: "arrayBuffer",
|
|
9
|
+
timeout: opts?.timeout ?? 1e4,
|
|
10
|
+
redirect: opts?.redirect ?? "follow",
|
|
11
|
+
ignoreResponseError: opts?.ignoreResponseError ?? false,
|
|
12
|
+
headers: opts?.headers
|
|
13
|
+
});
|
|
14
|
+
const data = response._data;
|
|
15
|
+
return {
|
|
16
|
+
base64: data ? Buffer.from(data).toString("base64") : "",
|
|
17
|
+
contentType: response.headers.get("content-type"),
|
|
18
|
+
status: response.status
|
|
19
|
+
};
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
name,
|
|
23
|
+
maxAge,
|
|
24
|
+
swr: true,
|
|
25
|
+
staleMaxAge: maxAge,
|
|
26
|
+
getKey: (url, opts) => {
|
|
27
|
+
if (!opts)
|
|
28
|
+
return url;
|
|
29
|
+
const parts = [url];
|
|
30
|
+
if (opts.headers) {
|
|
31
|
+
const entries = Object.entries(opts.headers).sort(([a], [b]) => a.localeCompare(b));
|
|
32
|
+
for (const [k, v] of entries)
|
|
33
|
+
parts.push(`${k}=${v}`);
|
|
34
|
+
}
|
|
35
|
+
if (opts.redirect)
|
|
36
|
+
parts.push(`redirect=${opts.redirect}`);
|
|
37
|
+
return parts.join("|");
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
);
|
|
41
|
+
return async (url, opts) => {
|
|
42
|
+
const result = await cached(url, opts);
|
|
43
|
+
return {
|
|
44
|
+
...result,
|
|
45
|
+
body: result.base64 ? Buffer.from(result.base64, "base64") : Buffer.alloc(0)
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
export function createCachedJsonFetch(name, maxAge, getKey) {
|
|
50
|
+
return defineCachedFunction(
|
|
51
|
+
async (url, opts) => {
|
|
52
|
+
return await $fetch(url, {
|
|
53
|
+
timeout: opts?.timeout ?? 1e4,
|
|
54
|
+
headers: opts?.headers
|
|
55
|
+
});
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name,
|
|
59
|
+
maxAge,
|
|
60
|
+
swr: true,
|
|
61
|
+
staleMaxAge: maxAge,
|
|
62
|
+
getKey
|
|
63
|
+
}
|
|
64
|
+
);
|
|
65
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mutate a tweet (and any quoted tweet) in place so every raw CDN image URL
|
|
3
|
+
* is rewritten to route through the site's `/embed/x-image` proxy. When a
|
|
4
|
+
* `secret` is provided, URLs are HMAC-signed and pass `withSigning` without a
|
|
5
|
+
* page token.
|
|
6
|
+
*
|
|
7
|
+
* Clone the input first if it came from a shared cache — this function does
|
|
8
|
+
* not copy.
|
|
9
|
+
*/
|
|
10
|
+
export declare function rewriteTweetImages(tweet: any, imagePath: string, secret?: string): void;
|
|
11
|
+
/**
|
|
12
|
+
* Mutate a Bluesky post in place so every CDN image URL routes through the
|
|
13
|
+
* site's `/embed/bluesky-image` proxy. Covers author avatar, embedded images
|
|
14
|
+
* (thumb + fullsize), and external embed thumbnails.
|
|
15
|
+
*
|
|
16
|
+
* Clone the input first if it came from a shared cache — this function does
|
|
17
|
+
* not copy.
|
|
18
|
+
*/
|
|
19
|
+
export declare function rewriteBlueskyPostImages(post: any, imagePath: string, secret?: string): void;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { buildProxyUrl } from "./proxy-url.js";
|
|
2
|
+
export function rewriteTweetImages(tweet, imagePath, secret) {
|
|
3
|
+
if (!tweet)
|
|
4
|
+
return;
|
|
5
|
+
if (tweet.user?.profile_image_url_https)
|
|
6
|
+
tweet.user.profile_image_url_https = buildProxyUrl(imagePath, { url: tweet.user.profile_image_url_https }, secret);
|
|
7
|
+
if (tweet.photos) {
|
|
8
|
+
for (const photo of tweet.photos) {
|
|
9
|
+
if (photo.url)
|
|
10
|
+
photo.url = buildProxyUrl(imagePath, { url: photo.url }, secret);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
if (tweet.entities?.media) {
|
|
14
|
+
for (const media of tweet.entities.media) {
|
|
15
|
+
if (media.media_url_https)
|
|
16
|
+
media.media_url_https = buildProxyUrl(imagePath, { url: media.media_url_https }, secret);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
if (tweet.video?.poster)
|
|
20
|
+
tweet.video.poster = buildProxyUrl(imagePath, { url: tweet.video.poster }, secret);
|
|
21
|
+
if (tweet.quoted_tweet)
|
|
22
|
+
rewriteTweetImages(tweet.quoted_tweet, imagePath, secret);
|
|
23
|
+
}
|
|
24
|
+
export function rewriteBlueskyPostImages(post, imagePath, secret) {
|
|
25
|
+
if (!post)
|
|
26
|
+
return;
|
|
27
|
+
const proxy = (url) => url ? buildProxyUrl(imagePath, { url }, secret) : url;
|
|
28
|
+
if (post.author?.avatar)
|
|
29
|
+
post.author.avatar = proxy(post.author.avatar);
|
|
30
|
+
const embed = post.embed;
|
|
31
|
+
if (embed?.images) {
|
|
32
|
+
for (const image of embed.images) {
|
|
33
|
+
if (image.thumb)
|
|
34
|
+
image.thumb = proxy(image.thumb);
|
|
35
|
+
if (image.fullsize)
|
|
36
|
+
image.fullsize = proxy(image.fullsize);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (embed?.external?.thumb)
|
|
40
|
+
embed.external.thumb = proxy(embed.external.thumb);
|
|
41
|
+
}
|
|
@@ -8,5 +8,7 @@ export interface ImageProxyConfig {
|
|
|
8
8
|
followRedirects?: boolean;
|
|
9
9
|
/** Decode & in URL query parameter */
|
|
10
10
|
decodeAmpersands?: boolean;
|
|
11
|
+
/** Unique name for the nitro cache group (defaults to derived from allowedDomains). */
|
|
12
|
+
cacheName?: string;
|
|
11
13
|
}
|
|
12
|
-
export declare function createImageProxyHandler(config: ImageProxyConfig): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<
|
|
14
|
+
export declare function createImageProxyHandler(config: ImageProxyConfig): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<Buffer<ArrayBufferLike>>>;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createError, defineEventHandler, getQuery, setHeader } from "h3";
|
|
2
|
-
import {
|
|
2
|
+
import { createCachedBinaryFetch } from "./cached-upstream.js";
|
|
3
|
+
import { withSigning } from "./withSigning.js";
|
|
3
4
|
const AMP_RE = /&/g;
|
|
4
5
|
export function createImageProxyHandler(config) {
|
|
5
6
|
const {
|
|
@@ -8,9 +9,11 @@ export function createImageProxyHandler(config) {
|
|
|
8
9
|
cacheMaxAge = 3600,
|
|
9
10
|
contentType = "image/jpeg",
|
|
10
11
|
followRedirects = true,
|
|
11
|
-
decodeAmpersands = false
|
|
12
|
+
decodeAmpersands = false,
|
|
13
|
+
cacheName = Array.isArray(config.allowedDomains) ? `nuxt-scripts-img:${config.allowedDomains[0] || "default"}` : "nuxt-scripts-img:custom"
|
|
12
14
|
} = config;
|
|
13
|
-
|
|
15
|
+
const cachedFetch = createCachedBinaryFetch(cacheName, cacheMaxAge);
|
|
16
|
+
return withSigning(defineEventHandler(async (event) => {
|
|
14
17
|
const query = getQuery(event);
|
|
15
18
|
let url = query.url;
|
|
16
19
|
if (decodeAmpersands && url)
|
|
@@ -46,7 +49,7 @@ export function createImageProxyHandler(config) {
|
|
|
46
49
|
const headers = { Accept: accept };
|
|
47
50
|
if (userAgent)
|
|
48
51
|
headers["User-Agent"] = userAgent;
|
|
49
|
-
const
|
|
52
|
+
const result = await cachedFetch(url, {
|
|
50
53
|
timeout: 5e3,
|
|
51
54
|
redirect: followRedirects ? "follow" : "manual",
|
|
52
55
|
ignoreResponseError: !followRedirects,
|
|
@@ -57,14 +60,14 @@ export function createImageProxyHandler(config) {
|
|
|
57
60
|
statusMessage: error.statusMessage || "Failed to fetch image"
|
|
58
61
|
});
|
|
59
62
|
});
|
|
60
|
-
if (!followRedirects &&
|
|
63
|
+
if (!followRedirects && result.status >= 300 && result.status < 400) {
|
|
61
64
|
throw createError({
|
|
62
65
|
statusCode: 403,
|
|
63
66
|
statusMessage: "Redirects not allowed"
|
|
64
67
|
});
|
|
65
68
|
}
|
|
66
|
-
setHeader(event, "Content-Type",
|
|
69
|
+
setHeader(event, "Content-Type", result.contentType || contentType);
|
|
67
70
|
setHeader(event, "Cache-Control", `public, max-age=${cacheMaxAge}, s-maxage=${cacheMaxAge}`);
|
|
68
|
-
return
|
|
69
|
-
});
|
|
71
|
+
return result.body;
|
|
72
|
+
}));
|
|
70
73
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare const RSRC_RE: RegExp;
|
|
2
|
+
export declare const AMP_RE: RegExp;
|
|
3
|
+
export declare const SCONTENT_RE: RegExp;
|
|
4
|
+
export declare const STATIC_CDN_RE: RegExp;
|
|
5
|
+
export declare const LOOKASIDE_RE: RegExp;
|
|
6
|
+
export declare const INSTAGRAM_IMAGE_HOSTS: string[];
|
|
7
|
+
export declare const INSTAGRAM_ASSET_HOST = "static.cdninstagram.com";
|
|
8
|
+
export declare function proxyImageUrl(url: string, prefix?: string, secret?: string): string;
|
|
9
|
+
export declare function proxyAssetUrl(url: string, prefix?: string, secret?: string): string;
|
|
10
|
+
export declare function rewriteUrl(url: string, prefix?: string, secret?: string): string;
|
|
11
|
+
export declare function rewriteUrlsInText(text: string, prefix?: string, secret?: string): string;
|
|
12
|
+
/**
|
|
13
|
+
* Scope CSS rules under a parent selector and strip global/page-level rules.
|
|
14
|
+
* Removes :root, html, body selectors and @charset/@import at-rules.
|
|
15
|
+
*/
|
|
16
|
+
export declare function scopeCss(css: string, scopeSelector: string): string;
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { buildProxyUrl } from "./proxy-url.js";
|
|
2
|
+
export const RSRC_RE = /url\(\/rsrc\.php([^)]+)\)/g;
|
|
3
|
+
export const AMP_RE = /&/g;
|
|
4
|
+
export const SCONTENT_RE = /https:\/\/scontent[^"'\s),]+\.cdninstagram\.com[^"'\s),]+/g;
|
|
5
|
+
export const STATIC_CDN_RE = /https:\/\/static\.cdninstagram\.com[^"'\s),]+/g;
|
|
6
|
+
export const LOOKASIDE_RE = /https:\/\/lookaside\.instagram\.com[^"'\s),]+/g;
|
|
7
|
+
export const INSTAGRAM_IMAGE_HOSTS = ["scontent.cdninstagram.com", "lookaside.instagram.com"];
|
|
8
|
+
export const INSTAGRAM_ASSET_HOST = "static.cdninstagram.com";
|
|
9
|
+
const CHARSET_RE = /@charset\s[^;]+;/gi;
|
|
10
|
+
const IMPORT_RE = /@import\s[^;]+;/gi;
|
|
11
|
+
const WHITESPACE_RE = /\s/;
|
|
12
|
+
const AT_RULE_NAME_RE = /@([\w-]+)/;
|
|
13
|
+
const MULTI_SPACE_RE = /\s+/g;
|
|
14
|
+
export function proxyImageUrl(url, prefix = "/_scripts", secret) {
|
|
15
|
+
return buildProxyUrl(`${prefix}/embed/instagram-image`, { url: url.replace(AMP_RE, "&") }, secret);
|
|
16
|
+
}
|
|
17
|
+
export function proxyAssetUrl(url, prefix = "/_scripts", secret) {
|
|
18
|
+
return buildProxyUrl(`${prefix}/embed/instagram-asset`, { url: url.replace(AMP_RE, "&") }, secret);
|
|
19
|
+
}
|
|
20
|
+
export function rewriteUrl(url, prefix = "/_scripts", secret) {
|
|
21
|
+
try {
|
|
22
|
+
const parsed = new URL(url);
|
|
23
|
+
if (parsed.hostname === INSTAGRAM_ASSET_HOST)
|
|
24
|
+
return proxyAssetUrl(url, prefix, secret);
|
|
25
|
+
if (INSTAGRAM_IMAGE_HOSTS.some((h) => parsed.hostname === h || parsed.hostname.endsWith(`.cdninstagram.com`)))
|
|
26
|
+
return proxyImageUrl(url, prefix, secret);
|
|
27
|
+
} catch {
|
|
28
|
+
}
|
|
29
|
+
return url;
|
|
30
|
+
}
|
|
31
|
+
export function rewriteUrlsInText(text, prefix = "/_scripts", secret) {
|
|
32
|
+
return text.replace(SCONTENT_RE, (m) => proxyImageUrl(m, prefix, secret)).replace(STATIC_CDN_RE, (m) => proxyAssetUrl(m, prefix, secret)).replace(LOOKASIDE_RE, (m) => proxyImageUrl(m, prefix, secret));
|
|
33
|
+
}
|
|
34
|
+
export function scopeCss(css, scopeSelector) {
|
|
35
|
+
let result = css.replace(CHARSET_RE, "");
|
|
36
|
+
result = result.replace(IMPORT_RE, "");
|
|
37
|
+
return processRules(result, scopeSelector);
|
|
38
|
+
}
|
|
39
|
+
function processRules(css, scopeSelector) {
|
|
40
|
+
const output = [];
|
|
41
|
+
let i = 0;
|
|
42
|
+
while (i < css.length) {
|
|
43
|
+
while (i < css.length && WHITESPACE_RE.test(css[i])) i++;
|
|
44
|
+
if (i >= css.length)
|
|
45
|
+
break;
|
|
46
|
+
if (css[i] === "@") {
|
|
47
|
+
const atRule = extractAtRule(css, i);
|
|
48
|
+
if (atRule) {
|
|
49
|
+
const atName = atRule.content.match(AT_RULE_NAME_RE)?.[1]?.toLowerCase();
|
|
50
|
+
if (atName === "media" || atName === "supports" || atName === "layer") {
|
|
51
|
+
const braceStart = atRule.content.indexOf("{");
|
|
52
|
+
if (braceStart === -1) {
|
|
53
|
+
output.push(atRule.content);
|
|
54
|
+
} else {
|
|
55
|
+
const innerCss = atRule.content.slice(braceStart + 1, -1);
|
|
56
|
+
const scopedInner = processRules(innerCss, scopeSelector);
|
|
57
|
+
output.push(`${atRule.content.slice(0, braceStart + 1)}${scopedInner}}`);
|
|
58
|
+
}
|
|
59
|
+
} else if (atName === "keyframes" || atName === "-webkit-keyframes" || atName === "font-face") {
|
|
60
|
+
output.push(atRule.content);
|
|
61
|
+
}
|
|
62
|
+
i = atRule.end;
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const bracePos = css.indexOf("{", i);
|
|
67
|
+
if (bracePos === -1)
|
|
68
|
+
break;
|
|
69
|
+
const selector = css.slice(i, bracePos).trim();
|
|
70
|
+
const block = extractBlock(css, bracePos);
|
|
71
|
+
if (!block)
|
|
72
|
+
break;
|
|
73
|
+
i = block.end;
|
|
74
|
+
if (!selector)
|
|
75
|
+
continue;
|
|
76
|
+
const selectors = splitTopLevel(selector, ",").map((s) => s.trim());
|
|
77
|
+
const filteredSelectors = selectors.filter((s) => {
|
|
78
|
+
const normalized = s.replace(MULTI_SPACE_RE, " ").trim().toLowerCase();
|
|
79
|
+
return normalized !== ":root" && normalized !== "html" && normalized !== "body" && !normalized.startsWith(":root ") && !normalized.startsWith("html ") && !normalized.startsWith("body ") && normalized !== "html, body";
|
|
80
|
+
});
|
|
81
|
+
if (filteredSelectors.length === 0)
|
|
82
|
+
continue;
|
|
83
|
+
const scopedSelectors = filteredSelectors.map((s) => `${scopeSelector} ${s}`);
|
|
84
|
+
output.push(`${scopedSelectors.join(", ")} ${block.content}`);
|
|
85
|
+
}
|
|
86
|
+
return output.join("\n");
|
|
87
|
+
}
|
|
88
|
+
function extractAtRule(css, start) {
|
|
89
|
+
const bracePos = css.indexOf("{", start);
|
|
90
|
+
const semiPos = css.indexOf(";", start);
|
|
91
|
+
if (semiPos !== -1 && (bracePos === -1 || semiPos < bracePos)) {
|
|
92
|
+
return { content: css.slice(start, semiPos + 1), end: semiPos + 1 };
|
|
93
|
+
}
|
|
94
|
+
if (bracePos === -1)
|
|
95
|
+
return null;
|
|
96
|
+
const block = extractBlock(css, bracePos);
|
|
97
|
+
if (!block)
|
|
98
|
+
return null;
|
|
99
|
+
return {
|
|
100
|
+
content: css.slice(start, bracePos) + block.content,
|
|
101
|
+
end: block.end
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function splitTopLevel(input, separator) {
|
|
105
|
+
const parts = [];
|
|
106
|
+
let depth = 0;
|
|
107
|
+
let quote = null;
|
|
108
|
+
let start = 0;
|
|
109
|
+
for (let i = 0; i < input.length; i++) {
|
|
110
|
+
const ch = input[i];
|
|
111
|
+
if (quote) {
|
|
112
|
+
if (ch === "\\") {
|
|
113
|
+
i++;
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
if (ch === quote)
|
|
117
|
+
quote = null;
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (ch === '"' || ch === "'") {
|
|
121
|
+
quote = ch;
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (ch === "(" || ch === "[") {
|
|
125
|
+
depth++;
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
if (ch === ")" || ch === "]") {
|
|
129
|
+
depth--;
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (ch === separator && depth === 0) {
|
|
133
|
+
parts.push(input.slice(start, i));
|
|
134
|
+
start = i + 1;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
parts.push(input.slice(start));
|
|
138
|
+
return parts;
|
|
139
|
+
}
|
|
140
|
+
function extractBlock(css, openBrace) {
|
|
141
|
+
let depth = 0;
|
|
142
|
+
for (let j = openBrace; j < css.length; j++) {
|
|
143
|
+
if (css[j] === "{") {
|
|
144
|
+
depth++;
|
|
145
|
+
} else if (css[j] === "}") {
|
|
146
|
+
depth--;
|
|
147
|
+
if (depth === 0) {
|
|
148
|
+
return { content: css.slice(openBrace, j + 1), end: j + 1 };
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build a proxy URL with query params, signing it when a secret is available.
|
|
3
|
+
*
|
|
4
|
+
* Used by embed handlers that inject proxy URLs into HTML/JSON responses.
|
|
5
|
+
* When `secret` is set, URLs are HMAC-signed so clients can fetch them without
|
|
6
|
+
* needing a page token. When it's undefined, URLs fall back to unsigned form
|
|
7
|
+
* (which is only safe when the `withSigning` middleware has no secret either).
|
|
8
|
+
*/
|
|
9
|
+
export declare function buildProxyUrl(path: string, query: Record<string, unknown>, secret?: string): string;
|