@nuxt/scripts 0.13.2 → 1.0.0-beta.2
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/README.md +15 -0
- package/dist/client/200.html +1 -1
- package/dist/client/404.html +1 -1
- package/dist/client/_nuxt/B66N9HCo.js +1 -0
- package/dist/client/_nuxt/B8XOar-X.js +162 -0
- package/dist/client/_nuxt/{Bje-0OHL.js → DfLgoB--.js} +1 -1
- package/dist/client/_nuxt/DvH517bE.js +1 -0
- package/dist/client/_nuxt/builds/latest.json +1 -1
- package/dist/client/_nuxt/builds/meta/133a46c5-a5c1-4a63-87d1-037947a5bcdb.json +1 -0
- package/dist/client/_nuxt/entry.D45OuV0w.css +1 -0
- package/dist/client/_nuxt/error-404.B57D-jUQ.css +1 -0
- package/dist/client/_nuxt/error-500.DTHUW7BI.css +1 -0
- package/dist/client/index.html +1 -1
- package/dist/module.d.mts +106 -4
- package/dist/module.json +1 -1
- package/dist/module.mjs +705 -173
- package/dist/registry.mjs +63 -0
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMaps.d.vue.ts +29 -1
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMaps.vue +35 -10
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMaps.vue.d.ts +29 -1
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsMarkerClusterer.d.vue.ts +20 -8
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsMarkerClusterer.vue +2 -2
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsMarkerClusterer.vue.d.ts +20 -8
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsPinElement.vue +7 -1
- package/dist/runtime/components/ScriptCrisp.d.vue.ts +1 -1
- package/dist/runtime/components/ScriptCrisp.vue.d.ts +1 -1
- package/dist/runtime/components/ScriptInstagramEmbed.d.vue.ts +53 -0
- package/dist/runtime/components/ScriptInstagramEmbed.vue +38 -0
- package/dist/runtime/components/ScriptInstagramEmbed.vue.d.ts +53 -0
- package/dist/runtime/components/ScriptIntercom.d.vue.ts +1 -1
- package/dist/runtime/components/ScriptIntercom.vue.d.ts +1 -1
- package/dist/runtime/components/ScriptVimeoPlayer.d.vue.ts +2 -2
- package/dist/runtime/components/ScriptVimeoPlayer.vue.d.ts +2 -2
- package/dist/runtime/components/ScriptXEmbed.d.vue.ts +82 -0
- package/dist/runtime/components/ScriptXEmbed.vue +76 -0
- package/dist/runtime/components/ScriptXEmbed.vue.d.ts +82 -0
- package/dist/runtime/components/ScriptYouTubePlayer.d.vue.ts +12 -1
- package/dist/runtime/components/ScriptYouTubePlayer.vue +41 -16
- package/dist/runtime/components/ScriptYouTubePlayer.vue.d.ts +12 -1
- package/dist/runtime/composables/useScript.js +34 -3
- package/dist/runtime/composables/useScriptTriggerServiceWorker.d.ts +7 -0
- package/dist/runtime/composables/useScriptTriggerServiceWorker.js +39 -0
- package/dist/runtime/npm-script-stub.d.ts +20 -0
- package/dist/runtime/npm-script-stub.js +73 -0
- package/dist/runtime/plugins/sw-register.client.d.ts +2 -0
- package/dist/runtime/plugins/sw-register.client.js +12 -0
- package/dist/runtime/registry/google-recaptcha.d.ts +27 -0
- package/dist/runtime/registry/google-recaptcha.js +45 -0
- package/dist/runtime/registry/google-sign-in.d.ts +84 -0
- package/dist/runtime/registry/google-sign-in.js +50 -0
- package/dist/runtime/registry/google-tag-manager.d.ts +3 -1
- package/dist/runtime/registry/google-tag-manager.js +15 -5
- package/dist/runtime/registry/instagram-embed.d.ts +23 -0
- package/dist/runtime/registry/instagram-embed.js +22 -0
- package/dist/runtime/registry/lemon-squeezy.d.ts +0 -1
- package/dist/runtime/registry/matomo-analytics.js +1 -1
- package/dist/runtime/registry/plausible-analytics.js +8 -6
- package/dist/runtime/registry/posthog.d.ts +26 -0
- package/dist/runtime/registry/posthog.js +92 -0
- package/dist/runtime/registry/rybbit-analytics.js +38 -8
- package/dist/runtime/registry/tiktok-pixel.d.ts +44 -0
- package/dist/runtime/registry/tiktok-pixel.js +44 -0
- package/dist/runtime/registry/x-embed.d.ts +77 -0
- package/dist/runtime/registry/x-embed.js +41 -0
- package/dist/runtime/server/google-static-maps-proxy.d.ts +2 -0
- package/dist/runtime/server/google-static-maps-proxy.js +54 -0
- package/dist/runtime/server/instagram-embed-asset.d.ts +2 -0
- package/dist/runtime/server/instagram-embed-asset.js +42 -0
- package/dist/runtime/server/instagram-embed-image.d.ts +2 -0
- package/dist/runtime/server/instagram-embed-image.js +54 -0
- package/dist/runtime/server/instagram-embed.d.ts +2 -0
- package/dist/runtime/server/instagram-embed.js +91 -0
- package/dist/runtime/server/proxy-handler.d.ts +6 -0
- package/dist/runtime/server/proxy-handler.js +230 -0
- package/dist/runtime/server/sw-handler.d.ts +2 -0
- package/dist/runtime/server/sw-handler.js +25 -0
- package/dist/runtime/server/utils/privacy.d.ts +97 -0
- package/dist/runtime/server/utils/privacy.js +268 -0
- package/dist/runtime/server/x-embed-image.d.ts +2 -0
- package/dist/runtime/server/x-embed-image.js +53 -0
- package/dist/runtime/server/x-embed.d.ts +49 -0
- package/dist/runtime/server/x-embed.js +31 -0
- package/dist/runtime/sw/proxy-sw.template.d.ts +1 -0
- package/dist/runtime/sw/proxy-sw.template.js +54 -0
- package/dist/runtime/types.d.ts +42 -1
- package/dist/runtime/utils/pure.d.ts +13 -0
- package/dist/runtime/utils/pure.js +67 -0
- package/dist/runtime/utils.d.ts +3 -2
- package/dist/runtime/utils.js +11 -1
- package/dist/types.d.mts +1 -1
- package/package.json +39 -32
- package/dist/client/_nuxt/DMut0W-e.js +0 -162
- package/dist/client/_nuxt/builds/meta/5e0206fe-a683-423c-8d59-2596d0b16fee.json +0 -1
- package/dist/client/_nuxt/entry.BjfcJo5q.css +0 -1
- package/dist/client/_nuxt/error-404.B0ZhSNwd.css +0 -1
- package/dist/client/_nuxt/error-500.D4MdgPaC.css +0 -1
- package/dist/client/_nuxt/iNmKC7TZ.js +0 -1
- package/dist/client/_nuxt/rttsH3SL.js +0 -1
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { RegistryScriptInput } from '#nuxt-scripts/types';
|
|
2
|
+
type StandardEvents = 'ViewContent' | 'ClickButton' | 'Search' | 'AddToWishlist' | 'AddToCart' | 'InitiateCheckout' | 'AddPaymentInfo' | 'CompletePayment' | 'PlaceAnOrder' | 'Contact' | 'Download' | 'SubmitForm' | 'CompleteRegistration' | 'Subscribe';
|
|
3
|
+
interface EventProperties {
|
|
4
|
+
content_id?: string;
|
|
5
|
+
content_type?: string;
|
|
6
|
+
content_name?: string;
|
|
7
|
+
contents?: Array<{
|
|
8
|
+
content_id: string;
|
|
9
|
+
content_type?: string;
|
|
10
|
+
content_name?: string;
|
|
11
|
+
price?: number;
|
|
12
|
+
quantity?: number;
|
|
13
|
+
}>;
|
|
14
|
+
currency?: string;
|
|
15
|
+
value?: number;
|
|
16
|
+
description?: string;
|
|
17
|
+
query?: string;
|
|
18
|
+
[key: string]: any;
|
|
19
|
+
}
|
|
20
|
+
interface IdentifyProperties {
|
|
21
|
+
email?: string;
|
|
22
|
+
phone_number?: string;
|
|
23
|
+
external_id?: string;
|
|
24
|
+
}
|
|
25
|
+
type TtqFns = ((cmd: 'track', event: StandardEvents | string, properties?: EventProperties) => void) & ((cmd: 'page') => void) & ((cmd: 'identify', properties: IdentifyProperties) => void) & ((cmd: string, ...args: any[]) => void);
|
|
26
|
+
export interface TikTokPixelApi {
|
|
27
|
+
ttq: TtqFns & {
|
|
28
|
+
push: TtqFns;
|
|
29
|
+
loaded: boolean;
|
|
30
|
+
queue: any[];
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
declare global {
|
|
34
|
+
interface Window extends TikTokPixelApi {
|
|
35
|
+
TiktokAnalyticsObject: string;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export declare const TikTokPixelOptions: import("valibot").ObjectSchema<{
|
|
39
|
+
readonly id: import("valibot").StringSchema<undefined>;
|
|
40
|
+
readonly trackPageView: import("valibot").OptionalSchema<import("valibot").BooleanSchema<undefined>, undefined>;
|
|
41
|
+
}, undefined>;
|
|
42
|
+
export type TikTokPixelInput = RegistryScriptInput<typeof TikTokPixelOptions, true, false, false>;
|
|
43
|
+
export declare function useScriptTikTokPixel<T extends TikTokPixelApi>(_options?: TikTokPixelInput): import("#nuxt-scripts/types").UseScriptContext<T>;
|
|
44
|
+
export {};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { withQuery } from "ufo";
|
|
2
|
+
import { useRegistryScript } from "../utils.js";
|
|
3
|
+
import { object, string, optional, boolean } from "#nuxt-scripts-validator";
|
|
4
|
+
export const TikTokPixelOptions = object({
|
|
5
|
+
id: string(),
|
|
6
|
+
trackPageView: optional(boolean())
|
|
7
|
+
// default true
|
|
8
|
+
});
|
|
9
|
+
export function useScriptTikTokPixel(_options) {
|
|
10
|
+
return useRegistryScript("tiktokPixel", (options) => ({
|
|
11
|
+
scriptInput: {
|
|
12
|
+
src: withQuery("https://analytics.tiktok.com/i18n/pixel/events.js", {
|
|
13
|
+
sdkid: options?.id,
|
|
14
|
+
lib: "ttq"
|
|
15
|
+
}),
|
|
16
|
+
crossorigin: false
|
|
17
|
+
},
|
|
18
|
+
schema: import.meta.dev ? TikTokPixelOptions : void 0,
|
|
19
|
+
scriptOptions: {
|
|
20
|
+
use() {
|
|
21
|
+
return { ttq: window.ttq };
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
clientInit: import.meta.server ? void 0 : () => {
|
|
25
|
+
window.TiktokAnalyticsObject = "ttq";
|
|
26
|
+
const ttq = window.ttq = function(...params) {
|
|
27
|
+
if (ttq.callMethod) {
|
|
28
|
+
ttq.callMethod(...params);
|
|
29
|
+
} else {
|
|
30
|
+
ttq.queue.push(params);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
ttq.push = ttq;
|
|
34
|
+
ttq.loaded = true;
|
|
35
|
+
ttq.queue = [];
|
|
36
|
+
if (options?.id) {
|
|
37
|
+
ttq("init", options.id);
|
|
38
|
+
if (options?.trackPageView !== false) {
|
|
39
|
+
ttq("page");
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}), _options);
|
|
44
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { RegistryScriptInput } from '#nuxt-scripts/types';
|
|
2
|
+
export interface XEmbedTweetData {
|
|
3
|
+
id_str: string;
|
|
4
|
+
text: string;
|
|
5
|
+
created_at: string;
|
|
6
|
+
favorite_count: number;
|
|
7
|
+
conversation_count: number;
|
|
8
|
+
user: {
|
|
9
|
+
name: string;
|
|
10
|
+
screen_name: string;
|
|
11
|
+
profile_image_url_https: string;
|
|
12
|
+
verified?: boolean;
|
|
13
|
+
is_blue_verified?: boolean;
|
|
14
|
+
};
|
|
15
|
+
entities?: {
|
|
16
|
+
media?: Array<{
|
|
17
|
+
media_url_https: string;
|
|
18
|
+
type: string;
|
|
19
|
+
sizes: Record<string, {
|
|
20
|
+
w: number;
|
|
21
|
+
h: number;
|
|
22
|
+
}>;
|
|
23
|
+
}>;
|
|
24
|
+
urls?: Array<{
|
|
25
|
+
url: string;
|
|
26
|
+
expanded_url: string;
|
|
27
|
+
display_url: string;
|
|
28
|
+
}>;
|
|
29
|
+
};
|
|
30
|
+
photos?: Array<{
|
|
31
|
+
url: string;
|
|
32
|
+
width: number;
|
|
33
|
+
height: number;
|
|
34
|
+
}>;
|
|
35
|
+
video?: {
|
|
36
|
+
poster: string;
|
|
37
|
+
variants: Array<{
|
|
38
|
+
type: string;
|
|
39
|
+
src: string;
|
|
40
|
+
}>;
|
|
41
|
+
};
|
|
42
|
+
quoted_tweet?: XEmbedTweetData;
|
|
43
|
+
parent?: {
|
|
44
|
+
user: {
|
|
45
|
+
screen_name: string;
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
export declare const XEmbedOptions: import("valibot").ObjectSchema<{
|
|
50
|
+
/**
|
|
51
|
+
* The tweet ID to embed
|
|
52
|
+
*/
|
|
53
|
+
readonly tweetId: import("valibot").StringSchema<undefined>;
|
|
54
|
+
/**
|
|
55
|
+
* Optional: Custom API endpoint for fetching tweet data
|
|
56
|
+
* @default '/api/_scripts/x-embed'
|
|
57
|
+
*/
|
|
58
|
+
readonly apiEndpoint: import("valibot").OptionalSchema<import("valibot").StringSchema<undefined>, undefined>;
|
|
59
|
+
/**
|
|
60
|
+
* Optional: Custom image proxy endpoint
|
|
61
|
+
* @default '/api/_scripts/x-embed-image'
|
|
62
|
+
*/
|
|
63
|
+
readonly imageProxyEndpoint: import("valibot").OptionalSchema<import("valibot").StringSchema<undefined>, undefined>;
|
|
64
|
+
}, undefined>;
|
|
65
|
+
export type XEmbedInput = RegistryScriptInput<typeof XEmbedOptions, false, false, false>;
|
|
66
|
+
/**
|
|
67
|
+
* Proxy an X/Twitter image URL through the server
|
|
68
|
+
*/
|
|
69
|
+
export declare function proxyXImageUrl(url: string, proxyEndpoint?: string): string;
|
|
70
|
+
/**
|
|
71
|
+
* Format a tweet date for display
|
|
72
|
+
*/
|
|
73
|
+
export declare function formatTweetDate(dateString: string): string;
|
|
74
|
+
/**
|
|
75
|
+
* Format a number for display (e.g., 1234 -> 1.2K)
|
|
76
|
+
*/
|
|
77
|
+
export declare function formatCount(count: number): string;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { object, optional, string } from "#nuxt-scripts-validator";
|
|
2
|
+
export const XEmbedOptions = object({
|
|
3
|
+
/**
|
|
4
|
+
* The tweet ID to embed
|
|
5
|
+
*/
|
|
6
|
+
tweetId: string(),
|
|
7
|
+
/**
|
|
8
|
+
* Optional: Custom API endpoint for fetching tweet data
|
|
9
|
+
* @default '/api/_scripts/x-embed'
|
|
10
|
+
*/
|
|
11
|
+
apiEndpoint: optional(string()),
|
|
12
|
+
/**
|
|
13
|
+
* Optional: Custom image proxy endpoint
|
|
14
|
+
* @default '/api/_scripts/x-embed-image'
|
|
15
|
+
*/
|
|
16
|
+
imageProxyEndpoint: optional(string())
|
|
17
|
+
});
|
|
18
|
+
export function proxyXImageUrl(url, proxyEndpoint = "/api/_scripts/x-embed-image") {
|
|
19
|
+
const separator = proxyEndpoint.includes("?") ? "&" : "?";
|
|
20
|
+
return `${proxyEndpoint}${separator}url=${encodeURIComponent(url)}`;
|
|
21
|
+
}
|
|
22
|
+
export function formatTweetDate(dateString) {
|
|
23
|
+
const date = new Date(dateString);
|
|
24
|
+
const time = date.toLocaleString("en-US", {
|
|
25
|
+
hour: "numeric",
|
|
26
|
+
minute: "numeric",
|
|
27
|
+
hour12: true,
|
|
28
|
+
timeZone: "UTC"
|
|
29
|
+
});
|
|
30
|
+
const day = date.toLocaleString("en-US", { month: "short", timeZone: "UTC" });
|
|
31
|
+
return `${time} \xB7 ${day} ${date.getUTCDate()}, ${date.getUTCFullYear()}`;
|
|
32
|
+
}
|
|
33
|
+
export function formatCount(count) {
|
|
34
|
+
if (count >= 1e6) {
|
|
35
|
+
return `${(count / 1e6).toFixed(1)}M`;
|
|
36
|
+
}
|
|
37
|
+
if (count >= 1e3) {
|
|
38
|
+
return `${(count / 1e3).toFixed(1)}K`;
|
|
39
|
+
}
|
|
40
|
+
return count.toString();
|
|
41
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { createError, defineEventHandler, getHeader, getQuery, setHeader } from "h3";
|
|
2
|
+
import { $fetch } from "ofetch";
|
|
3
|
+
import { withQuery } from "ufo";
|
|
4
|
+
import { useRuntimeConfig } from "#imports";
|
|
5
|
+
export default defineEventHandler(async (event) => {
|
|
6
|
+
const runtimeConfig = useRuntimeConfig();
|
|
7
|
+
const publicConfig = runtimeConfig.public["nuxt-scripts"]?.googleStaticMapsProxy;
|
|
8
|
+
const privateConfig = runtimeConfig["nuxt-scripts"]?.googleStaticMapsProxy;
|
|
9
|
+
if (!publicConfig?.enabled) {
|
|
10
|
+
throw createError({
|
|
11
|
+
statusCode: 404,
|
|
12
|
+
statusMessage: "Google Static Maps proxy is not enabled"
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
const apiKey = privateConfig?.apiKey;
|
|
16
|
+
if (!apiKey) {
|
|
17
|
+
throw createError({
|
|
18
|
+
statusCode: 500,
|
|
19
|
+
statusMessage: "Google Maps API key not configured for proxy"
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
const referer = getHeader(event, "referer");
|
|
23
|
+
const host = getHeader(event, "host");
|
|
24
|
+
if (referer && host) {
|
|
25
|
+
const refererUrl = new URL(referer).host;
|
|
26
|
+
if (refererUrl !== host) {
|
|
27
|
+
throw createError({
|
|
28
|
+
statusCode: 403,
|
|
29
|
+
statusMessage: "Invalid referer"
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const query = getQuery(event);
|
|
34
|
+
const { key: _clientKey, ...safeQuery } = query;
|
|
35
|
+
const googleMapsUrl = withQuery("https://maps.googleapis.com/maps/api/staticmap", {
|
|
36
|
+
...safeQuery,
|
|
37
|
+
key: apiKey
|
|
38
|
+
});
|
|
39
|
+
const response = await $fetch.raw(googleMapsUrl, {
|
|
40
|
+
headers: {
|
|
41
|
+
"User-Agent": "Nuxt Scripts Google Static Maps Proxy"
|
|
42
|
+
}
|
|
43
|
+
}).catch((error) => {
|
|
44
|
+
throw createError({
|
|
45
|
+
statusCode: error.statusCode || 500,
|
|
46
|
+
statusMessage: error.statusMessage || "Failed to fetch static map"
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
const cacheMaxAge = publicConfig.cacheMaxAge || 3600;
|
|
50
|
+
setHeader(event, "Content-Type", response.headers.get("content-type") || "image/png");
|
|
51
|
+
setHeader(event, "Cache-Control", `public, max-age=${cacheMaxAge}, s-maxage=${cacheMaxAge}`);
|
|
52
|
+
setHeader(event, "Vary", "Accept-Encoding");
|
|
53
|
+
return response._data;
|
|
54
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { createError, defineEventHandler, getQuery, setHeader } from "h3";
|
|
2
|
+
import { $fetch } from "ofetch";
|
|
3
|
+
export default defineEventHandler(async (event) => {
|
|
4
|
+
const query = getQuery(event);
|
|
5
|
+
const url = query.url?.replace(/&/g, "&");
|
|
6
|
+
if (!url) {
|
|
7
|
+
throw createError({
|
|
8
|
+
statusCode: 400,
|
|
9
|
+
statusMessage: "Asset URL is required"
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
let parsedUrl;
|
|
13
|
+
try {
|
|
14
|
+
parsedUrl = new URL(url);
|
|
15
|
+
} catch {
|
|
16
|
+
throw createError({
|
|
17
|
+
statusCode: 400,
|
|
18
|
+
statusMessage: "Invalid asset URL"
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
if (parsedUrl.hostname !== "static.cdninstagram.com") {
|
|
22
|
+
throw createError({
|
|
23
|
+
statusCode: 403,
|
|
24
|
+
statusMessage: "Domain not allowed"
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
const response = await $fetch.raw(url, {
|
|
28
|
+
headers: {
|
|
29
|
+
"Accept": "*/*",
|
|
30
|
+
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36"
|
|
31
|
+
}
|
|
32
|
+
}).catch((error) => {
|
|
33
|
+
throw createError({
|
|
34
|
+
statusCode: error.statusCode || 500,
|
|
35
|
+
statusMessage: error.statusMessage || "Failed to fetch asset"
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
const contentType = response.headers.get("content-type") || "application/octet-stream";
|
|
39
|
+
setHeader(event, "Content-Type", contentType);
|
|
40
|
+
setHeader(event, "Cache-Control", "public, max-age=86400, s-maxage=86400");
|
|
41
|
+
return response._data;
|
|
42
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { createError, defineEventHandler, getQuery, setHeader } from "h3";
|
|
2
|
+
import { $fetch } from "ofetch";
|
|
3
|
+
export default defineEventHandler(async (event) => {
|
|
4
|
+
const query = getQuery(event);
|
|
5
|
+
const url = query.url?.replace(/&/g, "&");
|
|
6
|
+
if (!url) {
|
|
7
|
+
throw createError({
|
|
8
|
+
statusCode: 400,
|
|
9
|
+
statusMessage: "Image URL is required"
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
let parsedUrl;
|
|
13
|
+
try {
|
|
14
|
+
parsedUrl = new URL(url);
|
|
15
|
+
} catch {
|
|
16
|
+
throw createError({
|
|
17
|
+
statusCode: 400,
|
|
18
|
+
statusMessage: "Invalid image URL"
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
if (parsedUrl.protocol !== "http:" && parsedUrl.protocol !== "https:") {
|
|
22
|
+
throw createError({
|
|
23
|
+
statusCode: 400,
|
|
24
|
+
statusMessage: "Invalid URL scheme"
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
if (!parsedUrl.hostname.endsWith(".cdninstagram.com") && parsedUrl.hostname !== "scontent.cdninstagram.com") {
|
|
28
|
+
throw createError({
|
|
29
|
+
statusCode: 403,
|
|
30
|
+
statusMessage: "Domain not allowed"
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
const response = await $fetch.raw(url, {
|
|
34
|
+
redirect: "manual",
|
|
35
|
+
headers: {
|
|
36
|
+
"Accept": "image/webp,image/jpeg,image/png,image/*,*/*;q=0.8",
|
|
37
|
+
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36"
|
|
38
|
+
}
|
|
39
|
+
}).catch((error) => {
|
|
40
|
+
throw createError({
|
|
41
|
+
statusCode: error.statusCode || 500,
|
|
42
|
+
statusMessage: error.statusMessage || "Failed to fetch image"
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
if (response.status >= 300 && response.status < 400) {
|
|
46
|
+
throw createError({
|
|
47
|
+
statusCode: 403,
|
|
48
|
+
statusMessage: "Redirects not allowed"
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
setHeader(event, "Content-Type", response.headers.get("content-type") || "image/jpeg");
|
|
52
|
+
setHeader(event, "Cache-Control", "public, max-age=3600, s-maxage=3600");
|
|
53
|
+
return response._data;
|
|
54
|
+
});
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { createError, defineEventHandler, getQuery, setHeader } from "h3";
|
|
2
|
+
import { $fetch } from "ofetch";
|
|
3
|
+
export default defineEventHandler(async (event) => {
|
|
4
|
+
const query = getQuery(event);
|
|
5
|
+
const postUrl = query.url;
|
|
6
|
+
const captions = query.captions === "true";
|
|
7
|
+
if (!postUrl) {
|
|
8
|
+
throw createError({
|
|
9
|
+
statusCode: 400,
|
|
10
|
+
statusMessage: "Post URL is required"
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
let parsedUrl;
|
|
14
|
+
try {
|
|
15
|
+
parsedUrl = new URL(postUrl);
|
|
16
|
+
} catch {
|
|
17
|
+
throw createError({
|
|
18
|
+
statusCode: 400,
|
|
19
|
+
statusMessage: "Invalid postUrl"
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
if (!["instagram.com", "www.instagram.com"].includes(parsedUrl.hostname)) {
|
|
23
|
+
throw createError({
|
|
24
|
+
statusCode: 400,
|
|
25
|
+
statusMessage: "Invalid Instagram URL"
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
const pathname = parsedUrl.pathname.endsWith("/") ? parsedUrl.pathname : `${parsedUrl.pathname}/`;
|
|
29
|
+
const cleanUrl = parsedUrl.origin + pathname;
|
|
30
|
+
const embedUrl = cleanUrl + "embed/" + (captions ? "captioned/" : "");
|
|
31
|
+
const html = await $fetch(embedUrl, {
|
|
32
|
+
headers: {
|
|
33
|
+
"Accept": "text/html",
|
|
34
|
+
// Use simple UA - full Chrome UA triggers JS-heavy version without static content
|
|
35
|
+
"User-Agent": "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
|
|
36
|
+
}
|
|
37
|
+
}).catch((error) => {
|
|
38
|
+
throw createError({
|
|
39
|
+
statusCode: error.statusCode || 500,
|
|
40
|
+
statusMessage: error.statusMessage || "Failed to fetch Instagram embed"
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
const cssUrls = [];
|
|
44
|
+
const linkRegex = /<link[^>]+rel=["']stylesheet["'][^>]+href=["']([^"']+)["'][^>]*>/gi;
|
|
45
|
+
let match;
|
|
46
|
+
while ((match = linkRegex.exec(html)) !== null) {
|
|
47
|
+
if (match[1])
|
|
48
|
+
cssUrls.push(match[1]);
|
|
49
|
+
}
|
|
50
|
+
const linkRegex2 = /<link[^>]+href=["']([^"']+)["'][^>]+rel=["']stylesheet["'][^>]*>/gi;
|
|
51
|
+
while ((match = linkRegex2.exec(html)) !== null) {
|
|
52
|
+
if (match[1])
|
|
53
|
+
cssUrls.push(match[1]);
|
|
54
|
+
}
|
|
55
|
+
const cssContents = await Promise.all(
|
|
56
|
+
cssUrls.map(
|
|
57
|
+
(url) => $fetch(url, {
|
|
58
|
+
headers: { Accept: "text/css" }
|
|
59
|
+
}).catch(() => "")
|
|
60
|
+
)
|
|
61
|
+
);
|
|
62
|
+
let combinedCss = cssContents.join("\n");
|
|
63
|
+
combinedCss = combinedCss.replace(
|
|
64
|
+
/url\(\/rsrc\.php([^)]+)\)/g,
|
|
65
|
+
(_m, path) => `url(/api/_scripts/instagram-embed-asset?url=${encodeURIComponent(`https://static.cdninstagram.com/rsrc.php${path}`)})`
|
|
66
|
+
);
|
|
67
|
+
const baseStyles = `
|
|
68
|
+
html { 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; }
|
|
69
|
+
#splash-screen { display: none !important; }
|
|
70
|
+
.Embed { opacity: 1 !important; visibility: visible !important; }
|
|
71
|
+
.EmbeddedMedia, .EmbeddedMediaImage { display: block !important; visibility: visible !important; }
|
|
72
|
+
`;
|
|
73
|
+
let rewrittenHtml = html.replace(/<script[\s\S]*?<\/script>/gi, "").replace(/<link[^>]+rel=["']stylesheet["'][^>]*>/gi, "").replace(/<link[^>]+href=["'][^"']+\.css[^"']*["'][^>]*>/gi, "").replace(/<noscript>[\s\S]*?<\/noscript>/gi, "").replace(
|
|
74
|
+
/https:\/\/scontent[^"'\s),]+\.cdninstagram\.com[^"'\s),]+/g,
|
|
75
|
+
(m) => `/api/_scripts/instagram-embed-image?url=${encodeURIComponent(m.replace(/&/g, "&"))}`
|
|
76
|
+
).replace(
|
|
77
|
+
/https:\/\/static\.cdninstagram\.com[^"'\s),]+/g,
|
|
78
|
+
(m) => `/api/_scripts/instagram-embed-asset?url=${encodeURIComponent(m.replace(/&/g, "&"))}`
|
|
79
|
+
).replace(
|
|
80
|
+
/https:\/\/lookaside\.instagram\.com[^"'\s),]+/g,
|
|
81
|
+
(m) => `/api/_scripts/instagram-embed-image?url=${encodeURIComponent(m.replace(/&/g, "&"))}`
|
|
82
|
+
);
|
|
83
|
+
rewrittenHtml = rewrittenHtml.replace(
|
|
84
|
+
"</head>",
|
|
85
|
+
`<style>${baseStyles}
|
|
86
|
+
${combinedCss}</style></head>`
|
|
87
|
+
);
|
|
88
|
+
setHeader(event, "Content-Type", "text/html");
|
|
89
|
+
setHeader(event, "Cache-Control", "public, max-age=600, s-maxage=600");
|
|
90
|
+
return rewrittenHtml;
|
|
91
|
+
});
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Privacy-aware proxy handler for first-party script collection endpoints.
|
|
3
|
+
* Routes requests to third-party analytics while protecting user privacy.
|
|
4
|
+
*/
|
|
5
|
+
declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<string | Buffer<ArrayBuffer>>>;
|
|
6
|
+
export default _default;
|