@redacto.io/consent-sdk-react-native 5.0.0-beta.0 → 5.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/README.md +15 -0
- package/dist/privacy-center.js +86 -5
- package/dist/privacy-center.mjs +86 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -136,6 +136,7 @@ import { RedactoNoticeConsent } from "@redacto.io/consent-sdk-react-native";
|
|
|
136
136
|
| `accessToken` | `string` | ✓ | JWT access token |
|
|
137
137
|
| `refreshToken` | `string` | ✓ | JWT refresh token |
|
|
138
138
|
| `baseUrl` | `string` | | Override API base URL |
|
|
139
|
+
| `ledgerBaseUrl` | `string` | | Go ledger base URL. When set, consent writes and prior-consent checks route through the ledger (falls back: ledger → baseUrl → default). Omit to keep the legacy consent-server path. |
|
|
139
140
|
| `settings` | `Partial<Settings>` | | UI customisation (colours, border radius, fonts) |
|
|
140
141
|
| `language` | `string` | | Initial language code (e.g. `"en"`) |
|
|
141
142
|
| `blockUI` | `boolean` | | Block the modal from being dismissed |
|
|
@@ -173,6 +174,7 @@ import { RedactoNoticeConsentInline } from "@redacto.io/consent-sdk-react-native
|
|
|
173
174
|
| `notice_uuid` | `string` | ✓ | Notice UUID |
|
|
174
175
|
| `accessToken` | `string` | | JWT access token (triggers auto-submit when set) |
|
|
175
176
|
| `baseUrl` | `string` | | Override API base URL |
|
|
177
|
+
| `ledgerBaseUrl` | `string` | | Go ledger base URL. When set, consent writes and already-consented checks route through the ledger. Omit for legacy behavior. |
|
|
176
178
|
| `language` | `string` | | Language code |
|
|
177
179
|
| `settings` | `Partial<Settings>` | | UI customisation |
|
|
178
180
|
| `applicationId` | `string` | | Application-specific UUID |
|
|
@@ -217,6 +219,7 @@ import { RedactoPrivacyCenter } from "@redacto.io/consent-sdk-react-native/priva
|
|
|
217
219
|
| Prop | Type | Required | Default | Description |
|
|
218
220
|
| -------------- | ----------------------------------------------------------------------------- | -------- | -------------------- | ---------------------------------------- |
|
|
219
221
|
| `baseUrl` | `string` | ✓ | - | Consent server base URL |
|
|
222
|
+
| `ledgerBaseUrl` | `string` | | - | Go ledger base URL. When set, user consents, group purposes, receipts, and manage-consent route through the ledger. Omit to use the legacy `/dsar/privacy-center/*` routes. See the note below on the DSAR form picker. |
|
|
220
223
|
| `slug` | `string` | ✓ | - | Workspace slug |
|
|
221
224
|
| `accessToken` | `string` | ✓ | - | JWT access token |
|
|
222
225
|
| `refreshToken` | `string` | ✓ | - | JWT refresh token |
|
|
@@ -274,6 +277,18 @@ For consumers who want fully custom UI, the headless core exposes the API client
|
|
|
274
277
|
- Keep API keys and server secrets on your backend only.
|
|
275
278
|
- In Expo apps, values read from `EXPO_PUBLIC_*` are bundled into the app and should only contain non-sensitive config like UUIDs and URLs.
|
|
276
279
|
- If you do not pass `baseUrl`, the SDK defaults to `https://api.redacto.io/consent` for both notice and Privacy Center surfaces.
|
|
280
|
+
- `ledgerBaseUrl` is optional and independent of `baseUrl` — pass both when routing consent through the Go ledger. Omitting it keeps the legacy consent-server behavior.
|
|
281
|
+
|
|
282
|
+
## Ledger mode
|
|
283
|
+
|
|
284
|
+
Passing `ledgerBaseUrl` routes consent-bearing reads and writes to the Go ledger:
|
|
285
|
+
|
|
286
|
+
- **Notice components** — `submit-consent` goes to the ledger, and the already-consented check uses the ledger's `check-consent` endpoint instead of the legacy HTTP 409 signal. Notice content, tokens, and TTS always stay on `baseUrl`.
|
|
287
|
+
- **Privacy Center** — user consents, group purposes, receipts (including the `/download` PDF path), and manage-consent (sent as a `partial: true` write) route to the ledger. DSAR form, cases, activities, OTP, and notifications stay on `baseUrl`.
|
|
288
|
+
|
|
289
|
+
Every ledger read degrades to the legacy path on failure, so a ledger outage does not break the consent flow.
|
|
290
|
+
|
|
291
|
+
> **Known limitation.** Unlike the React SDK, the DSAR form's product picker is **not** overlaid from ledger-served consents. Against a workspace cut over to the ledger — where the legacy `form/data` response carries no product groups — the picker renders empty. If your DSAR form depends on the product picker, stay on the legacy path until this is addressed.
|
|
277
292
|
|
|
278
293
|
## License
|
|
279
294
|
|
package/dist/privacy-center.js
CHANGED
|
@@ -14315,6 +14315,7 @@ var useTimePeriodOptions = () => {
|
|
|
14315
14315
|
};
|
|
14316
14316
|
var MAX_DISPLAYED_PURPOSE_NAMES_IN_ERASURE_MODAL = 3;
|
|
14317
14317
|
var SEARCH_DEBOUNCE_DELAY = 500;
|
|
14318
|
+
var FORM_PICKER_GROUP_LIMIT = 100;
|
|
14318
14319
|
var POLL_INTERVAL = 3e4;
|
|
14319
14320
|
var NOTIFICATION_PAGE_SIZE = 10;
|
|
14320
14321
|
var TAB_TO_CATEGORY = {
|
|
@@ -16271,6 +16272,41 @@ var styles32 = import_react_native43.StyleSheet.create({
|
|
|
16271
16272
|
|
|
16272
16273
|
// src/RedactoPrivacyCenter/components/groupHelpers.ts
|
|
16273
16274
|
var productGroupKey = (group) => group.nominator ? `${group.product_uuid}:${group.nominator.uuid}` : group.product_uuid;
|
|
16275
|
+
var consentGroupToFormProductGroup = (group) => {
|
|
16276
|
+
var _a3;
|
|
16277
|
+
return {
|
|
16278
|
+
product_uuid: group.product_uuid,
|
|
16279
|
+
product_name: group.product_name,
|
|
16280
|
+
product_description: group.product_description,
|
|
16281
|
+
nominator: group.nominator,
|
|
16282
|
+
purposes: ((_a3 = group.purposes) != null ? _a3 : []).map(purposeItemToFormPurpose)
|
|
16283
|
+
};
|
|
16284
|
+
};
|
|
16285
|
+
var purposeItemToFormPurpose = (purpose) => {
|
|
16286
|
+
var _a3, _b;
|
|
16287
|
+
return {
|
|
16288
|
+
uuid: purpose.purpose_uuid,
|
|
16289
|
+
purpose_uuid: purpose.purpose_uuid,
|
|
16290
|
+
name: purpose.name,
|
|
16291
|
+
description: (_a3 = purpose.description) != null ? _a3 : "",
|
|
16292
|
+
industries: "",
|
|
16293
|
+
selected: false,
|
|
16294
|
+
given_consent: false,
|
|
16295
|
+
status: purpose.status,
|
|
16296
|
+
validity: null,
|
|
16297
|
+
enabled: true,
|
|
16298
|
+
data_elements: ((_b = purpose.data_elements) != null ? _b : []).map(consentDataElementToFormDataElement)
|
|
16299
|
+
};
|
|
16300
|
+
};
|
|
16301
|
+
var consentDataElementToFormDataElement = (element) => ({
|
|
16302
|
+
uuid: element.uuid,
|
|
16303
|
+
name: element.name,
|
|
16304
|
+
description: "",
|
|
16305
|
+
enabled: element.enabled,
|
|
16306
|
+
required: element.required,
|
|
16307
|
+
given_consent: element.selected,
|
|
16308
|
+
selected: element.selected
|
|
16309
|
+
});
|
|
16274
16310
|
var buildGroupPurposes = (group) => group.purposes.filter((p) => p !== null && p !== void 0).map((p) => {
|
|
16275
16311
|
const validDataElements = Array.isArray(p.data_elements) ? p.data_elements.filter((de) => de !== null && de !== void 0 && Boolean(de.uuid)).map((de) => ({ ...de, selected: false })) : [];
|
|
16276
16312
|
return {
|
|
@@ -16305,7 +16341,7 @@ var isProductScopedRequestType = (rt) => PRODUCT_SCOPED_REQUEST_TYPES.has(rt);
|
|
|
16305
16341
|
function Form({ onCreateRequest }) {
|
|
16306
16342
|
const { t } = (0, import_react_i18next16.useTranslation)();
|
|
16307
16343
|
const { accessToken, refreshToken, email, organizationId, workspaceId, updateTokens, onError } = useAuth();
|
|
16308
|
-
const { consentClient, baseUrl } = useBaseUrl();
|
|
16344
|
+
const { consentClient, baseUrl, ledgerBaseUrl } = useBaseUrl();
|
|
16309
16345
|
const { colors } = useTheme();
|
|
16310
16346
|
const requestTypeLabels = useRequestTypeLabels();
|
|
16311
16347
|
const timePeriodOptions = useTimePeriodOptions();
|
|
@@ -16342,15 +16378,60 @@ function Form({ onCreateRequest }) {
|
|
|
16342
16378
|
});
|
|
16343
16379
|
const [uploadedDocs, setUploadedDocs] = (0, import_react29.useState)([]);
|
|
16344
16380
|
const [isUploading, setIsUploading] = (0, import_react29.useState)(false);
|
|
16381
|
+
const withLedgerPickerGroups = (0, import_react29.useCallback)(
|
|
16382
|
+
async (form, language) => {
|
|
16383
|
+
var _a3, _b;
|
|
16384
|
+
if (!ledgerBaseUrl || !organizationId || !workspaceId) {
|
|
16385
|
+
return form;
|
|
16386
|
+
}
|
|
16387
|
+
const consents = await fetchUserConsents({
|
|
16388
|
+
organizationId,
|
|
16389
|
+
workspaceId,
|
|
16390
|
+
// No status filter: include every status (active / expired / withdrawn
|
|
16391
|
+
// / declined), matching Python form/data, so a DSAR can target products
|
|
16392
|
+
// the user no longer actively consents to.
|
|
16393
|
+
limit: FORM_PICKER_GROUP_LIMIT,
|
|
16394
|
+
nominatedLimit: FORM_PICKER_GROUP_LIMIT,
|
|
16395
|
+
language,
|
|
16396
|
+
authContext,
|
|
16397
|
+
consentClient
|
|
16398
|
+
});
|
|
16399
|
+
if (!consents) {
|
|
16400
|
+
return form;
|
|
16401
|
+
}
|
|
16402
|
+
return {
|
|
16403
|
+
...form,
|
|
16404
|
+
direct_groups: ((_a3 = consents.direct) != null ? _a3 : []).map(consentGroupToFormProductGroup),
|
|
16405
|
+
nominated_groups: ((_b = consents.nominated) != null ? _b : []).map(consentGroupToFormProductGroup)
|
|
16406
|
+
};
|
|
16407
|
+
},
|
|
16408
|
+
[ledgerBaseUrl, organizationId, workspaceId, authContext, consentClient]
|
|
16409
|
+
);
|
|
16345
16410
|
(0, import_react29.useEffect)(() => {
|
|
16346
16411
|
if (!organizationId || !workspaceId || !contact) return;
|
|
16412
|
+
let isCancelled = false;
|
|
16347
16413
|
setIsLoading(true);
|
|
16348
|
-
getFormData(organizationId, workspaceId, contact, false, false, authContext, consentClient, langParam).then((data) => {
|
|
16349
|
-
if (data)
|
|
16414
|
+
getFormData(organizationId, workspaceId, contact, false, false, authContext, consentClient, langParam).then(async (data) => {
|
|
16415
|
+
if (!data) return;
|
|
16416
|
+
const withGroups = await withLedgerPickerGroups(data, langParam);
|
|
16417
|
+
if (!isCancelled) setFormDataResponse(withGroups);
|
|
16350
16418
|
}).catch((error) => {
|
|
16351
16419
|
console.warn("Error fetching form data:", error);
|
|
16352
|
-
}).finally(() =>
|
|
16353
|
-
|
|
16420
|
+
}).finally(() => {
|
|
16421
|
+
if (!isCancelled) setIsLoading(false);
|
|
16422
|
+
});
|
|
16423
|
+
return () => {
|
|
16424
|
+
isCancelled = true;
|
|
16425
|
+
};
|
|
16426
|
+
}, [
|
|
16427
|
+
organizationId,
|
|
16428
|
+
workspaceId,
|
|
16429
|
+
contact,
|
|
16430
|
+
authContext,
|
|
16431
|
+
consentClient,
|
|
16432
|
+
langParam,
|
|
16433
|
+
withLedgerPickerGroups
|
|
16434
|
+
]);
|
|
16354
16435
|
const allGroups = (0, import_react29.useMemo)(() => {
|
|
16355
16436
|
var _a3, _b;
|
|
16356
16437
|
if (!formDataResponse) return [];
|
package/dist/privacy-center.mjs
CHANGED
|
@@ -14324,6 +14324,7 @@ var useTimePeriodOptions = () => {
|
|
|
14324
14324
|
};
|
|
14325
14325
|
var MAX_DISPLAYED_PURPOSE_NAMES_IN_ERASURE_MODAL = 3;
|
|
14326
14326
|
var SEARCH_DEBOUNCE_DELAY = 500;
|
|
14327
|
+
var FORM_PICKER_GROUP_LIMIT = 100;
|
|
14327
14328
|
var POLL_INTERVAL = 3e4;
|
|
14328
14329
|
var NOTIFICATION_PAGE_SIZE = 10;
|
|
14329
14330
|
var TAB_TO_CATEGORY = {
|
|
@@ -16303,6 +16304,41 @@ var styles32 = StyleSheet33.create({
|
|
|
16303
16304
|
|
|
16304
16305
|
// src/RedactoPrivacyCenter/components/groupHelpers.ts
|
|
16305
16306
|
var productGroupKey = (group) => group.nominator ? `${group.product_uuid}:${group.nominator.uuid}` : group.product_uuid;
|
|
16307
|
+
var consentGroupToFormProductGroup = (group) => {
|
|
16308
|
+
var _a3;
|
|
16309
|
+
return {
|
|
16310
|
+
product_uuid: group.product_uuid,
|
|
16311
|
+
product_name: group.product_name,
|
|
16312
|
+
product_description: group.product_description,
|
|
16313
|
+
nominator: group.nominator,
|
|
16314
|
+
purposes: ((_a3 = group.purposes) != null ? _a3 : []).map(purposeItemToFormPurpose)
|
|
16315
|
+
};
|
|
16316
|
+
};
|
|
16317
|
+
var purposeItemToFormPurpose = (purpose) => {
|
|
16318
|
+
var _a3, _b;
|
|
16319
|
+
return {
|
|
16320
|
+
uuid: purpose.purpose_uuid,
|
|
16321
|
+
purpose_uuid: purpose.purpose_uuid,
|
|
16322
|
+
name: purpose.name,
|
|
16323
|
+
description: (_a3 = purpose.description) != null ? _a3 : "",
|
|
16324
|
+
industries: "",
|
|
16325
|
+
selected: false,
|
|
16326
|
+
given_consent: false,
|
|
16327
|
+
status: purpose.status,
|
|
16328
|
+
validity: null,
|
|
16329
|
+
enabled: true,
|
|
16330
|
+
data_elements: ((_b = purpose.data_elements) != null ? _b : []).map(consentDataElementToFormDataElement)
|
|
16331
|
+
};
|
|
16332
|
+
};
|
|
16333
|
+
var consentDataElementToFormDataElement = (element) => ({
|
|
16334
|
+
uuid: element.uuid,
|
|
16335
|
+
name: element.name,
|
|
16336
|
+
description: "",
|
|
16337
|
+
enabled: element.enabled,
|
|
16338
|
+
required: element.required,
|
|
16339
|
+
given_consent: element.selected,
|
|
16340
|
+
selected: element.selected
|
|
16341
|
+
});
|
|
16306
16342
|
var buildGroupPurposes = (group) => group.purposes.filter((p) => p !== null && p !== void 0).map((p) => {
|
|
16307
16343
|
const validDataElements = Array.isArray(p.data_elements) ? p.data_elements.filter((de) => de !== null && de !== void 0 && Boolean(de.uuid)).map((de) => ({ ...de, selected: false })) : [];
|
|
16308
16344
|
return {
|
|
@@ -16337,7 +16373,7 @@ var isProductScopedRequestType = (rt) => PRODUCT_SCOPED_REQUEST_TYPES.has(rt);
|
|
|
16337
16373
|
function Form({ onCreateRequest }) {
|
|
16338
16374
|
const { t } = useTranslation15();
|
|
16339
16375
|
const { accessToken, refreshToken, email, organizationId, workspaceId, updateTokens, onError } = useAuth();
|
|
16340
|
-
const { consentClient, baseUrl } = useBaseUrl();
|
|
16376
|
+
const { consentClient, baseUrl, ledgerBaseUrl } = useBaseUrl();
|
|
16341
16377
|
const { colors } = useTheme();
|
|
16342
16378
|
const requestTypeLabels = useRequestTypeLabels();
|
|
16343
16379
|
const timePeriodOptions = useTimePeriodOptions();
|
|
@@ -16374,15 +16410,60 @@ function Form({ onCreateRequest }) {
|
|
|
16374
16410
|
});
|
|
16375
16411
|
const [uploadedDocs, setUploadedDocs] = useState16([]);
|
|
16376
16412
|
const [isUploading, setIsUploading] = useState16(false);
|
|
16413
|
+
const withLedgerPickerGroups = useCallback10(
|
|
16414
|
+
async (form, language) => {
|
|
16415
|
+
var _a3, _b;
|
|
16416
|
+
if (!ledgerBaseUrl || !organizationId || !workspaceId) {
|
|
16417
|
+
return form;
|
|
16418
|
+
}
|
|
16419
|
+
const consents = await fetchUserConsents({
|
|
16420
|
+
organizationId,
|
|
16421
|
+
workspaceId,
|
|
16422
|
+
// No status filter: include every status (active / expired / withdrawn
|
|
16423
|
+
// / declined), matching Python form/data, so a DSAR can target products
|
|
16424
|
+
// the user no longer actively consents to.
|
|
16425
|
+
limit: FORM_PICKER_GROUP_LIMIT,
|
|
16426
|
+
nominatedLimit: FORM_PICKER_GROUP_LIMIT,
|
|
16427
|
+
language,
|
|
16428
|
+
authContext,
|
|
16429
|
+
consentClient
|
|
16430
|
+
});
|
|
16431
|
+
if (!consents) {
|
|
16432
|
+
return form;
|
|
16433
|
+
}
|
|
16434
|
+
return {
|
|
16435
|
+
...form,
|
|
16436
|
+
direct_groups: ((_a3 = consents.direct) != null ? _a3 : []).map(consentGroupToFormProductGroup),
|
|
16437
|
+
nominated_groups: ((_b = consents.nominated) != null ? _b : []).map(consentGroupToFormProductGroup)
|
|
16438
|
+
};
|
|
16439
|
+
},
|
|
16440
|
+
[ledgerBaseUrl, organizationId, workspaceId, authContext, consentClient]
|
|
16441
|
+
);
|
|
16377
16442
|
useEffect14(() => {
|
|
16378
16443
|
if (!organizationId || !workspaceId || !contact) return;
|
|
16444
|
+
let isCancelled = false;
|
|
16379
16445
|
setIsLoading(true);
|
|
16380
|
-
getFormData(organizationId, workspaceId, contact, false, false, authContext, consentClient, langParam).then((data) => {
|
|
16381
|
-
if (data)
|
|
16446
|
+
getFormData(organizationId, workspaceId, contact, false, false, authContext, consentClient, langParam).then(async (data) => {
|
|
16447
|
+
if (!data) return;
|
|
16448
|
+
const withGroups = await withLedgerPickerGroups(data, langParam);
|
|
16449
|
+
if (!isCancelled) setFormDataResponse(withGroups);
|
|
16382
16450
|
}).catch((error) => {
|
|
16383
16451
|
console.warn("Error fetching form data:", error);
|
|
16384
|
-
}).finally(() =>
|
|
16385
|
-
|
|
16452
|
+
}).finally(() => {
|
|
16453
|
+
if (!isCancelled) setIsLoading(false);
|
|
16454
|
+
});
|
|
16455
|
+
return () => {
|
|
16456
|
+
isCancelled = true;
|
|
16457
|
+
};
|
|
16458
|
+
}, [
|
|
16459
|
+
organizationId,
|
|
16460
|
+
workspaceId,
|
|
16461
|
+
contact,
|
|
16462
|
+
authContext,
|
|
16463
|
+
consentClient,
|
|
16464
|
+
langParam,
|
|
16465
|
+
withLedgerPickerGroups
|
|
16466
|
+
]);
|
|
16386
16467
|
const allGroups = useMemo14(() => {
|
|
16387
16468
|
var _a3, _b;
|
|
16388
16469
|
if (!formDataResponse) return [];
|
package/package.json
CHANGED