@feelflow/ffid-sdk 5.14.0 → 5.16.1
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-DrF5UXkd.d.cts} +37 -4
- package/dist/{FFIDCookieLink-D1WwE_ZO.d.ts → FFIDCookieLink-DrF5UXkd.d.ts} +37 -4
- package/dist/{chunk-R5WSZMUL.cjs → chunk-H2D52XCL.cjs} +43 -12
- package/dist/{chunk-M4N2PUX6.js → chunk-HAR75DTJ.js} +43 -12
- package/dist/{chunk-BVDUQQHP.cjs → chunk-SNSIVY3Q.cjs} +1 -1
- package/dist/{chunk-4II4R7NR.js → chunk-XT4BAOKZ.js} +1 -1
- 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
|
|
@@ -615,6 +621,16 @@ declare global {
|
|
|
615
621
|
interface GtagBridgeOptions {
|
|
616
622
|
/** GA Measurement ID, e.g., `G-XXXXXXX`. Empty/null = GA not wired. */
|
|
617
623
|
gaMeasurementId?: string | null;
|
|
624
|
+
/**
|
|
625
|
+
* Master switch for GA tag injection. When `false`, the bridge still manages
|
|
626
|
+
* Consent Mode dataLayer pushes (`setDefaultConsent` / `updateConsent`) but
|
|
627
|
+
* `applyGaInjection` never appends the `gtag.js` script — regardless of
|
|
628
|
+
* `gaMeasurementId` or analytics consent. Defaults to `true` (legacy
|
|
629
|
+
* behavior). Consumers gate this on their own environment so non-production
|
|
630
|
+
* builds don't pollute the production GA4 property (e.g.
|
|
631
|
+
* `gaEnabled={process.env.VERCEL_ENV === 'production'}`).
|
|
632
|
+
*/
|
|
633
|
+
gaEnabled?: boolean;
|
|
618
634
|
/** Override script src for testing. Default: official gtag URL. */
|
|
619
635
|
gtagScriptSrc?: string;
|
|
620
636
|
/** Document/window injection target (server-side stays no-op). */
|
|
@@ -703,6 +719,23 @@ interface FFIDAnalyticsProviderProps {
|
|
|
703
719
|
* avoiding the triple-state `string | null | undefined` anti-pattern.
|
|
704
720
|
*/
|
|
705
721
|
gaMeasurementId?: string | null;
|
|
722
|
+
/**
|
|
723
|
+
* Master switch for GA tag injection. When `false`, the provider still runs
|
|
724
|
+
* Consent Mode (default/update dataLayer pushes) but never injects the
|
|
725
|
+
* `gtag.js` script — regardless of `gaMeasurementId` or analytics consent.
|
|
726
|
+
* Defaults to `true` (legacy behavior, fully backward compatible).
|
|
727
|
+
*
|
|
728
|
+
* Gate this on your own deploy environment so non-production builds don't
|
|
729
|
+
* send hits to the production GA4 property, e.g.
|
|
730
|
+
* `gaEnabled={process.env.VERCEL_ENV === 'production'}`. Read the env signal
|
|
731
|
+
* server-side where possible (it is captured once at mount, not reactive).
|
|
732
|
+
*
|
|
733
|
+
* Prefer `gaEnabled={false}` over `gaMeasurementId={null}` when gating by
|
|
734
|
+
* environment: `gaEnabled={false}` keeps a stable measurement ID wired across
|
|
735
|
+
* builds and keeps Consent Mode pushes flowing, whereas `null` means "GA was
|
|
736
|
+
* never configured for this app".
|
|
737
|
+
*/
|
|
738
|
+
gaEnabled?: boolean;
|
|
706
739
|
/** When true, control Sentry session replay based on `functional` consent. */
|
|
707
740
|
sentryReplay?: boolean;
|
|
708
741
|
/** Sentry namespace override; defaults to `(window as any).Sentry`. */
|
|
@@ -751,7 +784,7 @@ interface FFIDAnalyticsProviderProps {
|
|
|
751
784
|
fetchImpl?: typeof fetch;
|
|
752
785
|
children: ReactNode;
|
|
753
786
|
}
|
|
754
|
-
declare function FFIDAnalyticsProvider({ baseUrl, serviceApiKey, getAccessToken, gaMeasurementId, sentryReplay, sentry, onConsentChange, onError, autoShowBanner, dismissalCooldownMinutes, mergeWarningMessages, fetchImpl, children, }: FFIDAnalyticsProviderProps): React.JSX.Element;
|
|
787
|
+
declare function FFIDAnalyticsProvider({ baseUrl, serviceApiKey, getAccessToken, gaMeasurementId, gaEnabled, sentryReplay, sentry, onConsentChange, onError, autoShowBanner, dismissalCooldownMinutes, mergeWarningMessages, fetchImpl, children, }: FFIDAnalyticsProviderProps): React.JSX.Element;
|
|
755
788
|
|
|
756
789
|
interface FFIDConsentContextValue {
|
|
757
790
|
/** 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
|
|
@@ -615,6 +621,16 @@ declare global {
|
|
|
615
621
|
interface GtagBridgeOptions {
|
|
616
622
|
/** GA Measurement ID, e.g., `G-XXXXXXX`. Empty/null = GA not wired. */
|
|
617
623
|
gaMeasurementId?: string | null;
|
|
624
|
+
/**
|
|
625
|
+
* Master switch for GA tag injection. When `false`, the bridge still manages
|
|
626
|
+
* Consent Mode dataLayer pushes (`setDefaultConsent` / `updateConsent`) but
|
|
627
|
+
* `applyGaInjection` never appends the `gtag.js` script — regardless of
|
|
628
|
+
* `gaMeasurementId` or analytics consent. Defaults to `true` (legacy
|
|
629
|
+
* behavior). Consumers gate this on their own environment so non-production
|
|
630
|
+
* builds don't pollute the production GA4 property (e.g.
|
|
631
|
+
* `gaEnabled={process.env.VERCEL_ENV === 'production'}`).
|
|
632
|
+
*/
|
|
633
|
+
gaEnabled?: boolean;
|
|
618
634
|
/** Override script src for testing. Default: official gtag URL. */
|
|
619
635
|
gtagScriptSrc?: string;
|
|
620
636
|
/** Document/window injection target (server-side stays no-op). */
|
|
@@ -703,6 +719,23 @@ interface FFIDAnalyticsProviderProps {
|
|
|
703
719
|
* avoiding the triple-state `string | null | undefined` anti-pattern.
|
|
704
720
|
*/
|
|
705
721
|
gaMeasurementId?: string | null;
|
|
722
|
+
/**
|
|
723
|
+
* Master switch for GA tag injection. When `false`, the provider still runs
|
|
724
|
+
* Consent Mode (default/update dataLayer pushes) but never injects the
|
|
725
|
+
* `gtag.js` script — regardless of `gaMeasurementId` or analytics consent.
|
|
726
|
+
* Defaults to `true` (legacy behavior, fully backward compatible).
|
|
727
|
+
*
|
|
728
|
+
* Gate this on your own deploy environment so non-production builds don't
|
|
729
|
+
* send hits to the production GA4 property, e.g.
|
|
730
|
+
* `gaEnabled={process.env.VERCEL_ENV === 'production'}`. Read the env signal
|
|
731
|
+
* server-side where possible (it is captured once at mount, not reactive).
|
|
732
|
+
*
|
|
733
|
+
* Prefer `gaEnabled={false}` over `gaMeasurementId={null}` when gating by
|
|
734
|
+
* environment: `gaEnabled={false}` keeps a stable measurement ID wired across
|
|
735
|
+
* builds and keeps Consent Mode pushes flowing, whereas `null` means "GA was
|
|
736
|
+
* never configured for this app".
|
|
737
|
+
*/
|
|
738
|
+
gaEnabled?: boolean;
|
|
706
739
|
/** When true, control Sentry session replay based on `functional` consent. */
|
|
707
740
|
sentryReplay?: boolean;
|
|
708
741
|
/** Sentry namespace override; defaults to `(window as any).Sentry`. */
|
|
@@ -751,7 +784,7 @@ interface FFIDAnalyticsProviderProps {
|
|
|
751
784
|
fetchImpl?: typeof fetch;
|
|
752
785
|
children: ReactNode;
|
|
753
786
|
}
|
|
754
|
-
declare function FFIDAnalyticsProvider({ baseUrl, serviceApiKey, getAccessToken, gaMeasurementId, sentryReplay, sentry, onConsentChange, onError, autoShowBanner, dismissalCooldownMinutes, mergeWarningMessages, fetchImpl, children, }: FFIDAnalyticsProviderProps): React.JSX.Element;
|
|
787
|
+
declare function FFIDAnalyticsProvider({ baseUrl, serviceApiKey, getAccessToken, gaMeasurementId, gaEnabled, sentryReplay, sentry, onConsentChange, onError, autoShowBanner, dismissalCooldownMinutes, mergeWarningMessages, fetchImpl, children, }: FFIDAnalyticsProviderProps): React.JSX.Element;
|
|
755
788
|
|
|
756
789
|
interface FFIDConsentContextValue {
|
|
757
790
|
/** Current consent state. Always non-null; "not yet decided" is encoded by `hasDecided=false`. */
|
|
@@ -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,16 +994,27 @@ 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() {
|
|
995
1020
|
const denyParams = mapCategoriesToGtagParams({
|
|
@@ -997,13 +1022,13 @@ function createGtagBridge(opts = {}) {
|
|
|
997
1022
|
analytics: false,
|
|
998
1023
|
marketing: false
|
|
999
1024
|
});
|
|
1000
|
-
|
|
1025
|
+
command("consent", "default", denyParams);
|
|
1001
1026
|
},
|
|
1002
1027
|
updateConsent(cats) {
|
|
1003
|
-
|
|
1004
|
-
pushDataLayer(win, ["consent", "update", params]);
|
|
1028
|
+
command("consent", "update", mapCategoriesToGtagParams(cats));
|
|
1005
1029
|
},
|
|
1006
1030
|
applyGaInjection(cats) {
|
|
1031
|
+
if (!gaEnabled) return;
|
|
1007
1032
|
if (!win || !gaId) return;
|
|
1008
1033
|
if (!cats.analytics) return;
|
|
1009
1034
|
if (gaScriptInjected) return;
|
|
@@ -1013,8 +1038,8 @@ function createGtagBridge(opts = {}) {
|
|
|
1013
1038
|
script.async = true;
|
|
1014
1039
|
script.src = `${scriptSrc}?id=${encodeURIComponent(gaId)}`;
|
|
1015
1040
|
doc.head.appendChild(script);
|
|
1016
|
-
|
|
1017
|
-
|
|
1041
|
+
command("js", /* @__PURE__ */ new Date());
|
|
1042
|
+
command("config", gaId);
|
|
1018
1043
|
gaScriptInjected = true;
|
|
1019
1044
|
}
|
|
1020
1045
|
};
|
|
@@ -1072,6 +1097,7 @@ function FFIDAnalyticsProvider({
|
|
|
1072
1097
|
serviceApiKey,
|
|
1073
1098
|
getAccessToken,
|
|
1074
1099
|
gaMeasurementId,
|
|
1100
|
+
gaEnabled,
|
|
1075
1101
|
sentryReplay,
|
|
1076
1102
|
sentry,
|
|
1077
1103
|
onConsentChange,
|
|
@@ -1120,9 +1146,11 @@ function FFIDAnalyticsProvider({
|
|
|
1120
1146
|
clientRef.current = createConsentClient(clientOpts);
|
|
1121
1147
|
}
|
|
1122
1148
|
if (!bridgeRef.current) {
|
|
1123
|
-
|
|
1149
|
+
const bridgeOpts = {
|
|
1124
1150
|
gaMeasurementId: gaMeasurementId ?? null
|
|
1125
|
-
}
|
|
1151
|
+
};
|
|
1152
|
+
if (gaEnabled !== void 0) bridgeOpts.gaEnabled = gaEnabled;
|
|
1153
|
+
bridgeRef.current = createGtagBridge(bridgeOpts);
|
|
1126
1154
|
bridgeRef.current.setDefaultConsent();
|
|
1127
1155
|
}
|
|
1128
1156
|
const applyCategoriesSideEffects = react.useCallback(
|
|
@@ -1189,14 +1217,16 @@ function FFIDAnalyticsProvider({
|
|
|
1189
1217
|
);
|
|
1190
1218
|
react.useEffect(() => {
|
|
1191
1219
|
let cancelled = false;
|
|
1220
|
+
const controller = new AbortController();
|
|
1192
1221
|
async function bootstrap() {
|
|
1193
1222
|
const local = readConsentSessionMirror() ?? readConsentCookie();
|
|
1194
1223
|
applyCategoriesSideEffects(local);
|
|
1195
1224
|
setState(stateFromLocalCategories(local));
|
|
1196
1225
|
const client = clientRef.current;
|
|
1197
|
-
const result = await client.getMe();
|
|
1226
|
+
const result = await client.getMe(controller.signal);
|
|
1198
1227
|
if (cancelled) return;
|
|
1199
1228
|
if (!result.ok) {
|
|
1229
|
+
if (result.error.code === "cookie_consent_aborted") return;
|
|
1200
1230
|
setError(result.error);
|
|
1201
1231
|
onError?.(result.error);
|
|
1202
1232
|
setIsLoading(false);
|
|
@@ -1220,6 +1250,7 @@ function FFIDAnalyticsProvider({
|
|
|
1220
1250
|
void bootstrap();
|
|
1221
1251
|
return () => {
|
|
1222
1252
|
cancelled = true;
|
|
1253
|
+
controller.abort();
|
|
1223
1254
|
};
|
|
1224
1255
|
}, []);
|
|
1225
1256
|
react.useEffect(() => {
|
|
@@ -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,16 +992,27 @@ 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() {
|
|
993
1018
|
const denyParams = mapCategoriesToGtagParams({
|
|
@@ -995,13 +1020,13 @@ function createGtagBridge(opts = {}) {
|
|
|
995
1020
|
analytics: false,
|
|
996
1021
|
marketing: false
|
|
997
1022
|
});
|
|
998
|
-
|
|
1023
|
+
command("consent", "default", denyParams);
|
|
999
1024
|
},
|
|
1000
1025
|
updateConsent(cats) {
|
|
1001
|
-
|
|
1002
|
-
pushDataLayer(win, ["consent", "update", params]);
|
|
1026
|
+
command("consent", "update", mapCategoriesToGtagParams(cats));
|
|
1003
1027
|
},
|
|
1004
1028
|
applyGaInjection(cats) {
|
|
1029
|
+
if (!gaEnabled) return;
|
|
1005
1030
|
if (!win || !gaId) return;
|
|
1006
1031
|
if (!cats.analytics) return;
|
|
1007
1032
|
if (gaScriptInjected) return;
|
|
@@ -1011,8 +1036,8 @@ function createGtagBridge(opts = {}) {
|
|
|
1011
1036
|
script.async = true;
|
|
1012
1037
|
script.src = `${scriptSrc}?id=${encodeURIComponent(gaId)}`;
|
|
1013
1038
|
doc.head.appendChild(script);
|
|
1014
|
-
|
|
1015
|
-
|
|
1039
|
+
command("js", /* @__PURE__ */ new Date());
|
|
1040
|
+
command("config", gaId);
|
|
1016
1041
|
gaScriptInjected = true;
|
|
1017
1042
|
}
|
|
1018
1043
|
};
|
|
@@ -1070,6 +1095,7 @@ function FFIDAnalyticsProvider({
|
|
|
1070
1095
|
serviceApiKey,
|
|
1071
1096
|
getAccessToken,
|
|
1072
1097
|
gaMeasurementId,
|
|
1098
|
+
gaEnabled,
|
|
1073
1099
|
sentryReplay,
|
|
1074
1100
|
sentry,
|
|
1075
1101
|
onConsentChange,
|
|
@@ -1118,9 +1144,11 @@ function FFIDAnalyticsProvider({
|
|
|
1118
1144
|
clientRef.current = createConsentClient(clientOpts);
|
|
1119
1145
|
}
|
|
1120
1146
|
if (!bridgeRef.current) {
|
|
1121
|
-
|
|
1147
|
+
const bridgeOpts = {
|
|
1122
1148
|
gaMeasurementId: gaMeasurementId ?? null
|
|
1123
|
-
}
|
|
1149
|
+
};
|
|
1150
|
+
if (gaEnabled !== void 0) bridgeOpts.gaEnabled = gaEnabled;
|
|
1151
|
+
bridgeRef.current = createGtagBridge(bridgeOpts);
|
|
1124
1152
|
bridgeRef.current.setDefaultConsent();
|
|
1125
1153
|
}
|
|
1126
1154
|
const applyCategoriesSideEffects = useCallback(
|
|
@@ -1187,14 +1215,16 @@ function FFIDAnalyticsProvider({
|
|
|
1187
1215
|
);
|
|
1188
1216
|
useEffect(() => {
|
|
1189
1217
|
let cancelled = false;
|
|
1218
|
+
const controller = new AbortController();
|
|
1190
1219
|
async function bootstrap() {
|
|
1191
1220
|
const local = readConsentSessionMirror() ?? readConsentCookie();
|
|
1192
1221
|
applyCategoriesSideEffects(local);
|
|
1193
1222
|
setState(stateFromLocalCategories(local));
|
|
1194
1223
|
const client = clientRef.current;
|
|
1195
|
-
const result = await client.getMe();
|
|
1224
|
+
const result = await client.getMe(controller.signal);
|
|
1196
1225
|
if (cancelled) return;
|
|
1197
1226
|
if (!result.ok) {
|
|
1227
|
+
if (result.error.code === "cookie_consent_aborted") return;
|
|
1198
1228
|
setError(result.error);
|
|
1199
1229
|
onError?.(result.error);
|
|
1200
1230
|
setIsLoading(false);
|
|
@@ -1218,6 +1248,7 @@ function FFIDAnalyticsProvider({
|
|
|
1218
1248
|
void bootstrap();
|
|
1219
1249
|
return () => {
|
|
1220
1250
|
cancelled = true;
|
|
1251
|
+
controller.abort();
|
|
1221
1252
|
};
|
|
1222
1253
|
}, []);
|
|
1223
1254
|
useEffect(() => {
|
|
@@ -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.1";
|
|
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() {
|
|
@@ -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.1";
|
|
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() {
|
|
@@ -1,34 +1,34 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkSNSIVY3Q_cjs = require('../chunk-SNSIVY3Q.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 chunkSNSIVY3Q_cjs.FFIDAnnouncementBadge; }
|
|
10
10
|
});
|
|
11
11
|
Object.defineProperty(exports, "FFIDAnnouncementList", {
|
|
12
12
|
enumerable: true,
|
|
13
|
-
get: function () { return
|
|
13
|
+
get: function () { return chunkSNSIVY3Q_cjs.FFIDAnnouncementList; }
|
|
14
14
|
});
|
|
15
15
|
Object.defineProperty(exports, "FFIDInquiryForm", {
|
|
16
16
|
enumerable: true,
|
|
17
|
-
get: function () { return
|
|
17
|
+
get: function () { return chunkSNSIVY3Q_cjs.FFIDInquiryForm; }
|
|
18
18
|
});
|
|
19
19
|
Object.defineProperty(exports, "FFIDLoginButton", {
|
|
20
20
|
enumerable: true,
|
|
21
|
-
get: function () { return
|
|
21
|
+
get: function () { return chunkSNSIVY3Q_cjs.FFIDLoginButton; }
|
|
22
22
|
});
|
|
23
23
|
Object.defineProperty(exports, "FFIDOrganizationSwitcher", {
|
|
24
24
|
enumerable: true,
|
|
25
|
-
get: function () { return
|
|
25
|
+
get: function () { return chunkSNSIVY3Q_cjs.FFIDOrganizationSwitcher; }
|
|
26
26
|
});
|
|
27
27
|
Object.defineProperty(exports, "FFIDSubscriptionBadge", {
|
|
28
28
|
enumerable: true,
|
|
29
|
-
get: function () { return
|
|
29
|
+
get: function () { return chunkSNSIVY3Q_cjs.FFIDSubscriptionBadge; }
|
|
30
30
|
});
|
|
31
31
|
Object.defineProperty(exports, "FFIDUserMenu", {
|
|
32
32
|
enumerable: true,
|
|
33
|
-
get: function () { return
|
|
33
|
+
get: function () { return chunkSNSIVY3Q_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-XT4BAOKZ.js';
|