@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 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
 
@@ -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) setFormDataResponse(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(() => setIsLoading(false));
16353
- }, [organizationId, workspaceId, contact, authContext, consentClient, langParam]);
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 [];
@@ -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) setFormDataResponse(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(() => setIsLoading(false));
16385
- }, [organizationId, workspaceId, contact, authContext, consentClient, langParam]);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redacto.io/consent-sdk-react-native",
3
- "version": "5.0.0-beta.0",
3
+ "version": "5.0.0-beta.1",
4
4
  "description": "React Native SDK for integrating Redacto consent notices and privacy center in mobile apps.",
5
5
  "keywords": [
6
6
  "consent",