@redacto.io/consent-sdk-js 1.0.0-beta.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/redacto.d.mts +33 -3
- package/dist/redacto.d.ts +33 -3
- package/dist/redacto.global.js +2 -2
- package/dist/redacto.global.js.map +1 -1
- package/dist/redacto.js +349 -96
- package/dist/redacto.mjs +349 -96
- package/package.json +1 -1
package/dist/redacto.js
CHANGED
|
@@ -182,17 +182,6 @@ function createMediaQuery(query) {
|
|
|
182
182
|
};
|
|
183
183
|
}
|
|
184
184
|
|
|
185
|
-
// src/core/jwt-decode.ts
|
|
186
|
-
var import_jwt_decode = require("jwt-decode");
|
|
187
|
-
function decodeTokenSafely(token) {
|
|
188
|
-
try {
|
|
189
|
-
return (0, import_jwt_decode.jwtDecode)(token);
|
|
190
|
-
} catch (error) {
|
|
191
|
-
console.warn("Failed to decode JWT token:", error);
|
|
192
|
-
return null;
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
185
|
// src/services/cache.ts
|
|
197
186
|
var apiCache = /* @__PURE__ */ new Map();
|
|
198
187
|
var CACHE_TTL = 5 * 60 * 1e3;
|
|
@@ -213,6 +202,133 @@ function clearApiCache() {
|
|
|
213
202
|
apiCache.clear();
|
|
214
203
|
}
|
|
215
204
|
|
|
205
|
+
// src/core/jwt-decode.ts
|
|
206
|
+
var import_jwt_decode = require("jwt-decode");
|
|
207
|
+
function decodeTokenSafely(token) {
|
|
208
|
+
try {
|
|
209
|
+
return (0, import_jwt_decode.jwtDecode)(token);
|
|
210
|
+
} catch (error) {
|
|
211
|
+
console.warn("Failed to decode JWT token:", error);
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// src/api/sandbox.ts
|
|
217
|
+
var SANDBOX_TOKEN_HEADER = "X-Consent-Token";
|
|
218
|
+
var resolveSandboxAuth = (options) => {
|
|
219
|
+
var _a, _b, _c, _d, _e;
|
|
220
|
+
const token = (_a = options.token) == null ? void 0 : _a.trim();
|
|
221
|
+
if (!token) {
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
const organisationUuid = (_b = options.organisationUuid) == null ? void 0 : _b.trim();
|
|
225
|
+
const workspaceUuid = (_c = options.workspaceUuid) == null ? void 0 : _c.trim();
|
|
226
|
+
const ucic = (_d = options.sandboxUcic) == null ? void 0 : _d.trim();
|
|
227
|
+
const subject = (_e = options.sandboxSubject) == null ? void 0 : _e.trim();
|
|
228
|
+
if (!organisationUuid || !workspaceUuid) {
|
|
229
|
+
throw new Error(
|
|
230
|
+
"Sandbox mode requires organisationUuid and workspaceUuid"
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
if (!ucic && !subject) {
|
|
234
|
+
throw new Error("Sandbox mode requires a UCIC, email, or mobile");
|
|
235
|
+
}
|
|
236
|
+
return { token, ucic, subject: subject != null ? subject : "", organisationUuid, workspaceUuid };
|
|
237
|
+
};
|
|
238
|
+
var resolveRequestAuth = (accessToken, options) => {
|
|
239
|
+
const sandbox = resolveSandboxAuth(options);
|
|
240
|
+
if (sandbox) {
|
|
241
|
+
return {
|
|
242
|
+
organisationUuid: sandbox.organisationUuid,
|
|
243
|
+
workspaceUuid: sandbox.workspaceUuid,
|
|
244
|
+
sandbox
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
const decodedToken = accessToken ? decodeTokenSafely(accessToken) : null;
|
|
248
|
+
if (!decodedToken) {
|
|
249
|
+
throw new Error("Invalid access token");
|
|
250
|
+
}
|
|
251
|
+
const { organisation_uuid, workspace_uuid } = decodedToken;
|
|
252
|
+
if (!organisation_uuid || !workspace_uuid) {
|
|
253
|
+
throw new Error("Invalid token: missing organization or workspace UUID");
|
|
254
|
+
}
|
|
255
|
+
return {
|
|
256
|
+
organisationUuid: organisation_uuid,
|
|
257
|
+
workspaceUuid: workspace_uuid,
|
|
258
|
+
sandbox: null
|
|
259
|
+
};
|
|
260
|
+
};
|
|
261
|
+
var buildAuthHeaders = (accessToken, sandbox) => {
|
|
262
|
+
if (sandbox) {
|
|
263
|
+
return { [SANDBOX_TOKEN_HEADER]: sandbox.token };
|
|
264
|
+
}
|
|
265
|
+
return { Authorization: `Bearer ${accessToken}` };
|
|
266
|
+
};
|
|
267
|
+
var sandboxBodyIdentity = (sandbox) => {
|
|
268
|
+
if (sandbox.ucic) {
|
|
269
|
+
return { org_user_id: sandbox.ucic };
|
|
270
|
+
}
|
|
271
|
+
return sandbox.subject.includes("@") ? { primary_email: sandbox.subject } : { primary_mobile: sandbox.subject };
|
|
272
|
+
};
|
|
273
|
+
var sandboxQueryIdentity = (sandbox) => sandboxBodyIdentity(sandbox);
|
|
274
|
+
|
|
275
|
+
// src/api/constants.ts
|
|
276
|
+
var CODE_BY_NAME = /* @__PURE__ */ new Map([
|
|
277
|
+
["english", "en"],
|
|
278
|
+
["assamese", "as"],
|
|
279
|
+
["bengali", "bn"],
|
|
280
|
+
["bodo", "brx"],
|
|
281
|
+
["dogri", "doi"],
|
|
282
|
+
["goan konkani", "gom"],
|
|
283
|
+
["gujarati", "gu"],
|
|
284
|
+
["hindi", "hi"],
|
|
285
|
+
["kannada", "kn"],
|
|
286
|
+
["kashmiri", "ks"],
|
|
287
|
+
["maithili", "mai"],
|
|
288
|
+
["malayalam", "ml"],
|
|
289
|
+
["manipuri", "mni-Mtei"],
|
|
290
|
+
["marathi", "mr"],
|
|
291
|
+
["nepali", "ne"],
|
|
292
|
+
["odia", "or"],
|
|
293
|
+
["punjabi", "pa"],
|
|
294
|
+
["sanskrit", "sa"],
|
|
295
|
+
["santali", "sat"],
|
|
296
|
+
["sindhi", "sd"],
|
|
297
|
+
["tamil", "ta"],
|
|
298
|
+
["telugu", "te"],
|
|
299
|
+
["urdu", "ur"]
|
|
300
|
+
]);
|
|
301
|
+
var CODE_BY_LOWER = new Map(
|
|
302
|
+
[...CODE_BY_NAME.values()].map((c) => [c.toLowerCase(), c])
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
// src/api/language.ts
|
|
306
|
+
function toLanguageCode(lang) {
|
|
307
|
+
var _a, _b;
|
|
308
|
+
if (!lang) {
|
|
309
|
+
return void 0;
|
|
310
|
+
}
|
|
311
|
+
const trimmed = lang.trim();
|
|
312
|
+
if (!trimmed) {
|
|
313
|
+
return void 0;
|
|
314
|
+
}
|
|
315
|
+
const lower = trimmed.toLowerCase();
|
|
316
|
+
return (_b = (_a = CODE_BY_LOWER.get(lower)) != null ? _a : CODE_BY_NAME.get(lower)) != null ? _b : trimmed;
|
|
317
|
+
}
|
|
318
|
+
function resolveDisplayLanguage(requested, defaultLanguage, translations) {
|
|
319
|
+
const fallback = defaultLanguage || "en";
|
|
320
|
+
const requestedCode = toLanguageCode(requested);
|
|
321
|
+
if (!requestedCode) {
|
|
322
|
+
return fallback;
|
|
323
|
+
}
|
|
324
|
+
for (const key of Object.keys(translations || {})) {
|
|
325
|
+
if (toLanguageCode(key) === requestedCode) {
|
|
326
|
+
return key;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
return fallback;
|
|
330
|
+
}
|
|
331
|
+
|
|
216
332
|
// src/api/index.ts
|
|
217
333
|
var BASE_URL = "https://api.redacto.io/consent";
|
|
218
334
|
var BASE_URL_PUBLIC = "https://api.redacto.tech/consent";
|
|
@@ -224,17 +340,22 @@ var fetchConsentContent = async ({
|
|
|
224
340
|
specific_uuid,
|
|
225
341
|
validate_against = "all",
|
|
226
342
|
include_fully_consented_data = false,
|
|
227
|
-
signal
|
|
343
|
+
signal,
|
|
344
|
+
...sandboxParams
|
|
228
345
|
}) => {
|
|
229
|
-
if (!noticeId || !
|
|
230
|
-
throw new Error("noticeId
|
|
346
|
+
if (!noticeId || !validate_against) {
|
|
347
|
+
throw new Error("noticeId is required");
|
|
231
348
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
349
|
+
if (!accessToken && !sandboxParams.token) {
|
|
350
|
+
throw new Error("accessToken or token is required");
|
|
351
|
+
}
|
|
352
|
+
const { organisationUuid: ORG, workspaceUuid: WS, sandbox } = resolveRequestAuth(
|
|
353
|
+
accessToken,
|
|
354
|
+
sandboxParams
|
|
355
|
+
);
|
|
236
356
|
const apiBaseUrl = baseUrl || BASE_URL;
|
|
237
|
-
const
|
|
357
|
+
const cacheIdentity = sandbox ? `sandbox:${sandbox.organisationUuid}:${sandbox.workspaceUuid}:${sandbox.ucic || sandbox.subject}` : accessToken;
|
|
358
|
+
const cacheKey = `${cacheIdentity}-${noticeId}-${validate_against}-${language}-${specific_uuid || ""}-${include_fully_consented_data}`;
|
|
238
359
|
if (validate_against === "all" && !specific_uuid) {
|
|
239
360
|
const cached = getCachedData(cacheKey);
|
|
240
361
|
if (cached) return cached;
|
|
@@ -243,10 +364,15 @@ var fetchConsentContent = async ({
|
|
|
243
364
|
if (specific_uuid) url.searchParams.append("specific_uuid", specific_uuid);
|
|
244
365
|
url.searchParams.append("validate_against", validate_against);
|
|
245
366
|
if (include_fully_consented_data) url.searchParams.append("include_fully_consented_data", "true");
|
|
367
|
+
if (sandbox) {
|
|
368
|
+
for (const [key, value] of Object.entries(sandboxQueryIdentity(sandbox))) {
|
|
369
|
+
url.searchParams.append(key, value);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
246
372
|
const response = await fetch(url.toString(), {
|
|
247
373
|
method: "GET",
|
|
248
374
|
headers: {
|
|
249
|
-
|
|
375
|
+
...buildAuthHeaders(accessToken, sandbox),
|
|
250
376
|
"Accept-Language": language
|
|
251
377
|
},
|
|
252
378
|
signal
|
|
@@ -272,18 +398,24 @@ var fetchConsentContentPublic = async ({
|
|
|
272
398
|
org_uuid,
|
|
273
399
|
workspace_uuid,
|
|
274
400
|
notice_uuid,
|
|
401
|
+
accessToken,
|
|
275
402
|
baseUrl,
|
|
276
403
|
language = "en",
|
|
277
404
|
specific_uuid
|
|
278
405
|
}) => {
|
|
279
406
|
const apiBaseUrl = baseUrl || BASE_URL_PUBLIC;
|
|
407
|
+
const noticesUrl = `${apiBaseUrl}/public/organisations/${org_uuid}/workspaces/${workspace_uuid}/notices`;
|
|
280
408
|
const url = new URL(
|
|
281
|
-
`${
|
|
409
|
+
accessToken ? `${noticesUrl}/${notice_uuid}` : `${noticesUrl}/get-notice/${notice_uuid}`
|
|
282
410
|
);
|
|
283
411
|
if (specific_uuid) url.searchParams.append("specific_uuid", specific_uuid);
|
|
284
412
|
const response = await fetch(url.toString(), {
|
|
285
413
|
method: "GET",
|
|
286
|
-
headers: {
|
|
414
|
+
headers: {
|
|
415
|
+
"Content-Type": "application/json",
|
|
416
|
+
"Accept-Language": language,
|
|
417
|
+
...accessToken ? { Authorization: `Bearer ${accessToken}` } : {}
|
|
418
|
+
}
|
|
287
419
|
});
|
|
288
420
|
if (response.status === 401) {
|
|
289
421
|
const error = new Error("Unauthorized");
|
|
@@ -310,16 +442,21 @@ var submitConsentEvent = async ({
|
|
|
310
442
|
meta_data,
|
|
311
443
|
guardianVerificationReference,
|
|
312
444
|
selfDeclaredAdult,
|
|
313
|
-
|
|
445
|
+
language,
|
|
446
|
+
signal,
|
|
447
|
+
...sandboxParams
|
|
314
448
|
}) => {
|
|
315
|
-
if (!noticeUuid || !
|
|
316
|
-
throw new Error("noticeUuid
|
|
449
|
+
if (!noticeUuid || !Array.isArray(purposes)) {
|
|
450
|
+
throw new Error("noticeUuid and purposes array are required");
|
|
317
451
|
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
452
|
+
if (!accessToken && !sandboxParams.token) {
|
|
453
|
+
throw new Error("accessToken or token is required");
|
|
454
|
+
}
|
|
455
|
+
const { organisationUuid: ORG, workspaceUuid: WS, sandbox } = resolveRequestAuth(
|
|
456
|
+
accessToken,
|
|
457
|
+
sandboxParams
|
|
458
|
+
);
|
|
459
|
+
const apiBaseUrl = (sandbox ? null : ledgerBaseUrl) || baseUrl || BASE_URL;
|
|
323
460
|
const validatedPurposes = purposes.map((purpose) => {
|
|
324
461
|
if (!purpose.uuid) throw new Error("Invalid purpose data: missing uuid");
|
|
325
462
|
const dataElements = (purpose.data_elements || []).filter((element) => element.enabled).map((element) => ({
|
|
@@ -340,17 +477,19 @@ var submitConsentEvent = async ({
|
|
|
340
477
|
declined: Boolean(declined),
|
|
341
478
|
meta_data: meta_data ? { specific_uuid: meta_data.specific_uuid, minor_age: meta_data.minor_age } : void 0,
|
|
342
479
|
guardian_verification_reference: guardianVerificationReference,
|
|
343
|
-
self_declared_adult: selfDeclaredAdult
|
|
480
|
+
self_declared_adult: selfDeclaredAdult,
|
|
481
|
+
language: toLanguageCode(language)
|
|
344
482
|
};
|
|
483
|
+
const requestBody = sandbox ? { ...payload, ...sandboxBodyIdentity(sandbox) } : payload;
|
|
345
484
|
const response = await fetch(
|
|
346
485
|
`${apiBaseUrl}/public/organisations/${ORG}/workspaces/${WS}/submit-consent`,
|
|
347
486
|
{
|
|
348
487
|
method: "POST",
|
|
349
488
|
headers: {
|
|
350
|
-
|
|
489
|
+
...buildAuthHeaders(accessToken, sandbox),
|
|
351
490
|
"Content-Type": "application/json"
|
|
352
491
|
},
|
|
353
|
-
body: JSON.stringify(
|
|
492
|
+
body: JSON.stringify(requestBody),
|
|
354
493
|
signal
|
|
355
494
|
}
|
|
356
495
|
);
|
|
@@ -382,7 +521,8 @@ var submitConsentEventPublic = async ({
|
|
|
382
521
|
noticeUuid,
|
|
383
522
|
purposes,
|
|
384
523
|
declined,
|
|
385
|
-
meta_data
|
|
524
|
+
meta_data,
|
|
525
|
+
language
|
|
386
526
|
}) => {
|
|
387
527
|
var _a, _b;
|
|
388
528
|
const apiBaseUrl = ledgerBaseUrl || baseUrl || BASE_URL_PUBLIC;
|
|
@@ -403,7 +543,8 @@ var submitConsentEventPublic = async ({
|
|
|
403
543
|
select_all_mandatory: false,
|
|
404
544
|
source: "WEB",
|
|
405
545
|
declined,
|
|
406
|
-
meta_data
|
|
546
|
+
meta_data,
|
|
547
|
+
language: toLanguageCode(language)
|
|
407
548
|
};
|
|
408
549
|
const response = await fetch(
|
|
409
550
|
`${apiBaseUrl}/public/organisations/${org_uuid}/workspaces/${workspace_uuid}/submit-consent`,
|
|
@@ -442,23 +583,32 @@ var submitConsentEventPublic = async ({
|
|
|
442
583
|
var fetchTTSAudioUrls = async ({
|
|
443
584
|
accessToken,
|
|
444
585
|
baseUrl,
|
|
586
|
+
ledgerBaseUrl,
|
|
445
587
|
noticeUuid,
|
|
446
588
|
language,
|
|
447
|
-
signal
|
|
589
|
+
signal,
|
|
590
|
+
...sandboxParams
|
|
448
591
|
}) => {
|
|
449
|
-
if (!accessToken || !noticeUuid || !language) {
|
|
450
|
-
throw new Error("accessToken, noticeUuid, and language are required");
|
|
592
|
+
if (!accessToken && !sandboxParams.token || !noticeUuid || !language) {
|
|
593
|
+
throw new Error("accessToken (or token), noticeUuid, and language are required");
|
|
451
594
|
}
|
|
452
|
-
const
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
const apiBaseUrl = baseUrl || BASE_URL;
|
|
457
|
-
const url =
|
|
458
|
-
|
|
595
|
+
const { organisationUuid: ORG, workspaceUuid: WS, sandbox } = resolveRequestAuth(
|
|
596
|
+
accessToken,
|
|
597
|
+
sandboxParams
|
|
598
|
+
);
|
|
599
|
+
const apiBaseUrl = (sandbox ? null : ledgerBaseUrl) || baseUrl || BASE_URL;
|
|
600
|
+
const url = new URL(
|
|
601
|
+
`${apiBaseUrl}/public/organisations/${ORG}/workspaces/${WS}/notices/${noticeUuid}/audio/${language}`
|
|
602
|
+
);
|
|
603
|
+
if (sandbox) {
|
|
604
|
+
for (const [key, value] of Object.entries(sandboxQueryIdentity(sandbox))) {
|
|
605
|
+
url.searchParams.append(key, value);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
const response = await fetch(url.toString(), {
|
|
459
609
|
method: "GET",
|
|
460
610
|
headers: {
|
|
461
|
-
|
|
611
|
+
...buildAuthHeaders(accessToken, sandbox),
|
|
462
612
|
Accept: "application/json"
|
|
463
613
|
},
|
|
464
614
|
signal
|
|
@@ -925,6 +1075,42 @@ var styles = {
|
|
|
925
1075
|
padding: 0,
|
|
926
1076
|
boxSizing: "border-box"
|
|
927
1077
|
},
|
|
1078
|
+
// The main notice header is two stacked rows: the logo alone on row 1,
|
|
1079
|
+
// aligned per active_config.logo_position, then the title and the
|
|
1080
|
+
// TTS/language controls on row 2. `topSection` and `topLeft` above keep their
|
|
1081
|
+
// single-row shape for the age-verification, guardian and verification
|
|
1082
|
+
// screens, which have no controls to place and share those styles.
|
|
1083
|
+
// Mirrors consent-sdk-react/src/RedactoNoticeConsent/styles.ts.
|
|
1084
|
+
headerStack: {
|
|
1085
|
+
display: "flex",
|
|
1086
|
+
flexDirection: "column",
|
|
1087
|
+
alignItems: "stretch",
|
|
1088
|
+
gap: "10px",
|
|
1089
|
+
paddingBottom: "15px",
|
|
1090
|
+
borderBottom: "none",
|
|
1091
|
+
backgroundColor: "#ffffff",
|
|
1092
|
+
margin: 0,
|
|
1093
|
+
boxSizing: "border-box",
|
|
1094
|
+
textAlign: "left"
|
|
1095
|
+
},
|
|
1096
|
+
logoRow: {
|
|
1097
|
+
display: "flex",
|
|
1098
|
+
alignItems: "center",
|
|
1099
|
+
width: "100%",
|
|
1100
|
+
margin: 0,
|
|
1101
|
+
padding: 0,
|
|
1102
|
+
boxSizing: "border-box"
|
|
1103
|
+
},
|
|
1104
|
+
headerRow: {
|
|
1105
|
+
display: "flex",
|
|
1106
|
+
alignItems: "center",
|
|
1107
|
+
justifyContent: "space-between",
|
|
1108
|
+
gap: "10px",
|
|
1109
|
+
width: "100%",
|
|
1110
|
+
margin: 0,
|
|
1111
|
+
padding: 0,
|
|
1112
|
+
boxSizing: "border-box"
|
|
1113
|
+
},
|
|
928
1114
|
logo: {
|
|
929
1115
|
height: "32px",
|
|
930
1116
|
width: "auto",
|
|
@@ -1156,6 +1342,9 @@ var styles = {
|
|
|
1156
1342
|
},
|
|
1157
1343
|
languageSelectorContainer: {
|
|
1158
1344
|
position: "relative",
|
|
1345
|
+
// Shares `headerRow` with the title, which grows; without this a long
|
|
1346
|
+
// heading squeezes the language button instead of wrapping itself.
|
|
1347
|
+
flexShrink: 0,
|
|
1159
1348
|
margin: 0,
|
|
1160
1349
|
padding: 0,
|
|
1161
1350
|
boxSizing: "border-box"
|
|
@@ -1718,6 +1907,12 @@ var updateConsentState = (updates) => {
|
|
|
1718
1907
|
};
|
|
1719
1908
|
|
|
1720
1909
|
// src/constants.ts
|
|
1910
|
+
var DEFAULT_LOGO_POSITION = "left";
|
|
1911
|
+
var LOGO_POSITION_JUSTIFY = {
|
|
1912
|
+
left: "flex-start",
|
|
1913
|
+
center: "center",
|
|
1914
|
+
right: "flex-end"
|
|
1915
|
+
};
|
|
1721
1916
|
var DPO_AUDIO_ELEMENT_MAP = [
|
|
1722
1917
|
{ audioKey: "grievance_text_audio_url", elementId: "dpo-grievance-text" },
|
|
1723
1918
|
{ audioKey: "grievance_anchor_text_audio_url", elementId: "dpo-grievance-link" },
|
|
@@ -1776,9 +1971,15 @@ var TTSManager = class {
|
|
|
1776
1971
|
const response = await fetchTTSAudioUrls({
|
|
1777
1972
|
accessToken: params.accessToken,
|
|
1778
1973
|
baseUrl: params.baseUrl,
|
|
1974
|
+
ledgerBaseUrl: params.ledgerBaseUrl,
|
|
1779
1975
|
noticeUuid: params.noticeUuid,
|
|
1780
1976
|
language: params.language,
|
|
1781
|
-
signal: this.abortController.signal
|
|
1977
|
+
signal: this.abortController.signal,
|
|
1978
|
+
token: params.token,
|
|
1979
|
+
sandboxUcic: params.sandboxUcic,
|
|
1980
|
+
sandboxSubject: params.sandboxSubject,
|
|
1981
|
+
organisationUuid: params.organisationUuid,
|
|
1982
|
+
workspaceUuid: params.workspaceUuid
|
|
1782
1983
|
});
|
|
1783
1984
|
this.audioUrls = response.detail;
|
|
1784
1985
|
this.buildSequences(params.content);
|
|
@@ -2743,6 +2944,14 @@ function resolveColors(settings, activeConfig) {
|
|
|
2743
2944
|
};
|
|
2744
2945
|
}
|
|
2745
2946
|
|
|
2947
|
+
// src/modal/layout.ts
|
|
2948
|
+
function resolveLogoPosition(raw) {
|
|
2949
|
+
return raw === "center" || raw === "right" ? raw : DEFAULT_LOGO_POSITION;
|
|
2950
|
+
}
|
|
2951
|
+
function logoRowJustify(position) {
|
|
2952
|
+
return LOGO_POSITION_JUSTIFY[position];
|
|
2953
|
+
}
|
|
2954
|
+
|
|
2746
2955
|
// src/modal/index.ts
|
|
2747
2956
|
var ModalController = class {
|
|
2748
2957
|
constructor(options) {
|
|
@@ -2847,21 +3056,41 @@ var ModalController = class {
|
|
|
2847
3056
|
this.langSelectorRefs = null;
|
|
2848
3057
|
}
|
|
2849
3058
|
// ─── Private ───
|
|
3059
|
+
/**
|
|
3060
|
+
* Sandbox auth options to forward to every API call. When `token` is set the
|
|
3061
|
+
* API layer sends the single `X-Consent-Token` header, injects the subject
|
|
3062
|
+
* into each request's payload, and reads org/workspace from these fields
|
|
3063
|
+
* instead of decoding the JWT. Empty in the normal JWT flow.
|
|
3064
|
+
*/
|
|
3065
|
+
sandboxParams() {
|
|
3066
|
+
return {
|
|
3067
|
+
token: this.options.token,
|
|
3068
|
+
// The static token carries no identity; read the acting principal from the
|
|
3069
|
+
// optional ucic/email/mobile. UCIC takes precedence server-side.
|
|
3070
|
+
// sandboxUcic / sandboxSubject stay as fallbacks.
|
|
3071
|
+
sandboxUcic: this.options.ucic || this.options.sandboxUcic,
|
|
3072
|
+
sandboxSubject: this.options.email || this.options.mobile || this.options.sandboxSubject,
|
|
3073
|
+
organisationUuid: this.options.organisationUuid,
|
|
3074
|
+
workspaceUuid: this.options.workspaceUuid
|
|
3075
|
+
};
|
|
3076
|
+
}
|
|
2850
3077
|
async fetchNotice() {
|
|
2851
3078
|
var _a, _b, _c, _d;
|
|
2852
3079
|
this.abortController = new AbortController();
|
|
2853
3080
|
this.store.setState({ isLoading: true, fetchError: null });
|
|
2854
3081
|
try {
|
|
3082
|
+
const noticeBaseUrl = this.options.ledgerBaseUrl && !this.options.applicationId && !this.options.token ? this.options.ledgerBaseUrl : this.options.baseUrl;
|
|
2855
3083
|
const data = await fetchConsentContent({
|
|
2856
3084
|
noticeId: this.options.noticeId,
|
|
2857
3085
|
accessToken: this.options.accessToken,
|
|
2858
3086
|
refreshToken: this.options.refreshToken,
|
|
2859
|
-
baseUrl:
|
|
3087
|
+
baseUrl: noticeBaseUrl,
|
|
2860
3088
|
language: this.options.language,
|
|
2861
3089
|
specific_uuid: this.options.applicationId,
|
|
2862
3090
|
validate_against: this.options.validateAgainst || "all",
|
|
2863
3091
|
include_fully_consented_data: this.options.includeFullyConsentedData,
|
|
2864
|
-
signal: this.abortController.signal
|
|
3092
|
+
signal: this.abortController.signal,
|
|
3093
|
+
...this.sandboxParams()
|
|
2865
3094
|
});
|
|
2866
3095
|
const activeConfig = data.detail.active_config;
|
|
2867
3096
|
let purposeSelections = data.detail.purpose_selections;
|
|
@@ -2923,10 +3152,11 @@ var ModalController = class {
|
|
|
2923
3152
|
if (this.options.includeFullyConsentedData && needsConsentPurposes.length === 0 && alreadyConsentedPurposes.length > 0) {
|
|
2924
3153
|
isFullyConsentedReviewMode = true;
|
|
2925
3154
|
}
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
3155
|
+
const selectedLanguage = resolveDisplayLanguage(
|
|
3156
|
+
this.options.language,
|
|
3157
|
+
activeConfig.default_language,
|
|
3158
|
+
activeConfig.supported_languages_and_translations
|
|
3159
|
+
);
|
|
2930
3160
|
let isMinorFlow = false;
|
|
2931
3161
|
let showAgeVerification = false;
|
|
2932
3162
|
let restoreState = {};
|
|
@@ -3224,49 +3454,32 @@ var ModalController = class {
|
|
|
3224
3454
|
(_g = this.ttsManager) == null ? void 0 : _g.fetchAudioUrls({
|
|
3225
3455
|
accessToken: this.options.accessToken,
|
|
3226
3456
|
baseUrl: this.options.baseUrl,
|
|
3457
|
+
ledgerBaseUrl: this.options.ledgerBaseUrl,
|
|
3227
3458
|
noticeUuid: activeConfig.notice_uuid,
|
|
3228
3459
|
language: this.getLanguageCode(state.selectedLanguage),
|
|
3229
|
-
content: activeConfig
|
|
3460
|
+
content: activeConfig,
|
|
3461
|
+
...this.sandboxParams()
|
|
3230
3462
|
});
|
|
3231
3463
|
const topRight = el("div", {
|
|
3232
3464
|
style: { display: "flex", alignItems: "center", gap: "8px", flexShrink: 0 }
|
|
3233
3465
|
}, this.ttsButton, this.langSelectorRefs.container);
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
{
|
|
3239
|
-
style: {
|
|
3240
|
-
display: "flex",
|
|
3241
|
-
alignItems: "center",
|
|
3242
|
-
gap: "10px",
|
|
3243
|
-
width: "100%"
|
|
3244
|
-
}
|
|
3245
|
-
},
|
|
3246
|
-
...logoImg ? [logoImg] : [],
|
|
3466
|
+
const logoPosition = resolveLogoPosition(activeConfig.logo_position);
|
|
3467
|
+
const headerRows = [];
|
|
3468
|
+
if (logoImg) {
|
|
3469
|
+
headerRows.push(
|
|
3247
3470
|
el(
|
|
3248
3471
|
"div",
|
|
3249
|
-
{ style: {
|
|
3250
|
-
|
|
3472
|
+
{ style: { ...styles.logoRow, justifyContent: logoRowJustify(logoPosition) } },
|
|
3473
|
+
logoImg
|
|
3251
3474
|
)
|
|
3252
3475
|
);
|
|
3253
|
-
topSection = el(
|
|
3254
|
-
"div",
|
|
3255
|
-
{
|
|
3256
|
-
style: {
|
|
3257
|
-
...styles.topSection,
|
|
3258
|
-
flexDirection: "column",
|
|
3259
|
-
alignItems: "stretch",
|
|
3260
|
-
flexWrap: "nowrap"
|
|
3261
|
-
}
|
|
3262
|
-
},
|
|
3263
|
-
headerRow,
|
|
3264
|
-
this.noticeTitleEl
|
|
3265
|
-
);
|
|
3266
|
-
} else {
|
|
3267
|
-
const topLeft = el("div", { style: styles.topLeft }, logoImg, this.noticeTitleEl);
|
|
3268
|
-
topSection = el("div", { style: styles.topSection }, topLeft, topRight);
|
|
3269
3476
|
}
|
|
3477
|
+
headerRows.push(el("div", { style: styles.headerRow }, this.noticeTitleEl, topRight));
|
|
3478
|
+
const topSection = el(
|
|
3479
|
+
"div",
|
|
3480
|
+
{ style: { ...styles.headerStack, gap: isMobile ? "12px" : "10px" } },
|
|
3481
|
+
...headerRows
|
|
3482
|
+
);
|
|
3270
3483
|
this.middleSection = el("div", { style: styles.middleSection });
|
|
3271
3484
|
const baseTextStyle = { ...styles.privacyText, color: textColor, fontFamily: font, fontSize: isMobile ? "14px" : "16px" };
|
|
3272
3485
|
const noticeTextSpan = el("span", { id: "privacy-notice-text" });
|
|
@@ -3701,7 +3914,9 @@ var ModalController = class {
|
|
|
3701
3914
|
meta_data: this.options.applicationId ? { specific_uuid: this.options.applicationId } : void 0,
|
|
3702
3915
|
guardianVerificationReference: state.verificationReference || void 0,
|
|
3703
3916
|
selfDeclaredAdult: state.selfDeclaredAdult || void 0,
|
|
3704
|
-
|
|
3917
|
+
language: state.selectedLanguage,
|
|
3918
|
+
signal: this.abortController.signal,
|
|
3919
|
+
...this.sandboxParams()
|
|
3705
3920
|
});
|
|
3706
3921
|
this.options.onAccept();
|
|
3707
3922
|
this.destroy();
|
|
@@ -4446,7 +4661,10 @@ var InlineController = class {
|
|
|
4446
4661
|
this.errorEl = null;
|
|
4447
4662
|
this.hasSubmitted = false;
|
|
4448
4663
|
this.hasCheckedLedgerConsent = false;
|
|
4664
|
+
this.fetchedWithToken = false;
|
|
4665
|
+
this.fetchGeneration = 0;
|
|
4449
4666
|
this.unsubscribe = null;
|
|
4667
|
+
this.abortController = null;
|
|
4450
4668
|
this.options = options;
|
|
4451
4669
|
this.store = new Store({
|
|
4452
4670
|
content: null,
|
|
@@ -4464,6 +4682,7 @@ var InlineController = class {
|
|
|
4464
4682
|
this.mediaQuery = createMediaQuery("(max-width: 768px)");
|
|
4465
4683
|
}
|
|
4466
4684
|
async init() {
|
|
4685
|
+
this.abortController = new AbortController();
|
|
4467
4686
|
if (typeof this.options.target === "string") {
|
|
4468
4687
|
this.targetEl = document.querySelector(this.options.target);
|
|
4469
4688
|
if (!this.targetEl) {
|
|
@@ -4486,8 +4705,9 @@ var InlineController = class {
|
|
|
4486
4705
|
await this.fetchNotice();
|
|
4487
4706
|
}
|
|
4488
4707
|
destroy() {
|
|
4489
|
-
var _a;
|
|
4490
|
-
(_a = this.
|
|
4708
|
+
var _a, _b;
|
|
4709
|
+
(_a = this.abortController) == null ? void 0 : _a.abort();
|
|
4710
|
+
(_b = this.unsubscribe) == null ? void 0 : _b.call(this);
|
|
4491
4711
|
this.mediaQuery.destroy();
|
|
4492
4712
|
this.store.destroy();
|
|
4493
4713
|
if (this.hostEl && this.hostEl.parentNode) {
|
|
@@ -4500,6 +4720,10 @@ var InlineController = class {
|
|
|
4500
4720
|
}
|
|
4501
4721
|
updateToken(accessToken) {
|
|
4502
4722
|
this.options = { ...this.options, accessToken };
|
|
4723
|
+
if (accessToken && !this.fetchedWithToken) {
|
|
4724
|
+
void this.fetchNotice();
|
|
4725
|
+
return;
|
|
4726
|
+
}
|
|
4503
4727
|
if (this.shouldCheckLedgerConsent()) {
|
|
4504
4728
|
void this.checkLedgerConsent();
|
|
4505
4729
|
return;
|
|
@@ -4507,18 +4731,30 @@ var InlineController = class {
|
|
|
4507
4731
|
this.tryAutoSubmit();
|
|
4508
4732
|
}
|
|
4509
4733
|
// ─── Private ───
|
|
4734
|
+
// destroy() aborts the controller, so an aborted signal is the marker that
|
|
4735
|
+
// teardown has happened and async continuations must not touch the host.
|
|
4736
|
+
isDestroyed() {
|
|
4737
|
+
var _a;
|
|
4738
|
+
return Boolean((_a = this.abortController) == null ? void 0 : _a.signal.aborted);
|
|
4739
|
+
}
|
|
4510
4740
|
async fetchNotice() {
|
|
4511
4741
|
var _a, _b, _c, _d;
|
|
4512
|
-
|
|
4742
|
+
const generation = ++this.fetchGeneration;
|
|
4743
|
+
this.fetchedWithToken = Boolean(this.options.accessToken);
|
|
4744
|
+
this.store.setState({ isLoading: true, fetchError: null, hasAlreadyConsented: false });
|
|
4513
4745
|
try {
|
|
4514
4746
|
const data = await fetchConsentContentPublic({
|
|
4515
4747
|
org_uuid: this.options.org_uuid,
|
|
4516
4748
|
workspace_uuid: this.options.workspace_uuid,
|
|
4517
4749
|
notice_uuid: this.options.notice_uuid,
|
|
4750
|
+
accessToken: this.options.accessToken,
|
|
4518
4751
|
baseUrl: this.options.baseUrl,
|
|
4519
4752
|
language: this.options.language,
|
|
4520
4753
|
specific_uuid: this.options.applicationId
|
|
4521
4754
|
});
|
|
4755
|
+
if (this.isDestroyed() || generation !== this.fetchGeneration) {
|
|
4756
|
+
return;
|
|
4757
|
+
}
|
|
4522
4758
|
const activeConfig = data.detail.active_config;
|
|
4523
4759
|
const initialCollapsed = {};
|
|
4524
4760
|
const initialPurposes = {};
|
|
@@ -4534,12 +4770,19 @@ var InlineController = class {
|
|
|
4534
4770
|
this.store.setState({
|
|
4535
4771
|
content: activeConfig,
|
|
4536
4772
|
isLoading: false,
|
|
4537
|
-
selectedLanguage:
|
|
4773
|
+
selectedLanguage: resolveDisplayLanguage(
|
|
4774
|
+
this.options.language,
|
|
4775
|
+
activeConfig.default_language,
|
|
4776
|
+
activeConfig.supported_languages_and_translations
|
|
4777
|
+
),
|
|
4538
4778
|
collapsedPurposes: initialCollapsed,
|
|
4539
4779
|
selectedPurposes: initialPurposes,
|
|
4540
4780
|
selectedDataElements: initialElements
|
|
4541
4781
|
});
|
|
4542
4782
|
} catch (err) {
|
|
4783
|
+
if (this.isDestroyed() || generation !== this.fetchGeneration) {
|
|
4784
|
+
return;
|
|
4785
|
+
}
|
|
4543
4786
|
const error = err;
|
|
4544
4787
|
if (error.status === 409) {
|
|
4545
4788
|
this.store.setState({ hasAlreadyConsented: true, isLoading: false });
|
|
@@ -4558,7 +4801,7 @@ var InlineController = class {
|
|
|
4558
4801
|
// (the 409 path governs) and for the magic-link applicationId flow. Any
|
|
4559
4802
|
// failure returns null and falls through to the normal flow.
|
|
4560
4803
|
async checkLedgerConsent() {
|
|
4561
|
-
var _a, _b;
|
|
4804
|
+
var _a, _b, _c;
|
|
4562
4805
|
if (!this.shouldCheckLedgerConsent()) return;
|
|
4563
4806
|
this.hasCheckedLedgerConsent = true;
|
|
4564
4807
|
this.store.setState({ isCheckingConsent: true });
|
|
@@ -4568,15 +4811,21 @@ var InlineController = class {
|
|
|
4568
4811
|
ledgerBaseUrl: this.options.ledgerBaseUrl,
|
|
4569
4812
|
organisationUuid: this.options.org_uuid,
|
|
4570
4813
|
workspaceUuid: this.options.workspace_uuid,
|
|
4571
|
-
noticeUuid: this.options.notice_uuid
|
|
4814
|
+
noticeUuid: this.options.notice_uuid,
|
|
4815
|
+
signal: (_a = this.abortController) == null ? void 0 : _a.signal
|
|
4572
4816
|
});
|
|
4817
|
+
if (this.isDestroyed()) {
|
|
4818
|
+
return;
|
|
4819
|
+
}
|
|
4573
4820
|
if (status && isNoticeFullyConsented(status)) {
|
|
4574
4821
|
this.store.setState({ hasAlreadyConsented: true });
|
|
4575
|
-
(
|
|
4822
|
+
(_c = (_b = this.options).onAccept) == null ? void 0 : _c.call(_b);
|
|
4576
4823
|
}
|
|
4577
4824
|
} finally {
|
|
4578
|
-
this.
|
|
4579
|
-
|
|
4825
|
+
if (!this.isDestroyed()) {
|
|
4826
|
+
this.store.setState({ isCheckingConsent: false });
|
|
4827
|
+
this.tryAutoSubmit();
|
|
4828
|
+
}
|
|
4580
4829
|
}
|
|
4581
4830
|
}
|
|
4582
4831
|
handleStateChange(state, prevState) {
|
|
@@ -4798,6 +5047,9 @@ var InlineController = class {
|
|
|
4798
5047
|
}
|
|
4799
5048
|
async tryAutoSubmit() {
|
|
4800
5049
|
var _a, _b;
|
|
5050
|
+
if (this.isDestroyed()) {
|
|
5051
|
+
return;
|
|
5052
|
+
}
|
|
4801
5053
|
const state = this.store.getState();
|
|
4802
5054
|
const accessToken = this.options.accessToken;
|
|
4803
5055
|
if (accessToken && state.content && this.areAllRequiredElementsChecked() && !state.isSubmitting && !state.isCheckingConsent && !state.hasAlreadyConsented && !this.hasSubmitted) {
|
|
@@ -4845,7 +5097,8 @@ var InlineController = class {
|
|
|
4845
5097
|
};
|
|
4846
5098
|
}),
|
|
4847
5099
|
declined: false,
|
|
4848
|
-
meta_data: this.options.applicationId ? { specific_uuid: this.options.applicationId } : void 0
|
|
5100
|
+
meta_data: this.options.applicationId ? { specific_uuid: this.options.applicationId } : void 0,
|
|
5101
|
+
language: state.selectedLanguage
|
|
4849
5102
|
});
|
|
4850
5103
|
(_b = (_a = this.options).onAccept) == null ? void 0 : _b.call(_a);
|
|
4851
5104
|
this.hasSubmitted = false;
|