@feelflow/ffid-sdk 5.14.0 → 5.16.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 +4 -0
- package/dist/{FFIDCookieLink-D1WwE_ZO.d.cts → FFIDCookieLink-BJgVcJyw.d.cts} +53 -5
- package/dist/{FFIDCookieLink-D1WwE_ZO.d.ts → FFIDCookieLink-BJgVcJyw.d.ts} +53 -5
- package/dist/{chunk-4II4R7NR.js → chunk-C7PURRNI.js} +1 -1
- package/dist/{chunk-BVDUQQHP.cjs → chunk-FLLTPPUP.cjs} +1 -1
- package/dist/{chunk-M4N2PUX6.js → chunk-G7VOX64X.js} +45 -12
- package/dist/{chunk-R5WSZMUL.cjs → chunk-H5O2CCAY.cjs} +45 -12
- package/dist/components/index.cjs +8 -8
- package/dist/components/index.js +1 -1
- package/dist/consent/index.cjs +64 -64
- package/dist/consent/index.d.cts +2 -2
- package/dist/consent/index.d.ts +2 -2
- package/dist/consent/index.js +1 -1
- package/dist/index.cjs +62 -62
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +3 -3
- package/dist/server/index.cjs +1 -1
- package/dist/server/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -46,6 +46,10 @@ export default function RootLayout({ children }) {
|
|
|
46
46
|
baseUrl={process.env.NEXT_PUBLIC_FFID_BASE_URL!}
|
|
47
47
|
serviceApiKey={process.env.FFID_SERVICE_API_KEY!}
|
|
48
48
|
gaMeasurementId={process.env.NEXT_PUBLIC_GA_ID ?? null}
|
|
49
|
+
// Production-only measurement (v5.16.0+): suppress GA on
|
|
50
|
+
// preview/staging/local so non-prod traffic never pollutes the
|
|
51
|
+
// production GA4 property. Defaults to true if omitted.
|
|
52
|
+
gaEnabled={process.env.VERCEL_ENV === 'production'}
|
|
49
53
|
>
|
|
50
54
|
{children}
|
|
51
55
|
<FFIDCookieBanner />
|
|
@@ -393,13 +393,19 @@ interface FFIDConsentSyncResult {
|
|
|
393
393
|
/**
|
|
394
394
|
* Error code literal union for consent SDK failures.
|
|
395
395
|
*
|
|
396
|
-
* - The
|
|
397
|
-
*
|
|
396
|
+
* - The `cookie_consent_*` codes reserved by spec §8.2 correlate across
|
|
397
|
+
* SDK / FFID API / ops dashboards.
|
|
398
|
+
* - `cookie_consent_aborted` is an SDK-only code (NOT spec §8.2 reserved):
|
|
399
|
+
* the in-flight fetch was cancelled via an `AbortSignal` (e.g. component
|
|
400
|
+
* unmount or page navigation). It is a benign teardown, not a backend
|
|
401
|
+
* failure, so consumers should generally NOT surface it as an error — it is
|
|
402
|
+
* kept distinct from `cookie_consent_api_unreachable` precisely so the
|
|
403
|
+
* provider can swallow it instead of emitting Sentry noise (#3869).
|
|
398
404
|
* - `auth_failure` is added by the SDK to cover 401 / 403 responses; it is
|
|
399
405
|
* NOT part of spec §8.2 reserved codes — the SDK shares this code with the
|
|
400
406
|
* wider FFID auth surface so consumer error handlers can branch uniformly.
|
|
401
407
|
*/
|
|
402
|
-
declare const FFID_CONSENT_ERROR_CODES: readonly ["cookie_consent_api_unreachable", "cookie_consent_response_invalid", "cookie_consent_rate_limited", "cookie_consent_cookie_write_failed", "cookie_consent_parse_failed", "cookie_consent_sync_conflict", "cookie_consent_immutable_violation", "cookie_consent_policy_outdated", "cookie_consent_capture_failed", "cookie_consent_db_write_failed", "auth_failure"];
|
|
408
|
+
declare const FFID_CONSENT_ERROR_CODES: readonly ["cookie_consent_api_unreachable", "cookie_consent_aborted", "cookie_consent_response_invalid", "cookie_consent_rate_limited", "cookie_consent_cookie_write_failed", "cookie_consent_parse_failed", "cookie_consent_sync_conflict", "cookie_consent_immutable_violation", "cookie_consent_policy_outdated", "cookie_consent_capture_failed", "cookie_consent_db_write_failed", "auth_failure"];
|
|
403
409
|
type FFIDConsentErrorCode = (typeof FFID_CONSENT_ERROR_CODES)[number];
|
|
404
410
|
/**
|
|
405
411
|
* Error class for all consent-related failures surfaced through SDK callbacks
|
|
@@ -610,11 +616,31 @@ declare global {
|
|
|
610
616
|
interface Window {
|
|
611
617
|
dataLayer?: unknown[];
|
|
612
618
|
gtag?: (...args: unknown[]) => void;
|
|
619
|
+
/**
|
|
620
|
+
* Set once `consent default` has been pushed onto THIS window's
|
|
621
|
+
* `dataLayer`. The default is page-global and only needs to land once
|
|
622
|
+
* before GA loads, so the bridge dedups on the window — the same scope
|
|
623
|
+
* that owns `dataLayer`/`gtag` — rather than per bridge instance. This
|
|
624
|
+
* survives provider remounts and multiple bridge instances, which a
|
|
625
|
+
* per-React-ref guard cannot (it never sees the shared global dataLayer),
|
|
626
|
+
* fixing the ≈142× duplicate `consent default` pushes seen in prod (#3982).
|
|
627
|
+
*/
|
|
628
|
+
__ffidConsentDefaultApplied?: boolean;
|
|
613
629
|
}
|
|
614
630
|
}
|
|
615
631
|
interface GtagBridgeOptions {
|
|
616
632
|
/** GA Measurement ID, e.g., `G-XXXXXXX`. Empty/null = GA not wired. */
|
|
617
633
|
gaMeasurementId?: string | null;
|
|
634
|
+
/**
|
|
635
|
+
* Master switch for GA tag injection. When `false`, the bridge still manages
|
|
636
|
+
* Consent Mode dataLayer pushes (`setDefaultConsent` / `updateConsent`) but
|
|
637
|
+
* `applyGaInjection` never appends the `gtag.js` script — regardless of
|
|
638
|
+
* `gaMeasurementId` or analytics consent. Defaults to `true` (legacy
|
|
639
|
+
* behavior). Consumers gate this on their own environment so non-production
|
|
640
|
+
* builds don't pollute the production GA4 property (e.g.
|
|
641
|
+
* `gaEnabled={process.env.VERCEL_ENV === 'production'}`).
|
|
642
|
+
*/
|
|
643
|
+
gaEnabled?: boolean;
|
|
618
644
|
/** Override script src for testing. Default: official gtag URL. */
|
|
619
645
|
gtagScriptSrc?: string;
|
|
620
646
|
/** Document/window injection target (server-side stays no-op). */
|
|
@@ -641,7 +667,12 @@ declare function mapCategoriesToGtagParams(cats: FFIDConsentCategories): GtagCon
|
|
|
641
667
|
interface GtagBridge {
|
|
642
668
|
/**
|
|
643
669
|
* Push `consent default` with ALL_DENIED_EXCEPT_NECESSARY. Must be called
|
|
644
|
-
* **synchronously** before any GA script tag is added.
|
|
670
|
+
* **synchronously** before any GA script tag is added.
|
|
671
|
+
*
|
|
672
|
+
* Idempotent at the window scope: `consent default` is pushed at most once
|
|
673
|
+
* per window's `dataLayer`, no matter how many bridge instances call it
|
|
674
|
+
* (provider remount, a second provider, strict-mode double-mount). The
|
|
675
|
+
* default is page-global and only needs to land once before GA loads (#3982).
|
|
645
676
|
*/
|
|
646
677
|
setDefaultConsent(): void;
|
|
647
678
|
/**
|
|
@@ -703,6 +734,23 @@ interface FFIDAnalyticsProviderProps {
|
|
|
703
734
|
* avoiding the triple-state `string | null | undefined` anti-pattern.
|
|
704
735
|
*/
|
|
705
736
|
gaMeasurementId?: string | null;
|
|
737
|
+
/**
|
|
738
|
+
* Master switch for GA tag injection. When `false`, the provider still runs
|
|
739
|
+
* Consent Mode (default/update dataLayer pushes) but never injects the
|
|
740
|
+
* `gtag.js` script — regardless of `gaMeasurementId` or analytics consent.
|
|
741
|
+
* Defaults to `true` (legacy behavior, fully backward compatible).
|
|
742
|
+
*
|
|
743
|
+
* Gate this on your own deploy environment so non-production builds don't
|
|
744
|
+
* send hits to the production GA4 property, e.g.
|
|
745
|
+
* `gaEnabled={process.env.VERCEL_ENV === 'production'}`. Read the env signal
|
|
746
|
+
* server-side where possible (it is captured once at mount, not reactive).
|
|
747
|
+
*
|
|
748
|
+
* Prefer `gaEnabled={false}` over `gaMeasurementId={null}` when gating by
|
|
749
|
+
* environment: `gaEnabled={false}` keeps a stable measurement ID wired across
|
|
750
|
+
* builds and keeps Consent Mode pushes flowing, whereas `null` means "GA was
|
|
751
|
+
* never configured for this app".
|
|
752
|
+
*/
|
|
753
|
+
gaEnabled?: boolean;
|
|
706
754
|
/** When true, control Sentry session replay based on `functional` consent. */
|
|
707
755
|
sentryReplay?: boolean;
|
|
708
756
|
/** Sentry namespace override; defaults to `(window as any).Sentry`. */
|
|
@@ -751,7 +799,7 @@ interface FFIDAnalyticsProviderProps {
|
|
|
751
799
|
fetchImpl?: typeof fetch;
|
|
752
800
|
children: ReactNode;
|
|
753
801
|
}
|
|
754
|
-
declare function FFIDAnalyticsProvider({ baseUrl, serviceApiKey, getAccessToken, gaMeasurementId, sentryReplay, sentry, onConsentChange, onError, autoShowBanner, dismissalCooldownMinutes, mergeWarningMessages, fetchImpl, children, }: FFIDAnalyticsProviderProps): React.JSX.Element;
|
|
802
|
+
declare function FFIDAnalyticsProvider({ baseUrl, serviceApiKey, getAccessToken, gaMeasurementId, gaEnabled, sentryReplay, sentry, onConsentChange, onError, autoShowBanner, dismissalCooldownMinutes, mergeWarningMessages, fetchImpl, children, }: FFIDAnalyticsProviderProps): React.JSX.Element;
|
|
755
803
|
|
|
756
804
|
interface FFIDConsentContextValue {
|
|
757
805
|
/** Current consent state. Always non-null; "not yet decided" is encoded by `hasDecided=false`. */
|
|
@@ -393,13 +393,19 @@ interface FFIDConsentSyncResult {
|
|
|
393
393
|
/**
|
|
394
394
|
* Error code literal union for consent SDK failures.
|
|
395
395
|
*
|
|
396
|
-
* - The
|
|
397
|
-
*
|
|
396
|
+
* - The `cookie_consent_*` codes reserved by spec §8.2 correlate across
|
|
397
|
+
* SDK / FFID API / ops dashboards.
|
|
398
|
+
* - `cookie_consent_aborted` is an SDK-only code (NOT spec §8.2 reserved):
|
|
399
|
+
* the in-flight fetch was cancelled via an `AbortSignal` (e.g. component
|
|
400
|
+
* unmount or page navigation). It is a benign teardown, not a backend
|
|
401
|
+
* failure, so consumers should generally NOT surface it as an error — it is
|
|
402
|
+
* kept distinct from `cookie_consent_api_unreachable` precisely so the
|
|
403
|
+
* provider can swallow it instead of emitting Sentry noise (#3869).
|
|
398
404
|
* - `auth_failure` is added by the SDK to cover 401 / 403 responses; it is
|
|
399
405
|
* NOT part of spec §8.2 reserved codes — the SDK shares this code with the
|
|
400
406
|
* wider FFID auth surface so consumer error handlers can branch uniformly.
|
|
401
407
|
*/
|
|
402
|
-
declare const FFID_CONSENT_ERROR_CODES: readonly ["cookie_consent_api_unreachable", "cookie_consent_response_invalid", "cookie_consent_rate_limited", "cookie_consent_cookie_write_failed", "cookie_consent_parse_failed", "cookie_consent_sync_conflict", "cookie_consent_immutable_violation", "cookie_consent_policy_outdated", "cookie_consent_capture_failed", "cookie_consent_db_write_failed", "auth_failure"];
|
|
408
|
+
declare const FFID_CONSENT_ERROR_CODES: readonly ["cookie_consent_api_unreachable", "cookie_consent_aborted", "cookie_consent_response_invalid", "cookie_consent_rate_limited", "cookie_consent_cookie_write_failed", "cookie_consent_parse_failed", "cookie_consent_sync_conflict", "cookie_consent_immutable_violation", "cookie_consent_policy_outdated", "cookie_consent_capture_failed", "cookie_consent_db_write_failed", "auth_failure"];
|
|
403
409
|
type FFIDConsentErrorCode = (typeof FFID_CONSENT_ERROR_CODES)[number];
|
|
404
410
|
/**
|
|
405
411
|
* Error class for all consent-related failures surfaced through SDK callbacks
|
|
@@ -610,11 +616,31 @@ declare global {
|
|
|
610
616
|
interface Window {
|
|
611
617
|
dataLayer?: unknown[];
|
|
612
618
|
gtag?: (...args: unknown[]) => void;
|
|
619
|
+
/**
|
|
620
|
+
* Set once `consent default` has been pushed onto THIS window's
|
|
621
|
+
* `dataLayer`. The default is page-global and only needs to land once
|
|
622
|
+
* before GA loads, so the bridge dedups on the window — the same scope
|
|
623
|
+
* that owns `dataLayer`/`gtag` — rather than per bridge instance. This
|
|
624
|
+
* survives provider remounts and multiple bridge instances, which a
|
|
625
|
+
* per-React-ref guard cannot (it never sees the shared global dataLayer),
|
|
626
|
+
* fixing the ≈142× duplicate `consent default` pushes seen in prod (#3982).
|
|
627
|
+
*/
|
|
628
|
+
__ffidConsentDefaultApplied?: boolean;
|
|
613
629
|
}
|
|
614
630
|
}
|
|
615
631
|
interface GtagBridgeOptions {
|
|
616
632
|
/** GA Measurement ID, e.g., `G-XXXXXXX`. Empty/null = GA not wired. */
|
|
617
633
|
gaMeasurementId?: string | null;
|
|
634
|
+
/**
|
|
635
|
+
* Master switch for GA tag injection. When `false`, the bridge still manages
|
|
636
|
+
* Consent Mode dataLayer pushes (`setDefaultConsent` / `updateConsent`) but
|
|
637
|
+
* `applyGaInjection` never appends the `gtag.js` script — regardless of
|
|
638
|
+
* `gaMeasurementId` or analytics consent. Defaults to `true` (legacy
|
|
639
|
+
* behavior). Consumers gate this on their own environment so non-production
|
|
640
|
+
* builds don't pollute the production GA4 property (e.g.
|
|
641
|
+
* `gaEnabled={process.env.VERCEL_ENV === 'production'}`).
|
|
642
|
+
*/
|
|
643
|
+
gaEnabled?: boolean;
|
|
618
644
|
/** Override script src for testing. Default: official gtag URL. */
|
|
619
645
|
gtagScriptSrc?: string;
|
|
620
646
|
/** Document/window injection target (server-side stays no-op). */
|
|
@@ -641,7 +667,12 @@ declare function mapCategoriesToGtagParams(cats: FFIDConsentCategories): GtagCon
|
|
|
641
667
|
interface GtagBridge {
|
|
642
668
|
/**
|
|
643
669
|
* Push `consent default` with ALL_DENIED_EXCEPT_NECESSARY. Must be called
|
|
644
|
-
* **synchronously** before any GA script tag is added.
|
|
670
|
+
* **synchronously** before any GA script tag is added.
|
|
671
|
+
*
|
|
672
|
+
* Idempotent at the window scope: `consent default` is pushed at most once
|
|
673
|
+
* per window's `dataLayer`, no matter how many bridge instances call it
|
|
674
|
+
* (provider remount, a second provider, strict-mode double-mount). The
|
|
675
|
+
* default is page-global and only needs to land once before GA loads (#3982).
|
|
645
676
|
*/
|
|
646
677
|
setDefaultConsent(): void;
|
|
647
678
|
/**
|
|
@@ -703,6 +734,23 @@ interface FFIDAnalyticsProviderProps {
|
|
|
703
734
|
* avoiding the triple-state `string | null | undefined` anti-pattern.
|
|
704
735
|
*/
|
|
705
736
|
gaMeasurementId?: string | null;
|
|
737
|
+
/**
|
|
738
|
+
* Master switch for GA tag injection. When `false`, the provider still runs
|
|
739
|
+
* Consent Mode (default/update dataLayer pushes) but never injects the
|
|
740
|
+
* `gtag.js` script — regardless of `gaMeasurementId` or analytics consent.
|
|
741
|
+
* Defaults to `true` (legacy behavior, fully backward compatible).
|
|
742
|
+
*
|
|
743
|
+
* Gate this on your own deploy environment so non-production builds don't
|
|
744
|
+
* send hits to the production GA4 property, e.g.
|
|
745
|
+
* `gaEnabled={process.env.VERCEL_ENV === 'production'}`. Read the env signal
|
|
746
|
+
* server-side where possible (it is captured once at mount, not reactive).
|
|
747
|
+
*
|
|
748
|
+
* Prefer `gaEnabled={false}` over `gaMeasurementId={null}` when gating by
|
|
749
|
+
* environment: `gaEnabled={false}` keeps a stable measurement ID wired across
|
|
750
|
+
* builds and keeps Consent Mode pushes flowing, whereas `null` means "GA was
|
|
751
|
+
* never configured for this app".
|
|
752
|
+
*/
|
|
753
|
+
gaEnabled?: boolean;
|
|
706
754
|
/** When true, control Sentry session replay based on `functional` consent. */
|
|
707
755
|
sentryReplay?: boolean;
|
|
708
756
|
/** Sentry namespace override; defaults to `(window as any).Sentry`. */
|
|
@@ -751,7 +799,7 @@ interface FFIDAnalyticsProviderProps {
|
|
|
751
799
|
fetchImpl?: typeof fetch;
|
|
752
800
|
children: ReactNode;
|
|
753
801
|
}
|
|
754
|
-
declare function FFIDAnalyticsProvider({ baseUrl, serviceApiKey, getAccessToken, gaMeasurementId, sentryReplay, sentry, onConsentChange, onError, autoShowBanner, dismissalCooldownMinutes, mergeWarningMessages, fetchImpl, children, }: FFIDAnalyticsProviderProps): React.JSX.Element;
|
|
802
|
+
declare function FFIDAnalyticsProvider({ baseUrl, serviceApiKey, getAccessToken, gaMeasurementId, gaEnabled, sentryReplay, sentry, onConsentChange, onError, autoShowBanner, dismissalCooldownMinutes, mergeWarningMessages, fetchImpl, children, }: FFIDAnalyticsProviderProps): React.JSX.Element;
|
|
755
803
|
|
|
756
804
|
interface FFIDConsentContextValue {
|
|
757
805
|
/** Current consent state. Always non-null; "not yet decided" is encoded by `hasDecided=false`. */
|
|
@@ -1093,7 +1093,7 @@ function createNonContractMethods(deps) {
|
|
|
1093
1093
|
}
|
|
1094
1094
|
|
|
1095
1095
|
// src/client/version-check.ts
|
|
1096
|
-
var SDK_VERSION = "5.
|
|
1096
|
+
var SDK_VERSION = "5.16.2";
|
|
1097
1097
|
var SDK_USER_AGENT = `FFID-SDK/${SDK_VERSION} (TypeScript)`;
|
|
1098
1098
|
var SDK_VERSION_HEADER = "X-FFID-SDK-Version";
|
|
1099
1099
|
function sdkHeaders() {
|
|
@@ -1095,7 +1095,7 @@ function createNonContractMethods(deps) {
|
|
|
1095
1095
|
}
|
|
1096
1096
|
|
|
1097
1097
|
// src/client/version-check.ts
|
|
1098
|
-
var SDK_VERSION = "5.
|
|
1098
|
+
var SDK_VERSION = "5.16.2";
|
|
1099
1099
|
var SDK_USER_AGENT = `FFID-SDK/${SDK_VERSION} (TypeScript)`;
|
|
1100
1100
|
var SDK_VERSION_HEADER = "X-FFID-SDK-Version";
|
|
1101
1101
|
function sdkHeaders() {
|
|
@@ -158,6 +158,7 @@ var ErrorBodyWireSchema = z.object({
|
|
|
158
158
|
var FFID_CONSENT_ERROR_CODES = [
|
|
159
159
|
// SDK client (network / response)
|
|
160
160
|
"cookie_consent_api_unreachable",
|
|
161
|
+
"cookie_consent_aborted",
|
|
161
162
|
"cookie_consent_response_invalid",
|
|
162
163
|
"cookie_consent_rate_limited",
|
|
163
164
|
// SDK storage
|
|
@@ -191,6 +192,7 @@ var FFIDConsentError = class extends Error {
|
|
|
191
192
|
};
|
|
192
193
|
var DEFAULT_CONSENT_ERROR_MESSAGES = {
|
|
193
194
|
cookie_consent_api_unreachable: "\u901A\u4FE1\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F\u3002\u3082\u3046\u4E00\u5EA6\u304A\u8A66\u3057\u304F\u3060\u3055\u3044",
|
|
195
|
+
cookie_consent_aborted: "\u901A\u4FE1\u304C\u4E2D\u65AD\u3055\u308C\u307E\u3057\u305F\u3002\u3082\u3046\u4E00\u5EA6\u304A\u8A66\u3057\u304F\u3060\u3055\u3044",
|
|
194
196
|
cookie_consent_response_invalid: "\u540C\u610F\u60C5\u5831\u306E\u53D6\u5F97\u306B\u5931\u6557\u3057\u307E\u3057\u305F\u3002\u6642\u9593\u3092\u7F6E\u3044\u3066\u518D\u5EA6\u304A\u8A66\u3057\u304F\u3060\u3055\u3044",
|
|
195
197
|
cookie_consent_rate_limited: "\u30B5\u30FC\u30D3\u30B9\u304C\u6DF7\u96D1\u3057\u3066\u3044\u307E\u3059\u3002\u3057\u3070\u3089\u304F\u7D4C\u3063\u3066\u304B\u3089\u518D\u5EA6\u304A\u8A66\u3057\u304F\u3060\u3055\u3044",
|
|
196
198
|
cookie_consent_cookie_write_failed: "\u30D6\u30E9\u30A6\u30B6\u306E\u8A2D\u5B9A\u306B\u3088\u308A\u540C\u610F\u60C5\u5831\u3092\u4FDD\u5B58\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002\u30D7\u30E9\u30A4\u30D9\u30FC\u30C8\u30D6\u30E9\u30A6\u30BA\u3084\u62E1\u5F35\u6A5F\u80FD\u3092\u3054\u78BA\u8A8D\u304F\u3060\u3055\u3044",
|
|
@@ -656,6 +658,9 @@ function jitteredDelayMs(baseSec) {
|
|
|
656
658
|
const jitter = (Math.random() * 2 - 1) * RETRY_JITTER_RATIO;
|
|
657
659
|
return Math.max(0, baseSec * MS_PER_SECOND * (1 + jitter));
|
|
658
660
|
}
|
|
661
|
+
function isAbortError(cause) {
|
|
662
|
+
return typeof cause === "object" && cause !== null && "name" in cause && cause.name === "AbortError";
|
|
663
|
+
}
|
|
659
664
|
function parseErrorBody(json, retryAfterHeader) {
|
|
660
665
|
const parsed = ErrorBodyWireSchema.safeParse(json);
|
|
661
666
|
const body = parsed.success ? parsed.data : {};
|
|
@@ -765,6 +770,15 @@ function createConsentClient(opts) {
|
|
|
765
770
|
const res = await fetchImpl(url, init);
|
|
766
771
|
return { res };
|
|
767
772
|
} catch (cause) {
|
|
773
|
+
if (isAbortError(cause)) {
|
|
774
|
+
return {
|
|
775
|
+
err: new FFIDConsentError(
|
|
776
|
+
"cookie_consent_aborted",
|
|
777
|
+
`Request to ${url} was aborted`,
|
|
778
|
+
{ cause }
|
|
779
|
+
)
|
|
780
|
+
};
|
|
781
|
+
}
|
|
768
782
|
return {
|
|
769
783
|
err: new FFIDConsentError(
|
|
770
784
|
"cookie_consent_api_unreachable",
|
|
@@ -978,30 +992,43 @@ function mapCategoriesToGtagParams(cats) {
|
|
|
978
992
|
ad_personalization: bool(cats.marketing)
|
|
979
993
|
};
|
|
980
994
|
}
|
|
981
|
-
function
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
995
|
+
function ensureGtag(win) {
|
|
996
|
+
const dataLayer = win.dataLayer = win.dataLayer ?? [];
|
|
997
|
+
let fn = win.gtag;
|
|
998
|
+
if (typeof fn !== "function") {
|
|
999
|
+
fn = function gtag() {
|
|
1000
|
+
dataLayer.push(arguments);
|
|
1001
|
+
};
|
|
1002
|
+
win.gtag = fn;
|
|
1003
|
+
}
|
|
1004
|
+
return fn;
|
|
985
1005
|
}
|
|
986
1006
|
function createGtagBridge(opts = {}) {
|
|
987
1007
|
const win = opts.win ?? (typeof window !== "undefined" ? window : void 0);
|
|
988
1008
|
const gaId = opts.gaMeasurementId ?? null;
|
|
1009
|
+
const gaEnabled = opts.gaEnabled ?? true;
|
|
989
1010
|
const scriptSrc = opts.gtagScriptSrc ?? DEFAULT_GTAG_SRC;
|
|
990
1011
|
let gaScriptInjected = false;
|
|
1012
|
+
function command(...args) {
|
|
1013
|
+
if (!win) return;
|
|
1014
|
+
ensureGtag(win)(...args);
|
|
1015
|
+
}
|
|
991
1016
|
return {
|
|
992
1017
|
setDefaultConsent() {
|
|
1018
|
+
if (!win || win.__ffidConsentDefaultApplied) return;
|
|
993
1019
|
const denyParams = mapCategoriesToGtagParams({
|
|
994
1020
|
functional: false,
|
|
995
1021
|
analytics: false,
|
|
996
1022
|
marketing: false
|
|
997
1023
|
});
|
|
998
|
-
|
|
1024
|
+
command("consent", "default", denyParams);
|
|
1025
|
+
win.__ffidConsentDefaultApplied = true;
|
|
999
1026
|
},
|
|
1000
1027
|
updateConsent(cats) {
|
|
1001
|
-
|
|
1002
|
-
pushDataLayer(win, ["consent", "update", params]);
|
|
1028
|
+
command("consent", "update", mapCategoriesToGtagParams(cats));
|
|
1003
1029
|
},
|
|
1004
1030
|
applyGaInjection(cats) {
|
|
1031
|
+
if (!gaEnabled) return;
|
|
1005
1032
|
if (!win || !gaId) return;
|
|
1006
1033
|
if (!cats.analytics) return;
|
|
1007
1034
|
if (gaScriptInjected) return;
|
|
@@ -1011,8 +1038,8 @@ function createGtagBridge(opts = {}) {
|
|
|
1011
1038
|
script.async = true;
|
|
1012
1039
|
script.src = `${scriptSrc}?id=${encodeURIComponent(gaId)}`;
|
|
1013
1040
|
doc.head.appendChild(script);
|
|
1014
|
-
|
|
1015
|
-
|
|
1041
|
+
command("js", /* @__PURE__ */ new Date());
|
|
1042
|
+
command("config", gaId);
|
|
1016
1043
|
gaScriptInjected = true;
|
|
1017
1044
|
}
|
|
1018
1045
|
};
|
|
@@ -1070,6 +1097,7 @@ function FFIDAnalyticsProvider({
|
|
|
1070
1097
|
serviceApiKey,
|
|
1071
1098
|
getAccessToken,
|
|
1072
1099
|
gaMeasurementId,
|
|
1100
|
+
gaEnabled,
|
|
1073
1101
|
sentryReplay,
|
|
1074
1102
|
sentry,
|
|
1075
1103
|
onConsentChange,
|
|
@@ -1118,9 +1146,11 @@ function FFIDAnalyticsProvider({
|
|
|
1118
1146
|
clientRef.current = createConsentClient(clientOpts);
|
|
1119
1147
|
}
|
|
1120
1148
|
if (!bridgeRef.current) {
|
|
1121
|
-
|
|
1149
|
+
const bridgeOpts = {
|
|
1122
1150
|
gaMeasurementId: gaMeasurementId ?? null
|
|
1123
|
-
}
|
|
1151
|
+
};
|
|
1152
|
+
if (gaEnabled !== void 0) bridgeOpts.gaEnabled = gaEnabled;
|
|
1153
|
+
bridgeRef.current = createGtagBridge(bridgeOpts);
|
|
1124
1154
|
bridgeRef.current.setDefaultConsent();
|
|
1125
1155
|
}
|
|
1126
1156
|
const applyCategoriesSideEffects = useCallback(
|
|
@@ -1187,14 +1217,16 @@ function FFIDAnalyticsProvider({
|
|
|
1187
1217
|
);
|
|
1188
1218
|
useEffect(() => {
|
|
1189
1219
|
let cancelled = false;
|
|
1220
|
+
const controller = new AbortController();
|
|
1190
1221
|
async function bootstrap() {
|
|
1191
1222
|
const local = readConsentSessionMirror() ?? readConsentCookie();
|
|
1192
1223
|
applyCategoriesSideEffects(local);
|
|
1193
1224
|
setState(stateFromLocalCategories(local));
|
|
1194
1225
|
const client = clientRef.current;
|
|
1195
|
-
const result = await client.getMe();
|
|
1226
|
+
const result = await client.getMe(controller.signal);
|
|
1196
1227
|
if (cancelled) return;
|
|
1197
1228
|
if (!result.ok) {
|
|
1229
|
+
if (result.error.code === "cookie_consent_aborted") return;
|
|
1198
1230
|
setError(result.error);
|
|
1199
1231
|
onError?.(result.error);
|
|
1200
1232
|
setIsLoading(false);
|
|
@@ -1218,6 +1250,7 @@ function FFIDAnalyticsProvider({
|
|
|
1218
1250
|
void bootstrap();
|
|
1219
1251
|
return () => {
|
|
1220
1252
|
cancelled = true;
|
|
1253
|
+
controller.abort();
|
|
1221
1254
|
};
|
|
1222
1255
|
}, []);
|
|
1223
1256
|
useEffect(() => {
|
|
@@ -160,6 +160,7 @@ var ErrorBodyWireSchema = zod.z.object({
|
|
|
160
160
|
var FFID_CONSENT_ERROR_CODES = [
|
|
161
161
|
// SDK client (network / response)
|
|
162
162
|
"cookie_consent_api_unreachable",
|
|
163
|
+
"cookie_consent_aborted",
|
|
163
164
|
"cookie_consent_response_invalid",
|
|
164
165
|
"cookie_consent_rate_limited",
|
|
165
166
|
// SDK storage
|
|
@@ -193,6 +194,7 @@ var FFIDConsentError = class extends Error {
|
|
|
193
194
|
};
|
|
194
195
|
var DEFAULT_CONSENT_ERROR_MESSAGES = {
|
|
195
196
|
cookie_consent_api_unreachable: "\u901A\u4FE1\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F\u3002\u3082\u3046\u4E00\u5EA6\u304A\u8A66\u3057\u304F\u3060\u3055\u3044",
|
|
197
|
+
cookie_consent_aborted: "\u901A\u4FE1\u304C\u4E2D\u65AD\u3055\u308C\u307E\u3057\u305F\u3002\u3082\u3046\u4E00\u5EA6\u304A\u8A66\u3057\u304F\u3060\u3055\u3044",
|
|
196
198
|
cookie_consent_response_invalid: "\u540C\u610F\u60C5\u5831\u306E\u53D6\u5F97\u306B\u5931\u6557\u3057\u307E\u3057\u305F\u3002\u6642\u9593\u3092\u7F6E\u3044\u3066\u518D\u5EA6\u304A\u8A66\u3057\u304F\u3060\u3055\u3044",
|
|
197
199
|
cookie_consent_rate_limited: "\u30B5\u30FC\u30D3\u30B9\u304C\u6DF7\u96D1\u3057\u3066\u3044\u307E\u3059\u3002\u3057\u3070\u3089\u304F\u7D4C\u3063\u3066\u304B\u3089\u518D\u5EA6\u304A\u8A66\u3057\u304F\u3060\u3055\u3044",
|
|
198
200
|
cookie_consent_cookie_write_failed: "\u30D6\u30E9\u30A6\u30B6\u306E\u8A2D\u5B9A\u306B\u3088\u308A\u540C\u610F\u60C5\u5831\u3092\u4FDD\u5B58\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002\u30D7\u30E9\u30A4\u30D9\u30FC\u30C8\u30D6\u30E9\u30A6\u30BA\u3084\u62E1\u5F35\u6A5F\u80FD\u3092\u3054\u78BA\u8A8D\u304F\u3060\u3055\u3044",
|
|
@@ -658,6 +660,9 @@ function jitteredDelayMs(baseSec) {
|
|
|
658
660
|
const jitter = (Math.random() * 2 - 1) * RETRY_JITTER_RATIO;
|
|
659
661
|
return Math.max(0, baseSec * MS_PER_SECOND * (1 + jitter));
|
|
660
662
|
}
|
|
663
|
+
function isAbortError(cause) {
|
|
664
|
+
return typeof cause === "object" && cause !== null && "name" in cause && cause.name === "AbortError";
|
|
665
|
+
}
|
|
661
666
|
function parseErrorBody(json, retryAfterHeader) {
|
|
662
667
|
const parsed = ErrorBodyWireSchema.safeParse(json);
|
|
663
668
|
const body = parsed.success ? parsed.data : {};
|
|
@@ -767,6 +772,15 @@ function createConsentClient(opts) {
|
|
|
767
772
|
const res = await fetchImpl(url, init);
|
|
768
773
|
return { res };
|
|
769
774
|
} catch (cause) {
|
|
775
|
+
if (isAbortError(cause)) {
|
|
776
|
+
return {
|
|
777
|
+
err: new FFIDConsentError(
|
|
778
|
+
"cookie_consent_aborted",
|
|
779
|
+
`Request to ${url} was aborted`,
|
|
780
|
+
{ cause }
|
|
781
|
+
)
|
|
782
|
+
};
|
|
783
|
+
}
|
|
770
784
|
return {
|
|
771
785
|
err: new FFIDConsentError(
|
|
772
786
|
"cookie_consent_api_unreachable",
|
|
@@ -980,30 +994,43 @@ function mapCategoriesToGtagParams(cats) {
|
|
|
980
994
|
ad_personalization: bool(cats.marketing)
|
|
981
995
|
};
|
|
982
996
|
}
|
|
983
|
-
function
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
997
|
+
function ensureGtag(win) {
|
|
998
|
+
const dataLayer = win.dataLayer = win.dataLayer ?? [];
|
|
999
|
+
let fn = win.gtag;
|
|
1000
|
+
if (typeof fn !== "function") {
|
|
1001
|
+
fn = function gtag() {
|
|
1002
|
+
dataLayer.push(arguments);
|
|
1003
|
+
};
|
|
1004
|
+
win.gtag = fn;
|
|
1005
|
+
}
|
|
1006
|
+
return fn;
|
|
987
1007
|
}
|
|
988
1008
|
function createGtagBridge(opts = {}) {
|
|
989
1009
|
const win = opts.win ?? (typeof window !== "undefined" ? window : void 0);
|
|
990
1010
|
const gaId = opts.gaMeasurementId ?? null;
|
|
1011
|
+
const gaEnabled = opts.gaEnabled ?? true;
|
|
991
1012
|
const scriptSrc = opts.gtagScriptSrc ?? DEFAULT_GTAG_SRC;
|
|
992
1013
|
let gaScriptInjected = false;
|
|
1014
|
+
function command(...args) {
|
|
1015
|
+
if (!win) return;
|
|
1016
|
+
ensureGtag(win)(...args);
|
|
1017
|
+
}
|
|
993
1018
|
return {
|
|
994
1019
|
setDefaultConsent() {
|
|
1020
|
+
if (!win || win.__ffidConsentDefaultApplied) return;
|
|
995
1021
|
const denyParams = mapCategoriesToGtagParams({
|
|
996
1022
|
functional: false,
|
|
997
1023
|
analytics: false,
|
|
998
1024
|
marketing: false
|
|
999
1025
|
});
|
|
1000
|
-
|
|
1026
|
+
command("consent", "default", denyParams);
|
|
1027
|
+
win.__ffidConsentDefaultApplied = true;
|
|
1001
1028
|
},
|
|
1002
1029
|
updateConsent(cats) {
|
|
1003
|
-
|
|
1004
|
-
pushDataLayer(win, ["consent", "update", params]);
|
|
1030
|
+
command("consent", "update", mapCategoriesToGtagParams(cats));
|
|
1005
1031
|
},
|
|
1006
1032
|
applyGaInjection(cats) {
|
|
1033
|
+
if (!gaEnabled) return;
|
|
1007
1034
|
if (!win || !gaId) return;
|
|
1008
1035
|
if (!cats.analytics) return;
|
|
1009
1036
|
if (gaScriptInjected) return;
|
|
@@ -1013,8 +1040,8 @@ function createGtagBridge(opts = {}) {
|
|
|
1013
1040
|
script.async = true;
|
|
1014
1041
|
script.src = `${scriptSrc}?id=${encodeURIComponent(gaId)}`;
|
|
1015
1042
|
doc.head.appendChild(script);
|
|
1016
|
-
|
|
1017
|
-
|
|
1043
|
+
command("js", /* @__PURE__ */ new Date());
|
|
1044
|
+
command("config", gaId);
|
|
1018
1045
|
gaScriptInjected = true;
|
|
1019
1046
|
}
|
|
1020
1047
|
};
|
|
@@ -1072,6 +1099,7 @@ function FFIDAnalyticsProvider({
|
|
|
1072
1099
|
serviceApiKey,
|
|
1073
1100
|
getAccessToken,
|
|
1074
1101
|
gaMeasurementId,
|
|
1102
|
+
gaEnabled,
|
|
1075
1103
|
sentryReplay,
|
|
1076
1104
|
sentry,
|
|
1077
1105
|
onConsentChange,
|
|
@@ -1120,9 +1148,11 @@ function FFIDAnalyticsProvider({
|
|
|
1120
1148
|
clientRef.current = createConsentClient(clientOpts);
|
|
1121
1149
|
}
|
|
1122
1150
|
if (!bridgeRef.current) {
|
|
1123
|
-
|
|
1151
|
+
const bridgeOpts = {
|
|
1124
1152
|
gaMeasurementId: gaMeasurementId ?? null
|
|
1125
|
-
}
|
|
1153
|
+
};
|
|
1154
|
+
if (gaEnabled !== void 0) bridgeOpts.gaEnabled = gaEnabled;
|
|
1155
|
+
bridgeRef.current = createGtagBridge(bridgeOpts);
|
|
1126
1156
|
bridgeRef.current.setDefaultConsent();
|
|
1127
1157
|
}
|
|
1128
1158
|
const applyCategoriesSideEffects = react.useCallback(
|
|
@@ -1189,14 +1219,16 @@ function FFIDAnalyticsProvider({
|
|
|
1189
1219
|
);
|
|
1190
1220
|
react.useEffect(() => {
|
|
1191
1221
|
let cancelled = false;
|
|
1222
|
+
const controller = new AbortController();
|
|
1192
1223
|
async function bootstrap() {
|
|
1193
1224
|
const local = readConsentSessionMirror() ?? readConsentCookie();
|
|
1194
1225
|
applyCategoriesSideEffects(local);
|
|
1195
1226
|
setState(stateFromLocalCategories(local));
|
|
1196
1227
|
const client = clientRef.current;
|
|
1197
|
-
const result = await client.getMe();
|
|
1228
|
+
const result = await client.getMe(controller.signal);
|
|
1198
1229
|
if (cancelled) return;
|
|
1199
1230
|
if (!result.ok) {
|
|
1231
|
+
if (result.error.code === "cookie_consent_aborted") return;
|
|
1200
1232
|
setError(result.error);
|
|
1201
1233
|
onError?.(result.error);
|
|
1202
1234
|
setIsLoading(false);
|
|
@@ -1220,6 +1252,7 @@ function FFIDAnalyticsProvider({
|
|
|
1220
1252
|
void bootstrap();
|
|
1221
1253
|
return () => {
|
|
1222
1254
|
cancelled = true;
|
|
1255
|
+
controller.abort();
|
|
1223
1256
|
};
|
|
1224
1257
|
}, []);
|
|
1225
1258
|
react.useEffect(() => {
|
|
@@ -1,34 +1,34 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkFLLTPPUP_cjs = require('../chunk-FLLTPPUP.cjs');
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
Object.defineProperty(exports, "FFIDAnnouncementBadge", {
|
|
8
8
|
enumerable: true,
|
|
9
|
-
get: function () { return
|
|
9
|
+
get: function () { return chunkFLLTPPUP_cjs.FFIDAnnouncementBadge; }
|
|
10
10
|
});
|
|
11
11
|
Object.defineProperty(exports, "FFIDAnnouncementList", {
|
|
12
12
|
enumerable: true,
|
|
13
|
-
get: function () { return
|
|
13
|
+
get: function () { return chunkFLLTPPUP_cjs.FFIDAnnouncementList; }
|
|
14
14
|
});
|
|
15
15
|
Object.defineProperty(exports, "FFIDInquiryForm", {
|
|
16
16
|
enumerable: true,
|
|
17
|
-
get: function () { return
|
|
17
|
+
get: function () { return chunkFLLTPPUP_cjs.FFIDInquiryForm; }
|
|
18
18
|
});
|
|
19
19
|
Object.defineProperty(exports, "FFIDLoginButton", {
|
|
20
20
|
enumerable: true,
|
|
21
|
-
get: function () { return
|
|
21
|
+
get: function () { return chunkFLLTPPUP_cjs.FFIDLoginButton; }
|
|
22
22
|
});
|
|
23
23
|
Object.defineProperty(exports, "FFIDOrganizationSwitcher", {
|
|
24
24
|
enumerable: true,
|
|
25
|
-
get: function () { return
|
|
25
|
+
get: function () { return chunkFLLTPPUP_cjs.FFIDOrganizationSwitcher; }
|
|
26
26
|
});
|
|
27
27
|
Object.defineProperty(exports, "FFIDSubscriptionBadge", {
|
|
28
28
|
enumerable: true,
|
|
29
|
-
get: function () { return
|
|
29
|
+
get: function () { return chunkFLLTPPUP_cjs.FFIDSubscriptionBadge; }
|
|
30
30
|
});
|
|
31
31
|
Object.defineProperty(exports, "FFIDUserMenu", {
|
|
32
32
|
enumerable: true,
|
|
33
|
-
get: function () { return
|
|
33
|
+
get: function () { return chunkFLLTPPUP_cjs.FFIDUserMenu; }
|
|
34
34
|
});
|
package/dist/components/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { FFIDAnnouncementBadge, FFIDAnnouncementList, FFIDInquiryForm, FFIDLoginButton, FFIDOrganizationSwitcher, FFIDSubscriptionBadge, FFIDUserMenu } from '../chunk-
|
|
1
|
+
export { FFIDAnnouncementBadge, FFIDAnnouncementList, FFIDInquiryForm, FFIDLoginButton, FFIDOrganizationSwitcher, FFIDSubscriptionBadge, FFIDUserMenu } from '../chunk-C7PURRNI.js';
|