@nuxt/scripts 1.0.0-rc.9 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/devtools-client/200.html +1 -1
- package/dist/devtools-client/404.html +1 -1
- package/dist/devtools-client/_nuxt/{BBS9G2Kb.js → Br5kvbNb.js} +1 -1
- package/dist/devtools-client/_nuxt/C25MBdR1.js +1 -0
- package/dist/devtools-client/_nuxt/{DCBsJT4N.js → Cg_OIb5q.js} +1 -1
- package/dist/devtools-client/_nuxt/{B4uHpJPz.js → D2o5loaz.js} +1 -1
- package/dist/devtools-client/_nuxt/De7Wf2b9.js +188 -0
- package/dist/devtools-client/_nuxt/{Cxq4HLPL.js → DnVCfhVR.js} +1 -1
- package/dist/devtools-client/_nuxt/builds/latest.json +1 -1
- package/dist/devtools-client/_nuxt/builds/meta/9d868e70-bc5a-425c-8c84-8defe5186920.json +1 -0
- package/dist/devtools-client/_nuxt/{entry.BwpOBArY.css → entry.BSxy0W1q.css} +1 -1
- package/dist/devtools-client/_nuxt/index.DZD1lwyI.css +1 -0
- package/dist/devtools-client/_nuxt/{DvZScWzI.js → pN4-T8ZD.js} +1 -1
- package/dist/devtools-client/docs/index.html +1 -1
- package/dist/devtools-client/first-party/index.html +1 -1
- package/dist/devtools-client/index.html +1 -1
- package/dist/devtools-client/registry/index.html +1 -1
- package/dist/module.d.mts +15 -0
- package/dist/module.d.ts +15 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +35 -8
- package/dist/registry.mjs +3 -3
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsStaticMap.vue +6 -2
- package/dist/runtime/components/ScriptBlueskyEmbed.d.vue.ts +0 -1
- package/dist/runtime/components/ScriptBlueskyEmbed.vue +12 -10
- package/dist/runtime/components/ScriptBlueskyEmbed.vue.d.ts +0 -1
- package/dist/runtime/components/ScriptInstagramEmbed.vue +3 -1
- package/dist/runtime/components/ScriptXEmbed.d.vue.ts +0 -1
- package/dist/runtime/components/ScriptXEmbed.vue +11 -9
- package/dist/runtime/components/ScriptXEmbed.vue.d.ts +0 -1
- 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 +6 -2
- package/dist/runtime/registry/bing-uet.js +13 -1
- 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 +22 -4
- package/dist/runtime/server/google-maps-geocode-proxy.js +8 -5
- package/dist/runtime/server/google-static-maps-proxy.d.ts +1 -1
- package/dist/runtime/server/google-static-maps-proxy.js +13 -8
- package/dist/runtime/server/gravatar-proxy.d.ts +1 -1
- package/dist/runtime/server/gravatar-proxy.js +6 -7
- 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.js +22 -10
- 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 +8 -6
- package/dist/runtime/server/utils/instagram-embed.d.ts +4 -4
- package/dist/runtime/server/utils/instagram-embed.js +10 -9
- 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 +2 -10
- package/dist/runtime/server/utils/sign.js +8 -5
- package/dist/runtime/server/utils/withSigning.js +3 -2
- package/dist/runtime/server/x-embed-image.d.ts +1 -1
- package/dist/runtime/server/x-embed.js +20 -2
- package/dist/runtime/types.d.ts +24 -1
- package/dist/types-source.mjs +104 -11
- package/package.json +2 -2
- package/dist/devtools-client/_nuxt/CQR4zIAm.js +0 -1
- package/dist/devtools-client/_nuxt/DTxy5P8N.js +0 -188
- package/dist/devtools-client/_nuxt/builds/meta/bd58b869-1eb5-4c50-871c-707f9b71e8f9.json +0 -1
- package/dist/devtools-client/_nuxt/index.CA-OpSj0.css +0 -1
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
import { any, array, boolean, custom, literal, minLength, number, object, optional, pipe, record, string, union } from "valibot";
|
|
2
|
+
const consentCategoryValue = union([literal("granted"), literal("denied")]);
|
|
3
|
+
const gcmConsentState = object({
|
|
4
|
+
ad_storage: optional(consentCategoryValue),
|
|
5
|
+
ad_user_data: optional(consentCategoryValue),
|
|
6
|
+
ad_personalization: optional(consentCategoryValue),
|
|
7
|
+
analytics_storage: optional(consentCategoryValue),
|
|
8
|
+
functionality_storage: optional(consentCategoryValue),
|
|
9
|
+
personalization_storage: optional(consentCategoryValue),
|
|
10
|
+
security_storage: optional(consentCategoryValue),
|
|
11
|
+
wait_for_update: optional(number()),
|
|
12
|
+
region: optional(array(string()))
|
|
13
|
+
});
|
|
2
14
|
export const BlueskyEmbedOptions = object({
|
|
3
15
|
/**
|
|
4
16
|
* The Bluesky post URL to embed.
|
|
@@ -21,7 +33,14 @@ export const ClarityOptions = object({
|
|
|
21
33
|
* The Clarity token.
|
|
22
34
|
* @see https://learn.microsoft.com/en-us/clarity/setup-clarity
|
|
23
35
|
*/
|
|
24
|
-
id: pipe(string(), minLength(10))
|
|
36
|
+
id: pipe(string(), minLength(10)),
|
|
37
|
+
/**
|
|
38
|
+
* Default consent state applied before Clarity starts.
|
|
39
|
+
* - `boolean` - enable / disable cookies.
|
|
40
|
+
* - `Record<string, string>` - advanced consent vector (see Clarity docs).
|
|
41
|
+
* @see https://learn.microsoft.com/en-us/clarity/setup-and-installation/cookie-consent
|
|
42
|
+
*/
|
|
43
|
+
defaultConsent: optional(union([boolean(), record(string(), string())]))
|
|
25
44
|
});
|
|
26
45
|
export const CloudflareWebAnalyticsOptions = object({
|
|
27
46
|
/**
|
|
@@ -264,7 +283,12 @@ export const GoogleAnalyticsOptions = object({
|
|
|
264
283
|
* @default 'dataLayer'
|
|
265
284
|
* @see https://developers.google.com/analytics/devguides/collection/gtagjs/setting-up-gtag#rename_the_data_layer
|
|
266
285
|
*/
|
|
267
|
-
l: optional(string())
|
|
286
|
+
l: optional(string()),
|
|
287
|
+
/**
|
|
288
|
+
* Default GCMv2 consent state fired as `gtag('consent', 'default', ...)` before `gtag('js', ...)`.
|
|
289
|
+
* @see https://developers.google.com/tag-platform/security/guides/consent
|
|
290
|
+
*/
|
|
291
|
+
defaultConsent: optional(gcmConsentState)
|
|
268
292
|
});
|
|
269
293
|
export const GoogleMapsOptions = object({
|
|
270
294
|
/**
|
|
@@ -527,14 +551,28 @@ export const MatomoAnalyticsOptions = object({
|
|
|
527
551
|
* Automatically track page views on route change.
|
|
528
552
|
* @default true
|
|
529
553
|
*/
|
|
530
|
-
watch: optional(boolean())
|
|
554
|
+
watch: optional(boolean()),
|
|
555
|
+
/**
|
|
556
|
+
* Default tracking-consent state applied BEFORE the tracker is initialised.
|
|
557
|
+
* - `'required'` — call `requireConsent` without granting (user must opt in later).
|
|
558
|
+
* - `'given'` — call `requireConsent` then `setConsentGiven`.
|
|
559
|
+
* - `'not-required'` — no consent gating (default Matomo behaviour).
|
|
560
|
+
* @see https://developer.matomo.org/guides/tracking-consent
|
|
561
|
+
*/
|
|
562
|
+
defaultConsent: optional(union([literal("required"), literal("given"), literal("not-required")]))
|
|
531
563
|
});
|
|
532
564
|
export const MetaPixelOptions = object({
|
|
533
565
|
/**
|
|
534
566
|
* Your Meta (Facebook) Pixel ID.
|
|
535
567
|
* @see https://developers.facebook.com/docs/meta-pixel/get-started
|
|
536
568
|
*/
|
|
537
|
-
id: union([string(), number()])
|
|
569
|
+
id: union([string(), number()]),
|
|
570
|
+
/**
|
|
571
|
+
* Default consent state. `'granted'` fires `fbq('consent', 'grant')`,
|
|
572
|
+
* `'denied'` fires `fbq('consent', 'revoke')`, both called before `fbq('init', id)`.
|
|
573
|
+
* @see https://www.facebook.com/business/help/1151321516677370
|
|
574
|
+
*/
|
|
575
|
+
defaultConsent: optional(union([literal("granted"), literal("denied")]))
|
|
538
576
|
});
|
|
539
577
|
export const NpmOptions = object({
|
|
540
578
|
/**
|
|
@@ -627,7 +665,14 @@ export const PostHogOptions = object({
|
|
|
627
665
|
* Additional PostHog configuration options passed directly to `posthog.init()`.
|
|
628
666
|
* @see https://posthog.com/docs/libraries/js#config
|
|
629
667
|
*/
|
|
630
|
-
config: optional(record(string(), any()))
|
|
668
|
+
config: optional(record(string(), any())),
|
|
669
|
+
/**
|
|
670
|
+
* Default capture-consent state for PostHog.
|
|
671
|
+
* - `'opt-out'`: passed as `opt_out_capturing_by_default: true` to `posthog.init`, so capturing is suppressed from the first event.
|
|
672
|
+
* - `'opt-in'`: applied after `posthog.init` via `posthog.opt_in_capturing()` on the returned instance.
|
|
673
|
+
* @see https://posthog.com/docs/privacy/opting-out
|
|
674
|
+
*/
|
|
675
|
+
defaultConsent: optional(union([literal("opt-in"), literal("opt-out")]))
|
|
631
676
|
});
|
|
632
677
|
export const RedditPixelOptions = object({
|
|
633
678
|
/**
|
|
@@ -700,7 +745,14 @@ export const MixpanelAnalyticsOptions = object({
|
|
|
700
745
|
* Your Mixpanel project token.
|
|
701
746
|
* @see https://docs.mixpanel.com/docs/tracking-methods/sdks/javascript#1-initialize-the-library
|
|
702
747
|
*/
|
|
703
|
-
token: string()
|
|
748
|
+
token: string(),
|
|
749
|
+
/**
|
|
750
|
+
* Default tracking-consent state for Mixpanel.
|
|
751
|
+
* - `'opt-out'`: passed as `opt_out_tracking_by_default: true` to `mixpanel.init`, so tracking is suppressed from the first call.
|
|
752
|
+
* - `'opt-in'`: queued via `mixpanel.push(['opt_in_tracking'])` so the real SDK runs it immediately after load.
|
|
753
|
+
* @see https://docs.mixpanel.com/docs/privacy/opt-out-of-tracking
|
|
754
|
+
*/
|
|
755
|
+
defaultConsent: optional(union([literal("opt-in"), literal("opt-out")]))
|
|
704
756
|
});
|
|
705
757
|
export const BingUetOptions = object({
|
|
706
758
|
/**
|
|
@@ -712,7 +764,14 @@ export const BingUetOptions = object({
|
|
|
712
764
|
* Enable automatic SPA page tracking.
|
|
713
765
|
* @default true
|
|
714
766
|
*/
|
|
715
|
-
enableAutoSpaTracking: optional(boolean())
|
|
767
|
+
enableAutoSpaTracking: optional(boolean()),
|
|
768
|
+
/**
|
|
769
|
+
* Default consent state fired as `uetq.push('consent', 'default', ...)` before UET init.
|
|
770
|
+
* @see https://help.ads.microsoft.com/#apex/ads/en/60119/1-500
|
|
771
|
+
*/
|
|
772
|
+
defaultConsent: optional(object({
|
|
773
|
+
ad_storage: optional(consentCategoryValue)
|
|
774
|
+
}))
|
|
716
775
|
});
|
|
717
776
|
export const SegmentOptions = object({
|
|
718
777
|
/**
|
|
@@ -807,7 +866,15 @@ export const TikTokPixelOptions = object({
|
|
|
807
866
|
* Whether to automatically track a page view on initialization.
|
|
808
867
|
* @default true
|
|
809
868
|
*/
|
|
810
|
-
trackPageView: optional(boolean())
|
|
869
|
+
trackPageView: optional(boolean()),
|
|
870
|
+
/**
|
|
871
|
+
* Default consent state, applied before `ttq('init', id)`.
|
|
872
|
+
* - `'granted'` fires `ttq.grantConsent()`
|
|
873
|
+
* - `'denied'` fires `ttq.revokeConsent()`
|
|
874
|
+
* - `'hold'` fires `ttq.holdConsent()` to defer until an explicit update
|
|
875
|
+
* @see https://business-api.tiktok.com/portal/docs?id=1739585600931842
|
|
876
|
+
*/
|
|
877
|
+
defaultConsent: optional(union([literal("granted"), literal("denied"), literal("hold")]))
|
|
811
878
|
});
|
|
812
879
|
export const UmamiAnalyticsOptions = object({
|
|
813
880
|
/**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { RegistryScriptInput } from '#nuxt-scripts/types';
|
|
1
|
+
import type { RegistryScriptInput, UseScriptContext } from '#nuxt-scripts/types';
|
|
2
2
|
import { TikTokPixelOptions } from './schemas.js';
|
|
3
3
|
type StandardEvents = 'ViewContent' | 'ClickButton' | 'Search' | 'AddToWishlist' | 'AddToCart' | 'InitiateCheckout' | 'AddPaymentInfo' | 'CompletePayment' | 'PlaceAnOrder' | 'Contact' | 'Download' | 'SubmitForm' | 'CompleteRegistration' | 'Subscribe';
|
|
4
4
|
interface EventProperties {
|
|
@@ -29,6 +29,12 @@ export interface TikTokPixelApi {
|
|
|
29
29
|
push: TtqFns;
|
|
30
30
|
loaded: boolean;
|
|
31
31
|
queue: any[];
|
|
32
|
+
/** Opt user in to tracking. Queued before the script loads; live once `events.js` binds. */
|
|
33
|
+
grantConsent: () => void;
|
|
34
|
+
/** Opt user out of tracking. Queued before the script loads; live once `events.js` binds. */
|
|
35
|
+
revokeConsent: () => void;
|
|
36
|
+
/** Defer consent until an explicit grant/revoke. Queued before the script loads; live once `events.js` binds. */
|
|
37
|
+
holdConsent: () => void;
|
|
32
38
|
};
|
|
33
39
|
}
|
|
34
40
|
declare global {
|
|
@@ -38,4 +44,12 @@ declare global {
|
|
|
38
44
|
}
|
|
39
45
|
export { TikTokPixelOptions };
|
|
40
46
|
export type TikTokPixelInput = RegistryScriptInput<typeof TikTokPixelOptions, true, false>;
|
|
41
|
-
export
|
|
47
|
+
export interface TikTokPixelConsent {
|
|
48
|
+
/** Call `ttq.grantConsent()`. */
|
|
49
|
+
grant: () => void;
|
|
50
|
+
/** Call `ttq.revokeConsent()`. */
|
|
51
|
+
revoke: () => void;
|
|
52
|
+
/** Call `ttq.holdConsent()` to defer the decision. */
|
|
53
|
+
hold: () => void;
|
|
54
|
+
}
|
|
55
|
+
export declare function useScriptTikTokPixel<T extends TikTokPixelApi>(_options?: TikTokPixelInput): UseScriptContext<T, TikTokPixelConsent>;
|
|
@@ -3,7 +3,7 @@ import { useRegistryScript } from "../utils.js";
|
|
|
3
3
|
import { TikTokPixelOptions } from "./schemas.js";
|
|
4
4
|
export { TikTokPixelOptions };
|
|
5
5
|
export function useScriptTikTokPixel(_options) {
|
|
6
|
-
|
|
6
|
+
const instance = useRegistryScript("tiktokPixel", (options) => ({
|
|
7
7
|
scriptInput: {
|
|
8
8
|
src: withQuery("https://analytics.tiktok.com/i18n/pixel/events.js", {
|
|
9
9
|
sdkid: options?.id,
|
|
@@ -29,6 +29,19 @@ export function useScriptTikTokPixel(_options) {
|
|
|
29
29
|
ttq.push = ttq;
|
|
30
30
|
ttq.loaded = true;
|
|
31
31
|
ttq.queue = [];
|
|
32
|
+
const consentMethods = ["grantConsent", "revokeConsent", "holdConsent"];
|
|
33
|
+
for (const name of consentMethods) {
|
|
34
|
+
;
|
|
35
|
+
ttq[name] = function(...params) {
|
|
36
|
+
ttq.queue.push([name, ...params]);
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
if (options?.defaultConsent === "granted")
|
|
40
|
+
ttq.grantConsent();
|
|
41
|
+
else if (options?.defaultConsent === "denied")
|
|
42
|
+
ttq.revokeConsent();
|
|
43
|
+
else if (options?.defaultConsent === "hold")
|
|
44
|
+
ttq.holdConsent();
|
|
32
45
|
if (options?.id) {
|
|
33
46
|
ttq("init", options.id);
|
|
34
47
|
if (options?.trackPageView !== false) {
|
|
@@ -37,4 +50,12 @@ export function useScriptTikTokPixel(_options) {
|
|
|
37
50
|
}
|
|
38
51
|
}
|
|
39
52
|
}), _options);
|
|
53
|
+
if (import.meta.client && !instance.consent) {
|
|
54
|
+
instance.consent = {
|
|
55
|
+
grant: () => instance.proxy.ttq.grantConsent(),
|
|
56
|
+
revoke: () => instance.proxy.ttq.revokeConsent(),
|
|
57
|
+
hold: () => instance.proxy.ttq.holdConsent()
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
return instance;
|
|
40
61
|
}
|
|
@@ -49,10 +49,6 @@ export interface XEmbedTweetData {
|
|
|
49
49
|
};
|
|
50
50
|
}
|
|
51
51
|
export type XEmbedInput = RegistryScriptInput<typeof XEmbedOptions, false, false>;
|
|
52
|
-
/**
|
|
53
|
-
* Proxy an X/Twitter image URL through the server
|
|
54
|
-
*/
|
|
55
|
-
export declare function proxyXImageUrl(url: string, proxyEndpoint?: string): string;
|
|
56
52
|
/**
|
|
57
53
|
* Format a tweet date for display
|
|
58
54
|
*/
|
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
import { XEmbedOptions } from "./schemas.js";
|
|
2
2
|
export { XEmbedOptions };
|
|
3
|
-
export function proxyXImageUrl(url, proxyEndpoint = "/_scripts/embed/x-image") {
|
|
4
|
-
const separator = proxyEndpoint.includes("?") ? "&" : "?";
|
|
5
|
-
return `${proxyEndpoint}${separator}url=${encodeURIComponent(url)}`;
|
|
6
|
-
}
|
|
7
3
|
export function formatTweetDate(dateString) {
|
|
8
4
|
const date = new Date(dateString);
|
|
9
5
|
const time = date.toLocaleString("en-US", {
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<
|
|
1
|
+
declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<Buffer<ArrayBufferLike>>>;
|
|
2
2
|
export default _default;
|
|
@@ -1,16 +1,2 @@
|
|
|
1
|
-
declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<
|
|
2
|
-
uri: string;
|
|
3
|
-
cid: string;
|
|
4
|
-
author: Record<string, any>;
|
|
5
|
-
record: Record<string, any>;
|
|
6
|
-
embed?: Record<string, any>;
|
|
7
|
-
likeCount: number;
|
|
8
|
-
repostCount: number;
|
|
9
|
-
replyCount: number;
|
|
10
|
-
quoteCount: number;
|
|
11
|
-
indexedAt: string;
|
|
12
|
-
labels: Array<{
|
|
13
|
-
val: string;
|
|
14
|
-
}>;
|
|
15
|
-
}>>;
|
|
1
|
+
declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<Record<string, any>>>;
|
|
16
2
|
export default _default;
|
|
@@ -1,7 +1,20 @@
|
|
|
1
1
|
import { createError, defineEventHandler, getQuery, setHeader } from "h3";
|
|
2
|
-
import {
|
|
2
|
+
import { useRuntimeConfig } from "nitropack/runtime";
|
|
3
|
+
import { createCachedJsonFetch } from "./utils/cached-upstream.js";
|
|
4
|
+
import { rewriteBlueskyPostImages } from "./utils/embed-rewriters.js";
|
|
3
5
|
import { withSigning } from "./utils/withSigning.js";
|
|
4
6
|
const BSKY_POST_URL_RE = /^https:\/\/bsky\.app\/profile\/([^/]+)\/post\/([^/?]+)$/;
|
|
7
|
+
const EMBED_BSKY_SUFFIX_RE = /\/embed\/bluesky$/;
|
|
8
|
+
const cachedProfileFetch = createCachedJsonFetch(
|
|
9
|
+
"nuxt-scripts-bsky-profile",
|
|
10
|
+
86400,
|
|
11
|
+
(url) => url
|
|
12
|
+
);
|
|
13
|
+
const cachedPostFetch = createCachedJsonFetch(
|
|
14
|
+
"nuxt-scripts-bsky-post",
|
|
15
|
+
600,
|
|
16
|
+
(url) => url
|
|
17
|
+
);
|
|
5
18
|
export default withSigning(defineEventHandler(async (event) => {
|
|
6
19
|
const query = getQuery(event);
|
|
7
20
|
const postUrl = query.url;
|
|
@@ -21,7 +34,7 @@ export default withSigning(defineEventHandler(async (event) => {
|
|
|
21
34
|
const [, actor, rkey] = match;
|
|
22
35
|
let did = actor;
|
|
23
36
|
if (!actor.startsWith("did:")) {
|
|
24
|
-
const profile = await
|
|
37
|
+
const profile = await cachedProfileFetch(
|
|
25
38
|
`https://public.api.bsky.app/xrpc/app.bsky.actor.getProfile?actor=${encodeURIComponent(actor)}`
|
|
26
39
|
).catch((error) => {
|
|
27
40
|
throw createError({
|
|
@@ -32,7 +45,7 @@ export default withSigning(defineEventHandler(async (event) => {
|
|
|
32
45
|
did = profile.did;
|
|
33
46
|
}
|
|
34
47
|
const uri = `at://${did}/app.bsky.feed.post/${rkey}`;
|
|
35
|
-
const response = await
|
|
48
|
+
const response = await cachedPostFetch(
|
|
36
49
|
`https://public.api.bsky.app/xrpc/app.bsky.feed.getPostThread?uri=${encodeURIComponent(uri)}&depth=0&parentHeight=0`
|
|
37
50
|
).catch((error) => {
|
|
38
51
|
throw createError({
|
|
@@ -46,7 +59,7 @@ export default withSigning(defineEventHandler(async (event) => {
|
|
|
46
59
|
statusMessage: "Post not found"
|
|
47
60
|
});
|
|
48
61
|
}
|
|
49
|
-
const post = response.thread.post;
|
|
62
|
+
const post = structuredClone(response.thread.post);
|
|
50
63
|
const hasOptOut = post.labels?.some((l) => l.val === "!no-unauthenticated") || post.author?.labels?.some((l) => l.val === "!no-unauthenticated");
|
|
51
64
|
if (hasOptOut) {
|
|
52
65
|
throw createError({
|
|
@@ -54,6 +67,11 @@ export default withSigning(defineEventHandler(async (event) => {
|
|
|
54
67
|
statusMessage: "Author has opted out of external embedding"
|
|
55
68
|
});
|
|
56
69
|
}
|
|
70
|
+
const handlerPath = event.path?.split("?")[0] || "";
|
|
71
|
+
const prefix = handlerPath.replace(EMBED_BSKY_SUFFIX_RE, "") || "/_scripts";
|
|
72
|
+
const imagePath = `${prefix}/embed/bluesky-image`;
|
|
73
|
+
const secret = useRuntimeConfig(event)["nuxt-scripts"]?.proxySecret;
|
|
74
|
+
rewriteBlueskyPostImages(post, imagePath, secret);
|
|
57
75
|
setHeader(event, "Content-Type", "application/json");
|
|
58
76
|
setHeader(event, "Cache-Control", "public, max-age=600, s-maxage=600");
|
|
59
77
|
return post;
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import { createError, defineEventHandler, getQuery, setHeader } from "h3";
|
|
2
2
|
import { useRuntimeConfig } from "nitropack/runtime";
|
|
3
|
-
import { $fetch } from "ofetch";
|
|
4
3
|
import { withQuery } from "ufo";
|
|
4
|
+
import { createCachedJsonFetch } from "./utils/cached-upstream.js";
|
|
5
5
|
import { withSigning } from "./utils/withSigning.js";
|
|
6
|
+
const cachedGeocodeFetch = createCachedJsonFetch(
|
|
7
|
+
"nuxt-scripts-geocode",
|
|
8
|
+
2592e3,
|
|
9
|
+
(url) => url
|
|
10
|
+
);
|
|
6
11
|
export default withSigning(defineEventHandler(async (event) => {
|
|
7
12
|
const runtimeConfig = useRuntimeConfig();
|
|
8
13
|
const privateConfig = runtimeConfig["nuxt-scripts"]?.googleMapsGeocodeProxy;
|
|
@@ -19,10 +24,8 @@ export default withSigning(defineEventHandler(async (event) => {
|
|
|
19
24
|
...safeQuery,
|
|
20
25
|
key: apiKey
|
|
21
26
|
});
|
|
22
|
-
const data = await
|
|
23
|
-
headers: {
|
|
24
|
-
"User-Agent": "Nuxt Scripts Google Geocode Proxy"
|
|
25
|
-
}
|
|
27
|
+
const data = await cachedGeocodeFetch(geocodeUrl, {
|
|
28
|
+
headers: { "User-Agent": "Nuxt Scripts Google Geocode Proxy" }
|
|
26
29
|
}).catch((error) => {
|
|
27
30
|
throw createError({
|
|
28
31
|
statusCode: error.statusCode || 500,
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<
|
|
1
|
+
declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<Buffer<ArrayBufferLike>>>;
|
|
2
2
|
export default _default;
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { createError, defineEventHandler, getQuery, setHeader } from "h3";
|
|
2
2
|
import { useRuntimeConfig } from "nitropack/runtime";
|
|
3
|
-
import { $fetch } from "ofetch";
|
|
4
3
|
import { withQuery } from "ufo";
|
|
4
|
+
import { createCachedBinaryFetch } from "./utils/cached-upstream.js";
|
|
5
|
+
import { PAGE_TOKEN_PARAM, PAGE_TOKEN_TS_PARAM, SIG_PARAM } from "./utils/sign-constants.js";
|
|
5
6
|
import { withSigning } from "./utils/withSigning.js";
|
|
7
|
+
const cachedMapFetch = createCachedBinaryFetch("nuxt-scripts-static-map", 604800);
|
|
8
|
+
const STRIP_PARAMS = /* @__PURE__ */ new Set([SIG_PARAM, PAGE_TOKEN_PARAM, PAGE_TOKEN_TS_PARAM, "key"]);
|
|
6
9
|
export default withSigning(defineEventHandler(async (event) => {
|
|
7
10
|
const runtimeConfig = useRuntimeConfig();
|
|
8
11
|
const publicConfig = runtimeConfig.public["nuxt-scripts"]?.googleStaticMapsProxy;
|
|
@@ -21,15 +24,17 @@ export default withSigning(defineEventHandler(async (event) => {
|
|
|
21
24
|
});
|
|
22
25
|
}
|
|
23
26
|
const query = getQuery(event);
|
|
24
|
-
const
|
|
27
|
+
const safeQuery = {};
|
|
28
|
+
for (const [k, v] of Object.entries(query)) {
|
|
29
|
+
if (!STRIP_PARAMS.has(k))
|
|
30
|
+
safeQuery[k] = v;
|
|
31
|
+
}
|
|
25
32
|
const googleMapsUrl = withQuery("https://maps.googleapis.com/maps/api/staticmap", {
|
|
26
33
|
...safeQuery,
|
|
27
34
|
key: apiKey
|
|
28
35
|
});
|
|
29
|
-
const
|
|
30
|
-
headers: {
|
|
31
|
-
"User-Agent": "Nuxt Scripts Google Static Maps Proxy"
|
|
32
|
-
}
|
|
36
|
+
const result = await cachedMapFetch(googleMapsUrl, {
|
|
37
|
+
headers: { "User-Agent": "Nuxt Scripts Google Static Maps Proxy" }
|
|
33
38
|
}).catch((error) => {
|
|
34
39
|
throw createError({
|
|
35
40
|
statusCode: error.statusCode || 500,
|
|
@@ -37,8 +42,8 @@ export default withSigning(defineEventHandler(async (event) => {
|
|
|
37
42
|
});
|
|
38
43
|
});
|
|
39
44
|
const cacheMaxAge = publicConfig.cacheMaxAge || 3600;
|
|
40
|
-
setHeader(event, "Content-Type",
|
|
45
|
+
setHeader(event, "Content-Type", result.contentType || "image/png");
|
|
41
46
|
setHeader(event, "Cache-Control", `public, max-age=${cacheMaxAge}, s-maxage=${cacheMaxAge}`);
|
|
42
47
|
setHeader(event, "Vary", "Accept-Encoding");
|
|
43
|
-
return
|
|
48
|
+
return result.body;
|
|
44
49
|
}));
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<
|
|
1
|
+
declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<Buffer<ArrayBufferLike>>>;
|
|
2
2
|
export default _default;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { createError, defineEventHandler, getQuery, setHeader } from "h3";
|
|
2
2
|
import { useRuntimeConfig } from "nitropack/runtime";
|
|
3
|
-
import { $fetch } from "ofetch";
|
|
4
3
|
import { withQuery } from "ufo";
|
|
4
|
+
import { createCachedBinaryFetch } from "./utils/cached-upstream.js";
|
|
5
5
|
import { withSigning } from "./utils/withSigning.js";
|
|
6
|
+
const cachedGravatarFetch = createCachedBinaryFetch("nuxt-scripts-gravatar", 3600);
|
|
6
7
|
export default withSigning(defineEventHandler(async (event) => {
|
|
7
8
|
const runtimeConfig = useRuntimeConfig();
|
|
8
9
|
const proxyConfig = runtimeConfig.public["nuxt-scripts"]?.gravatarProxy;
|
|
@@ -29,10 +30,8 @@ export default withSigning(defineEventHandler(async (event) => {
|
|
|
29
30
|
d: defaultImg,
|
|
30
31
|
r: rating
|
|
31
32
|
});
|
|
32
|
-
const
|
|
33
|
-
headers: {
|
|
34
|
-
"User-Agent": "Nuxt Scripts Gravatar Proxy"
|
|
35
|
-
}
|
|
33
|
+
const result = await cachedGravatarFetch(gravatarUrl, {
|
|
34
|
+
headers: { "User-Agent": "Nuxt Scripts Gravatar Proxy" }
|
|
36
35
|
}).catch((error) => {
|
|
37
36
|
throw createError({
|
|
38
37
|
statusCode: error.statusCode || 500,
|
|
@@ -40,8 +39,8 @@ export default withSigning(defineEventHandler(async (event) => {
|
|
|
40
39
|
});
|
|
41
40
|
});
|
|
42
41
|
const cacheMaxAge = proxyConfig?.cacheMaxAge ?? 3600;
|
|
43
|
-
setHeader(event, "Content-Type",
|
|
42
|
+
setHeader(event, "Content-Type", result.contentType || "image/jpeg");
|
|
44
43
|
setHeader(event, "Cache-Control", `public, max-age=${cacheMaxAge}, s-maxage=${cacheMaxAge}`);
|
|
45
44
|
setHeader(event, "Vary", "Accept-Encoding");
|
|
46
|
-
return
|
|
45
|
+
return result.body;
|
|
47
46
|
}));
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<
|
|
1
|
+
declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<Buffer<ArrayBufferLike>>>;
|
|
2
2
|
export default _default;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<
|
|
1
|
+
declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<Buffer<ArrayBufferLike>>>;
|
|
2
2
|
export default _default;
|
|
@@ -1,11 +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
|
-
import {
|
|
4
|
+
import { createCachedJsonFetch } from "./utils/cached-upstream.js";
|
|
5
|
+
import { proxyAssetUrl, rewriteUrl, rewriteUrlsInText, RSRC_RE, scopeCss } from "./utils/instagram-embed.js";
|
|
5
6
|
import { withSigning } from "./utils/withSigning.js";
|
|
6
7
|
export { proxyAssetUrl, proxyImageUrl, rewriteUrl, rewriteUrlsInText, scopeCss } from "./utils/instagram-embed.js";
|
|
7
8
|
const EMBED_INSTAGRAM_SUFFIX_RE = /\/embed\/instagram$/;
|
|
8
9
|
const SRCSET_SPLIT_RE = /\s+/;
|
|
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
|
+
);
|
|
9
20
|
function removeNode(node) {
|
|
10
21
|
node.type = TEXT_NODE;
|
|
11
22
|
node.value = "";
|
|
@@ -16,6 +27,7 @@ function removeNode(node) {
|
|
|
16
27
|
export default withSigning(defineEventHandler(async (event) => {
|
|
17
28
|
const handlerPath = event.path?.split("?")[0] || "";
|
|
18
29
|
const prefix = handlerPath.replace(EMBED_INSTAGRAM_SUFFIX_RE, "") || "/_scripts";
|
|
30
|
+
const secret = useRuntimeConfig(event)["nuxt-scripts"]?.proxySecret;
|
|
19
31
|
const query = getQuery(event);
|
|
20
32
|
const postUrl = query.url;
|
|
21
33
|
const captions = query.captions === "true";
|
|
@@ -43,7 +55,7 @@ export default withSigning(defineEventHandler(async (event) => {
|
|
|
43
55
|
const pathname = parsedUrl.pathname.endsWith("/") ? parsedUrl.pathname : `${parsedUrl.pathname}/`;
|
|
44
56
|
const cleanUrl = parsedUrl.origin + pathname;
|
|
45
57
|
const embedUrl = `${cleanUrl}embed/${captions ? "captioned/" : ""}`;
|
|
46
|
-
const html = await
|
|
58
|
+
const html = await cachedEmbedFetch(embedUrl, {
|
|
47
59
|
headers: {
|
|
48
60
|
"Accept": "text/html",
|
|
49
61
|
"User-Agent": "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
|
|
@@ -70,22 +82,22 @@ export default withSigning(defineEventHandler(async (event) => {
|
|
|
70
82
|
}
|
|
71
83
|
for (const attr of ["src", "poster"]) {
|
|
72
84
|
if (node.attributes[attr])
|
|
73
|
-
node.attributes[attr] = rewriteUrl(node.attributes[attr], prefix);
|
|
85
|
+
node.attributes[attr] = rewriteUrl(node.attributes[attr], prefix, secret);
|
|
74
86
|
}
|
|
75
87
|
if (node.attributes.srcset) {
|
|
76
88
|
node.attributes.srcset = node.attributes.srcset.split(",").map((entry) => {
|
|
77
89
|
const parts = entry.trim().split(SRCSET_SPLIT_RE);
|
|
78
90
|
const url = parts[0];
|
|
79
91
|
const descriptor = parts.slice(1).join(" ");
|
|
80
|
-
return url ? `${rewriteUrl(url, prefix)}${descriptor ? ` ${descriptor}` : ""}` : entry;
|
|
92
|
+
return url ? `${rewriteUrl(url, prefix, secret)}${descriptor ? ` ${descriptor}` : ""}` : entry;
|
|
81
93
|
}).join(", ");
|
|
82
94
|
}
|
|
83
95
|
if (node.attributes.style)
|
|
84
|
-
node.attributes.style = rewriteUrlsInText(node.attributes.style, prefix);
|
|
96
|
+
node.attributes.style = rewriteUrlsInText(node.attributes.style, prefix, secret);
|
|
85
97
|
});
|
|
86
98
|
walkSync(ast, (node) => {
|
|
87
99
|
if (node.type === TEXT_NODE && node.value)
|
|
88
|
-
node.value = rewriteUrlsInText(node.value, prefix);
|
|
100
|
+
node.value = rewriteUrlsInText(node.value, prefix, secret);
|
|
89
101
|
});
|
|
90
102
|
let bodyNode = null;
|
|
91
103
|
walkSync(ast, (node) => {
|
|
@@ -95,7 +107,7 @@ export default withSigning(defineEventHandler(async (event) => {
|
|
|
95
107
|
const bodyHtml = bodyNode ? bodyNode.children.map((child) => renderSync(child)).join("") : renderSync(ast);
|
|
96
108
|
const cssContents = await Promise.all(
|
|
97
109
|
cssUrls.map(
|
|
98
|
-
(url) =>
|
|
110
|
+
(url) => cachedCssFetch(url, {
|
|
99
111
|
headers: { Accept: "text/css" }
|
|
100
112
|
}).catch(() => "")
|
|
101
113
|
)
|
|
@@ -103,9 +115,9 @@ export default withSigning(defineEventHandler(async (event) => {
|
|
|
103
115
|
let combinedCss = cssContents.join("\n");
|
|
104
116
|
combinedCss = combinedCss.replace(
|
|
105
117
|
RSRC_RE,
|
|
106
|
-
(_m, path) => `url(${
|
|
118
|
+
(_m, path) => `url(${proxyAssetUrl(`https://static.cdninstagram.com/rsrc.php${path}`, prefix, secret)})`
|
|
107
119
|
);
|
|
108
|
-
combinedCss = rewriteUrlsInText(combinedCss, prefix);
|
|
120
|
+
combinedCss = rewriteUrlsInText(combinedCss, prefix, secret);
|
|
109
121
|
combinedCss = scopeCss(combinedCss, ".instagram-embed-root");
|
|
110
122
|
const baseStyles = `
|
|
111
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; }
|
|
@@ -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>;
|