@feelflow/ffid-sdk 4.2.0 → 5.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/README.md +35 -0
- package/dist/FFIDCookieLink-B-9ggxsL.d.cts +864 -0
- package/dist/FFIDCookieLink-B-9ggxsL.d.ts +864 -0
- package/dist/chunk-CISVLEY5.js +1727 -0
- package/dist/{chunk-U4XDH7TI.cjs → chunk-CJA3XQUF.cjs} +36 -3
- package/dist/chunk-PAQ4GZXN.cjs +1787 -0
- package/dist/{chunk-WI645CPU.js → chunk-XAPFTOZN.js} +36 -3
- package/dist/components/index.cjs +8 -8
- package/dist/components/index.d.cts +1 -1
- package/dist/components/index.d.ts +1 -1
- package/dist/components/index.js +1 -1
- package/dist/consent/index.cjs +242 -0
- package/dist/consent/index.d.cts +166 -0
- package/dist/consent/index.d.ts +166 -0
- package/dist/consent/index.js +1 -0
- package/dist/{ffid-client-CUOFknXy.d.ts → ffid-client-BaStAONh.d.ts} +20 -1
- package/dist/{ffid-client-CKMGqqPi.d.cts → ffid-client-DgprK2ec.d.cts} +20 -1
- package/dist/{index-DXgTH5vK.d.cts → index-CInGR4I9.d.cts} +16 -1
- package/dist/{index-DXgTH5vK.d.ts → index-CInGR4I9.d.ts} +16 -1
- package/dist/index.cjs +97 -32
- package/dist/index.d.cts +9 -3
- package/dist/index.d.ts +9 -3
- package/dist/index.js +3 -2
- package/dist/server/index.cjs +134 -3
- package/dist/server/index.d.cts +151 -3
- package/dist/server/index.d.ts +151 -3
- package/dist/server/index.js +128 -4
- package/dist/server/test/index.d.cts +1 -1
- package/dist/server/test/index.d.ts +1 -1
- package/package.json +8 -2
|
@@ -0,0 +1,1727 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { createContext, useState, useRef, useCallback, useEffect, useMemo, useContext } from 'react';
|
|
3
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
4
|
+
|
|
5
|
+
var FFIDConsentCategoryCodeSchema = z.enum([
|
|
6
|
+
"necessary",
|
|
7
|
+
"functional",
|
|
8
|
+
"analytics",
|
|
9
|
+
"marketing"
|
|
10
|
+
]);
|
|
11
|
+
var FFIDConsentCategoriesSchema = z.object({
|
|
12
|
+
necessary: z.literal(true),
|
|
13
|
+
functional: z.boolean(),
|
|
14
|
+
analytics: z.boolean(),
|
|
15
|
+
marketing: z.boolean()
|
|
16
|
+
});
|
|
17
|
+
var FFIDPublicConsentSourceSchema = z.enum([
|
|
18
|
+
"banner",
|
|
19
|
+
"preference_center"
|
|
20
|
+
]);
|
|
21
|
+
var FFIDInternalConsentSourceSchema = z.enum([
|
|
22
|
+
"sdk_default",
|
|
23
|
+
"api_withdraw",
|
|
24
|
+
"sync_merge",
|
|
25
|
+
"persisted_unknown"
|
|
26
|
+
]);
|
|
27
|
+
var FFIDConsentSourceSchema = z.union([
|
|
28
|
+
FFIDPublicConsentSourceSchema,
|
|
29
|
+
FFIDInternalConsentSourceSchema
|
|
30
|
+
]);
|
|
31
|
+
var FFIDConsentMergeStrategySchema = z.enum([
|
|
32
|
+
"db_wins",
|
|
33
|
+
"device_promoted",
|
|
34
|
+
"device_promoted_with_drift",
|
|
35
|
+
"cross_user_insert",
|
|
36
|
+
"first_insert"
|
|
37
|
+
]);
|
|
38
|
+
var FFIDConsentMergeWarningSchema = z.enum([
|
|
39
|
+
"shared_device_detected",
|
|
40
|
+
"local_state_overridden",
|
|
41
|
+
"drift_detected"
|
|
42
|
+
]);
|
|
43
|
+
var FFIDConsentStateSchema = z.discriminatedUnion("hasDecided", [
|
|
44
|
+
z.object({
|
|
45
|
+
hasDecided: z.literal(false),
|
|
46
|
+
needsRenewal: z.boolean(),
|
|
47
|
+
categories: FFIDConsentCategoriesSchema,
|
|
48
|
+
decidedAt: z.null(),
|
|
49
|
+
cookiePolicyVersion: z.null(),
|
|
50
|
+
source: z.null()
|
|
51
|
+
}),
|
|
52
|
+
z.object({
|
|
53
|
+
hasDecided: z.literal(true),
|
|
54
|
+
needsRenewal: z.boolean(),
|
|
55
|
+
categories: FFIDConsentCategoriesSchema,
|
|
56
|
+
decidedAt: z.coerce.date(),
|
|
57
|
+
cookiePolicyVersion: z.string().min(1),
|
|
58
|
+
source: FFIDConsentSourceSchema
|
|
59
|
+
})
|
|
60
|
+
]);
|
|
61
|
+
var FFIDConsentUpdateSchema = z.object({
|
|
62
|
+
functional: z.boolean().optional(),
|
|
63
|
+
analytics: z.boolean().optional(),
|
|
64
|
+
marketing: z.boolean().optional()
|
|
65
|
+
});
|
|
66
|
+
var FFIDConsentCategoryMetadataSchema = z.object({
|
|
67
|
+
code: FFIDConsentCategoryCodeSchema,
|
|
68
|
+
displayOrder: z.number().int().nonnegative(),
|
|
69
|
+
isRequired: z.boolean(),
|
|
70
|
+
label: z.string().min(1),
|
|
71
|
+
description: z.string().min(1),
|
|
72
|
+
exampleServices: z.array(z.string())
|
|
73
|
+
});
|
|
74
|
+
var UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
75
|
+
var UuidLikeSchema = z.string().regex(UUID_REGEX, "must be a UUID string");
|
|
76
|
+
var FFIDCookiePolicySchema = z.object({
|
|
77
|
+
id: UuidLikeSchema,
|
|
78
|
+
version: z.string().min(1),
|
|
79
|
+
title: z.string().min(1),
|
|
80
|
+
summary: z.string(),
|
|
81
|
+
content: z.string(),
|
|
82
|
+
effectiveDate: z.iso.date(),
|
|
83
|
+
publishedAt: z.iso.datetime()
|
|
84
|
+
});
|
|
85
|
+
var PostConsentRequestSchema = z.object({
|
|
86
|
+
categories: z.object({
|
|
87
|
+
functional: z.boolean(),
|
|
88
|
+
analytics: z.boolean(),
|
|
89
|
+
marketing: z.boolean()
|
|
90
|
+
}).strict(),
|
|
91
|
+
source: FFIDPublicConsentSourceSchema,
|
|
92
|
+
cookie_policy_version: z.string().min(1)
|
|
93
|
+
});
|
|
94
|
+
var PostSyncRequestSchema = z.object({
|
|
95
|
+
local_consent: z.object({
|
|
96
|
+
necessary: z.literal(true),
|
|
97
|
+
functional: z.boolean(),
|
|
98
|
+
analytics: z.boolean(),
|
|
99
|
+
marketing: z.boolean(),
|
|
100
|
+
cookie_policy_version: z.string().min(1)
|
|
101
|
+
}).nullable()
|
|
102
|
+
});
|
|
103
|
+
var ConsentRowWireSchema = z.object({
|
|
104
|
+
necessary: z.boolean(),
|
|
105
|
+
functional: z.boolean(),
|
|
106
|
+
analytics: z.boolean(),
|
|
107
|
+
marketing: z.boolean(),
|
|
108
|
+
cookie_policy_version: z.string().min(1),
|
|
109
|
+
decided_at: z.iso.datetime()
|
|
110
|
+
});
|
|
111
|
+
var ConsentRowWithIdWireSchema = ConsentRowWireSchema.extend({
|
|
112
|
+
id: UuidLikeSchema
|
|
113
|
+
});
|
|
114
|
+
var WithdrawalRowWireSchema = ConsentRowWithIdWireSchema.extend({
|
|
115
|
+
is_withdrawal: z.literal(true),
|
|
116
|
+
supersedes_id: UuidLikeSchema.nullable()
|
|
117
|
+
});
|
|
118
|
+
var GetConsentMeWireSchema = z.object({
|
|
119
|
+
consent: ConsentRowWireSchema.nullable(),
|
|
120
|
+
default_state: FFIDConsentCategoriesSchema.optional(),
|
|
121
|
+
current_policy_version: z.string().min(1),
|
|
122
|
+
needs_renewal: z.boolean(),
|
|
123
|
+
/**
|
|
124
|
+
* Spec §8.1: server signals `degraded: true` when DB read failed and the
|
|
125
|
+
* response was reconstructed from a cookie-only fallback. SDK consumers
|
|
126
|
+
* must NOT treat `needs_renewal` as authoritative when degraded.
|
|
127
|
+
*/
|
|
128
|
+
degraded: z.boolean().optional()
|
|
129
|
+
});
|
|
130
|
+
var PostConsentWireSchema = z.object({
|
|
131
|
+
consent: ConsentRowWithIdWireSchema
|
|
132
|
+
});
|
|
133
|
+
var PostSyncWireSchema = z.object({
|
|
134
|
+
consent: ConsentRowWithIdWireSchema,
|
|
135
|
+
merge_strategy: FFIDConsentMergeStrategySchema,
|
|
136
|
+
warning: FFIDConsentMergeWarningSchema.nullable()
|
|
137
|
+
});
|
|
138
|
+
var PostWithdrawWireSchema = z.object({
|
|
139
|
+
consent: WithdrawalRowWireSchema
|
|
140
|
+
});
|
|
141
|
+
var GetCategoriesWireSchema = z.object({
|
|
142
|
+
categories: z.array(FFIDConsentCategoryMetadataSchema)
|
|
143
|
+
});
|
|
144
|
+
var GetDocumentWireSchema = z.object({
|
|
145
|
+
document: FFIDCookiePolicySchema
|
|
146
|
+
});
|
|
147
|
+
var DeviceIdSchema = z.string().regex(UUID_REGEX, "must be a UUID string (UUIDv4 or UUIDv7)");
|
|
148
|
+
var ErrorBodyWireSchema = z.object({
|
|
149
|
+
error: z.string().optional(),
|
|
150
|
+
retry_after_ms: z.number().nonnegative().optional(),
|
|
151
|
+
current_version: z.string().optional(),
|
|
152
|
+
submitted_version: z.string().optional()
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// src/consent/errors.ts
|
|
156
|
+
var FFID_CONSENT_ERROR_CODES = [
|
|
157
|
+
// SDK client (network / response)
|
|
158
|
+
"cookie_consent_api_unreachable",
|
|
159
|
+
"cookie_consent_response_invalid",
|
|
160
|
+
"cookie_consent_rate_limited",
|
|
161
|
+
// SDK storage
|
|
162
|
+
"cookie_consent_cookie_write_failed",
|
|
163
|
+
"cookie_consent_parse_failed",
|
|
164
|
+
// API surface (mapped from server status codes)
|
|
165
|
+
"cookie_consent_sync_conflict",
|
|
166
|
+
"cookie_consent_immutable_violation",
|
|
167
|
+
"cookie_consent_policy_outdated",
|
|
168
|
+
"cookie_consent_capture_failed",
|
|
169
|
+
"cookie_consent_db_write_failed",
|
|
170
|
+
// SDK-wide (NOT spec §8.2 reserved — mapped from 401/403)
|
|
171
|
+
"auth_failure"
|
|
172
|
+
];
|
|
173
|
+
var FFIDConsentError = class extends Error {
|
|
174
|
+
code;
|
|
175
|
+
httpStatus;
|
|
176
|
+
/** For rate-limited errors (HTTP 429): seconds suggested by `Retry-After`. */
|
|
177
|
+
retryAfterSec;
|
|
178
|
+
cause;
|
|
179
|
+
constructor(code, message, options) {
|
|
180
|
+
super(message);
|
|
181
|
+
this.name = "FFIDConsentError";
|
|
182
|
+
this.code = code;
|
|
183
|
+
if (options?.httpStatus !== void 0) this.httpStatus = options.httpStatus;
|
|
184
|
+
if (options?.retryAfterSec !== void 0) {
|
|
185
|
+
this.retryAfterSec = options.retryAfterSec;
|
|
186
|
+
}
|
|
187
|
+
if (options?.cause !== void 0) this.cause = options.cause;
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
var DEFAULT_CONSENT_ERROR_MESSAGES = {
|
|
191
|
+
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",
|
|
192
|
+
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",
|
|
193
|
+
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",
|
|
194
|
+
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",
|
|
195
|
+
cookie_consent_parse_failed: "\u4FDD\u5B58\u3055\u308C\u3066\u3044\u305F\u540C\u610F\u60C5\u5831\u304C\u7121\u52B9\u3067\u3057\u305F\u3002\u540C\u610F\u3092\u518D\u53D6\u5F97\u3057\u307E\u3059",
|
|
196
|
+
cookie_consent_sync_conflict: "\u5225\u306E\u7AEF\u672B\u3067\u540C\u610F\u60C5\u5831\u304C\u66F4\u65B0\u3055\u308C\u3066\u3044\u307E\u3059\u3002\u6700\u65B0\u306E\u8A2D\u5B9A\u306B\u540C\u671F\u3057\u307E\u3057\u305F",
|
|
197
|
+
cookie_consent_immutable_violation: "\u540C\u610F\u5C65\u6B74\u306F\u66F8\u304D\u63DB\u3048\u3067\u304D\u306A\u3044\u4ED5\u7D44\u307F\u3067\u3059\u3002\u65B0\u3057\u3044\u540C\u610F\u3068\u3057\u3066\u8A18\u9332\u3055\u308C\u307E\u3057\u305F",
|
|
198
|
+
cookie_consent_policy_outdated: "Cookie \u30DD\u30EA\u30B7\u30FC\u304C\u66F4\u65B0\u3055\u308C\u307E\u3057\u305F\u3002\u6539\u3081\u3066\u540C\u610F\u3092\u304A\u9858\u3044\u3057\u307E\u3059",
|
|
199
|
+
cookie_consent_capture_failed: "\u540C\u610F\u306E\u4FDD\u5B58\u306B\u5931\u6557\u3057\u307E\u3057\u305F\u3002\u3082\u3046\u4E00\u5EA6\u304A\u8A66\u3057\u304F\u3060\u3055\u3044",
|
|
200
|
+
cookie_consent_db_write_failed: "\u30B5\u30FC\u30D0\u30FC\u5074\u3067\u540C\u610F\u306E\u4FDD\u5B58\u306B\u5931\u6557\u3057\u307E\u3057\u305F\u3002\u6642\u9593\u3092\u7F6E\u3044\u3066\u518D\u5EA6\u304A\u8A66\u3057\u304F\u3060\u3055\u3044",
|
|
201
|
+
auth_failure: "\u8A8D\u8A3C\u306B\u5931\u6557\u3057\u307E\u3057\u305F\u3002\u30ED\u30B0\u30A4\u30F3\u3057\u76F4\u3057\u3066\u304B\u3089\u518D\u5EA6\u304A\u8A66\u3057\u304F\u3060\u3055\u3044"
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
// src/consent/storage/encode.ts
|
|
205
|
+
var COOKIE_VERSION = "v1";
|
|
206
|
+
var ALL_DENIED_EXCEPT_NECESSARY = {
|
|
207
|
+
necessary: true,
|
|
208
|
+
functional: false,
|
|
209
|
+
analytics: false,
|
|
210
|
+
marketing: false
|
|
211
|
+
};
|
|
212
|
+
var CATEGORY_LETTERS = {
|
|
213
|
+
n: "necessary",
|
|
214
|
+
f: "functional",
|
|
215
|
+
a: "analytics",
|
|
216
|
+
m: "marketing"
|
|
217
|
+
};
|
|
218
|
+
var ORDERED_LETTERS = ["n", "f", "a", "m"];
|
|
219
|
+
function encodeConsentCookie(cats) {
|
|
220
|
+
const parts = ORDERED_LETTERS.map((letter) => {
|
|
221
|
+
const key = CATEGORY_LETTERS[letter];
|
|
222
|
+
return `${letter}${cats[key] ? "+" : "-"}`;
|
|
223
|
+
});
|
|
224
|
+
return `${COOKIE_VERSION}:${parts.join(",")}`;
|
|
225
|
+
}
|
|
226
|
+
function decodeConsentCookie(raw) {
|
|
227
|
+
if (typeof raw !== "string" || raw.length === 0) {
|
|
228
|
+
return { ...ALL_DENIED_EXCEPT_NECESSARY };
|
|
229
|
+
}
|
|
230
|
+
const colonIdx = raw.indexOf(":");
|
|
231
|
+
if (colonIdx === -1) {
|
|
232
|
+
return { ...ALL_DENIED_EXCEPT_NECESSARY };
|
|
233
|
+
}
|
|
234
|
+
const version = raw.slice(0, colonIdx);
|
|
235
|
+
if (version !== COOKIE_VERSION) {
|
|
236
|
+
return { ...ALL_DENIED_EXCEPT_NECESSARY };
|
|
237
|
+
}
|
|
238
|
+
const body = raw.slice(colonIdx + 1);
|
|
239
|
+
if (body.length === 0) {
|
|
240
|
+
return { ...ALL_DENIED_EXCEPT_NECESSARY };
|
|
241
|
+
}
|
|
242
|
+
const tokens = body.split(",");
|
|
243
|
+
if (tokens.length !== ORDERED_LETTERS.length) {
|
|
244
|
+
return { ...ALL_DENIED_EXCEPT_NECESSARY };
|
|
245
|
+
}
|
|
246
|
+
const seen = /* @__PURE__ */ new Set();
|
|
247
|
+
const result = {};
|
|
248
|
+
for (const token of tokens) {
|
|
249
|
+
if (token.length !== 2) {
|
|
250
|
+
return { ...ALL_DENIED_EXCEPT_NECESSARY };
|
|
251
|
+
}
|
|
252
|
+
const letter = token[0];
|
|
253
|
+
const value = token[1];
|
|
254
|
+
if (!CATEGORY_LETTERS[letter] || value !== "+" && value !== "-") {
|
|
255
|
+
return { ...ALL_DENIED_EXCEPT_NECESSARY };
|
|
256
|
+
}
|
|
257
|
+
if (seen.has(letter)) {
|
|
258
|
+
return { ...ALL_DENIED_EXCEPT_NECESSARY };
|
|
259
|
+
}
|
|
260
|
+
seen.add(letter);
|
|
261
|
+
const key = CATEGORY_LETTERS[letter];
|
|
262
|
+
result[key] = value === "+";
|
|
263
|
+
}
|
|
264
|
+
if (result.necessary !== true) {
|
|
265
|
+
return { ...ALL_DENIED_EXCEPT_NECESSARY };
|
|
266
|
+
}
|
|
267
|
+
return {
|
|
268
|
+
necessary: true,
|
|
269
|
+
functional: result.functional ?? false,
|
|
270
|
+
analytics: result.analytics ?? false,
|
|
271
|
+
marketing: result.marketing ?? false
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// src/consent/storage/cookie.ts
|
|
276
|
+
var CONSENT_COOKIE_NAME = "ffid_consent";
|
|
277
|
+
var SECONDS_PER_MINUTE = 60;
|
|
278
|
+
var MINUTES_PER_HOUR = 60;
|
|
279
|
+
var HOURS_PER_DAY = 24;
|
|
280
|
+
var DAYS_PER_YEAR = 365;
|
|
281
|
+
var CONSENT_COOKIE_MAX_AGE_SEC = SECONDS_PER_MINUTE * MINUTES_PER_HOUR * HOURS_PER_DAY * DAYS_PER_YEAR;
|
|
282
|
+
var CONSENT_SESSION_STORAGE_KEY = "ffid_consent_state";
|
|
283
|
+
function hasDocument() {
|
|
284
|
+
return typeof document !== "undefined";
|
|
285
|
+
}
|
|
286
|
+
function defaultSecure() {
|
|
287
|
+
if (typeof process === "undefined") return true;
|
|
288
|
+
return process.env?.NODE_ENV === "production";
|
|
289
|
+
}
|
|
290
|
+
function buildCookieAttrs(args) {
|
|
291
|
+
const attrs = [
|
|
292
|
+
`${args.name}=${args.value}`,
|
|
293
|
+
`Max-Age=${args.maxAgeSec}`,
|
|
294
|
+
`Path=${args.path}`,
|
|
295
|
+
"SameSite=Lax"
|
|
296
|
+
];
|
|
297
|
+
if (args.domain) attrs.push(`Domain=${args.domain}`);
|
|
298
|
+
if (args.secure) attrs.push("Secure");
|
|
299
|
+
return attrs.join("; ");
|
|
300
|
+
}
|
|
301
|
+
function readConsentCookie() {
|
|
302
|
+
if (!hasDocument()) return { ...ALL_DENIED_EXCEPT_NECESSARY };
|
|
303
|
+
const raw = document.cookie.split(";").map((c) => c.trim()).find((c) => c.startsWith(`${CONSENT_COOKIE_NAME}=`));
|
|
304
|
+
if (!raw) return { ...ALL_DENIED_EXCEPT_NECESSARY };
|
|
305
|
+
const value = raw.slice(CONSENT_COOKIE_NAME.length + 1);
|
|
306
|
+
return decodeConsentCookie(decodeURIComponent(value));
|
|
307
|
+
}
|
|
308
|
+
function writeConsentCookie(cats, opts = {}) {
|
|
309
|
+
if (!hasDocument()) return false;
|
|
310
|
+
const encoded = encodeConsentCookie(cats);
|
|
311
|
+
const secure = opts.secure ?? defaultSecure();
|
|
312
|
+
const path = opts.path ?? "/";
|
|
313
|
+
document.cookie = buildCookieAttrs({
|
|
314
|
+
name: CONSENT_COOKIE_NAME,
|
|
315
|
+
value: encodeURIComponent(encoded),
|
|
316
|
+
maxAgeSec: CONSENT_COOKIE_MAX_AGE_SEC,
|
|
317
|
+
path,
|
|
318
|
+
...opts.domain !== void 0 ? { domain: opts.domain } : {},
|
|
319
|
+
secure
|
|
320
|
+
});
|
|
321
|
+
const verified = readConsentCookie();
|
|
322
|
+
return verified.necessary === cats.necessary && verified.functional === cats.functional && verified.analytics === cats.analytics && verified.marketing === cats.marketing;
|
|
323
|
+
}
|
|
324
|
+
function deleteConsentCookie(opts = {}) {
|
|
325
|
+
if (!hasDocument()) return false;
|
|
326
|
+
const path = opts.path ?? "/";
|
|
327
|
+
document.cookie = buildCookieAttrs({
|
|
328
|
+
name: CONSENT_COOKIE_NAME,
|
|
329
|
+
value: "",
|
|
330
|
+
maxAgeSec: 0,
|
|
331
|
+
path,
|
|
332
|
+
...opts.domain !== void 0 ? { domain: opts.domain } : {}
|
|
333
|
+
});
|
|
334
|
+
return !document.cookie.split(";").map((c) => c.trim()).some((c) => c.startsWith(`${CONSENT_COOKIE_NAME}=`));
|
|
335
|
+
}
|
|
336
|
+
function hasSessionStorage() {
|
|
337
|
+
if (typeof sessionStorage === "undefined") return false;
|
|
338
|
+
try {
|
|
339
|
+
const probe = "__ffid_consent_probe__";
|
|
340
|
+
sessionStorage.setItem(probe, "1");
|
|
341
|
+
sessionStorage.removeItem(probe);
|
|
342
|
+
return true;
|
|
343
|
+
} catch {
|
|
344
|
+
return false;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
function readConsentSessionMirror() {
|
|
348
|
+
if (!hasSessionStorage()) return null;
|
|
349
|
+
try {
|
|
350
|
+
const raw = sessionStorage.getItem(CONSENT_SESSION_STORAGE_KEY);
|
|
351
|
+
if (!raw) return null;
|
|
352
|
+
return decodeConsentCookie(raw);
|
|
353
|
+
} catch {
|
|
354
|
+
return null;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
function writeConsentSessionMirror(cats) {
|
|
358
|
+
if (!hasSessionStorage()) return false;
|
|
359
|
+
try {
|
|
360
|
+
sessionStorage.setItem(CONSENT_SESSION_STORAGE_KEY, encodeConsentCookie(cats));
|
|
361
|
+
return true;
|
|
362
|
+
} catch {
|
|
363
|
+
return false;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
function clearConsentSessionMirror() {
|
|
367
|
+
if (!hasSessionStorage()) return;
|
|
368
|
+
try {
|
|
369
|
+
sessionStorage.removeItem(CONSENT_SESSION_STORAGE_KEY);
|
|
370
|
+
} catch {
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// src/consent/storage/device-id.ts
|
|
375
|
+
var DEVICE_ID_LOCAL_STORAGE_KEY = "ffid_device_id";
|
|
376
|
+
var DEVICE_ID_SESSION_STORAGE_KEY = "ffid_device_id_ephemeral";
|
|
377
|
+
var UUIDV7_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
378
|
+
var UUID_ANY_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
379
|
+
var TIMESTAMP_BYTES = 6;
|
|
380
|
+
var RANDOM_BYTES = 10;
|
|
381
|
+
var UUID_TOTAL_BYTES = 16;
|
|
382
|
+
var BYTE_MASK = 255;
|
|
383
|
+
var BYTE_SIZE = 256;
|
|
384
|
+
var VERSION_NIBBLE_MASK = 15;
|
|
385
|
+
var VERSION_V7_BITS = 112;
|
|
386
|
+
var VARIANT_BITS_MASK = 63;
|
|
387
|
+
var VARIANT_RFC_BITS = 128;
|
|
388
|
+
var HEX_GROUP_1_END = 8;
|
|
389
|
+
var HEX_GROUP_2_END = 12;
|
|
390
|
+
var HEX_GROUP_3_END = 16;
|
|
391
|
+
var HEX_GROUP_4_END = 20;
|
|
392
|
+
var HEX_GROUP_5_END = 32;
|
|
393
|
+
var HEX_RADIX = 16;
|
|
394
|
+
var HEX_BYTE_PAD = 2;
|
|
395
|
+
var RANDOM_32BIT_MAX = 4294967296;
|
|
396
|
+
var TIMESTAMP_HEX_PAD = 12;
|
|
397
|
+
var HEX_PAD_8 = 8;
|
|
398
|
+
var HEX_TS_GROUP_1 = 8;
|
|
399
|
+
var HEX_TS_GROUP_2 = 12;
|
|
400
|
+
var RAND_SLICE_START = 1;
|
|
401
|
+
var RAND_SLICE_END_3 = 4;
|
|
402
|
+
var RAND_TAIL_TOTAL = 12;
|
|
403
|
+
function generateDeviceId() {
|
|
404
|
+
if (typeof crypto === "undefined") {
|
|
405
|
+
return generateUuidV7Fallback();
|
|
406
|
+
}
|
|
407
|
+
if (typeof crypto.getRandomValues === "function") {
|
|
408
|
+
return generateUuidV7Crypto();
|
|
409
|
+
}
|
|
410
|
+
if (typeof crypto.randomUUID === "function") {
|
|
411
|
+
return crypto.randomUUID();
|
|
412
|
+
}
|
|
413
|
+
return generateUuidV7Fallback();
|
|
414
|
+
}
|
|
415
|
+
function generateUuidV7Crypto() {
|
|
416
|
+
const now = Date.now();
|
|
417
|
+
const tsBytes = new Uint8Array(TIMESTAMP_BYTES);
|
|
418
|
+
let remaining = now;
|
|
419
|
+
for (let i = TIMESTAMP_BYTES - 1; i >= 0; i--) {
|
|
420
|
+
tsBytes[i] = remaining & BYTE_MASK;
|
|
421
|
+
remaining = Math.floor(remaining / BYTE_SIZE);
|
|
422
|
+
}
|
|
423
|
+
const rand = new Uint8Array(RANDOM_BYTES);
|
|
424
|
+
crypto.getRandomValues(rand);
|
|
425
|
+
rand[0] = (rand[0] ?? 0) & VERSION_NIBBLE_MASK | VERSION_V7_BITS;
|
|
426
|
+
rand[2] = (rand[2] ?? 0) & VARIANT_BITS_MASK | VARIANT_RFC_BITS;
|
|
427
|
+
const all = new Uint8Array(UUID_TOTAL_BYTES);
|
|
428
|
+
all.set(tsBytes, 0);
|
|
429
|
+
all.set(rand, TIMESTAMP_BYTES);
|
|
430
|
+
const hex = Array.from(all, (b) => b.toString(HEX_RADIX).padStart(HEX_BYTE_PAD, "0")).join("");
|
|
431
|
+
return [
|
|
432
|
+
hex.slice(0, HEX_GROUP_1_END),
|
|
433
|
+
hex.slice(HEX_GROUP_1_END, HEX_GROUP_2_END),
|
|
434
|
+
hex.slice(HEX_GROUP_2_END, HEX_GROUP_3_END),
|
|
435
|
+
hex.slice(HEX_GROUP_3_END, HEX_GROUP_4_END),
|
|
436
|
+
hex.slice(HEX_GROUP_4_END, HEX_GROUP_5_END)
|
|
437
|
+
].join("-");
|
|
438
|
+
}
|
|
439
|
+
function generateUuidV7Fallback() {
|
|
440
|
+
const now = Date.now();
|
|
441
|
+
const r = () => Math.floor(Math.random() * RANDOM_32BIT_MAX).toString(HEX_RADIX).padStart(HEX_PAD_8, "0");
|
|
442
|
+
const tsHex = now.toString(HEX_RADIX).padStart(TIMESTAMP_HEX_PAD, "0").slice(-TIMESTAMP_HEX_PAD);
|
|
443
|
+
return [
|
|
444
|
+
tsHex.slice(0, HEX_TS_GROUP_1),
|
|
445
|
+
tsHex.slice(HEX_TS_GROUP_1, HEX_TS_GROUP_2),
|
|
446
|
+
`7${r().slice(RAND_SLICE_START, RAND_SLICE_END_3)}`,
|
|
447
|
+
`8${r().slice(RAND_SLICE_START, RAND_SLICE_END_3)}`,
|
|
448
|
+
(r() + r()).slice(0, RAND_TAIL_TOTAL)
|
|
449
|
+
].join("-");
|
|
450
|
+
}
|
|
451
|
+
function isValidDeviceId(id) {
|
|
452
|
+
return UUID_ANY_REGEX.test(id);
|
|
453
|
+
}
|
|
454
|
+
function isUuidV7(id) {
|
|
455
|
+
return UUIDV7_REGEX.test(id);
|
|
456
|
+
}
|
|
457
|
+
function tryLocalStorage() {
|
|
458
|
+
if (typeof localStorage === "undefined") return null;
|
|
459
|
+
try {
|
|
460
|
+
const probe = "__ffid_device_probe__";
|
|
461
|
+
localStorage.setItem(probe, "1");
|
|
462
|
+
localStorage.removeItem(probe);
|
|
463
|
+
return localStorage;
|
|
464
|
+
} catch {
|
|
465
|
+
return null;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
function trySessionStorage() {
|
|
469
|
+
if (typeof sessionStorage === "undefined") return null;
|
|
470
|
+
try {
|
|
471
|
+
const probe = "__ffid_device_probe__";
|
|
472
|
+
sessionStorage.setItem(probe, "1");
|
|
473
|
+
sessionStorage.removeItem(probe);
|
|
474
|
+
return sessionStorage;
|
|
475
|
+
} catch {
|
|
476
|
+
return null;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
var warnedEphemeralFallback = false;
|
|
480
|
+
function warnEphemeralOnce(reason) {
|
|
481
|
+
if (warnedEphemeralFallback) return;
|
|
482
|
+
warnedEphemeralFallback = true;
|
|
483
|
+
if (typeof console !== "undefined" && typeof console.warn === "function") {
|
|
484
|
+
console.warn(
|
|
485
|
+
`[ffid-sdk] device-id falling back to ephemeral storage (${reason}). Each request may use a different device id, which breaks server-side shared_device_detected merge UX.`
|
|
486
|
+
);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
function getOrCreateDeviceId() {
|
|
490
|
+
const local = tryLocalStorage();
|
|
491
|
+
if (local) {
|
|
492
|
+
const existing = local.getItem(DEVICE_ID_LOCAL_STORAGE_KEY);
|
|
493
|
+
if (existing && isValidDeviceId(existing)) return existing;
|
|
494
|
+
const fresh = generateDeviceId();
|
|
495
|
+
try {
|
|
496
|
+
local.setItem(DEVICE_ID_LOCAL_STORAGE_KEY, fresh);
|
|
497
|
+
} catch {
|
|
498
|
+
const session2 = trySessionStorage();
|
|
499
|
+
if (session2) {
|
|
500
|
+
try {
|
|
501
|
+
session2.setItem(DEVICE_ID_SESSION_STORAGE_KEY, fresh);
|
|
502
|
+
} catch {
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
return fresh;
|
|
507
|
+
}
|
|
508
|
+
const session = trySessionStorage();
|
|
509
|
+
if (session) {
|
|
510
|
+
const existing = session.getItem(DEVICE_ID_SESSION_STORAGE_KEY);
|
|
511
|
+
if (existing && isValidDeviceId(existing)) return existing;
|
|
512
|
+
const fresh = generateDeviceId();
|
|
513
|
+
try {
|
|
514
|
+
session.setItem(DEVICE_ID_SESSION_STORAGE_KEY, fresh);
|
|
515
|
+
} catch {
|
|
516
|
+
}
|
|
517
|
+
return fresh;
|
|
518
|
+
}
|
|
519
|
+
warnEphemeralOnce("no localStorage / sessionStorage available");
|
|
520
|
+
return generateDeviceId();
|
|
521
|
+
}
|
|
522
|
+
function clearDeviceId() {
|
|
523
|
+
const local = tryLocalStorage();
|
|
524
|
+
if (local) {
|
|
525
|
+
try {
|
|
526
|
+
local.removeItem(DEVICE_ID_LOCAL_STORAGE_KEY);
|
|
527
|
+
} catch {
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
const session = trySessionStorage();
|
|
531
|
+
if (session) {
|
|
532
|
+
try {
|
|
533
|
+
session.removeItem(DEVICE_ID_SESSION_STORAGE_KEY);
|
|
534
|
+
} catch {
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// src/consent/client/mappers.ts
|
|
540
|
+
function categoriesFromWire(row) {
|
|
541
|
+
return {
|
|
542
|
+
necessary: true,
|
|
543
|
+
functional: row.functional,
|
|
544
|
+
analytics: row.analytics,
|
|
545
|
+
marketing: row.marketing
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
function decidedStateFromRow(row, source, needsRenewal = false) {
|
|
549
|
+
return {
|
|
550
|
+
hasDecided: true,
|
|
551
|
+
needsRenewal,
|
|
552
|
+
categories: categoriesFromWire(row),
|
|
553
|
+
decidedAt: new Date(row.decided_at),
|
|
554
|
+
cookiePolicyVersion: row.cookie_policy_version,
|
|
555
|
+
source
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
function mapMeWireToState(wire) {
|
|
559
|
+
if (!wire.consent) {
|
|
560
|
+
return {
|
|
561
|
+
hasDecided: false,
|
|
562
|
+
needsRenewal: wire.needs_renewal,
|
|
563
|
+
categories: wire.default_state ?? { ...ALL_DENIED_EXCEPT_NECESSARY },
|
|
564
|
+
decidedAt: null,
|
|
565
|
+
cookiePolicyVersion: null,
|
|
566
|
+
source: null
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
return decidedStateFromRow(wire.consent, "persisted_unknown", wire.needs_renewal);
|
|
570
|
+
}
|
|
571
|
+
function mapConsentWireToState(wire, source) {
|
|
572
|
+
return decidedStateFromRow(wire.consent, source);
|
|
573
|
+
}
|
|
574
|
+
function mapWithdrawWireToState(wire) {
|
|
575
|
+
return decidedStateFromRow(wire.consent, "api_withdraw");
|
|
576
|
+
}
|
|
577
|
+
function mapSyncWireToResult(wire) {
|
|
578
|
+
return {
|
|
579
|
+
state: decidedStateFromRow(wire.consent, "sync_merge"),
|
|
580
|
+
mergeStrategy: wire.merge_strategy,
|
|
581
|
+
warning: wire.warning
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
function stateFromLocalCategories(cats) {
|
|
585
|
+
return {
|
|
586
|
+
hasDecided: false,
|
|
587
|
+
needsRenewal: false,
|
|
588
|
+
categories: cats,
|
|
589
|
+
decidedAt: null,
|
|
590
|
+
cookiePolicyVersion: null,
|
|
591
|
+
source: null
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// src/consent/client/consent-client.ts
|
|
596
|
+
var HTTP_TOO_MANY_REQUESTS = 429;
|
|
597
|
+
var HTTP_UNAUTHORIZED = 401;
|
|
598
|
+
var HTTP_FORBIDDEN = 403;
|
|
599
|
+
var HTTP_BAD_REQUEST = 400;
|
|
600
|
+
var HTTP_NOT_FOUND = 404;
|
|
601
|
+
var HTTP_CONFLICT = 409;
|
|
602
|
+
var HTTP_INTERNAL_SERVER_ERROR = 500;
|
|
603
|
+
var MS_PER_SECOND = 1e3;
|
|
604
|
+
var RETRY_JITTER_RATIO = 0.2;
|
|
605
|
+
var DEFAULT_RETRY_AFTER_SEC = 1;
|
|
606
|
+
function parseRetryAfterHeader(header) {
|
|
607
|
+
if (!header) return void 0;
|
|
608
|
+
const asNumber = Number(header);
|
|
609
|
+
if (Number.isFinite(asNumber) && asNumber >= 0) return asNumber;
|
|
610
|
+
const ms = Date.parse(header);
|
|
611
|
+
if (!Number.isFinite(ms)) return void 0;
|
|
612
|
+
const diffSec = (ms - Date.now()) / MS_PER_SECOND;
|
|
613
|
+
return diffSec > 0 ? Math.ceil(diffSec) : 0;
|
|
614
|
+
}
|
|
615
|
+
function jitteredDelayMs(baseSec) {
|
|
616
|
+
const jitter = (Math.random() * 2 - 1) * RETRY_JITTER_RATIO;
|
|
617
|
+
return Math.max(0, baseSec * MS_PER_SECOND * (1 + jitter));
|
|
618
|
+
}
|
|
619
|
+
function parseErrorBody(json, retryAfterHeader) {
|
|
620
|
+
const parsed = ErrorBodyWireSchema.safeParse(json);
|
|
621
|
+
const body = parsed.success ? parsed.data : {};
|
|
622
|
+
const fromHeader = parseRetryAfterHeader(retryAfterHeader);
|
|
623
|
+
const fromBody = body.retry_after_ms !== void 0 ? Math.ceil(body.retry_after_ms / MS_PER_SECOND) : void 0;
|
|
624
|
+
return {
|
|
625
|
+
serverCode: body.error,
|
|
626
|
+
retryAfterSec: fromHeader ?? fromBody,
|
|
627
|
+
body
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
function mapHttpToConsentError(status, parsed, url) {
|
|
631
|
+
const { serverCode, retryAfterSec, body } = parsed;
|
|
632
|
+
if (status === HTTP_TOO_MANY_REQUESTS) {
|
|
633
|
+
return new FFIDConsentError(
|
|
634
|
+
"cookie_consent_rate_limited",
|
|
635
|
+
`Rate limit exceeded for ${url}`,
|
|
636
|
+
{
|
|
637
|
+
httpStatus: status,
|
|
638
|
+
...retryAfterSec !== void 0 ? { retryAfterSec } : {}
|
|
639
|
+
}
|
|
640
|
+
);
|
|
641
|
+
}
|
|
642
|
+
if (status === HTTP_UNAUTHORIZED || status === HTTP_FORBIDDEN) {
|
|
643
|
+
return new FFIDConsentError(
|
|
644
|
+
"auth_failure",
|
|
645
|
+
`Authentication failed (${serverCode ?? "unauthorized"})`,
|
|
646
|
+
{ httpStatus: status }
|
|
647
|
+
);
|
|
648
|
+
}
|
|
649
|
+
if (status === HTTP_NOT_FOUND) {
|
|
650
|
+
return new FFIDConsentError(
|
|
651
|
+
"cookie_consent_api_unreachable",
|
|
652
|
+
`Consent endpoint not found at ${url} (check baseUrl / API version)`,
|
|
653
|
+
{ httpStatus: status }
|
|
654
|
+
);
|
|
655
|
+
}
|
|
656
|
+
if (status === HTTP_BAD_REQUEST && serverCode === "policy_version_outdated") {
|
|
657
|
+
return new FFIDConsentError(
|
|
658
|
+
"cookie_consent_policy_outdated",
|
|
659
|
+
`Cookie policy version outdated (current=${body.current_version ?? "?"})`,
|
|
660
|
+
{ httpStatus: status }
|
|
661
|
+
);
|
|
662
|
+
}
|
|
663
|
+
if (status === HTTP_CONFLICT) {
|
|
664
|
+
if (serverCode === "sync_conflict") {
|
|
665
|
+
return new FFIDConsentError(
|
|
666
|
+
"cookie_consent_sync_conflict",
|
|
667
|
+
"Concurrent consent update detected on the server",
|
|
668
|
+
{ httpStatus: status }
|
|
669
|
+
);
|
|
670
|
+
}
|
|
671
|
+
return new FFIDConsentError(
|
|
672
|
+
"cookie_consent_immutable_violation",
|
|
673
|
+
"Consent row is immutable; new row will be recorded instead",
|
|
674
|
+
{ httpStatus: status }
|
|
675
|
+
);
|
|
676
|
+
}
|
|
677
|
+
if (status >= HTTP_INTERNAL_SERVER_ERROR) {
|
|
678
|
+
if (serverCode === "db_write_failed") {
|
|
679
|
+
return new FFIDConsentError(
|
|
680
|
+
"cookie_consent_db_write_failed",
|
|
681
|
+
"Server-side database write failed",
|
|
682
|
+
{ httpStatus: status }
|
|
683
|
+
);
|
|
684
|
+
}
|
|
685
|
+
return new FFIDConsentError(
|
|
686
|
+
"cookie_consent_capture_failed",
|
|
687
|
+
`Server error for ${url} (HTTP ${status})`,
|
|
688
|
+
{ httpStatus: status }
|
|
689
|
+
);
|
|
690
|
+
}
|
|
691
|
+
return new FFIDConsentError(
|
|
692
|
+
"cookie_consent_capture_failed",
|
|
693
|
+
`Unexpected status ${status} for ${url}`,
|
|
694
|
+
{ httpStatus: status }
|
|
695
|
+
);
|
|
696
|
+
}
|
|
697
|
+
function createConsentClient(opts) {
|
|
698
|
+
const baseUrl = opts.baseUrl.replace(/\/+$/, "");
|
|
699
|
+
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
700
|
+
const defaultLang = opts.defaultLang ?? "ja";
|
|
701
|
+
const maxAutoRetry429 = opts.maxAutoRetry429 ?? 1;
|
|
702
|
+
async function buildHeaders(withBearer) {
|
|
703
|
+
const deviceId = opts.getDeviceId();
|
|
704
|
+
if (!deviceId) {
|
|
705
|
+
throw new FFIDConsentError(
|
|
706
|
+
"auth_failure",
|
|
707
|
+
"Device ID not initialized (SDK header builder)"
|
|
708
|
+
);
|
|
709
|
+
}
|
|
710
|
+
const headers = {
|
|
711
|
+
"X-Service-Api-Key": opts.serviceApiKey,
|
|
712
|
+
"X-FFID-Device-Id": deviceId,
|
|
713
|
+
"Content-Type": "application/json"
|
|
714
|
+
};
|
|
715
|
+
if (withBearer && opts.getAccessToken) {
|
|
716
|
+
const token = await opts.getAccessToken();
|
|
717
|
+
if (token) {
|
|
718
|
+
headers["Authorization"] = `Bearer ${token}`;
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
return headers;
|
|
722
|
+
}
|
|
723
|
+
async function performFetch(url, init) {
|
|
724
|
+
try {
|
|
725
|
+
const res = await fetchImpl(url, init);
|
|
726
|
+
return { res };
|
|
727
|
+
} catch (cause) {
|
|
728
|
+
return {
|
|
729
|
+
err: new FFIDConsentError(
|
|
730
|
+
"cookie_consent_api_unreachable",
|
|
731
|
+
`Network error reaching ${url}`,
|
|
732
|
+
{ cause }
|
|
733
|
+
)
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
async function request(reqOpts, parseFn) {
|
|
738
|
+
const url = `${baseUrl}${reqOpts.path}`;
|
|
739
|
+
const withBearer = reqOpts.withBearer ?? Boolean(opts.getAccessToken);
|
|
740
|
+
let headers;
|
|
741
|
+
try {
|
|
742
|
+
headers = await buildHeaders(withBearer);
|
|
743
|
+
} catch (err) {
|
|
744
|
+
if (err instanceof FFIDConsentError) return { ok: false, error: err };
|
|
745
|
+
return {
|
|
746
|
+
ok: false,
|
|
747
|
+
error: new FFIDConsentError(
|
|
748
|
+
"cookie_consent_api_unreachable",
|
|
749
|
+
"Failed to acquire SDK headers (token / device-id)",
|
|
750
|
+
{ cause: err }
|
|
751
|
+
)
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
const init = {
|
|
755
|
+
method: reqOpts.method,
|
|
756
|
+
headers,
|
|
757
|
+
credentials: "include"
|
|
758
|
+
};
|
|
759
|
+
if (reqOpts.body !== void 0) init.body = JSON.stringify(reqOpts.body);
|
|
760
|
+
if (reqOpts.signal) init.signal = reqOpts.signal;
|
|
761
|
+
let res;
|
|
762
|
+
for (let attempt = 0; attempt <= maxAutoRetry429; attempt++) {
|
|
763
|
+
const fetched = await performFetch(url, init);
|
|
764
|
+
if (fetched.err) return { ok: false, error: fetched.err };
|
|
765
|
+
res = fetched.res;
|
|
766
|
+
if (res.status !== HTTP_TOO_MANY_REQUESTS || attempt >= maxAutoRetry429) {
|
|
767
|
+
break;
|
|
768
|
+
}
|
|
769
|
+
const parsedHeader = parseRetryAfterHeader(res.headers.get("Retry-After"));
|
|
770
|
+
const delaySec = parsedHeader ?? DEFAULT_RETRY_AFTER_SEC;
|
|
771
|
+
await new Promise((resolve) => {
|
|
772
|
+
setTimeout(resolve, jitteredDelayMs(delaySec));
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
if (!res) {
|
|
776
|
+
return {
|
|
777
|
+
ok: false,
|
|
778
|
+
error: new FFIDConsentError(
|
|
779
|
+
"cookie_consent_api_unreachable",
|
|
780
|
+
`Unreachable: fetch loop produced no response for ${url}`
|
|
781
|
+
)
|
|
782
|
+
};
|
|
783
|
+
}
|
|
784
|
+
if (!res.ok) {
|
|
785
|
+
let errorJson = {};
|
|
786
|
+
try {
|
|
787
|
+
errorJson = await res.json();
|
|
788
|
+
} catch {
|
|
789
|
+
}
|
|
790
|
+
const parsed = parseErrorBody(errorJson, res.headers.get("Retry-After"));
|
|
791
|
+
return {
|
|
792
|
+
ok: false,
|
|
793
|
+
error: mapHttpToConsentError(res.status, parsed, url)
|
|
794
|
+
};
|
|
795
|
+
}
|
|
796
|
+
let json;
|
|
797
|
+
try {
|
|
798
|
+
json = await res.json();
|
|
799
|
+
} catch (err) {
|
|
800
|
+
return {
|
|
801
|
+
ok: false,
|
|
802
|
+
error: new FFIDConsentError(
|
|
803
|
+
"cookie_consent_response_invalid",
|
|
804
|
+
`Invalid JSON in response from ${url}`,
|
|
805
|
+
{ cause: err }
|
|
806
|
+
)
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
try {
|
|
810
|
+
return { ok: true, value: parseFn(json) };
|
|
811
|
+
} catch (err) {
|
|
812
|
+
return {
|
|
813
|
+
ok: false,
|
|
814
|
+
error: new FFIDConsentError(
|
|
815
|
+
"cookie_consent_response_invalid",
|
|
816
|
+
`Response shape mismatch for ${url}`,
|
|
817
|
+
{ cause: err }
|
|
818
|
+
)
|
|
819
|
+
};
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
return {
|
|
823
|
+
async getMe(signal) {
|
|
824
|
+
return request(
|
|
825
|
+
{ method: "GET", path: "/api/v1/ext/consent/me", ...signal ? { signal } : {} },
|
|
826
|
+
(json) => {
|
|
827
|
+
const parsed = GetConsentMeWireSchema.parse(json);
|
|
828
|
+
return mapMeWireToState(parsed);
|
|
829
|
+
}
|
|
830
|
+
);
|
|
831
|
+
},
|
|
832
|
+
async submit(categories, source, cookiePolicyVersion, signal) {
|
|
833
|
+
return request(
|
|
834
|
+
{
|
|
835
|
+
method: "POST",
|
|
836
|
+
path: "/api/v1/ext/consent",
|
|
837
|
+
body: {
|
|
838
|
+
categories,
|
|
839
|
+
source,
|
|
840
|
+
cookie_policy_version: cookiePolicyVersion
|
|
841
|
+
},
|
|
842
|
+
...signal ? { signal } : {}
|
|
843
|
+
},
|
|
844
|
+
(json) => {
|
|
845
|
+
const parsed = PostConsentWireSchema.parse(json);
|
|
846
|
+
return mapConsentWireToState(parsed, source);
|
|
847
|
+
}
|
|
848
|
+
);
|
|
849
|
+
},
|
|
850
|
+
async sync(localConsent, signal) {
|
|
851
|
+
const body = {
|
|
852
|
+
local_consent: localConsent ? {
|
|
853
|
+
necessary: true,
|
|
854
|
+
functional: localConsent.functional,
|
|
855
|
+
analytics: localConsent.analytics,
|
|
856
|
+
marketing: localConsent.marketing,
|
|
857
|
+
cookie_policy_version: localConsent.cookiePolicyVersion
|
|
858
|
+
} : null
|
|
859
|
+
};
|
|
860
|
+
return request(
|
|
861
|
+
{
|
|
862
|
+
method: "POST",
|
|
863
|
+
path: "/api/v1/ext/consent/sync",
|
|
864
|
+
body,
|
|
865
|
+
withBearer: true,
|
|
866
|
+
...signal ? { signal } : {}
|
|
867
|
+
},
|
|
868
|
+
(json) => {
|
|
869
|
+
const parsed = PostSyncWireSchema.parse(json);
|
|
870
|
+
return mapSyncWireToResult(parsed);
|
|
871
|
+
}
|
|
872
|
+
);
|
|
873
|
+
},
|
|
874
|
+
async withdraw(signal) {
|
|
875
|
+
return request(
|
|
876
|
+
{
|
|
877
|
+
method: "POST",
|
|
878
|
+
path: "/api/v1/ext/consent/withdraw",
|
|
879
|
+
body: {},
|
|
880
|
+
...signal ? { signal } : {}
|
|
881
|
+
},
|
|
882
|
+
(json) => {
|
|
883
|
+
const parsed = PostWithdrawWireSchema.parse(json);
|
|
884
|
+
return mapWithdrawWireToState(parsed);
|
|
885
|
+
}
|
|
886
|
+
);
|
|
887
|
+
},
|
|
888
|
+
async getCategories(lang, signal) {
|
|
889
|
+
const resolvedLang = lang ?? defaultLang;
|
|
890
|
+
return request(
|
|
891
|
+
{
|
|
892
|
+
method: "GET",
|
|
893
|
+
path: `/api/v1/ext/consent/categories?lang=${resolvedLang}`,
|
|
894
|
+
...signal ? { signal } : {}
|
|
895
|
+
},
|
|
896
|
+
(json) => {
|
|
897
|
+
const parsed = GetCategoriesWireSchema.parse(json);
|
|
898
|
+
return parsed.categories;
|
|
899
|
+
}
|
|
900
|
+
);
|
|
901
|
+
},
|
|
902
|
+
async getDocument(lang, signal) {
|
|
903
|
+
const resolvedLang = lang ?? defaultLang;
|
|
904
|
+
return request(
|
|
905
|
+
{
|
|
906
|
+
method: "GET",
|
|
907
|
+
path: `/api/v1/ext/consent/document?lang=${resolvedLang}`,
|
|
908
|
+
...signal ? { signal } : {}
|
|
909
|
+
},
|
|
910
|
+
(json) => {
|
|
911
|
+
const parsed = GetDocumentWireSchema.parse(json);
|
|
912
|
+
return parsed.document;
|
|
913
|
+
}
|
|
914
|
+
);
|
|
915
|
+
}
|
|
916
|
+
};
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
// src/consent/client/gtag-bridge.ts
|
|
920
|
+
var DEFAULT_GTAG_SRC = "https://www.googletagmanager.com/gtag/js";
|
|
921
|
+
function bool(g) {
|
|
922
|
+
return g ? "granted" : "denied";
|
|
923
|
+
}
|
|
924
|
+
function mapCategoriesToGtagParams(cats) {
|
|
925
|
+
return {
|
|
926
|
+
security_storage: "granted",
|
|
927
|
+
// necessary is always granted
|
|
928
|
+
functionality_storage: bool(cats.functional),
|
|
929
|
+
personalization_storage: bool(cats.functional),
|
|
930
|
+
analytics_storage: bool(cats.analytics),
|
|
931
|
+
ad_storage: bool(cats.marketing),
|
|
932
|
+
ad_user_data: bool(cats.marketing),
|
|
933
|
+
ad_personalization: bool(cats.marketing)
|
|
934
|
+
};
|
|
935
|
+
}
|
|
936
|
+
function pushDataLayer(win, args) {
|
|
937
|
+
if (!win) return;
|
|
938
|
+
win.dataLayer = win.dataLayer ?? [];
|
|
939
|
+
win.dataLayer.push(args);
|
|
940
|
+
}
|
|
941
|
+
function createGtagBridge(opts = {}) {
|
|
942
|
+
const win = opts.win ?? (typeof window !== "undefined" ? window : void 0);
|
|
943
|
+
const gaId = opts.gaMeasurementId ?? null;
|
|
944
|
+
const scriptSrc = opts.gtagScriptSrc ?? DEFAULT_GTAG_SRC;
|
|
945
|
+
let gaScriptInjected = false;
|
|
946
|
+
return {
|
|
947
|
+
setDefaultConsent() {
|
|
948
|
+
const denyParams = mapCategoriesToGtagParams({
|
|
949
|
+
functional: false,
|
|
950
|
+
analytics: false,
|
|
951
|
+
marketing: false
|
|
952
|
+
});
|
|
953
|
+
pushDataLayer(win, ["consent", "default", denyParams]);
|
|
954
|
+
},
|
|
955
|
+
updateConsent(cats) {
|
|
956
|
+
const params = mapCategoriesToGtagParams(cats);
|
|
957
|
+
pushDataLayer(win, ["consent", "update", params]);
|
|
958
|
+
},
|
|
959
|
+
applyGaInjection(cats) {
|
|
960
|
+
if (!win || !gaId) return;
|
|
961
|
+
if (!cats.analytics) return;
|
|
962
|
+
if (gaScriptInjected) return;
|
|
963
|
+
const doc = win.document;
|
|
964
|
+
if (!doc) return;
|
|
965
|
+
const script = doc.createElement("script");
|
|
966
|
+
script.async = true;
|
|
967
|
+
script.src = `${scriptSrc}?id=${encodeURIComponent(gaId)}`;
|
|
968
|
+
doc.head.appendChild(script);
|
|
969
|
+
pushDataLayer(win, ["js", /* @__PURE__ */ new Date()]);
|
|
970
|
+
pushDataLayer(win, ["config", gaId]);
|
|
971
|
+
gaScriptInjected = true;
|
|
972
|
+
}
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
var FFID_CONSENT_NOT_DECIDED_STATE = {
|
|
976
|
+
hasDecided: false,
|
|
977
|
+
needsRenewal: false,
|
|
978
|
+
categories: { ...ALL_DENIED_EXCEPT_NECESSARY },
|
|
979
|
+
decidedAt: null,
|
|
980
|
+
cookiePolicyVersion: null,
|
|
981
|
+
source: null
|
|
982
|
+
};
|
|
983
|
+
function defaultAction() {
|
|
984
|
+
throw new Error(
|
|
985
|
+
"[ffid-sdk] consent action called outside of <FFIDAnalyticsProvider>"
|
|
986
|
+
);
|
|
987
|
+
}
|
|
988
|
+
var FALLBACK_VALUE = {
|
|
989
|
+
state: FFID_CONSENT_NOT_DECIDED_STATE,
|
|
990
|
+
isLoading: false,
|
|
991
|
+
error: null,
|
|
992
|
+
categoryMetadata: null,
|
|
993
|
+
policy: null,
|
|
994
|
+
isBannerOpen: false,
|
|
995
|
+
isPreferencesOpen: false,
|
|
996
|
+
openBanner: defaultAction,
|
|
997
|
+
closeBanner: defaultAction,
|
|
998
|
+
openPreferences: defaultAction,
|
|
999
|
+
closePreferences: defaultAction,
|
|
1000
|
+
acceptAll: defaultAction,
|
|
1001
|
+
acceptNecessaryOnly: defaultAction,
|
|
1002
|
+
setCategories: defaultAction,
|
|
1003
|
+
withdraw: defaultAction,
|
|
1004
|
+
refresh: defaultAction,
|
|
1005
|
+
syncWithUser: defaultAction
|
|
1006
|
+
};
|
|
1007
|
+
var FFIDConsentContext = createContext(FALLBACK_VALUE);
|
|
1008
|
+
var DEFAULT_MERGE_WARNING_MESSAGES = {
|
|
1009
|
+
local_state_overridden: "\u5225\u306E\u7AEF\u672B\u3067\u4FDD\u5B58\u3055\u308C\u305F Cookie \u540C\u610F\u8A2D\u5B9A\u3067\u66F4\u65B0\u3055\u308C\u307E\u3057\u305F",
|
|
1010
|
+
shared_device_detected: "\u3053\u306E\u7AEF\u672B\u306F\u5225\u306E\u30A2\u30AB\u30A6\u30F3\u30C8\u3067\u3082\u4F7F\u7528\u3055\u308C\u3066\u3044\u307E\u3059\u3002Cookie \u8A2D\u5B9A\u306F\u30A2\u30AB\u30A6\u30F3\u30C8\u3054\u3068\u306B\u4FDD\u5B58\u3055\u308C\u3066\u3044\u307E\u3059",
|
|
1011
|
+
drift_detected: "\u4FDD\u5B58\u3055\u308C\u3066\u3044\u305F\u8A2D\u5B9A\u3068\u7570\u306A\u308B\u9805\u76EE\u304C\u3042\u3063\u305F\u305F\u3081\u3001\u65B0\u3057\u304F\u8A18\u9332\u3055\u308C\u307E\u3057\u305F"
|
|
1012
|
+
};
|
|
1013
|
+
function applyChanges(base, changes) {
|
|
1014
|
+
return {
|
|
1015
|
+
necessary: true,
|
|
1016
|
+
functional: changes.functional ?? base.functional,
|
|
1017
|
+
analytics: changes.analytics ?? base.analytics,
|
|
1018
|
+
marketing: changes.marketing ?? base.marketing
|
|
1019
|
+
};
|
|
1020
|
+
}
|
|
1021
|
+
function FFIDAnalyticsProvider({
|
|
1022
|
+
baseUrl,
|
|
1023
|
+
serviceApiKey,
|
|
1024
|
+
getAccessToken,
|
|
1025
|
+
gaMeasurementId,
|
|
1026
|
+
sentryReplay,
|
|
1027
|
+
sentry,
|
|
1028
|
+
onConsentChange,
|
|
1029
|
+
onError,
|
|
1030
|
+
autoShowBanner = true,
|
|
1031
|
+
fetchImpl,
|
|
1032
|
+
children
|
|
1033
|
+
}) {
|
|
1034
|
+
const [state, setState] = useState(
|
|
1035
|
+
FFID_CONSENT_NOT_DECIDED_STATE
|
|
1036
|
+
);
|
|
1037
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
1038
|
+
const [error, setError] = useState(null);
|
|
1039
|
+
const [isBannerOpen, setIsBannerOpen] = useState(false);
|
|
1040
|
+
const [isPreferencesOpen, setIsPreferencesOpen] = useState(false);
|
|
1041
|
+
const [categoryMetadata, setCategoryMetadata] = useState(null);
|
|
1042
|
+
const [policy, setPolicy] = useState(null);
|
|
1043
|
+
const clientRef = useRef(null);
|
|
1044
|
+
const bridgeRef = useRef(null);
|
|
1045
|
+
const deviceIdRef = useRef(null);
|
|
1046
|
+
if (!clientRef.current) {
|
|
1047
|
+
deviceIdRef.current = getOrCreateDeviceId();
|
|
1048
|
+
const clientOpts = {
|
|
1049
|
+
baseUrl,
|
|
1050
|
+
serviceApiKey,
|
|
1051
|
+
getDeviceId: () => deviceIdRef.current ?? ""
|
|
1052
|
+
};
|
|
1053
|
+
if (getAccessToken !== void 0) clientOpts.getAccessToken = getAccessToken;
|
|
1054
|
+
if (fetchImpl !== void 0) clientOpts.fetchImpl = fetchImpl;
|
|
1055
|
+
clientRef.current = createConsentClient(clientOpts);
|
|
1056
|
+
}
|
|
1057
|
+
if (!bridgeRef.current) {
|
|
1058
|
+
bridgeRef.current = createGtagBridge({
|
|
1059
|
+
gaMeasurementId: gaMeasurementId ?? null
|
|
1060
|
+
});
|
|
1061
|
+
bridgeRef.current.setDefaultConsent();
|
|
1062
|
+
}
|
|
1063
|
+
const applyCategoriesSideEffects = useCallback(
|
|
1064
|
+
(cats) => {
|
|
1065
|
+
const bridge = bridgeRef.current;
|
|
1066
|
+
if (bridge) {
|
|
1067
|
+
bridge.updateConsent(cats);
|
|
1068
|
+
bridge.applyGaInjection(cats);
|
|
1069
|
+
}
|
|
1070
|
+
if (sentryReplay) {
|
|
1071
|
+
const sdk = sentry ?? (typeof window !== "undefined" ? window.Sentry : void 0);
|
|
1072
|
+
const replay = sdk?.getReplay?.();
|
|
1073
|
+
if (replay) {
|
|
1074
|
+
try {
|
|
1075
|
+
if (cats.functional) {
|
|
1076
|
+
replay.start();
|
|
1077
|
+
} else {
|
|
1078
|
+
const maybe = replay.stop();
|
|
1079
|
+
if (maybe && typeof maybe.catch === "function") {
|
|
1080
|
+
;
|
|
1081
|
+
maybe.catch(() => {
|
|
1082
|
+
});
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
} catch (err) {
|
|
1086
|
+
if (typeof console !== "undefined") {
|
|
1087
|
+
console.warn("[ffid-sdk] Sentry replay control failed", err);
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
},
|
|
1093
|
+
[sentry, sentryReplay]
|
|
1094
|
+
);
|
|
1095
|
+
const persistCategories = useCallback(
|
|
1096
|
+
(cats) => {
|
|
1097
|
+
const cookieWritten = writeConsentCookie(cats);
|
|
1098
|
+
const mirrorWritten = writeConsentSessionMirror(cats);
|
|
1099
|
+
if (!cookieWritten) {
|
|
1100
|
+
const writeErr = new FFIDConsentError(
|
|
1101
|
+
"cookie_consent_cookie_write_failed",
|
|
1102
|
+
mirrorWritten ? "\u540C\u610F\u60C5\u5831\u3092 Cookie \u306B\u4FDD\u5B58\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002\u3053\u306E\u30BB\u30C3\u30B7\u30E7\u30F3\u306E\u307F\u6709\u52B9\u3067\u3059" : DEFAULT_CONSENT_ERROR_MESSAGES.cookie_consent_cookie_write_failed
|
|
1103
|
+
);
|
|
1104
|
+
setError(writeErr);
|
|
1105
|
+
onError?.(writeErr);
|
|
1106
|
+
}
|
|
1107
|
+
},
|
|
1108
|
+
[onError]
|
|
1109
|
+
);
|
|
1110
|
+
const handleResult = useCallback(
|
|
1111
|
+
(result, warning) => {
|
|
1112
|
+
if (!result.ok) {
|
|
1113
|
+
setError(result.error);
|
|
1114
|
+
onError?.(result.error);
|
|
1115
|
+
return;
|
|
1116
|
+
}
|
|
1117
|
+
setError(null);
|
|
1118
|
+
setState(result.value);
|
|
1119
|
+
applyCategoriesSideEffects(result.value.categories);
|
|
1120
|
+
persistCategories(result.value.categories);
|
|
1121
|
+
onConsentChange?.(result.value, warning ?? null);
|
|
1122
|
+
},
|
|
1123
|
+
[applyCategoriesSideEffects, onConsentChange, onError, persistCategories]
|
|
1124
|
+
);
|
|
1125
|
+
useEffect(() => {
|
|
1126
|
+
let cancelled = false;
|
|
1127
|
+
async function bootstrap() {
|
|
1128
|
+
const local = readConsentSessionMirror() ?? readConsentCookie();
|
|
1129
|
+
applyCategoriesSideEffects(local);
|
|
1130
|
+
setState(stateFromLocalCategories(local));
|
|
1131
|
+
const client = clientRef.current;
|
|
1132
|
+
const result = await client.getMe();
|
|
1133
|
+
if (cancelled) return;
|
|
1134
|
+
if (!result.ok) {
|
|
1135
|
+
setError(result.error);
|
|
1136
|
+
onError?.(result.error);
|
|
1137
|
+
setIsLoading(false);
|
|
1138
|
+
return;
|
|
1139
|
+
}
|
|
1140
|
+
setState(result.value);
|
|
1141
|
+
applyCategoriesSideEffects(result.value.categories);
|
|
1142
|
+
writeConsentSessionMirror(result.value.categories);
|
|
1143
|
+
setIsLoading(false);
|
|
1144
|
+
if (!result.value.hasDecided && autoShowBanner) {
|
|
1145
|
+
setIsBannerOpen(true);
|
|
1146
|
+
}
|
|
1147
|
+
if (result.value.needsRenewal && autoShowBanner) {
|
|
1148
|
+
setIsBannerOpen(true);
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
void bootstrap();
|
|
1152
|
+
return () => {
|
|
1153
|
+
cancelled = true;
|
|
1154
|
+
};
|
|
1155
|
+
}, []);
|
|
1156
|
+
const ensurePolicyVersion = useCallback(async () => {
|
|
1157
|
+
if (policy) return { ok: true, version: policy.version };
|
|
1158
|
+
const client = clientRef.current;
|
|
1159
|
+
const result = await client.getDocument();
|
|
1160
|
+
if (!result.ok) {
|
|
1161
|
+
setError(result.error);
|
|
1162
|
+
onError?.(result.error);
|
|
1163
|
+
return { ok: false, error: result.error };
|
|
1164
|
+
}
|
|
1165
|
+
setPolicy(result.value);
|
|
1166
|
+
return { ok: true, version: result.value.version };
|
|
1167
|
+
}, [onError, policy]);
|
|
1168
|
+
const submitFromBanner = useCallback(
|
|
1169
|
+
async (cats) => {
|
|
1170
|
+
const version = await ensurePolicyVersion();
|
|
1171
|
+
if (!version.ok) return { ok: false, error: version.error };
|
|
1172
|
+
const client = clientRef.current;
|
|
1173
|
+
const result = await client.submit(cats, "banner", version.version);
|
|
1174
|
+
if (!result.ok && result.error.code === "cookie_consent_policy_outdated") {
|
|
1175
|
+
setPolicy(null);
|
|
1176
|
+
}
|
|
1177
|
+
handleResult(result);
|
|
1178
|
+
if (result.ok) setIsBannerOpen(false);
|
|
1179
|
+
return result;
|
|
1180
|
+
},
|
|
1181
|
+
[ensurePolicyVersion, handleResult]
|
|
1182
|
+
);
|
|
1183
|
+
const acceptAll = useCallback(
|
|
1184
|
+
() => submitFromBanner({
|
|
1185
|
+
functional: true,
|
|
1186
|
+
analytics: true,
|
|
1187
|
+
marketing: true
|
|
1188
|
+
}),
|
|
1189
|
+
[submitFromBanner]
|
|
1190
|
+
);
|
|
1191
|
+
const acceptNecessaryOnly = useCallback(
|
|
1192
|
+
() => submitFromBanner({
|
|
1193
|
+
functional: false,
|
|
1194
|
+
analytics: false,
|
|
1195
|
+
marketing: false
|
|
1196
|
+
}),
|
|
1197
|
+
[submitFromBanner]
|
|
1198
|
+
);
|
|
1199
|
+
const setCategories = useCallback(
|
|
1200
|
+
async (changes, source = "preference_center") => {
|
|
1201
|
+
const version = await ensurePolicyVersion();
|
|
1202
|
+
if (!version.ok) return { ok: false, error: version.error };
|
|
1203
|
+
const base = state.categories;
|
|
1204
|
+
const next = applyChanges(base, changes);
|
|
1205
|
+
const client = clientRef.current;
|
|
1206
|
+
const result = await client.submit(
|
|
1207
|
+
{
|
|
1208
|
+
functional: next.functional,
|
|
1209
|
+
analytics: next.analytics,
|
|
1210
|
+
marketing: next.marketing
|
|
1211
|
+
},
|
|
1212
|
+
source,
|
|
1213
|
+
version.version
|
|
1214
|
+
);
|
|
1215
|
+
if (!result.ok && result.error.code === "cookie_consent_policy_outdated") {
|
|
1216
|
+
setPolicy(null);
|
|
1217
|
+
}
|
|
1218
|
+
handleResult(result);
|
|
1219
|
+
return result;
|
|
1220
|
+
},
|
|
1221
|
+
[ensurePolicyVersion, handleResult, state.categories]
|
|
1222
|
+
);
|
|
1223
|
+
const withdraw = useCallback(async () => {
|
|
1224
|
+
const client = clientRef.current;
|
|
1225
|
+
const result = await client.withdraw();
|
|
1226
|
+
handleResult(result);
|
|
1227
|
+
return result;
|
|
1228
|
+
}, [handleResult]);
|
|
1229
|
+
const refresh = useCallback(async () => {
|
|
1230
|
+
const client = clientRef.current;
|
|
1231
|
+
setIsLoading(true);
|
|
1232
|
+
const result = await client.getMe();
|
|
1233
|
+
handleResult(result);
|
|
1234
|
+
setIsLoading(false);
|
|
1235
|
+
}, [handleResult]);
|
|
1236
|
+
const syncWithUser = useCallback(async () => {
|
|
1237
|
+
const client = clientRef.current;
|
|
1238
|
+
const localCats = readConsentCookie();
|
|
1239
|
+
const ver = await ensurePolicyVersion();
|
|
1240
|
+
const result = await client.sync(
|
|
1241
|
+
ver.ok ? {
|
|
1242
|
+
necessary: true,
|
|
1243
|
+
functional: localCats.functional,
|
|
1244
|
+
analytics: localCats.analytics,
|
|
1245
|
+
marketing: localCats.marketing,
|
|
1246
|
+
cookiePolicyVersion: ver.version
|
|
1247
|
+
} : null
|
|
1248
|
+
);
|
|
1249
|
+
if (!result.ok) {
|
|
1250
|
+
setError(result.error);
|
|
1251
|
+
onError?.(result.error);
|
|
1252
|
+
return result;
|
|
1253
|
+
}
|
|
1254
|
+
setError(null);
|
|
1255
|
+
setState(result.value.state);
|
|
1256
|
+
applyCategoriesSideEffects(result.value.state.categories);
|
|
1257
|
+
persistCategories(result.value.state.categories);
|
|
1258
|
+
const warning = result.value.warning ? {
|
|
1259
|
+
code: result.value.warning,
|
|
1260
|
+
message: DEFAULT_MERGE_WARNING_MESSAGES[result.value.warning]
|
|
1261
|
+
} : null;
|
|
1262
|
+
onConsentChange?.(result.value.state, warning);
|
|
1263
|
+
return result;
|
|
1264
|
+
}, [
|
|
1265
|
+
applyCategoriesSideEffects,
|
|
1266
|
+
ensurePolicyVersion,
|
|
1267
|
+
onConsentChange,
|
|
1268
|
+
onError,
|
|
1269
|
+
persistCategories
|
|
1270
|
+
]);
|
|
1271
|
+
const openBanner = useCallback(() => setIsBannerOpen(true), []);
|
|
1272
|
+
const closeBanner = useCallback(() => setIsBannerOpen(false), []);
|
|
1273
|
+
const openPreferences = useCallback(async () => {
|
|
1274
|
+
if (!categoryMetadata) {
|
|
1275
|
+
const client = clientRef.current;
|
|
1276
|
+
const result = await client.getCategories();
|
|
1277
|
+
if (!result.ok) {
|
|
1278
|
+
setError(result.error);
|
|
1279
|
+
onError?.(result.error);
|
|
1280
|
+
} else {
|
|
1281
|
+
setCategoryMetadata(result.value);
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
setIsPreferencesOpen(true);
|
|
1285
|
+
}, [categoryMetadata, onError]);
|
|
1286
|
+
const closePreferences = useCallback(() => setIsPreferencesOpen(false), []);
|
|
1287
|
+
const contextValue = useMemo(
|
|
1288
|
+
() => ({
|
|
1289
|
+
state,
|
|
1290
|
+
isLoading,
|
|
1291
|
+
error,
|
|
1292
|
+
categoryMetadata,
|
|
1293
|
+
policy,
|
|
1294
|
+
isBannerOpen,
|
|
1295
|
+
isPreferencesOpen,
|
|
1296
|
+
openBanner,
|
|
1297
|
+
closeBanner,
|
|
1298
|
+
openPreferences,
|
|
1299
|
+
closePreferences,
|
|
1300
|
+
acceptAll,
|
|
1301
|
+
acceptNecessaryOnly,
|
|
1302
|
+
setCategories,
|
|
1303
|
+
withdraw,
|
|
1304
|
+
refresh,
|
|
1305
|
+
syncWithUser
|
|
1306
|
+
}),
|
|
1307
|
+
[
|
|
1308
|
+
acceptAll,
|
|
1309
|
+
acceptNecessaryOnly,
|
|
1310
|
+
categoryMetadata,
|
|
1311
|
+
closeBanner,
|
|
1312
|
+
closePreferences,
|
|
1313
|
+
error,
|
|
1314
|
+
isBannerOpen,
|
|
1315
|
+
isLoading,
|
|
1316
|
+
isPreferencesOpen,
|
|
1317
|
+
openBanner,
|
|
1318
|
+
openPreferences,
|
|
1319
|
+
policy,
|
|
1320
|
+
refresh,
|
|
1321
|
+
setCategories,
|
|
1322
|
+
state,
|
|
1323
|
+
syncWithUser,
|
|
1324
|
+
withdraw
|
|
1325
|
+
]
|
|
1326
|
+
);
|
|
1327
|
+
return /* @__PURE__ */ jsx(FFIDConsentContext.Provider, { value: contextValue, children });
|
|
1328
|
+
}
|
|
1329
|
+
function useFFIDConsent() {
|
|
1330
|
+
const ctx = useContext(FFIDConsentContext);
|
|
1331
|
+
return {
|
|
1332
|
+
state: ctx.state,
|
|
1333
|
+
categories: ctx.state.categories,
|
|
1334
|
+
isLoading: ctx.isLoading,
|
|
1335
|
+
error: ctx.error,
|
|
1336
|
+
isBannerOpen: ctx.isBannerOpen,
|
|
1337
|
+
isPreferencesOpen: ctx.isPreferencesOpen,
|
|
1338
|
+
openBanner: ctx.openBanner,
|
|
1339
|
+
closeBanner: ctx.closeBanner,
|
|
1340
|
+
openPreferences: ctx.openPreferences,
|
|
1341
|
+
closePreferences: ctx.closePreferences,
|
|
1342
|
+
acceptAll: ctx.acceptAll,
|
|
1343
|
+
acceptNecessaryOnly: ctx.acceptNecessaryOnly,
|
|
1344
|
+
setCategories: ctx.setCategories,
|
|
1345
|
+
withdraw: ctx.withdraw,
|
|
1346
|
+
refresh: ctx.refresh
|
|
1347
|
+
};
|
|
1348
|
+
}
|
|
1349
|
+
function useFFIDConsentPreferences() {
|
|
1350
|
+
const ctx = useContext(FFIDConsentContext);
|
|
1351
|
+
return {
|
|
1352
|
+
state: ctx.state,
|
|
1353
|
+
categoryMetadata: ctx.categoryMetadata,
|
|
1354
|
+
policy: ctx.policy,
|
|
1355
|
+
isLoading: ctx.isLoading,
|
|
1356
|
+
save: (changes) => ctx.setCategories(changes, "preference_center"),
|
|
1357
|
+
withdraw: ctx.withdraw
|
|
1358
|
+
};
|
|
1359
|
+
}
|
|
1360
|
+
var DEFAULT_STYLE = {
|
|
1361
|
+
position: "fixed",
|
|
1362
|
+
bottom: 0,
|
|
1363
|
+
left: 0,
|
|
1364
|
+
right: 0,
|
|
1365
|
+
padding: "16px",
|
|
1366
|
+
backgroundColor: "#fff",
|
|
1367
|
+
borderTop: "1px solid #e5e7eb",
|
|
1368
|
+
boxShadow: "0 -4px 12px rgba(0,0,0,0.08)",
|
|
1369
|
+
zIndex: 9999,
|
|
1370
|
+
display: "flex",
|
|
1371
|
+
flexDirection: "column",
|
|
1372
|
+
gap: "12px"
|
|
1373
|
+
};
|
|
1374
|
+
var DEFAULT_BUTTON_GROUP_STYLE = {
|
|
1375
|
+
display: "flex",
|
|
1376
|
+
gap: "8px",
|
|
1377
|
+
flexWrap: "wrap"
|
|
1378
|
+
};
|
|
1379
|
+
var DEFAULT_BUTTON_STYLE = {
|
|
1380
|
+
flex: "1 1 auto",
|
|
1381
|
+
minWidth: "120px",
|
|
1382
|
+
padding: "10px 16px",
|
|
1383
|
+
borderRadius: "6px",
|
|
1384
|
+
border: "1px solid #d1d5db",
|
|
1385
|
+
background: "#f9fafb",
|
|
1386
|
+
cursor: "pointer",
|
|
1387
|
+
fontSize: "14px"
|
|
1388
|
+
};
|
|
1389
|
+
var DEFAULT_PRIMARY_STYLE = {
|
|
1390
|
+
...DEFAULT_BUTTON_STYLE,
|
|
1391
|
+
background: "#111827",
|
|
1392
|
+
color: "#fff",
|
|
1393
|
+
border: "1px solid #111827"
|
|
1394
|
+
};
|
|
1395
|
+
function FFIDCookieBanner({
|
|
1396
|
+
className,
|
|
1397
|
+
classNames,
|
|
1398
|
+
message,
|
|
1399
|
+
acceptAllLabel,
|
|
1400
|
+
necessaryOnlyLabel,
|
|
1401
|
+
preferencesLabel,
|
|
1402
|
+
style
|
|
1403
|
+
}) {
|
|
1404
|
+
const {
|
|
1405
|
+
state,
|
|
1406
|
+
isBannerOpen,
|
|
1407
|
+
acceptAll,
|
|
1408
|
+
acceptNecessaryOnly,
|
|
1409
|
+
openPreferences,
|
|
1410
|
+
error
|
|
1411
|
+
} = useFFIDConsent();
|
|
1412
|
+
if (!isBannerOpen && state.hasDecided && !state.needsRenewal) return null;
|
|
1413
|
+
return /* @__PURE__ */ jsxs(
|
|
1414
|
+
"div",
|
|
1415
|
+
{
|
|
1416
|
+
role: "dialog",
|
|
1417
|
+
"aria-modal": "false",
|
|
1418
|
+
"aria-label": "Cookie \u540C\u610F",
|
|
1419
|
+
className: className ?? classNames?.root,
|
|
1420
|
+
style: style ?? DEFAULT_STYLE,
|
|
1421
|
+
"data-testid": "ffid-cookie-banner",
|
|
1422
|
+
children: [
|
|
1423
|
+
/* @__PURE__ */ jsx("p", { className: classNames?.message, style: { margin: 0, fontSize: "14px" }, children: message ?? "\u5F53\u30B5\u30A4\u30C8\u3067\u306F\u3001\u30B5\u30FC\u30D3\u30B9\u306E\u54C1\u8CEA\u5411\u4E0A\u306E\u305F\u3081 Cookie \u3092\u4F7F\u7528\u3057\u307E\u3059\u3002" }),
|
|
1424
|
+
error ? /* @__PURE__ */ jsx(
|
|
1425
|
+
"p",
|
|
1426
|
+
{
|
|
1427
|
+
role: "alert",
|
|
1428
|
+
style: { margin: 0, color: "#dc2626", fontSize: "13px" },
|
|
1429
|
+
children: error.message
|
|
1430
|
+
}
|
|
1431
|
+
) : null,
|
|
1432
|
+
/* @__PURE__ */ jsxs(
|
|
1433
|
+
"div",
|
|
1434
|
+
{
|
|
1435
|
+
className: classNames?.buttonGroup,
|
|
1436
|
+
style: DEFAULT_BUTTON_GROUP_STYLE,
|
|
1437
|
+
children: [
|
|
1438
|
+
/* @__PURE__ */ jsx(
|
|
1439
|
+
"button",
|
|
1440
|
+
{
|
|
1441
|
+
type: "button",
|
|
1442
|
+
className: classNames?.primaryButton,
|
|
1443
|
+
style: DEFAULT_PRIMARY_STYLE,
|
|
1444
|
+
onClick: () => {
|
|
1445
|
+
void acceptAll();
|
|
1446
|
+
},
|
|
1447
|
+
"data-testid": "ffid-cookie-banner-accept-all",
|
|
1448
|
+
children: acceptAllLabel ?? "\u3059\u3079\u3066\u540C\u610F"
|
|
1449
|
+
}
|
|
1450
|
+
),
|
|
1451
|
+
/* @__PURE__ */ jsx(
|
|
1452
|
+
"button",
|
|
1453
|
+
{
|
|
1454
|
+
type: "button",
|
|
1455
|
+
className: classNames?.secondaryButton,
|
|
1456
|
+
style: DEFAULT_BUTTON_STYLE,
|
|
1457
|
+
onClick: () => {
|
|
1458
|
+
void acceptNecessaryOnly();
|
|
1459
|
+
},
|
|
1460
|
+
"data-testid": "ffid-cookie-banner-necessary-only",
|
|
1461
|
+
children: necessaryOnlyLabel ?? "\u5FC5\u8981\u306A\u3082\u306E\u3060\u3051"
|
|
1462
|
+
}
|
|
1463
|
+
),
|
|
1464
|
+
/* @__PURE__ */ jsx(
|
|
1465
|
+
"button",
|
|
1466
|
+
{
|
|
1467
|
+
type: "button",
|
|
1468
|
+
className: classNames?.linkButton,
|
|
1469
|
+
style: DEFAULT_BUTTON_STYLE,
|
|
1470
|
+
onClick: () => {
|
|
1471
|
+
void openPreferences();
|
|
1472
|
+
},
|
|
1473
|
+
"data-testid": "ffid-cookie-banner-preferences",
|
|
1474
|
+
children: preferencesLabel ?? "\u8A73\u7D30\u8A2D\u5B9A"
|
|
1475
|
+
}
|
|
1476
|
+
)
|
|
1477
|
+
]
|
|
1478
|
+
}
|
|
1479
|
+
)
|
|
1480
|
+
]
|
|
1481
|
+
}
|
|
1482
|
+
);
|
|
1483
|
+
}
|
|
1484
|
+
var DEFAULT_BACKDROP_STYLE = {
|
|
1485
|
+
position: "fixed",
|
|
1486
|
+
inset: 0,
|
|
1487
|
+
background: "rgba(0,0,0,0.4)",
|
|
1488
|
+
display: "flex",
|
|
1489
|
+
alignItems: "center",
|
|
1490
|
+
justifyContent: "center",
|
|
1491
|
+
zIndex: 1e4
|
|
1492
|
+
};
|
|
1493
|
+
var DEFAULT_MODAL_STYLE = {
|
|
1494
|
+
width: "min(90vw, 480px)",
|
|
1495
|
+
maxHeight: "85vh",
|
|
1496
|
+
overflow: "auto",
|
|
1497
|
+
background: "#fff",
|
|
1498
|
+
borderRadius: "8px",
|
|
1499
|
+
padding: "20px",
|
|
1500
|
+
boxShadow: "0 8px 32px rgba(0,0,0,0.18)",
|
|
1501
|
+
display: "flex",
|
|
1502
|
+
flexDirection: "column",
|
|
1503
|
+
gap: "16px"
|
|
1504
|
+
};
|
|
1505
|
+
var ROW_STYLE = {
|
|
1506
|
+
display: "flex",
|
|
1507
|
+
alignItems: "center",
|
|
1508
|
+
justifyContent: "space-between",
|
|
1509
|
+
gap: "12px",
|
|
1510
|
+
padding: "12px 0",
|
|
1511
|
+
borderBottom: "1px solid #f3f4f6"
|
|
1512
|
+
};
|
|
1513
|
+
function FFIDCookieSettings({
|
|
1514
|
+
className,
|
|
1515
|
+
classNames,
|
|
1516
|
+
open,
|
|
1517
|
+
showWithdrawButton = true
|
|
1518
|
+
}) {
|
|
1519
|
+
const { isPreferencesOpen, closePreferences } = useFFIDConsent();
|
|
1520
|
+
const { state, categoryMetadata, save, withdraw } = useFFIDConsentPreferences();
|
|
1521
|
+
const visible = open ?? isPreferencesOpen;
|
|
1522
|
+
const [draft, setDraft] = useState({
|
|
1523
|
+
functional: state.categories.functional,
|
|
1524
|
+
analytics: state.categories.analytics,
|
|
1525
|
+
marketing: state.categories.marketing
|
|
1526
|
+
});
|
|
1527
|
+
useEffect(() => {
|
|
1528
|
+
setDraft({
|
|
1529
|
+
functional: state.categories.functional,
|
|
1530
|
+
analytics: state.categories.analytics,
|
|
1531
|
+
marketing: state.categories.marketing
|
|
1532
|
+
});
|
|
1533
|
+
}, [
|
|
1534
|
+
state.categories.functional,
|
|
1535
|
+
state.categories.analytics,
|
|
1536
|
+
state.categories.marketing
|
|
1537
|
+
]);
|
|
1538
|
+
const openerRef = useRef(null);
|
|
1539
|
+
useEffect(() => {
|
|
1540
|
+
if (!visible) return;
|
|
1541
|
+
if (typeof document === "undefined") return;
|
|
1542
|
+
openerRef.current = document.activeElement;
|
|
1543
|
+
const onKey = (e) => {
|
|
1544
|
+
if (e.key === "Escape") closePreferences();
|
|
1545
|
+
};
|
|
1546
|
+
document.addEventListener("keydown", onKey);
|
|
1547
|
+
return () => {
|
|
1548
|
+
document.removeEventListener("keydown", onKey);
|
|
1549
|
+
openerRef.current?.focus?.();
|
|
1550
|
+
};
|
|
1551
|
+
}, [visible, closePreferences]);
|
|
1552
|
+
if (!visible) return null;
|
|
1553
|
+
return /* @__PURE__ */ jsx(
|
|
1554
|
+
"div",
|
|
1555
|
+
{
|
|
1556
|
+
role: "presentation",
|
|
1557
|
+
className: classNames?.backdrop,
|
|
1558
|
+
style: DEFAULT_BACKDROP_STYLE,
|
|
1559
|
+
onClick: (e) => {
|
|
1560
|
+
if (e.target === e.currentTarget) closePreferences();
|
|
1561
|
+
},
|
|
1562
|
+
"data-testid": "ffid-cookie-settings-backdrop",
|
|
1563
|
+
children: /* @__PURE__ */ jsxs(
|
|
1564
|
+
"div",
|
|
1565
|
+
{
|
|
1566
|
+
role: "dialog",
|
|
1567
|
+
"aria-modal": "true",
|
|
1568
|
+
"aria-label": "Cookie \u8A2D\u5B9A",
|
|
1569
|
+
className: className ?? classNames?.root,
|
|
1570
|
+
style: DEFAULT_MODAL_STYLE,
|
|
1571
|
+
"data-testid": "ffid-cookie-settings",
|
|
1572
|
+
children: [
|
|
1573
|
+
/* @__PURE__ */ jsx("header", { className: classNames?.header, children: /* @__PURE__ */ jsx("h2", { style: { margin: 0, fontSize: "18px" }, children: "Cookie \u8A2D\u5B9A" }) }),
|
|
1574
|
+
/* @__PURE__ */ jsx("div", { children: (categoryMetadata ?? FALLBACK_CATEGORIES).map((cat) => {
|
|
1575
|
+
const checked = cat.code === "necessary" ? true : draft[cat.code] ?? false;
|
|
1576
|
+
return /* @__PURE__ */ jsxs(
|
|
1577
|
+
"label",
|
|
1578
|
+
{
|
|
1579
|
+
className: classNames?.categoryRow,
|
|
1580
|
+
style: ROW_STYLE,
|
|
1581
|
+
children: [
|
|
1582
|
+
/* @__PURE__ */ jsxs("div", { style: { flex: 1 }, children: [
|
|
1583
|
+
/* @__PURE__ */ jsx("div", { style: { fontWeight: 600, fontSize: "14px" }, children: cat.label }),
|
|
1584
|
+
/* @__PURE__ */ jsx("div", { style: { color: "#6b7280", fontSize: "13px" }, children: cat.description })
|
|
1585
|
+
] }),
|
|
1586
|
+
/* @__PURE__ */ jsx(
|
|
1587
|
+
"input",
|
|
1588
|
+
{
|
|
1589
|
+
type: "checkbox",
|
|
1590
|
+
checked,
|
|
1591
|
+
disabled: cat.isRequired,
|
|
1592
|
+
"aria-disabled": cat.isRequired,
|
|
1593
|
+
onChange: (e) => {
|
|
1594
|
+
if (cat.isRequired) return;
|
|
1595
|
+
setDraft((d) => ({
|
|
1596
|
+
...d,
|
|
1597
|
+
[cat.code]: e.target.checked
|
|
1598
|
+
}));
|
|
1599
|
+
},
|
|
1600
|
+
"data-testid": `ffid-cookie-toggle-${cat.code}`
|
|
1601
|
+
}
|
|
1602
|
+
)
|
|
1603
|
+
]
|
|
1604
|
+
},
|
|
1605
|
+
cat.code
|
|
1606
|
+
);
|
|
1607
|
+
}) }),
|
|
1608
|
+
/* @__PURE__ */ jsxs(
|
|
1609
|
+
"div",
|
|
1610
|
+
{
|
|
1611
|
+
style: { display: "flex", gap: "8px", justifyContent: "flex-end" },
|
|
1612
|
+
children: [
|
|
1613
|
+
showWithdrawButton ? /* @__PURE__ */ jsx(
|
|
1614
|
+
"button",
|
|
1615
|
+
{
|
|
1616
|
+
type: "button",
|
|
1617
|
+
className: classNames?.secondaryButton,
|
|
1618
|
+
onClick: () => {
|
|
1619
|
+
void withdraw().then(() => closePreferences());
|
|
1620
|
+
},
|
|
1621
|
+
style: {
|
|
1622
|
+
padding: "10px 16px",
|
|
1623
|
+
borderRadius: "6px",
|
|
1624
|
+
border: "1px solid #d1d5db",
|
|
1625
|
+
background: "#fff",
|
|
1626
|
+
cursor: "pointer"
|
|
1627
|
+
},
|
|
1628
|
+
"data-testid": "ffid-cookie-settings-withdraw",
|
|
1629
|
+
children: "\u3059\u3079\u3066\u64A4\u56DE"
|
|
1630
|
+
}
|
|
1631
|
+
) : null,
|
|
1632
|
+
/* @__PURE__ */ jsx(
|
|
1633
|
+
"button",
|
|
1634
|
+
{
|
|
1635
|
+
type: "button",
|
|
1636
|
+
className: classNames?.primaryButton,
|
|
1637
|
+
onClick: () => {
|
|
1638
|
+
void save(draft).then(() => closePreferences());
|
|
1639
|
+
},
|
|
1640
|
+
style: {
|
|
1641
|
+
padding: "10px 16px",
|
|
1642
|
+
borderRadius: "6px",
|
|
1643
|
+
border: "1px solid #111827",
|
|
1644
|
+
background: "#111827",
|
|
1645
|
+
color: "#fff",
|
|
1646
|
+
cursor: "pointer"
|
|
1647
|
+
},
|
|
1648
|
+
"data-testid": "ffid-cookie-settings-save",
|
|
1649
|
+
children: "\u5909\u66F4\u3092\u4FDD\u5B58"
|
|
1650
|
+
}
|
|
1651
|
+
)
|
|
1652
|
+
]
|
|
1653
|
+
}
|
|
1654
|
+
)
|
|
1655
|
+
]
|
|
1656
|
+
}
|
|
1657
|
+
)
|
|
1658
|
+
}
|
|
1659
|
+
);
|
|
1660
|
+
}
|
|
1661
|
+
var FALLBACK_CATEGORIES = [
|
|
1662
|
+
{
|
|
1663
|
+
code: "necessary",
|
|
1664
|
+
displayOrder: 0,
|
|
1665
|
+
isRequired: true,
|
|
1666
|
+
label: "\u5FC5\u9808",
|
|
1667
|
+
description: "\u30B5\u30FC\u30D3\u30B9\u306E\u52D5\u4F5C\u306B\u4E0D\u53EF\u6B20\u306A Cookie \u3067\u3059\u3002\u7121\u52B9\u306B\u3059\u308B\u3053\u3068\u306F\u3067\u304D\u307E\u305B\u3093\u3002",
|
|
1668
|
+
exampleServices: []
|
|
1669
|
+
},
|
|
1670
|
+
{
|
|
1671
|
+
code: "functional",
|
|
1672
|
+
displayOrder: 1,
|
|
1673
|
+
isRequired: false,
|
|
1674
|
+
label: "\u6A5F\u80FD",
|
|
1675
|
+
description: "\u30E6\u30FC\u30B6\u30FC\u4F53\u9A13\u3092\u5411\u4E0A\u3055\u305B\u308B Cookie \u3067\u3059\u3002",
|
|
1676
|
+
exampleServices: []
|
|
1677
|
+
},
|
|
1678
|
+
{
|
|
1679
|
+
code: "analytics",
|
|
1680
|
+
displayOrder: 2,
|
|
1681
|
+
isRequired: false,
|
|
1682
|
+
label: "\u5206\u6790",
|
|
1683
|
+
description: "\u30B5\u30A4\u30C8\u5229\u7528\u72B6\u6CC1\u306E\u8A08\u6E2C\u306B\u4F7F\u7528\u3057\u307E\u3059\u3002",
|
|
1684
|
+
exampleServices: []
|
|
1685
|
+
},
|
|
1686
|
+
{
|
|
1687
|
+
code: "marketing",
|
|
1688
|
+
displayOrder: 3,
|
|
1689
|
+
isRequired: false,
|
|
1690
|
+
label: "\u5E83\u544A",
|
|
1691
|
+
description: "\u95A2\u5FC3\u306B\u5408\u3063\u305F\u5E83\u544A\u914D\u4FE1\u306B\u4F7F\u7528\u3057\u307E\u3059\u3002",
|
|
1692
|
+
exampleServices: []
|
|
1693
|
+
}
|
|
1694
|
+
];
|
|
1695
|
+
var DEFAULT_LINK_STYLE = {
|
|
1696
|
+
background: "transparent",
|
|
1697
|
+
border: 0,
|
|
1698
|
+
padding: 0,
|
|
1699
|
+
cursor: "pointer",
|
|
1700
|
+
color: "inherit",
|
|
1701
|
+
textDecoration: "underline",
|
|
1702
|
+
font: "inherit"
|
|
1703
|
+
};
|
|
1704
|
+
function FFIDCookieLink({
|
|
1705
|
+
children,
|
|
1706
|
+
className,
|
|
1707
|
+
style,
|
|
1708
|
+
id
|
|
1709
|
+
}) {
|
|
1710
|
+
const { openPreferences } = useFFIDConsent();
|
|
1711
|
+
return /* @__PURE__ */ jsx(
|
|
1712
|
+
"button",
|
|
1713
|
+
{
|
|
1714
|
+
type: "button",
|
|
1715
|
+
className,
|
|
1716
|
+
style: style ?? DEFAULT_LINK_STYLE,
|
|
1717
|
+
onClick: () => {
|
|
1718
|
+
void openPreferences();
|
|
1719
|
+
},
|
|
1720
|
+
"data-testid": "ffid-cookie-link",
|
|
1721
|
+
...id !== void 0 ? { id } : {},
|
|
1722
|
+
children: children ?? "Cookie \u8A2D\u5B9A"
|
|
1723
|
+
}
|
|
1724
|
+
);
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
export { ALL_DENIED_EXCEPT_NECESSARY, CONSENT_COOKIE_MAX_AGE_SEC, CONSENT_COOKIE_NAME, CONSENT_SESSION_STORAGE_KEY, COOKIE_VERSION, DEFAULT_CONSENT_ERROR_MESSAGES, DEFAULT_MERGE_WARNING_MESSAGES, DEVICE_ID_LOCAL_STORAGE_KEY, DEVICE_ID_SESSION_STORAGE_KEY, DeviceIdSchema, FFIDAnalyticsProvider, FFIDConsentCategoriesSchema, FFIDConsentCategoryCodeSchema, FFIDConsentCategoryMetadataSchema, FFIDConsentContext, FFIDConsentError, FFIDConsentMergeStrategySchema, FFIDConsentMergeWarningSchema, FFIDConsentSourceSchema, FFIDConsentStateSchema, FFIDConsentUpdateSchema, FFIDCookieBanner, FFIDCookieLink, FFIDCookiePolicySchema, FFIDCookieSettings, FFIDInternalConsentSourceSchema, FFIDPublicConsentSourceSchema, FFID_CONSENT_ERROR_CODES, FFID_CONSENT_NOT_DECIDED_STATE, GetCategoriesWireSchema, GetConsentMeWireSchema, GetDocumentWireSchema, PostConsentRequestSchema, PostConsentWireSchema, PostSyncRequestSchema, PostSyncWireSchema, PostWithdrawWireSchema, clearConsentSessionMirror, clearDeviceId, createConsentClient, createGtagBridge, decodeConsentCookie, deleteConsentCookie, encodeConsentCookie, generateDeviceId, getOrCreateDeviceId, isUuidV7, isValidDeviceId, mapCategoriesToGtagParams, mapConsentWireToState, mapMeWireToState, mapSyncWireToResult, mapWithdrawWireToState, readConsentCookie, readConsentSessionMirror, useFFIDConsent, useFFIDConsentPreferences, writeConsentCookie, writeConsentSessionMirror };
|