@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.
Files changed (164) hide show
  1. package/bin/cli.mjs +2 -0
  2. package/dist/cli.d.mts +2 -0
  3. package/dist/cli.d.ts +2 -0
  4. package/dist/cli.mjs +50 -0
  5. package/dist/devtools-client/200.html +1 -1
  6. package/dist/devtools-client/404.html +1 -1
  7. package/dist/devtools-client/_nuxt/{ajngqPCs.js → BgPDxVUn.js} +1 -1
  8. package/dist/devtools-client/_nuxt/{DKL6PHO3.js → BmlapxLP.js} +1 -1
  9. package/dist/devtools-client/_nuxt/CM2vefXI.js +188 -0
  10. package/dist/devtools-client/_nuxt/{CfOsp0mU.js → DAF5Qk9P.js} +1 -1
  11. package/dist/devtools-client/_nuxt/{B3kN3DAy.js → Dx6HhVmj.js} +1 -1
  12. package/dist/devtools-client/_nuxt/{dlaR8P-P.js → S8LiR9M1.js} +1 -1
  13. package/dist/devtools-client/_nuxt/builds/latest.json +1 -1
  14. package/dist/devtools-client/_nuxt/builds/meta/5458a3f2-af35-479c-8852-bf6f92fed611.json +1 -0
  15. package/dist/devtools-client/_nuxt/{entry.BwpOBArY.css → entry.BKkVrcJj.css} +1 -1
  16. package/dist/devtools-client/_nuxt/error-404.d44aGwWI.css +1 -0
  17. package/dist/devtools-client/_nuxt/error-500.NthMfIEt.css +1 -0
  18. package/dist/devtools-client/_nuxt/index.DZD1lwyI.css +1 -0
  19. package/dist/devtools-client/_nuxt/vBkR1GJq.js +1 -0
  20. package/dist/devtools-client/docs/index.html +1 -1
  21. package/dist/devtools-client/first-party/index.html +1 -1
  22. package/dist/devtools-client/index.html +1 -1
  23. package/dist/devtools-client/registry/index.html +1 -1
  24. package/dist/module.d.mts +66 -2
  25. package/dist/module.d.ts +66 -2
  26. package/dist/module.json +1 -1
  27. package/dist/module.mjs +144 -28
  28. package/dist/registry.d.mts +1 -0
  29. package/dist/registry.d.ts +1 -0
  30. package/dist/registry.mjs +14 -14
  31. package/dist/runtime/components/GoogleMaps/ScriptGoogleMaps.d.vue.ts +73 -97
  32. package/dist/runtime/components/GoogleMaps/ScriptGoogleMaps.vue +81 -58
  33. package/dist/runtime/components/GoogleMaps/ScriptGoogleMaps.vue.d.ts +73 -97
  34. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsInfoWindow.d.vue.ts +2 -3
  35. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsInfoWindow.vue +1 -0
  36. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsInfoWindow.vue.d.ts +2 -3
  37. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsMarker.d.vue.ts +2 -3
  38. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsMarker.vue +2 -1
  39. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsMarker.vue.d.ts +2 -3
  40. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsMarkerClusterer.d.vue.ts +10 -43
  41. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsMarkerClusterer.vue +3 -1
  42. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsMarkerClusterer.vue.d.ts +10 -43
  43. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsOverlayView.d.vue.ts +50 -30
  44. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsOverlayView.vue +145 -104
  45. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsOverlayView.vue.d.ts +50 -30
  46. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsStaticMap.vue +7 -2
  47. package/dist/runtime/components/GoogleMaps/types.d.ts +42 -0
  48. package/dist/runtime/components/GoogleMaps/types.js +1 -0
  49. package/dist/runtime/components/GoogleMaps/useGoogleMapsResource.d.ts +50 -0
  50. package/dist/runtime/components/GoogleMaps/useGoogleMapsResource.js +76 -1
  51. package/dist/runtime/components/ScriptBlueskyEmbed.d.vue.ts +10 -12
  52. package/dist/runtime/components/ScriptBlueskyEmbed.vue +13 -10
  53. package/dist/runtime/components/ScriptBlueskyEmbed.vue.d.ts +10 -12
  54. package/dist/runtime/components/ScriptCarbonAds.d.vue.ts +4 -7
  55. package/dist/runtime/components/ScriptCarbonAds.vue +1 -0
  56. package/dist/runtime/components/ScriptCarbonAds.vue.d.ts +4 -7
  57. package/dist/runtime/components/ScriptCrisp.d.vue.ts +7 -11
  58. package/dist/runtime/components/ScriptCrisp.vue +1 -0
  59. package/dist/runtime/components/ScriptCrisp.vue.d.ts +7 -11
  60. package/dist/runtime/components/ScriptGoogleAdsense.d.vue.ts +4 -7
  61. package/dist/runtime/components/ScriptGoogleAdsense.vue +1 -0
  62. package/dist/runtime/components/ScriptGoogleAdsense.vue.d.ts +4 -7
  63. package/dist/runtime/components/ScriptInstagramEmbed.d.vue.ts +11 -13
  64. package/dist/runtime/components/ScriptInstagramEmbed.vue +4 -1
  65. package/dist/runtime/components/ScriptInstagramEmbed.vue.d.ts +11 -13
  66. package/dist/runtime/components/ScriptIntercom.d.vue.ts +7 -11
  67. package/dist/runtime/components/ScriptIntercom.vue +1 -0
  68. package/dist/runtime/components/ScriptIntercom.vue.d.ts +7 -11
  69. package/dist/runtime/components/ScriptLemonSqueezy.d.vue.ts +2 -3
  70. package/dist/runtime/components/ScriptLemonSqueezy.vue +1 -0
  71. package/dist/runtime/components/ScriptLemonSqueezy.vue.d.ts +2 -3
  72. package/dist/runtime/components/ScriptPayPalButtons.d.vue.ts +8 -13
  73. package/dist/runtime/components/ScriptPayPalButtons.vue +1 -0
  74. package/dist/runtime/components/ScriptPayPalButtons.vue.d.ts +8 -13
  75. package/dist/runtime/components/ScriptPayPalMessages.d.vue.ts +8 -13
  76. package/dist/runtime/components/ScriptPayPalMessages.vue +1 -0
  77. package/dist/runtime/components/ScriptPayPalMessages.vue.d.ts +8 -13
  78. package/dist/runtime/components/ScriptStripePricingTable.d.vue.ts +5 -9
  79. package/dist/runtime/components/ScriptStripePricingTable.vue +1 -0
  80. package/dist/runtime/components/ScriptStripePricingTable.vue.d.ts +5 -9
  81. package/dist/runtime/components/ScriptVimeoPlayer.d.vue.ts +8 -11
  82. package/dist/runtime/components/ScriptVimeoPlayer.vue +1 -0
  83. package/dist/runtime/components/ScriptVimeoPlayer.vue.d.ts +8 -11
  84. package/dist/runtime/components/ScriptXEmbed.d.vue.ts +10 -12
  85. package/dist/runtime/components/ScriptXEmbed.vue +12 -9
  86. package/dist/runtime/components/ScriptXEmbed.vue.d.ts +10 -12
  87. package/dist/runtime/components/ScriptYouTubePlayer.d.vue.ts +8 -13
  88. package/dist/runtime/components/ScriptYouTubePlayer.vue +1 -0
  89. package/dist/runtime/components/ScriptYouTubePlayer.vue.d.ts +8 -13
  90. package/dist/runtime/composables/useScript.js +17 -6
  91. package/dist/runtime/composables/useScriptProxyToken.d.ts +12 -0
  92. package/dist/runtime/composables/useScriptProxyToken.js +4 -0
  93. package/dist/runtime/composables/useScriptProxyUrl.d.ts +12 -0
  94. package/dist/runtime/composables/useScriptProxyUrl.js +27 -0
  95. package/dist/runtime/plugins/proxy-token.server.d.ts +10 -0
  96. package/dist/runtime/plugins/proxy-token.server.js +17 -0
  97. package/dist/runtime/registry/bing-uet.d.ts +189 -11
  98. package/dist/runtime/registry/bing-uet.js +16 -2
  99. package/dist/runtime/registry/bluesky-embed.d.ts +0 -4
  100. package/dist/runtime/registry/bluesky-embed.js +0 -4
  101. package/dist/runtime/registry/clarity.d.ts +6 -2
  102. package/dist/runtime/registry/clarity.js +12 -1
  103. package/dist/runtime/registry/google-analytics.d.ts +6 -2
  104. package/dist/runtime/registry/google-analytics.js +12 -1
  105. package/dist/runtime/registry/google-tag-manager.d.ts +6 -2
  106. package/dist/runtime/registry/google-tag-manager.js +10 -1
  107. package/dist/runtime/registry/gravatar.js +10 -13
  108. package/dist/runtime/registry/matomo-analytics.d.ts +9 -3
  109. package/dist/runtime/registry/matomo-analytics.js +28 -1
  110. package/dist/runtime/registry/meta-pixel.d.ts +8 -2
  111. package/dist/runtime/registry/meta-pixel.js +10 -1
  112. package/dist/runtime/registry/mixpanel-analytics.d.ts +12 -2
  113. package/dist/runtime/registry/mixpanel-analytics.js +16 -4
  114. package/dist/runtime/registry/posthog.d.ts +8 -2
  115. package/dist/runtime/registry/posthog.js +15 -4
  116. package/dist/runtime/registry/schemas.d.ts +65 -0
  117. package/dist/runtime/registry/schemas.js +75 -8
  118. package/dist/runtime/registry/tiktok-pixel.d.ts +16 -2
  119. package/dist/runtime/registry/tiktok-pixel.js +22 -1
  120. package/dist/runtime/registry/x-embed.d.ts +0 -4
  121. package/dist/runtime/registry/x-embed.js +0 -4
  122. package/dist/runtime/server/bluesky-embed-image.d.ts +1 -1
  123. package/dist/runtime/server/bluesky-embed.d.ts +1 -15
  124. package/dist/runtime/server/bluesky-embed.js +25 -6
  125. package/dist/runtime/server/google-maps-geocode-proxy.js +12 -8
  126. package/dist/runtime/server/google-static-maps-proxy.d.ts +1 -1
  127. package/dist/runtime/server/google-static-maps-proxy.js +17 -11
  128. package/dist/runtime/server/gravatar-proxy.d.ts +1 -1
  129. package/dist/runtime/server/gravatar-proxy.js +10 -10
  130. package/dist/runtime/server/instagram-embed-asset.d.ts +1 -1
  131. package/dist/runtime/server/instagram-embed-image.d.ts +1 -1
  132. package/dist/runtime/server/instagram-embed.d.ts +1 -16
  133. package/dist/runtime/server/instagram-embed.js +26 -125
  134. package/dist/runtime/server/proxy-handler.js +1 -2
  135. package/dist/runtime/server/utils/cached-upstream.d.ts +55 -0
  136. package/dist/runtime/server/utils/cached-upstream.js +65 -0
  137. package/dist/runtime/server/utils/embed-rewriters.d.ts +19 -0
  138. package/dist/runtime/server/utils/embed-rewriters.js +41 -0
  139. package/dist/runtime/server/utils/image-proxy.d.ts +3 -1
  140. package/dist/runtime/server/utils/image-proxy.js +11 -8
  141. package/dist/runtime/server/utils/instagram-embed.d.ts +16 -0
  142. package/dist/runtime/server/utils/instagram-embed.js +153 -0
  143. package/dist/runtime/server/utils/proxy-url.d.ts +9 -0
  144. package/dist/runtime/server/utils/proxy-url.js +21 -0
  145. package/dist/runtime/server/utils/sign-constants.d.ts +16 -0
  146. package/dist/runtime/server/utils/sign-constants.js +5 -0
  147. package/dist/runtime/server/utils/sign.d.ts +101 -0
  148. package/dist/runtime/server/utils/sign.js +91 -0
  149. package/dist/runtime/server/utils/withSigning.d.ts +23 -0
  150. package/dist/runtime/server/utils/withSigning.js +19 -0
  151. package/dist/runtime/server/x-embed-image.d.ts +1 -1
  152. package/dist/runtime/server/x-embed.js +23 -4
  153. package/dist/runtime/types.d.ts +41 -6
  154. package/dist/runtime/types.js +1 -0
  155. package/dist/stats.mjs +298 -338
  156. package/dist/types-source.mjs +537 -164
  157. package/dist/types.d.mts +2 -2
  158. package/package.json +10 -6
  159. package/dist/devtools-client/_nuxt/C8jhSQ8l.js +0 -1
  160. package/dist/devtools-client/_nuxt/CJD6wrkT.js +0 -188
  161. package/dist/devtools-client/_nuxt/builds/meta/b800a0be-5cab-4ea6-89e3-dd3a85690a73.json +0 -1
  162. package/dist/devtools-client/_nuxt/error-404.CvOVjXeC.css +0 -1
  163. package/dist/devtools-client/_nuxt/error-500.BIm53nmx.css +0 -1
  164. package/dist/devtools-client/_nuxt/index.CA-OpSj0.css +0 -1
@@ -1,18 +1,3 @@
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): 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 { $fetch } from "ofetch";
2
+ import { useRuntimeConfig } from "nitropack/runtime";
3
3
  import { ELEMENT_NODE, parse, renderSync, TEXT_NODE, walkSync } from "ultrahtml";
4
- export const RSRC_RE = /url\(\/rsrc\.php([^)]+)\)/g;
5
- export const AMP_RE = /&amp;/g;
6
- export const SCONTENT_RE = /https:\/\/scontent[^"'\s),]+\.cdninstagram\.com[^"'\s),]+/g;
7
- export const STATIC_CDN_RE = /https:\/\/static\.cdninstagram\.com[^"'\s),]+/g;
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
- export function proxyImageUrl(url, prefix = "/_scripts") {
19
- return `${prefix}/embed/instagram-image?url=${encodeURIComponent(url.replace(AMP_RE, "&"))}`;
20
- }
21
- export function proxyAssetUrl(url, prefix = "/_scripts") {
22
- return `${prefix}/embed/instagram-asset?url=${encodeURIComponent(url.replace(AMP_RE, "&"))}`;
23
- }
24
- export function rewriteUrl(url, prefix = "/_scripts") {
25
- try {
26
- const parsed = new URL(url);
27
- if (parsed.hostname === INSTAGRAM_ASSET_HOST)
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 function scopeCss(css, scopeSelector) {
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 $fetch(embedUrl, {
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) => $fetch(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(${prefix}/embed/instagram-asset?url=${encodeURIComponent(`https://static.cdninstagram.com/rsrc.php${path}`)})`
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 &amp; 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<any>>;
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 { $fetch } from "ofetch";
2
+ import { createCachedBinaryFetch } from "./cached-upstream.js";
3
+ import { withSigning } from "./withSigning.js";
3
4
  const AMP_RE = /&amp;/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
- return defineEventHandler(async (event) => {
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 response = await $fetch.raw(url, {
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 && response.status >= 300 && response.status < 400) {
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", response.headers.get("content-type") || contentType);
69
+ setHeader(event, "Content-Type", result.contentType || contentType);
67
70
  setHeader(event, "Cache-Control", `public, max-age=${cacheMaxAge}, s-maxage=${cacheMaxAge}`);
68
- return response._data;
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 = /&amp;/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;