@redacto.io/consent-sdk-react-native 4.0.1-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.
@@ -9982,7 +9982,15 @@ function arrayBufferToBase64(buffer) {
9982
9982
  }
9983
9983
  return result;
9984
9984
  }
9985
- function createConsentClient(baseUrl) {
9985
+ function createConsentClient(baseUrl, ledgerBaseUrl) {
9986
+ const useLedger = Boolean(ledgerBaseUrl);
9987
+ const ledgerBase = (ledgerBaseUrl != null ? ledgerBaseUrl : "").replace(/\/$/, "");
9988
+ function ledgerUrl(baseRequest, path) {
9989
+ if (!useLedger) {
9990
+ throw new Error("ledgerUrl called without a configured ledgerBaseUrl");
9991
+ }
9992
+ return `${ledgerBase}/public/organisations/${baseRequest.organisationUuid}/workspaces/${baseRequest.workspaceUuid}${path}`;
9993
+ }
9986
9994
  async function privacyCenterApiFetch(url, options) {
9987
9995
  const { authToken, getAuthToken, headers = {}, ...fetchOptions } = options;
9988
9996
  const requestHeaders = new Headers();
@@ -10184,7 +10192,8 @@ function createConsentClient(baseUrl) {
10184
10192
  if (params.product_uuid) queryParams.append("product_uuid", params.product_uuid);
10185
10193
  if (params.language) queryParams.append("language", params.language);
10186
10194
  const queryString = queryParams.toString();
10187
- const url = `${baseUrl}/organisations/${baseRequest.organisationUuid}/workspaces/${baseRequest.workspaceUuid}/dsar/privacy-center/user-consents${queryString ? `?${queryString}` : ""}`;
10195
+ const consentsSuffix = `/user-consents${queryString ? `?${queryString}` : ""}`;
10196
+ const url = useLedger ? ledgerUrl(baseRequest, consentsSuffix) : `${baseUrl}/organisations/${baseRequest.organisationUuid}/workspaces/${baseRequest.workspaceUuid}/dsar/privacy-center${consentsSuffix}`;
10188
10197
  return privacyCenterApiFetch(url, {
10189
10198
  ...options,
10190
10199
  method: "GET",
@@ -10204,7 +10213,8 @@ function createConsentClient(baseUrl) {
10204
10213
  if (params.language) queryParams.append("language", params.language);
10205
10214
  if (params.nominator_uuid) queryParams.append("nominator_uuid", params.nominator_uuid);
10206
10215
  const queryString = queryParams.toString();
10207
- const url = `${baseUrl}/organisations/${baseRequest.organisationUuid}/workspaces/${baseRequest.workspaceUuid}/dsar/privacy-center/user-consents/${productUuid}/purposes${queryString ? `?${queryString}` : ""}`;
10216
+ const purposesSuffix = `/user-consents/${productUuid}/purposes${queryString ? `?${queryString}` : ""}`;
10217
+ const url = useLedger ? ledgerUrl(baseRequest, purposesSuffix) : `${baseUrl}/organisations/${baseRequest.organisationUuid}/workspaces/${baseRequest.workspaceUuid}/dsar/privacy-center${purposesSuffix}`;
10208
10218
  return privacyCenterApiFetch(url, {
10209
10219
  ...options,
10210
10220
  method: "GET",
@@ -10212,17 +10222,66 @@ function createConsentClient(baseUrl) {
10212
10222
  });
10213
10223
  },
10214
10224
  /**
10215
- * Manage consent (privacy center) - revoke, regrant, or renew
10225
+ * Manage consent (privacy center) - revoke, regrant, or renew.
10226
+ *
10227
+ * The Go ledger has no manage-consent route, so with a ledger configured
10228
+ * this is expressed as a `partial` submit-consent on the ledger — mirroring
10229
+ * the Python dual-write relay: the affected purpose is re-submitted with
10230
+ * `selected: false` to revoke or `selected: true` to regrant/renew, and
10231
+ * `partial: true` so the rest of the notice's purposes are left untouched.
10232
+ * The principal is resolved from the JWT, so no `contact`/identifiers are
10233
+ * sent in the body.
10234
+ *
10235
+ * Without a ledger, the legacy Python `/manage-consent?status=` endpoint is
10236
+ * used with its `{ purpose_uuid, contact, ... }` payload.
10216
10237
  */
10217
- async manageConsent(baseRequest, purposeId, contact, status, authToken, options, nominatorContact, dataElementUuids) {
10218
- const url = `${baseUrl}/organisations/${baseRequest.organisationUuid}/workspaces/${baseRequest.workspaceUuid}/dsar/privacy-center/manage-consent?status=${status}`;
10219
- const body = { purpose_uuid: purposeId, contact };
10220
- if (nominatorContact) {
10221
- body.nominator_contact = nominatorContact;
10238
+ async manageConsent(baseRequest, purposeId, contact, status, authToken, options, nominatorContact, dataElementUuids, noticeUuid, dataElements) {
10239
+ if (!useLedger) {
10240
+ const url2 = `${baseUrl}/organisations/${baseRequest.organisationUuid}/workspaces/${baseRequest.workspaceUuid}/dsar/privacy-center/manage-consent?status=${status}`;
10241
+ const body2 = { purpose_uuid: purposeId, contact };
10242
+ if (nominatorContact) {
10243
+ body2.nominator_contact = nominatorContact;
10244
+ }
10245
+ if (Array.isArray(dataElementUuids) && dataElementUuids.length > 0) {
10246
+ body2.data_element_uuids = dataElementUuids;
10247
+ }
10248
+ return privacyCenterApiFetch(url2, {
10249
+ ...options,
10250
+ method: "POST",
10251
+ body: JSON.stringify(body2),
10252
+ authToken: authToken || baseRequest.authToken
10253
+ });
10222
10254
  }
10223
- if (Array.isArray(dataElementUuids) && dataElementUuids.length > 0) {
10224
- body.data_element_uuids = dataElementUuids;
10255
+ if (!noticeUuid) {
10256
+ throw new Error("notice_uuid is required to manage consent via the ledger");
10225
10257
  }
10258
+ const purposeSelected = status !== "revoke";
10259
+ const elements = (dataElements != null ? dataElements : []).map((de) => {
10260
+ let selected;
10261
+ if (status === "revoke") {
10262
+ selected = false;
10263
+ } else if (status === "regrant") {
10264
+ selected = Array.isArray(dataElementUuids) ? dataElementUuids.includes(de.uuid) : de.selected;
10265
+ } else {
10266
+ selected = de.selected;
10267
+ }
10268
+ return { uuid: de.uuid, selected };
10269
+ });
10270
+ const body = {
10271
+ notice_uuid: noticeUuid,
10272
+ partial: true,
10273
+ declined: false,
10274
+ select_all_mandatory: false,
10275
+ self_declared_adult: false,
10276
+ purposes: [
10277
+ {
10278
+ purpose_uuid: purposeId,
10279
+ selected: purposeSelected,
10280
+ data_elements: elements
10281
+ }
10282
+ ]
10283
+ };
10284
+ const url = ledgerUrl(baseRequest, "/submit-consent");
10226
10285
  return privacyCenterApiFetch(url, {
10227
10286
  ...options,
10228
10287
  method: "POST",
@@ -10259,7 +10318,8 @@ function createConsentClient(baseUrl) {
10259
10318
  if (params.notice_uuid) queryParams.append("notice_uuid", params.notice_uuid);
10260
10319
  if (params.language) queryParams.append("language", params.language);
10261
10320
  const queryString = queryParams.toString();
10262
- const url = `${baseUrl}/organisations/${baseRequest.organisationUuid}/workspaces/${baseRequest.workspaceUuid}/dsar/privacy-center/receipts${queryString ? `?${queryString}` : ""}`;
10321
+ const receiptsSuffix = `/receipts${queryString ? `?${queryString}` : ""}`;
10322
+ const url = useLedger ? ledgerUrl(baseRequest, receiptsSuffix) : `${baseUrl}/organisations/${baseRequest.organisationUuid}/workspaces/${baseRequest.workspaceUuid}/dsar/privacy-center${receiptsSuffix}`;
10263
10323
  return privacyCenterApiFetch(url, {
10264
10324
  ...options,
10265
10325
  method: "GET",
@@ -10273,7 +10333,7 @@ function createConsentClient(baseUrl) {
10273
10333
  * the RN UI writes to a temp file to open/share. 404 => not found.
10274
10334
  */
10275
10335
  async getReceiptPdf(baseRequest, receiptUuid, authToken) {
10276
- const url = `${baseUrl}/organisations/${baseRequest.organisationUuid}/workspaces/${baseRequest.workspaceUuid}/dsar/privacy-center/receipts/${receiptUuid}/pdf`;
10336
+ const url = useLedger ? ledgerUrl(baseRequest, `/receipts/${receiptUuid}/download`) : `${baseUrl}/organisations/${baseRequest.organisationUuid}/workspaces/${baseRequest.workspaceUuid}/dsar/privacy-center/receipts/${receiptUuid}/pdf`;
10277
10337
  const token = authToken || baseRequest.authToken;
10278
10338
  const requestHeaders = new Headers();
10279
10339
  requestHeaders.set("Accept", "application/pdf");
@@ -10432,11 +10492,16 @@ function createConsentClient(baseUrl) {
10432
10492
  // src/RedactoPrivacyCenter/context/BaseUrlContext.tsx
10433
10493
  var import_jsx_runtime3 = require("react/jsx-runtime");
10434
10494
  var BaseUrlContext = (0, import_react2.createContext)(null);
10435
- function BaseUrlProvider({ children, baseUrl }) {
10495
+ function BaseUrlProvider({
10496
+ children,
10497
+ baseUrl,
10498
+ ledgerBaseUrl
10499
+ }) {
10436
10500
  const value = (0, import_react2.useMemo)(() => ({
10437
10501
  baseUrl,
10438
- consentClient: createConsentClient(baseUrl)
10439
- }), [baseUrl]);
10502
+ ledgerBaseUrl,
10503
+ consentClient: createConsentClient(baseUrl, ledgerBaseUrl)
10504
+ }), [baseUrl, ledgerBaseUrl]);
10440
10505
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(BaseUrlContext.Provider, { value, children });
10441
10506
  }
10442
10507
  function useBaseUrl() {
@@ -10635,7 +10700,6 @@ async function fetchUserConsentsServer(params) {
10635
10700
  }
10636
10701
  }
10637
10702
  async function manageConsentServer(params) {
10638
- var _a3;
10639
10703
  try {
10640
10704
  if (!params.consentClient) {
10641
10705
  throw new Error("Consent client is required");
@@ -10653,9 +10717,14 @@ async function manageConsentServer(params) {
10653
10717
  params.authToken,
10654
10718
  void 0,
10655
10719
  params.nominatorContact,
10656
- params.dataElementUuids
10720
+ params.dataElementUuids,
10721
+ params.noticeUuid,
10722
+ params.dataElements
10657
10723
  );
10658
- return (_a3 = result == null ? void 0 : result.success) != null ? _a3 : null;
10724
+ if (result && typeof result === "object" && "success" in result) {
10725
+ return Boolean(result.success);
10726
+ }
10727
+ return true;
10659
10728
  } catch (error) {
10660
10729
  if (isAuthError(error)) {
10661
10730
  throw error;
@@ -11139,7 +11208,9 @@ var manageConsent = async ({
11139
11208
  authContext,
11140
11209
  consentClient,
11141
11210
  nominatorContact,
11142
- dataElementUuids
11211
+ dataElementUuids,
11212
+ noticeUuid,
11213
+ dataElements
11143
11214
  }) => {
11144
11215
  try {
11145
11216
  const accessToken = await ensureValidToken(organizationId, workspaceId, void 0, authContext, consentClient);
@@ -11155,7 +11226,9 @@ var manageConsent = async ({
11155
11226
  authToken: accessToken,
11156
11227
  consentClient,
11157
11228
  nominatorContact,
11158
- dataElementUuids
11229
+ dataElementUuids,
11230
+ noticeUuid,
11231
+ dataElements
11159
11232
  });
11160
11233
  return result;
11161
11234
  } catch (error) {
@@ -11174,7 +11247,9 @@ var manageConsent = async ({
11174
11247
  authToken: newAccessToken || void 0,
11175
11248
  consentClient,
11176
11249
  nominatorContact,
11177
- dataElementUuids
11250
+ dataElementUuids,
11251
+ noticeUuid,
11252
+ dataElements
11178
11253
  });
11179
11254
  }
11180
11255
  console.error(`Error ${action} consent:`, error);
@@ -14240,6 +14315,7 @@ var useTimePeriodOptions = () => {
14240
14315
  };
14241
14316
  var MAX_DISPLAYED_PURPOSE_NAMES_IN_ERASURE_MODAL = 3;
14242
14317
  var SEARCH_DEBOUNCE_DELAY = 500;
14318
+ var FORM_PICKER_GROUP_LIMIT = 100;
14243
14319
  var POLL_INTERVAL = 3e4;
14244
14320
  var NOTIFICATION_PAGE_SIZE = 10;
14245
14321
  var TAB_TO_CATEGORY = {
@@ -14993,6 +15069,7 @@ function ConsentManager() {
14993
15069
  }, [fetchData]);
14994
15070
  const handleManageConsent = (0, import_react25.useCallback)(
14995
15071
  async (purposeId, action, nominatorContact, dataElementUuids) => {
15072
+ var _a3, _b;
14996
15073
  if (!organizationId || !workspaceId || !email) return;
14997
15074
  setIsManageLoading(true);
14998
15075
  try {
@@ -15005,7 +15082,14 @@ function ConsentManager() {
15005
15082
  authContext,
15006
15083
  consentClient,
15007
15084
  nominatorContact,
15008
- dataElementUuids
15085
+ dataElementUuids,
15086
+ // Ledger mode: the partial submit-consent needs the notice and the
15087
+ // purpose's data elements; both ride on the selected (ledger-served) row.
15088
+ noticeUuid: (_a3 = selectedRecord == null ? void 0 : selectedRecord.notice_uuid) != null ? _a3 : void 0,
15089
+ dataElements: (_b = selectedRecord == null ? void 0 : selectedRecord.data_elements) == null ? void 0 : _b.map((de) => ({
15090
+ uuid: de.uuid,
15091
+ selected: de.selected
15092
+ }))
15009
15093
  });
15010
15094
  setIsModalOpen(false);
15011
15095
  setSelectedRecord(null);
@@ -15017,7 +15101,7 @@ function ConsentManager() {
15017
15101
  setIsManageLoading(false);
15018
15102
  }
15019
15103
  },
15020
- [organizationId, workspaceId, email, authContext, consentClient, fetchData, t]
15104
+ [organizationId, workspaceId, email, authContext, consentClient, selectedRecord, fetchData, t]
15021
15105
  );
15022
15106
  const handleExport = (0, import_react25.useCallback)(async () => {
15023
15107
  if (!consentData.length) return;
@@ -16188,6 +16272,41 @@ var styles32 = import_react_native43.StyleSheet.create({
16188
16272
 
16189
16273
  // src/RedactoPrivacyCenter/components/groupHelpers.ts
16190
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
+ });
16191
16310
  var buildGroupPurposes = (group) => group.purposes.filter((p) => p !== null && p !== void 0).map((p) => {
16192
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 })) : [];
16193
16312
  return {
@@ -16222,7 +16341,7 @@ var isProductScopedRequestType = (rt) => PRODUCT_SCOPED_REQUEST_TYPES.has(rt);
16222
16341
  function Form({ onCreateRequest }) {
16223
16342
  const { t } = (0, import_react_i18next16.useTranslation)();
16224
16343
  const { accessToken, refreshToken, email, organizationId, workspaceId, updateTokens, onError } = useAuth();
16225
- const { consentClient, baseUrl } = useBaseUrl();
16344
+ const { consentClient, baseUrl, ledgerBaseUrl } = useBaseUrl();
16226
16345
  const { colors } = useTheme();
16227
16346
  const requestTypeLabels = useRequestTypeLabels();
16228
16347
  const timePeriodOptions = useTimePeriodOptions();
@@ -16259,15 +16378,60 @@ function Form({ onCreateRequest }) {
16259
16378
  });
16260
16379
  const [uploadedDocs, setUploadedDocs] = (0, import_react29.useState)([]);
16261
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
+ );
16262
16410
  (0, import_react29.useEffect)(() => {
16263
16411
  if (!organizationId || !workspaceId || !contact) return;
16412
+ let isCancelled = false;
16264
16413
  setIsLoading(true);
16265
- getFormData(organizationId, workspaceId, contact, false, false, authContext, consentClient, langParam).then((data) => {
16266
- 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);
16267
16418
  }).catch((error) => {
16268
16419
  console.warn("Error fetching form data:", error);
16269
- }).finally(() => setIsLoading(false));
16270
- }, [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
+ ]);
16271
16435
  const allGroups = (0, import_react29.useMemo)(() => {
16272
16436
  var _a3, _b;
16273
16437
  if (!formDataResponse) return [];
@@ -20407,6 +20571,7 @@ function PrivacyCenterContent({ onBack }) {
20407
20571
  }
20408
20572
  function RedactoPrivacyCenter({
20409
20573
  baseUrl,
20574
+ ledgerBaseUrl,
20410
20575
  slug,
20411
20576
  // `theme` is accepted for backwards compatibility but no longer used — the
20412
20577
  // Privacy Center themes itself from per-org branding.
@@ -20418,7 +20583,7 @@ function RedactoPrivacyCenter({
20418
20583
  initialCaseUuid,
20419
20584
  onBack
20420
20585
  }) {
20421
- return /* @__PURE__ */ (0, import_jsx_runtime64.jsx)(SafeAreaBoundary, { children: /* @__PURE__ */ (0, import_jsx_runtime64.jsx)(KeyboardProviderCompat, { children: /* @__PURE__ */ (0, import_jsx_runtime64.jsx)(import_react_query2.QueryClientProvider, { client: privacyCenterQueryClient, children: /* @__PURE__ */ (0, import_jsx_runtime64.jsx)(import_react_i18next29.I18nextProvider, { i18n: i18n_default, children: /* @__PURE__ */ (0, import_jsx_runtime64.jsx)(ThemeProvider, { slug, baseUrl, children: /* @__PURE__ */ (0, import_jsx_runtime64.jsx)(BaseUrlProvider, { baseUrl, children: /* @__PURE__ */ (0, import_jsx_runtime64.jsx)(
20586
+ return /* @__PURE__ */ (0, import_jsx_runtime64.jsx)(SafeAreaBoundary, { children: /* @__PURE__ */ (0, import_jsx_runtime64.jsx)(KeyboardProviderCompat, { children: /* @__PURE__ */ (0, import_jsx_runtime64.jsx)(import_react_query2.QueryClientProvider, { client: privacyCenterQueryClient, children: /* @__PURE__ */ (0, import_jsx_runtime64.jsx)(import_react_i18next29.I18nextProvider, { i18n: i18n_default, children: /* @__PURE__ */ (0, import_jsx_runtime64.jsx)(ThemeProvider, { slug, baseUrl, children: /* @__PURE__ */ (0, import_jsx_runtime64.jsx)(BaseUrlProvider, { baseUrl, ledgerBaseUrl, children: /* @__PURE__ */ (0, import_jsx_runtime64.jsx)(
20422
20587
  AuthProvider,
20423
20588
  {
20424
20589
  accessToken,