@redacto.io/consent-sdk-js 0.1.2-beta.0 → 1.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/redacto.d.mts +4 -0
- package/dist/redacto.d.ts +4 -0
- package/dist/redacto.global.js +2 -2
- package/dist/redacto.global.js.map +1 -1
- package/dist/redacto.js +179 -10
- package/dist/redacto.mjs +179 -10
- package/package.json +1 -1
package/dist/redacto.js
CHANGED
|
@@ -303,6 +303,7 @@ var fetchConsentContentPublic = async ({
|
|
|
303
303
|
var submitConsentEvent = async ({
|
|
304
304
|
accessToken,
|
|
305
305
|
baseUrl,
|
|
306
|
+
ledgerBaseUrl,
|
|
306
307
|
noticeUuid,
|
|
307
308
|
purposes,
|
|
308
309
|
declined,
|
|
@@ -318,7 +319,7 @@ var submitConsentEvent = async ({
|
|
|
318
319
|
if (!decodedToken) throw new Error("Invalid access token");
|
|
319
320
|
const { organisation_uuid: ORG, workspace_uuid: WS } = decodedToken;
|
|
320
321
|
if (!ORG || !WS) throw new Error("Invalid token: missing organization or workspace UUID");
|
|
321
|
-
const apiBaseUrl = baseUrl || BASE_URL;
|
|
322
|
+
const apiBaseUrl = ledgerBaseUrl || baseUrl || BASE_URL;
|
|
322
323
|
const validatedPurposes = purposes.map((purpose) => {
|
|
323
324
|
if (!purpose.uuid) throw new Error("Invalid purpose data: missing uuid");
|
|
324
325
|
const dataElements = (purpose.data_elements || []).filter((element) => element.enabled).map((element) => ({
|
|
@@ -377,13 +378,14 @@ var submitConsentEventPublic = async ({
|
|
|
377
378
|
workspace_uuid,
|
|
378
379
|
accessToken,
|
|
379
380
|
baseUrl,
|
|
381
|
+
ledgerBaseUrl,
|
|
380
382
|
noticeUuid,
|
|
381
383
|
purposes,
|
|
382
384
|
declined,
|
|
383
385
|
meta_data
|
|
384
386
|
}) => {
|
|
385
387
|
var _a, _b;
|
|
386
|
-
const apiBaseUrl = baseUrl || BASE_URL_PUBLIC;
|
|
388
|
+
const apiBaseUrl = ledgerBaseUrl || baseUrl || BASE_URL_PUBLIC;
|
|
387
389
|
const payload = {
|
|
388
390
|
notice_uuid: noticeUuid,
|
|
389
391
|
purposes: purposes.map((purpose) => {
|
|
@@ -616,6 +618,95 @@ var verifyGuardianStatus = async ({
|
|
|
616
618
|
};
|
|
617
619
|
};
|
|
618
620
|
|
|
621
|
+
// src/api/notice-consent-status.ts
|
|
622
|
+
var fetchNoticeConsentStatusForScope = async ({
|
|
623
|
+
accessToken,
|
|
624
|
+
ledgerBaseUrl,
|
|
625
|
+
organisationUuid,
|
|
626
|
+
workspaceUuid,
|
|
627
|
+
noticeUuid,
|
|
628
|
+
signal
|
|
629
|
+
}) => {
|
|
630
|
+
if (!accessToken || !ledgerBaseUrl || !organisationUuid || !workspaceUuid || !noticeUuid) {
|
|
631
|
+
return null;
|
|
632
|
+
}
|
|
633
|
+
try {
|
|
634
|
+
const response = await fetch(
|
|
635
|
+
`${ledgerBaseUrl}/public/organisations/${organisationUuid}/workspaces/${workspaceUuid}/notices/${noticeUuid}/check-consent`,
|
|
636
|
+
{
|
|
637
|
+
method: "POST",
|
|
638
|
+
headers: {
|
|
639
|
+
Authorization: `Bearer ${accessToken}`,
|
|
640
|
+
"Content-Type": "application/json"
|
|
641
|
+
},
|
|
642
|
+
body: "{}",
|
|
643
|
+
signal
|
|
644
|
+
}
|
|
645
|
+
);
|
|
646
|
+
if (!response.ok) return null;
|
|
647
|
+
const data = await response.json();
|
|
648
|
+
const body = data && typeof data === "object" && "detail" in data && data.detail ? data.detail : data;
|
|
649
|
+
if (!body || !Array.isArray(body.purposes)) return null;
|
|
650
|
+
return body;
|
|
651
|
+
} catch {
|
|
652
|
+
return null;
|
|
653
|
+
}
|
|
654
|
+
};
|
|
655
|
+
var fetchNoticeConsentStatus = async ({
|
|
656
|
+
accessToken,
|
|
657
|
+
ledgerBaseUrl,
|
|
658
|
+
noticeUuid,
|
|
659
|
+
signal
|
|
660
|
+
}) => {
|
|
661
|
+
if (!accessToken || !ledgerBaseUrl || !noticeUuid) return null;
|
|
662
|
+
const decodedToken = decodeTokenSafely(accessToken);
|
|
663
|
+
if (!decodedToken) return null;
|
|
664
|
+
const { organisation_uuid: ORG, workspace_uuid: WS } = decodedToken;
|
|
665
|
+
if (!ORG || !WS) return null;
|
|
666
|
+
return fetchNoticeConsentStatusForScope({
|
|
667
|
+
accessToken,
|
|
668
|
+
ledgerBaseUrl,
|
|
669
|
+
organisationUuid: ORG,
|
|
670
|
+
workspaceUuid: WS,
|
|
671
|
+
noticeUuid,
|
|
672
|
+
signal
|
|
673
|
+
});
|
|
674
|
+
};
|
|
675
|
+
var isNoticeFullyConsented = (status) => status.consented && status.all_mandatory_active;
|
|
676
|
+
var buildLedgerConsentOverlay = (status, configPurposes) => {
|
|
677
|
+
var _a, _b;
|
|
678
|
+
const byUuid = new Map(status.purposes.map((p) => [p.uuid, p]));
|
|
679
|
+
const hasHistory = status.purposes.some((p) => (p.status || "INACTIVE") !== "INACTIVE");
|
|
680
|
+
if (!hasHistory) return null;
|
|
681
|
+
const purpose_selections = {};
|
|
682
|
+
for (const cp of configPurposes) {
|
|
683
|
+
const lp = byUuid.get(cp.uuid);
|
|
684
|
+
const purposeStatus = (lp == null ? void 0 : lp.status) || "INACTIVE";
|
|
685
|
+
const selectedByUuid = new Map(((lp == null ? void 0 : lp.data_elements) || []).map((de) => [de.uuid, de.selected]));
|
|
686
|
+
const data_elements = {};
|
|
687
|
+
for (const de of cp.data_elements) {
|
|
688
|
+
data_elements[de.uuid] = {
|
|
689
|
+
selected: (_a = selectedByUuid.get(de.uuid)) != null ? _a : false,
|
|
690
|
+
enabled: de.enabled,
|
|
691
|
+
required: de.required
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
purpose_selections[cp.uuid] = {
|
|
695
|
+
selected: (_b = lp == null ? void 0 : lp.selected) != null ? _b : false,
|
|
696
|
+
status: purposeStatus,
|
|
697
|
+
needs_reconsent: purposeStatus !== "ACTIVE",
|
|
698
|
+
data_elements
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
const reconsent_required = configPurposes.some(
|
|
702
|
+
(cp) => {
|
|
703
|
+
var _a2;
|
|
704
|
+
return (((_a2 = purpose_selections[cp.uuid]) == null ? void 0 : _a2.status) || "INACTIVE") !== "ACTIVE";
|
|
705
|
+
}
|
|
706
|
+
);
|
|
707
|
+
return { reconsent_required, purpose_selections };
|
|
708
|
+
};
|
|
709
|
+
|
|
619
710
|
// src/styles/inject.ts
|
|
620
711
|
var STYLE_ID = "redacto-consent-checkbox-styles";
|
|
621
712
|
var STYLE_CONTENT = `
|
|
@@ -2757,7 +2848,7 @@ var ModalController = class {
|
|
|
2757
2848
|
}
|
|
2758
2849
|
// ─── Private ───
|
|
2759
2850
|
async fetchNotice() {
|
|
2760
|
-
var _a, _b, _c;
|
|
2851
|
+
var _a, _b, _c, _d;
|
|
2761
2852
|
this.abortController = new AbortController();
|
|
2762
2853
|
this.store.setState({ isLoading: true, fetchError: null });
|
|
2763
2854
|
try {
|
|
@@ -2773,7 +2864,23 @@ var ModalController = class {
|
|
|
2773
2864
|
signal: this.abortController.signal
|
|
2774
2865
|
});
|
|
2775
2866
|
const activeConfig = data.detail.active_config;
|
|
2776
|
-
|
|
2867
|
+
let purposeSelections = data.detail.purpose_selections;
|
|
2868
|
+
let reconsentRequired = data.detail.reconsent_required;
|
|
2869
|
+
let ledgerOverlayApplied = false;
|
|
2870
|
+
if (this.options.ledgerBaseUrl && !this.options.applicationId) {
|
|
2871
|
+
const ledgerStatus = await fetchNoticeConsentStatus({
|
|
2872
|
+
accessToken: this.options.accessToken,
|
|
2873
|
+
ledgerBaseUrl: this.options.ledgerBaseUrl,
|
|
2874
|
+
noticeUuid: this.options.noticeId,
|
|
2875
|
+
signal: this.abortController.signal
|
|
2876
|
+
});
|
|
2877
|
+
if (ledgerStatus) {
|
|
2878
|
+
const overlay = buildLedgerConsentOverlay(ledgerStatus, activeConfig.purposes);
|
|
2879
|
+
reconsentRequired = (_a = overlay == null ? void 0 : overlay.reconsent_required) != null ? _a : false;
|
|
2880
|
+
purposeSelections = overlay == null ? void 0 : overlay.purpose_selections;
|
|
2881
|
+
ledgerOverlayApplied = Boolean(overlay);
|
|
2882
|
+
}
|
|
2883
|
+
}
|
|
2777
2884
|
const initialCollapsed = {};
|
|
2778
2885
|
const initialPurposes = {};
|
|
2779
2886
|
const initialElements = {};
|
|
@@ -2781,7 +2888,7 @@ var ModalController = class {
|
|
|
2781
2888
|
let needsConsentPurposes = [];
|
|
2782
2889
|
let alreadyConsentedPurposes = [];
|
|
2783
2890
|
let isFullyConsentedReviewMode = false;
|
|
2784
|
-
if (purposeSelections &&
|
|
2891
|
+
if (purposeSelections && (reconsentRequired || ledgerOverlayApplied)) {
|
|
2785
2892
|
for (const purpose of activeConfig.purposes) {
|
|
2786
2893
|
const selection = purposeSelections[purpose.uuid];
|
|
2787
2894
|
initialCollapsed[purpose.uuid] = true;
|
|
@@ -2790,7 +2897,7 @@ var ModalController = class {
|
|
|
2790
2897
|
initialPurposes[purpose.uuid] = true;
|
|
2791
2898
|
for (const de of purpose.data_elements) {
|
|
2792
2899
|
const combinedId = `${purpose.uuid}-${de.uuid}`;
|
|
2793
|
-
const deSelection = (
|
|
2900
|
+
const deSelection = (_b = selection.data_elements) == null ? void 0 : _b[de.uuid];
|
|
2794
2901
|
const wasSelected = (deSelection == null ? void 0 : deSelection.selected) || false;
|
|
2795
2902
|
initialElements[combinedId] = wasSelected;
|
|
2796
2903
|
initialDataElementSelections[combinedId] = wasSelected;
|
|
@@ -2879,7 +2986,7 @@ var ModalController = class {
|
|
|
2879
2986
|
this.options.onAccept();
|
|
2880
2987
|
} else {
|
|
2881
2988
|
this.store.setState({ fetchError: error, isLoading: false });
|
|
2882
|
-
(
|
|
2989
|
+
(_d = (_c = this.options).onError) == null ? void 0 : _d.call(_c, error);
|
|
2883
2990
|
}
|
|
2884
2991
|
}
|
|
2885
2992
|
}
|
|
@@ -3587,6 +3694,7 @@ var ModalController = class {
|
|
|
3587
3694
|
await submitConsentEvent({
|
|
3588
3695
|
accessToken: this.options.accessToken,
|
|
3589
3696
|
baseUrl: this.options.baseUrl,
|
|
3697
|
+
ledgerBaseUrl: this.options.ledgerBaseUrl,
|
|
3590
3698
|
noticeUuid: state.content.detail.uuid,
|
|
3591
3699
|
purposes,
|
|
3592
3700
|
declined: false,
|
|
@@ -4337,7 +4445,9 @@ var InlineController = class {
|
|
|
4337
4445
|
this.purposeRefs = null;
|
|
4338
4446
|
this.errorEl = null;
|
|
4339
4447
|
this.hasSubmitted = false;
|
|
4448
|
+
this.hasCheckedLedgerConsent = false;
|
|
4340
4449
|
this.unsubscribe = null;
|
|
4450
|
+
this.abortController = null;
|
|
4341
4451
|
this.options = options;
|
|
4342
4452
|
this.store = new Store({
|
|
4343
4453
|
content: null,
|
|
@@ -4348,12 +4458,14 @@ var InlineController = class {
|
|
|
4348
4458
|
selectedPurposes: {},
|
|
4349
4459
|
selectedDataElements: {},
|
|
4350
4460
|
hasAlreadyConsented: false,
|
|
4461
|
+
isCheckingConsent: false,
|
|
4351
4462
|
fetchError: null,
|
|
4352
4463
|
errorMessage: null
|
|
4353
4464
|
});
|
|
4354
4465
|
this.mediaQuery = createMediaQuery("(max-width: 768px)");
|
|
4355
4466
|
}
|
|
4356
4467
|
async init() {
|
|
4468
|
+
this.abortController = new AbortController();
|
|
4357
4469
|
if (typeof this.options.target === "string") {
|
|
4358
4470
|
this.targetEl = document.querySelector(this.options.target);
|
|
4359
4471
|
if (!this.targetEl) {
|
|
@@ -4376,8 +4488,9 @@ var InlineController = class {
|
|
|
4376
4488
|
await this.fetchNotice();
|
|
4377
4489
|
}
|
|
4378
4490
|
destroy() {
|
|
4379
|
-
var _a;
|
|
4380
|
-
(_a = this.
|
|
4491
|
+
var _a, _b;
|
|
4492
|
+
(_a = this.abortController) == null ? void 0 : _a.abort();
|
|
4493
|
+
(_b = this.unsubscribe) == null ? void 0 : _b.call(this);
|
|
4381
4494
|
this.mediaQuery.destroy();
|
|
4382
4495
|
this.store.destroy();
|
|
4383
4496
|
if (this.hostEl && this.hostEl.parentNode) {
|
|
@@ -4390,9 +4503,19 @@ var InlineController = class {
|
|
|
4390
4503
|
}
|
|
4391
4504
|
updateToken(accessToken) {
|
|
4392
4505
|
this.options = { ...this.options, accessToken };
|
|
4506
|
+
if (this.shouldCheckLedgerConsent()) {
|
|
4507
|
+
void this.checkLedgerConsent();
|
|
4508
|
+
return;
|
|
4509
|
+
}
|
|
4393
4510
|
this.tryAutoSubmit();
|
|
4394
4511
|
}
|
|
4395
4512
|
// ─── Private ───
|
|
4513
|
+
// destroy() aborts the controller, so an aborted signal is the marker that
|
|
4514
|
+
// teardown has happened and async continuations must not touch the host.
|
|
4515
|
+
isDestroyed() {
|
|
4516
|
+
var _a;
|
|
4517
|
+
return Boolean((_a = this.abortController) == null ? void 0 : _a.signal.aborted);
|
|
4518
|
+
}
|
|
4396
4519
|
async fetchNotice() {
|
|
4397
4520
|
var _a, _b, _c, _d;
|
|
4398
4521
|
this.store.setState({ isLoading: true, fetchError: null });
|
|
@@ -4405,6 +4528,9 @@ var InlineController = class {
|
|
|
4405
4528
|
language: this.options.language,
|
|
4406
4529
|
specific_uuid: this.options.applicationId
|
|
4407
4530
|
});
|
|
4531
|
+
if (this.isDestroyed()) {
|
|
4532
|
+
return;
|
|
4533
|
+
}
|
|
4408
4534
|
const activeConfig = data.detail.active_config;
|
|
4409
4535
|
const initialCollapsed = {};
|
|
4410
4536
|
const initialPurposes = {};
|
|
@@ -4416,6 +4542,7 @@ var InlineController = class {
|
|
|
4416
4542
|
initialElements[`${purpose.uuid}-${de.uuid}`] = false;
|
|
4417
4543
|
}
|
|
4418
4544
|
}
|
|
4545
|
+
void this.checkLedgerConsent();
|
|
4419
4546
|
this.store.setState({
|
|
4420
4547
|
content: activeConfig,
|
|
4421
4548
|
isLoading: false,
|
|
@@ -4425,6 +4552,9 @@ var InlineController = class {
|
|
|
4425
4552
|
selectedDataElements: initialElements
|
|
4426
4553
|
});
|
|
4427
4554
|
} catch (err) {
|
|
4555
|
+
if (this.isDestroyed()) {
|
|
4556
|
+
return;
|
|
4557
|
+
}
|
|
4428
4558
|
const error = err;
|
|
4429
4559
|
if (error.status === 409) {
|
|
4430
4560
|
this.store.setState({ hasAlreadyConsented: true, isLoading: false });
|
|
@@ -4435,6 +4565,41 @@ var InlineController = class {
|
|
|
4435
4565
|
}
|
|
4436
4566
|
}
|
|
4437
4567
|
}
|
|
4568
|
+
shouldCheckLedgerConsent() {
|
|
4569
|
+
return Boolean(this.options.ledgerBaseUrl) && !this.options.applicationId && Boolean(this.options.accessToken) && !this.hasCheckedLedgerConsent;
|
|
4570
|
+
}
|
|
4571
|
+
// Ask the Go ledger whether this principal has already fully consented —
|
|
4572
|
+
// the ledger equivalent of the legacy Python 409. Skipped without a ledger
|
|
4573
|
+
// (the 409 path governs) and for the magic-link applicationId flow. Any
|
|
4574
|
+
// failure returns null and falls through to the normal flow.
|
|
4575
|
+
async checkLedgerConsent() {
|
|
4576
|
+
var _a, _b, _c;
|
|
4577
|
+
if (!this.shouldCheckLedgerConsent()) return;
|
|
4578
|
+
this.hasCheckedLedgerConsent = true;
|
|
4579
|
+
this.store.setState({ isCheckingConsent: true });
|
|
4580
|
+
try {
|
|
4581
|
+
const status = await fetchNoticeConsentStatusForScope({
|
|
4582
|
+
accessToken: this.options.accessToken,
|
|
4583
|
+
ledgerBaseUrl: this.options.ledgerBaseUrl,
|
|
4584
|
+
organisationUuid: this.options.org_uuid,
|
|
4585
|
+
workspaceUuid: this.options.workspace_uuid,
|
|
4586
|
+
noticeUuid: this.options.notice_uuid,
|
|
4587
|
+
signal: (_a = this.abortController) == null ? void 0 : _a.signal
|
|
4588
|
+
});
|
|
4589
|
+
if (this.isDestroyed()) {
|
|
4590
|
+
return;
|
|
4591
|
+
}
|
|
4592
|
+
if (status && isNoticeFullyConsented(status)) {
|
|
4593
|
+
this.store.setState({ hasAlreadyConsented: true });
|
|
4594
|
+
(_c = (_b = this.options).onAccept) == null ? void 0 : _c.call(_b);
|
|
4595
|
+
}
|
|
4596
|
+
} finally {
|
|
4597
|
+
if (!this.isDestroyed()) {
|
|
4598
|
+
this.store.setState({ isCheckingConsent: false });
|
|
4599
|
+
this.tryAutoSubmit();
|
|
4600
|
+
}
|
|
4601
|
+
}
|
|
4602
|
+
}
|
|
4438
4603
|
handleStateChange(state, prevState) {
|
|
4439
4604
|
if (prevState.isLoading && !state.isLoading && state.content) {
|
|
4440
4605
|
this.rebuildUI();
|
|
@@ -4654,9 +4819,12 @@ var InlineController = class {
|
|
|
4654
4819
|
}
|
|
4655
4820
|
async tryAutoSubmit() {
|
|
4656
4821
|
var _a, _b;
|
|
4822
|
+
if (this.isDestroyed()) {
|
|
4823
|
+
return;
|
|
4824
|
+
}
|
|
4657
4825
|
const state = this.store.getState();
|
|
4658
4826
|
const accessToken = this.options.accessToken;
|
|
4659
|
-
if (accessToken && state.content && this.areAllRequiredElementsChecked() && !state.isSubmitting && !this.hasSubmitted) {
|
|
4827
|
+
if (accessToken && state.content && this.areAllRequiredElementsChecked() && !state.isSubmitting && !state.isCheckingConsent && !state.hasAlreadyConsented && !this.hasSubmitted) {
|
|
4660
4828
|
this.hasSubmitted = true;
|
|
4661
4829
|
try {
|
|
4662
4830
|
await this.handleSubmit(accessToken);
|
|
@@ -4681,6 +4849,7 @@ var InlineController = class {
|
|
|
4681
4849
|
workspace_uuid: this.options.workspace_uuid,
|
|
4682
4850
|
accessToken: tokenToUse,
|
|
4683
4851
|
baseUrl: this.options.baseUrl,
|
|
4852
|
+
ledgerBaseUrl: this.options.ledgerBaseUrl,
|
|
4684
4853
|
noticeUuid: content.notice_uuid,
|
|
4685
4854
|
purposes: content.purposes.map((purpose) => {
|
|
4686
4855
|
const hasSelectedRequired = purpose.data_elements.some(
|
package/dist/redacto.mjs
CHANGED
|
@@ -273,6 +273,7 @@ var fetchConsentContentPublic = async ({
|
|
|
273
273
|
var submitConsentEvent = async ({
|
|
274
274
|
accessToken,
|
|
275
275
|
baseUrl,
|
|
276
|
+
ledgerBaseUrl,
|
|
276
277
|
noticeUuid,
|
|
277
278
|
purposes,
|
|
278
279
|
declined,
|
|
@@ -288,7 +289,7 @@ var submitConsentEvent = async ({
|
|
|
288
289
|
if (!decodedToken) throw new Error("Invalid access token");
|
|
289
290
|
const { organisation_uuid: ORG, workspace_uuid: WS } = decodedToken;
|
|
290
291
|
if (!ORG || !WS) throw new Error("Invalid token: missing organization or workspace UUID");
|
|
291
|
-
const apiBaseUrl = baseUrl || BASE_URL;
|
|
292
|
+
const apiBaseUrl = ledgerBaseUrl || baseUrl || BASE_URL;
|
|
292
293
|
const validatedPurposes = purposes.map((purpose) => {
|
|
293
294
|
if (!purpose.uuid) throw new Error("Invalid purpose data: missing uuid");
|
|
294
295
|
const dataElements = (purpose.data_elements || []).filter((element) => element.enabled).map((element) => ({
|
|
@@ -347,13 +348,14 @@ var submitConsentEventPublic = async ({
|
|
|
347
348
|
workspace_uuid,
|
|
348
349
|
accessToken,
|
|
349
350
|
baseUrl,
|
|
351
|
+
ledgerBaseUrl,
|
|
350
352
|
noticeUuid,
|
|
351
353
|
purposes,
|
|
352
354
|
declined,
|
|
353
355
|
meta_data
|
|
354
356
|
}) => {
|
|
355
357
|
var _a, _b;
|
|
356
|
-
const apiBaseUrl = baseUrl || BASE_URL_PUBLIC;
|
|
358
|
+
const apiBaseUrl = ledgerBaseUrl || baseUrl || BASE_URL_PUBLIC;
|
|
357
359
|
const payload = {
|
|
358
360
|
notice_uuid: noticeUuid,
|
|
359
361
|
purposes: purposes.map((purpose) => {
|
|
@@ -586,6 +588,95 @@ var verifyGuardianStatus = async ({
|
|
|
586
588
|
};
|
|
587
589
|
};
|
|
588
590
|
|
|
591
|
+
// src/api/notice-consent-status.ts
|
|
592
|
+
var fetchNoticeConsentStatusForScope = async ({
|
|
593
|
+
accessToken,
|
|
594
|
+
ledgerBaseUrl,
|
|
595
|
+
organisationUuid,
|
|
596
|
+
workspaceUuid,
|
|
597
|
+
noticeUuid,
|
|
598
|
+
signal
|
|
599
|
+
}) => {
|
|
600
|
+
if (!accessToken || !ledgerBaseUrl || !organisationUuid || !workspaceUuid || !noticeUuid) {
|
|
601
|
+
return null;
|
|
602
|
+
}
|
|
603
|
+
try {
|
|
604
|
+
const response = await fetch(
|
|
605
|
+
`${ledgerBaseUrl}/public/organisations/${organisationUuid}/workspaces/${workspaceUuid}/notices/${noticeUuid}/check-consent`,
|
|
606
|
+
{
|
|
607
|
+
method: "POST",
|
|
608
|
+
headers: {
|
|
609
|
+
Authorization: `Bearer ${accessToken}`,
|
|
610
|
+
"Content-Type": "application/json"
|
|
611
|
+
},
|
|
612
|
+
body: "{}",
|
|
613
|
+
signal
|
|
614
|
+
}
|
|
615
|
+
);
|
|
616
|
+
if (!response.ok) return null;
|
|
617
|
+
const data = await response.json();
|
|
618
|
+
const body = data && typeof data === "object" && "detail" in data && data.detail ? data.detail : data;
|
|
619
|
+
if (!body || !Array.isArray(body.purposes)) return null;
|
|
620
|
+
return body;
|
|
621
|
+
} catch {
|
|
622
|
+
return null;
|
|
623
|
+
}
|
|
624
|
+
};
|
|
625
|
+
var fetchNoticeConsentStatus = async ({
|
|
626
|
+
accessToken,
|
|
627
|
+
ledgerBaseUrl,
|
|
628
|
+
noticeUuid,
|
|
629
|
+
signal
|
|
630
|
+
}) => {
|
|
631
|
+
if (!accessToken || !ledgerBaseUrl || !noticeUuid) return null;
|
|
632
|
+
const decodedToken = decodeTokenSafely(accessToken);
|
|
633
|
+
if (!decodedToken) return null;
|
|
634
|
+
const { organisation_uuid: ORG, workspace_uuid: WS } = decodedToken;
|
|
635
|
+
if (!ORG || !WS) return null;
|
|
636
|
+
return fetchNoticeConsentStatusForScope({
|
|
637
|
+
accessToken,
|
|
638
|
+
ledgerBaseUrl,
|
|
639
|
+
organisationUuid: ORG,
|
|
640
|
+
workspaceUuid: WS,
|
|
641
|
+
noticeUuid,
|
|
642
|
+
signal
|
|
643
|
+
});
|
|
644
|
+
};
|
|
645
|
+
var isNoticeFullyConsented = (status) => status.consented && status.all_mandatory_active;
|
|
646
|
+
var buildLedgerConsentOverlay = (status, configPurposes) => {
|
|
647
|
+
var _a, _b;
|
|
648
|
+
const byUuid = new Map(status.purposes.map((p) => [p.uuid, p]));
|
|
649
|
+
const hasHistory = status.purposes.some((p) => (p.status || "INACTIVE") !== "INACTIVE");
|
|
650
|
+
if (!hasHistory) return null;
|
|
651
|
+
const purpose_selections = {};
|
|
652
|
+
for (const cp of configPurposes) {
|
|
653
|
+
const lp = byUuid.get(cp.uuid);
|
|
654
|
+
const purposeStatus = (lp == null ? void 0 : lp.status) || "INACTIVE";
|
|
655
|
+
const selectedByUuid = new Map(((lp == null ? void 0 : lp.data_elements) || []).map((de) => [de.uuid, de.selected]));
|
|
656
|
+
const data_elements = {};
|
|
657
|
+
for (const de of cp.data_elements) {
|
|
658
|
+
data_elements[de.uuid] = {
|
|
659
|
+
selected: (_a = selectedByUuid.get(de.uuid)) != null ? _a : false,
|
|
660
|
+
enabled: de.enabled,
|
|
661
|
+
required: de.required
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
purpose_selections[cp.uuid] = {
|
|
665
|
+
selected: (_b = lp == null ? void 0 : lp.selected) != null ? _b : false,
|
|
666
|
+
status: purposeStatus,
|
|
667
|
+
needs_reconsent: purposeStatus !== "ACTIVE",
|
|
668
|
+
data_elements
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
const reconsent_required = configPurposes.some(
|
|
672
|
+
(cp) => {
|
|
673
|
+
var _a2;
|
|
674
|
+
return (((_a2 = purpose_selections[cp.uuid]) == null ? void 0 : _a2.status) || "INACTIVE") !== "ACTIVE";
|
|
675
|
+
}
|
|
676
|
+
);
|
|
677
|
+
return { reconsent_required, purpose_selections };
|
|
678
|
+
};
|
|
679
|
+
|
|
589
680
|
// src/styles/inject.ts
|
|
590
681
|
var STYLE_ID = "redacto-consent-checkbox-styles";
|
|
591
682
|
var STYLE_CONTENT = `
|
|
@@ -2727,7 +2818,7 @@ var ModalController = class {
|
|
|
2727
2818
|
}
|
|
2728
2819
|
// ─── Private ───
|
|
2729
2820
|
async fetchNotice() {
|
|
2730
|
-
var _a, _b, _c;
|
|
2821
|
+
var _a, _b, _c, _d;
|
|
2731
2822
|
this.abortController = new AbortController();
|
|
2732
2823
|
this.store.setState({ isLoading: true, fetchError: null });
|
|
2733
2824
|
try {
|
|
@@ -2743,7 +2834,23 @@ var ModalController = class {
|
|
|
2743
2834
|
signal: this.abortController.signal
|
|
2744
2835
|
});
|
|
2745
2836
|
const activeConfig = data.detail.active_config;
|
|
2746
|
-
|
|
2837
|
+
let purposeSelections = data.detail.purpose_selections;
|
|
2838
|
+
let reconsentRequired = data.detail.reconsent_required;
|
|
2839
|
+
let ledgerOverlayApplied = false;
|
|
2840
|
+
if (this.options.ledgerBaseUrl && !this.options.applicationId) {
|
|
2841
|
+
const ledgerStatus = await fetchNoticeConsentStatus({
|
|
2842
|
+
accessToken: this.options.accessToken,
|
|
2843
|
+
ledgerBaseUrl: this.options.ledgerBaseUrl,
|
|
2844
|
+
noticeUuid: this.options.noticeId,
|
|
2845
|
+
signal: this.abortController.signal
|
|
2846
|
+
});
|
|
2847
|
+
if (ledgerStatus) {
|
|
2848
|
+
const overlay = buildLedgerConsentOverlay(ledgerStatus, activeConfig.purposes);
|
|
2849
|
+
reconsentRequired = (_a = overlay == null ? void 0 : overlay.reconsent_required) != null ? _a : false;
|
|
2850
|
+
purposeSelections = overlay == null ? void 0 : overlay.purpose_selections;
|
|
2851
|
+
ledgerOverlayApplied = Boolean(overlay);
|
|
2852
|
+
}
|
|
2853
|
+
}
|
|
2747
2854
|
const initialCollapsed = {};
|
|
2748
2855
|
const initialPurposes = {};
|
|
2749
2856
|
const initialElements = {};
|
|
@@ -2751,7 +2858,7 @@ var ModalController = class {
|
|
|
2751
2858
|
let needsConsentPurposes = [];
|
|
2752
2859
|
let alreadyConsentedPurposes = [];
|
|
2753
2860
|
let isFullyConsentedReviewMode = false;
|
|
2754
|
-
if (purposeSelections &&
|
|
2861
|
+
if (purposeSelections && (reconsentRequired || ledgerOverlayApplied)) {
|
|
2755
2862
|
for (const purpose of activeConfig.purposes) {
|
|
2756
2863
|
const selection = purposeSelections[purpose.uuid];
|
|
2757
2864
|
initialCollapsed[purpose.uuid] = true;
|
|
@@ -2760,7 +2867,7 @@ var ModalController = class {
|
|
|
2760
2867
|
initialPurposes[purpose.uuid] = true;
|
|
2761
2868
|
for (const de of purpose.data_elements) {
|
|
2762
2869
|
const combinedId = `${purpose.uuid}-${de.uuid}`;
|
|
2763
|
-
const deSelection = (
|
|
2870
|
+
const deSelection = (_b = selection.data_elements) == null ? void 0 : _b[de.uuid];
|
|
2764
2871
|
const wasSelected = (deSelection == null ? void 0 : deSelection.selected) || false;
|
|
2765
2872
|
initialElements[combinedId] = wasSelected;
|
|
2766
2873
|
initialDataElementSelections[combinedId] = wasSelected;
|
|
@@ -2849,7 +2956,7 @@ var ModalController = class {
|
|
|
2849
2956
|
this.options.onAccept();
|
|
2850
2957
|
} else {
|
|
2851
2958
|
this.store.setState({ fetchError: error, isLoading: false });
|
|
2852
|
-
(
|
|
2959
|
+
(_d = (_c = this.options).onError) == null ? void 0 : _d.call(_c, error);
|
|
2853
2960
|
}
|
|
2854
2961
|
}
|
|
2855
2962
|
}
|
|
@@ -3557,6 +3664,7 @@ var ModalController = class {
|
|
|
3557
3664
|
await submitConsentEvent({
|
|
3558
3665
|
accessToken: this.options.accessToken,
|
|
3559
3666
|
baseUrl: this.options.baseUrl,
|
|
3667
|
+
ledgerBaseUrl: this.options.ledgerBaseUrl,
|
|
3560
3668
|
noticeUuid: state.content.detail.uuid,
|
|
3561
3669
|
purposes,
|
|
3562
3670
|
declined: false,
|
|
@@ -4307,7 +4415,9 @@ var InlineController = class {
|
|
|
4307
4415
|
this.purposeRefs = null;
|
|
4308
4416
|
this.errorEl = null;
|
|
4309
4417
|
this.hasSubmitted = false;
|
|
4418
|
+
this.hasCheckedLedgerConsent = false;
|
|
4310
4419
|
this.unsubscribe = null;
|
|
4420
|
+
this.abortController = null;
|
|
4311
4421
|
this.options = options;
|
|
4312
4422
|
this.store = new Store({
|
|
4313
4423
|
content: null,
|
|
@@ -4318,12 +4428,14 @@ var InlineController = class {
|
|
|
4318
4428
|
selectedPurposes: {},
|
|
4319
4429
|
selectedDataElements: {},
|
|
4320
4430
|
hasAlreadyConsented: false,
|
|
4431
|
+
isCheckingConsent: false,
|
|
4321
4432
|
fetchError: null,
|
|
4322
4433
|
errorMessage: null
|
|
4323
4434
|
});
|
|
4324
4435
|
this.mediaQuery = createMediaQuery("(max-width: 768px)");
|
|
4325
4436
|
}
|
|
4326
4437
|
async init() {
|
|
4438
|
+
this.abortController = new AbortController();
|
|
4327
4439
|
if (typeof this.options.target === "string") {
|
|
4328
4440
|
this.targetEl = document.querySelector(this.options.target);
|
|
4329
4441
|
if (!this.targetEl) {
|
|
@@ -4346,8 +4458,9 @@ var InlineController = class {
|
|
|
4346
4458
|
await this.fetchNotice();
|
|
4347
4459
|
}
|
|
4348
4460
|
destroy() {
|
|
4349
|
-
var _a;
|
|
4350
|
-
(_a = this.
|
|
4461
|
+
var _a, _b;
|
|
4462
|
+
(_a = this.abortController) == null ? void 0 : _a.abort();
|
|
4463
|
+
(_b = this.unsubscribe) == null ? void 0 : _b.call(this);
|
|
4351
4464
|
this.mediaQuery.destroy();
|
|
4352
4465
|
this.store.destroy();
|
|
4353
4466
|
if (this.hostEl && this.hostEl.parentNode) {
|
|
@@ -4360,9 +4473,19 @@ var InlineController = class {
|
|
|
4360
4473
|
}
|
|
4361
4474
|
updateToken(accessToken) {
|
|
4362
4475
|
this.options = { ...this.options, accessToken };
|
|
4476
|
+
if (this.shouldCheckLedgerConsent()) {
|
|
4477
|
+
void this.checkLedgerConsent();
|
|
4478
|
+
return;
|
|
4479
|
+
}
|
|
4363
4480
|
this.tryAutoSubmit();
|
|
4364
4481
|
}
|
|
4365
4482
|
// ─── Private ───
|
|
4483
|
+
// destroy() aborts the controller, so an aborted signal is the marker that
|
|
4484
|
+
// teardown has happened and async continuations must not touch the host.
|
|
4485
|
+
isDestroyed() {
|
|
4486
|
+
var _a;
|
|
4487
|
+
return Boolean((_a = this.abortController) == null ? void 0 : _a.signal.aborted);
|
|
4488
|
+
}
|
|
4366
4489
|
async fetchNotice() {
|
|
4367
4490
|
var _a, _b, _c, _d;
|
|
4368
4491
|
this.store.setState({ isLoading: true, fetchError: null });
|
|
@@ -4375,6 +4498,9 @@ var InlineController = class {
|
|
|
4375
4498
|
language: this.options.language,
|
|
4376
4499
|
specific_uuid: this.options.applicationId
|
|
4377
4500
|
});
|
|
4501
|
+
if (this.isDestroyed()) {
|
|
4502
|
+
return;
|
|
4503
|
+
}
|
|
4378
4504
|
const activeConfig = data.detail.active_config;
|
|
4379
4505
|
const initialCollapsed = {};
|
|
4380
4506
|
const initialPurposes = {};
|
|
@@ -4386,6 +4512,7 @@ var InlineController = class {
|
|
|
4386
4512
|
initialElements[`${purpose.uuid}-${de.uuid}`] = false;
|
|
4387
4513
|
}
|
|
4388
4514
|
}
|
|
4515
|
+
void this.checkLedgerConsent();
|
|
4389
4516
|
this.store.setState({
|
|
4390
4517
|
content: activeConfig,
|
|
4391
4518
|
isLoading: false,
|
|
@@ -4395,6 +4522,9 @@ var InlineController = class {
|
|
|
4395
4522
|
selectedDataElements: initialElements
|
|
4396
4523
|
});
|
|
4397
4524
|
} catch (err) {
|
|
4525
|
+
if (this.isDestroyed()) {
|
|
4526
|
+
return;
|
|
4527
|
+
}
|
|
4398
4528
|
const error = err;
|
|
4399
4529
|
if (error.status === 409) {
|
|
4400
4530
|
this.store.setState({ hasAlreadyConsented: true, isLoading: false });
|
|
@@ -4405,6 +4535,41 @@ var InlineController = class {
|
|
|
4405
4535
|
}
|
|
4406
4536
|
}
|
|
4407
4537
|
}
|
|
4538
|
+
shouldCheckLedgerConsent() {
|
|
4539
|
+
return Boolean(this.options.ledgerBaseUrl) && !this.options.applicationId && Boolean(this.options.accessToken) && !this.hasCheckedLedgerConsent;
|
|
4540
|
+
}
|
|
4541
|
+
// Ask the Go ledger whether this principal has already fully consented —
|
|
4542
|
+
// the ledger equivalent of the legacy Python 409. Skipped without a ledger
|
|
4543
|
+
// (the 409 path governs) and for the magic-link applicationId flow. Any
|
|
4544
|
+
// failure returns null and falls through to the normal flow.
|
|
4545
|
+
async checkLedgerConsent() {
|
|
4546
|
+
var _a, _b, _c;
|
|
4547
|
+
if (!this.shouldCheckLedgerConsent()) return;
|
|
4548
|
+
this.hasCheckedLedgerConsent = true;
|
|
4549
|
+
this.store.setState({ isCheckingConsent: true });
|
|
4550
|
+
try {
|
|
4551
|
+
const status = await fetchNoticeConsentStatusForScope({
|
|
4552
|
+
accessToken: this.options.accessToken,
|
|
4553
|
+
ledgerBaseUrl: this.options.ledgerBaseUrl,
|
|
4554
|
+
organisationUuid: this.options.org_uuid,
|
|
4555
|
+
workspaceUuid: this.options.workspace_uuid,
|
|
4556
|
+
noticeUuid: this.options.notice_uuid,
|
|
4557
|
+
signal: (_a = this.abortController) == null ? void 0 : _a.signal
|
|
4558
|
+
});
|
|
4559
|
+
if (this.isDestroyed()) {
|
|
4560
|
+
return;
|
|
4561
|
+
}
|
|
4562
|
+
if (status && isNoticeFullyConsented(status)) {
|
|
4563
|
+
this.store.setState({ hasAlreadyConsented: true });
|
|
4564
|
+
(_c = (_b = this.options).onAccept) == null ? void 0 : _c.call(_b);
|
|
4565
|
+
}
|
|
4566
|
+
} finally {
|
|
4567
|
+
if (!this.isDestroyed()) {
|
|
4568
|
+
this.store.setState({ isCheckingConsent: false });
|
|
4569
|
+
this.tryAutoSubmit();
|
|
4570
|
+
}
|
|
4571
|
+
}
|
|
4572
|
+
}
|
|
4408
4573
|
handleStateChange(state, prevState) {
|
|
4409
4574
|
if (prevState.isLoading && !state.isLoading && state.content) {
|
|
4410
4575
|
this.rebuildUI();
|
|
@@ -4624,9 +4789,12 @@ var InlineController = class {
|
|
|
4624
4789
|
}
|
|
4625
4790
|
async tryAutoSubmit() {
|
|
4626
4791
|
var _a, _b;
|
|
4792
|
+
if (this.isDestroyed()) {
|
|
4793
|
+
return;
|
|
4794
|
+
}
|
|
4627
4795
|
const state = this.store.getState();
|
|
4628
4796
|
const accessToken = this.options.accessToken;
|
|
4629
|
-
if (accessToken && state.content && this.areAllRequiredElementsChecked() && !state.isSubmitting && !this.hasSubmitted) {
|
|
4797
|
+
if (accessToken && state.content && this.areAllRequiredElementsChecked() && !state.isSubmitting && !state.isCheckingConsent && !state.hasAlreadyConsented && !this.hasSubmitted) {
|
|
4630
4798
|
this.hasSubmitted = true;
|
|
4631
4799
|
try {
|
|
4632
4800
|
await this.handleSubmit(accessToken);
|
|
@@ -4651,6 +4819,7 @@ var InlineController = class {
|
|
|
4651
4819
|
workspace_uuid: this.options.workspace_uuid,
|
|
4652
4820
|
accessToken: tokenToUse,
|
|
4653
4821
|
baseUrl: this.options.baseUrl,
|
|
4822
|
+
ledgerBaseUrl: this.options.ledgerBaseUrl,
|
|
4654
4823
|
noticeUuid: content.notice_uuid,
|
|
4655
4824
|
purposes: content.purposes.map((purpose) => {
|
|
4656
4825
|
const hasSelectedRequired = purpose.data_elements.some(
|