@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.
@@ -9868,7 +9868,15 @@ function arrayBufferToBase64(buffer) {
9868
9868
  }
9869
9869
  return result;
9870
9870
  }
9871
- function createConsentClient(baseUrl) {
9871
+ function createConsentClient(baseUrl, ledgerBaseUrl) {
9872
+ const useLedger = Boolean(ledgerBaseUrl);
9873
+ const ledgerBase = (ledgerBaseUrl != null ? ledgerBaseUrl : "").replace(/\/$/, "");
9874
+ function ledgerUrl(baseRequest, path) {
9875
+ if (!useLedger) {
9876
+ throw new Error("ledgerUrl called without a configured ledgerBaseUrl");
9877
+ }
9878
+ return `${ledgerBase}/public/organisations/${baseRequest.organisationUuid}/workspaces/${baseRequest.workspaceUuid}${path}`;
9879
+ }
9872
9880
  async function privacyCenterApiFetch(url, options) {
9873
9881
  const { authToken, getAuthToken, headers = {}, ...fetchOptions } = options;
9874
9882
  const requestHeaders = new Headers();
@@ -10070,7 +10078,8 @@ function createConsentClient(baseUrl) {
10070
10078
  if (params.product_uuid) queryParams.append("product_uuid", params.product_uuid);
10071
10079
  if (params.language) queryParams.append("language", params.language);
10072
10080
  const queryString = queryParams.toString();
10073
- const url = `${baseUrl}/organisations/${baseRequest.organisationUuid}/workspaces/${baseRequest.workspaceUuid}/dsar/privacy-center/user-consents${queryString ? `?${queryString}` : ""}`;
10081
+ const consentsSuffix = `/user-consents${queryString ? `?${queryString}` : ""}`;
10082
+ const url = useLedger ? ledgerUrl(baseRequest, consentsSuffix) : `${baseUrl}/organisations/${baseRequest.organisationUuid}/workspaces/${baseRequest.workspaceUuid}/dsar/privacy-center${consentsSuffix}`;
10074
10083
  return privacyCenterApiFetch(url, {
10075
10084
  ...options,
10076
10085
  method: "GET",
@@ -10090,7 +10099,8 @@ function createConsentClient(baseUrl) {
10090
10099
  if (params.language) queryParams.append("language", params.language);
10091
10100
  if (params.nominator_uuid) queryParams.append("nominator_uuid", params.nominator_uuid);
10092
10101
  const queryString = queryParams.toString();
10093
- const url = `${baseUrl}/organisations/${baseRequest.organisationUuid}/workspaces/${baseRequest.workspaceUuid}/dsar/privacy-center/user-consents/${productUuid}/purposes${queryString ? `?${queryString}` : ""}`;
10102
+ const purposesSuffix = `/user-consents/${productUuid}/purposes${queryString ? `?${queryString}` : ""}`;
10103
+ const url = useLedger ? ledgerUrl(baseRequest, purposesSuffix) : `${baseUrl}/organisations/${baseRequest.organisationUuid}/workspaces/${baseRequest.workspaceUuid}/dsar/privacy-center${purposesSuffix}`;
10094
10104
  return privacyCenterApiFetch(url, {
10095
10105
  ...options,
10096
10106
  method: "GET",
@@ -10098,17 +10108,66 @@ function createConsentClient(baseUrl) {
10098
10108
  });
10099
10109
  },
10100
10110
  /**
10101
- * Manage consent (privacy center) - revoke, regrant, or renew
10111
+ * Manage consent (privacy center) - revoke, regrant, or renew.
10112
+ *
10113
+ * The Go ledger has no manage-consent route, so with a ledger configured
10114
+ * this is expressed as a `partial` submit-consent on the ledger — mirroring
10115
+ * the Python dual-write relay: the affected purpose is re-submitted with
10116
+ * `selected: false` to revoke or `selected: true` to regrant/renew, and
10117
+ * `partial: true` so the rest of the notice's purposes are left untouched.
10118
+ * The principal is resolved from the JWT, so no `contact`/identifiers are
10119
+ * sent in the body.
10120
+ *
10121
+ * Without a ledger, the legacy Python `/manage-consent?status=` endpoint is
10122
+ * used with its `{ purpose_uuid, contact, ... }` payload.
10102
10123
  */
10103
- async manageConsent(baseRequest, purposeId, contact, status, authToken, options, nominatorContact, dataElementUuids) {
10104
- const url = `${baseUrl}/organisations/${baseRequest.organisationUuid}/workspaces/${baseRequest.workspaceUuid}/dsar/privacy-center/manage-consent?status=${status}`;
10105
- const body = { purpose_uuid: purposeId, contact };
10106
- if (nominatorContact) {
10107
- body.nominator_contact = nominatorContact;
10124
+ async manageConsent(baseRequest, purposeId, contact, status, authToken, options, nominatorContact, dataElementUuids, noticeUuid, dataElements) {
10125
+ if (!useLedger) {
10126
+ const url2 = `${baseUrl}/organisations/${baseRequest.organisationUuid}/workspaces/${baseRequest.workspaceUuid}/dsar/privacy-center/manage-consent?status=${status}`;
10127
+ const body2 = { purpose_uuid: purposeId, contact };
10128
+ if (nominatorContact) {
10129
+ body2.nominator_contact = nominatorContact;
10130
+ }
10131
+ if (Array.isArray(dataElementUuids) && dataElementUuids.length > 0) {
10132
+ body2.data_element_uuids = dataElementUuids;
10133
+ }
10134
+ return privacyCenterApiFetch(url2, {
10135
+ ...options,
10136
+ method: "POST",
10137
+ body: JSON.stringify(body2),
10138
+ authToken: authToken || baseRequest.authToken
10139
+ });
10108
10140
  }
10109
- if (Array.isArray(dataElementUuids) && dataElementUuids.length > 0) {
10110
- body.data_element_uuids = dataElementUuids;
10141
+ if (!noticeUuid) {
10142
+ throw new Error("notice_uuid is required to manage consent via the ledger");
10111
10143
  }
10144
+ const purposeSelected = status !== "revoke";
10145
+ const elements = (dataElements != null ? dataElements : []).map((de) => {
10146
+ let selected;
10147
+ if (status === "revoke") {
10148
+ selected = false;
10149
+ } else if (status === "regrant") {
10150
+ selected = Array.isArray(dataElementUuids) ? dataElementUuids.includes(de.uuid) : de.selected;
10151
+ } else {
10152
+ selected = de.selected;
10153
+ }
10154
+ return { uuid: de.uuid, selected };
10155
+ });
10156
+ const body = {
10157
+ notice_uuid: noticeUuid,
10158
+ partial: true,
10159
+ declined: false,
10160
+ select_all_mandatory: false,
10161
+ self_declared_adult: false,
10162
+ purposes: [
10163
+ {
10164
+ purpose_uuid: purposeId,
10165
+ selected: purposeSelected,
10166
+ data_elements: elements
10167
+ }
10168
+ ]
10169
+ };
10170
+ const url = ledgerUrl(baseRequest, "/submit-consent");
10112
10171
  return privacyCenterApiFetch(url, {
10113
10172
  ...options,
10114
10173
  method: "POST",
@@ -10145,7 +10204,8 @@ function createConsentClient(baseUrl) {
10145
10204
  if (params.notice_uuid) queryParams.append("notice_uuid", params.notice_uuid);
10146
10205
  if (params.language) queryParams.append("language", params.language);
10147
10206
  const queryString = queryParams.toString();
10148
- const url = `${baseUrl}/organisations/${baseRequest.organisationUuid}/workspaces/${baseRequest.workspaceUuid}/dsar/privacy-center/receipts${queryString ? `?${queryString}` : ""}`;
10207
+ const receiptsSuffix = `/receipts${queryString ? `?${queryString}` : ""}`;
10208
+ const url = useLedger ? ledgerUrl(baseRequest, receiptsSuffix) : `${baseUrl}/organisations/${baseRequest.organisationUuid}/workspaces/${baseRequest.workspaceUuid}/dsar/privacy-center${receiptsSuffix}`;
10149
10209
  return privacyCenterApiFetch(url, {
10150
10210
  ...options,
10151
10211
  method: "GET",
@@ -10159,7 +10219,7 @@ function createConsentClient(baseUrl) {
10159
10219
  * the RN UI writes to a temp file to open/share. 404 => not found.
10160
10220
  */
10161
10221
  async getReceiptPdf(baseRequest, receiptUuid, authToken) {
10162
- const url = `${baseUrl}/organisations/${baseRequest.organisationUuid}/workspaces/${baseRequest.workspaceUuid}/dsar/privacy-center/receipts/${receiptUuid}/pdf`;
10222
+ const url = useLedger ? ledgerUrl(baseRequest, `/receipts/${receiptUuid}/download`) : `${baseUrl}/organisations/${baseRequest.organisationUuid}/workspaces/${baseRequest.workspaceUuid}/dsar/privacy-center/receipts/${receiptUuid}/pdf`;
10163
10223
  const token = authToken || baseRequest.authToken;
10164
10224
  const requestHeaders = new Headers();
10165
10225
  requestHeaders.set("Accept", "application/pdf");
@@ -10318,11 +10378,16 @@ function createConsentClient(baseUrl) {
10318
10378
  // src/RedactoPrivacyCenter/context/BaseUrlContext.tsx
10319
10379
  import { jsx as jsx3 } from "react/jsx-runtime";
10320
10380
  var BaseUrlContext = createContext(null);
10321
- function BaseUrlProvider({ children, baseUrl }) {
10381
+ function BaseUrlProvider({
10382
+ children,
10383
+ baseUrl,
10384
+ ledgerBaseUrl
10385
+ }) {
10322
10386
  const value = useMemo(() => ({
10323
10387
  baseUrl,
10324
- consentClient: createConsentClient(baseUrl)
10325
- }), [baseUrl]);
10388
+ ledgerBaseUrl,
10389
+ consentClient: createConsentClient(baseUrl, ledgerBaseUrl)
10390
+ }), [baseUrl, ledgerBaseUrl]);
10326
10391
  return /* @__PURE__ */ jsx3(BaseUrlContext.Provider, { value, children });
10327
10392
  }
10328
10393
  function useBaseUrl() {
@@ -10521,7 +10586,6 @@ async function fetchUserConsentsServer(params) {
10521
10586
  }
10522
10587
  }
10523
10588
  async function manageConsentServer(params) {
10524
- var _a3;
10525
10589
  try {
10526
10590
  if (!params.consentClient) {
10527
10591
  throw new Error("Consent client is required");
@@ -10539,9 +10603,14 @@ async function manageConsentServer(params) {
10539
10603
  params.authToken,
10540
10604
  void 0,
10541
10605
  params.nominatorContact,
10542
- params.dataElementUuids
10606
+ params.dataElementUuids,
10607
+ params.noticeUuid,
10608
+ params.dataElements
10543
10609
  );
10544
- return (_a3 = result == null ? void 0 : result.success) != null ? _a3 : null;
10610
+ if (result && typeof result === "object" && "success" in result) {
10611
+ return Boolean(result.success);
10612
+ }
10613
+ return true;
10545
10614
  } catch (error) {
10546
10615
  if (isAuthError(error)) {
10547
10616
  throw error;
@@ -11025,7 +11094,9 @@ var manageConsent = async ({
11025
11094
  authContext,
11026
11095
  consentClient,
11027
11096
  nominatorContact,
11028
- dataElementUuids
11097
+ dataElementUuids,
11098
+ noticeUuid,
11099
+ dataElements
11029
11100
  }) => {
11030
11101
  try {
11031
11102
  const accessToken = await ensureValidToken(organizationId, workspaceId, void 0, authContext, consentClient);
@@ -11041,7 +11112,9 @@ var manageConsent = async ({
11041
11112
  authToken: accessToken,
11042
11113
  consentClient,
11043
11114
  nominatorContact,
11044
- dataElementUuids
11115
+ dataElementUuids,
11116
+ noticeUuid,
11117
+ dataElements
11045
11118
  });
11046
11119
  return result;
11047
11120
  } catch (error) {
@@ -11060,7 +11133,9 @@ var manageConsent = async ({
11060
11133
  authToken: newAccessToken || void 0,
11061
11134
  consentClient,
11062
11135
  nominatorContact,
11063
- dataElementUuids
11136
+ dataElementUuids,
11137
+ noticeUuid,
11138
+ dataElements
11064
11139
  });
11065
11140
  }
11066
11141
  console.error(`Error ${action} consent:`, error);
@@ -14249,6 +14324,7 @@ var useTimePeriodOptions = () => {
14249
14324
  };
14250
14325
  var MAX_DISPLAYED_PURPOSE_NAMES_IN_ERASURE_MODAL = 3;
14251
14326
  var SEARCH_DEBOUNCE_DELAY = 500;
14327
+ var FORM_PICKER_GROUP_LIMIT = 100;
14252
14328
  var POLL_INTERVAL = 3e4;
14253
14329
  var NOTIFICATION_PAGE_SIZE = 10;
14254
14330
  var TAB_TO_CATEGORY = {
@@ -15008,6 +15084,7 @@ function ConsentManager() {
15008
15084
  }, [fetchData]);
15009
15085
  const handleManageConsent = useCallback6(
15010
15086
  async (purposeId, action, nominatorContact, dataElementUuids) => {
15087
+ var _a3, _b;
15011
15088
  if (!organizationId || !workspaceId || !email) return;
15012
15089
  setIsManageLoading(true);
15013
15090
  try {
@@ -15020,7 +15097,14 @@ function ConsentManager() {
15020
15097
  authContext,
15021
15098
  consentClient,
15022
15099
  nominatorContact,
15023
- dataElementUuids
15100
+ dataElementUuids,
15101
+ // Ledger mode: the partial submit-consent needs the notice and the
15102
+ // purpose's data elements; both ride on the selected (ledger-served) row.
15103
+ noticeUuid: (_a3 = selectedRecord == null ? void 0 : selectedRecord.notice_uuid) != null ? _a3 : void 0,
15104
+ dataElements: (_b = selectedRecord == null ? void 0 : selectedRecord.data_elements) == null ? void 0 : _b.map((de) => ({
15105
+ uuid: de.uuid,
15106
+ selected: de.selected
15107
+ }))
15024
15108
  });
15025
15109
  setIsModalOpen(false);
15026
15110
  setSelectedRecord(null);
@@ -15032,7 +15116,7 @@ function ConsentManager() {
15032
15116
  setIsManageLoading(false);
15033
15117
  }
15034
15118
  },
15035
- [organizationId, workspaceId, email, authContext, consentClient, fetchData, t]
15119
+ [organizationId, workspaceId, email, authContext, consentClient, selectedRecord, fetchData, t]
15036
15120
  );
15037
15121
  const handleExport = useCallback6(async () => {
15038
15122
  if (!consentData.length) return;
@@ -16220,6 +16304,41 @@ var styles32 = StyleSheet33.create({
16220
16304
 
16221
16305
  // src/RedactoPrivacyCenter/components/groupHelpers.ts
16222
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
+ });
16223
16342
  var buildGroupPurposes = (group) => group.purposes.filter((p) => p !== null && p !== void 0).map((p) => {
16224
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 })) : [];
16225
16344
  return {
@@ -16254,7 +16373,7 @@ var isProductScopedRequestType = (rt) => PRODUCT_SCOPED_REQUEST_TYPES.has(rt);
16254
16373
  function Form({ onCreateRequest }) {
16255
16374
  const { t } = useTranslation15();
16256
16375
  const { accessToken, refreshToken, email, organizationId, workspaceId, updateTokens, onError } = useAuth();
16257
- const { consentClient, baseUrl } = useBaseUrl();
16376
+ const { consentClient, baseUrl, ledgerBaseUrl } = useBaseUrl();
16258
16377
  const { colors } = useTheme();
16259
16378
  const requestTypeLabels = useRequestTypeLabels();
16260
16379
  const timePeriodOptions = useTimePeriodOptions();
@@ -16291,15 +16410,60 @@ function Form({ onCreateRequest }) {
16291
16410
  });
16292
16411
  const [uploadedDocs, setUploadedDocs] = useState16([]);
16293
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
+ );
16294
16442
  useEffect14(() => {
16295
16443
  if (!organizationId || !workspaceId || !contact) return;
16444
+ let isCancelled = false;
16296
16445
  setIsLoading(true);
16297
- getFormData(organizationId, workspaceId, contact, false, false, authContext, consentClient, langParam).then((data) => {
16298
- 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);
16299
16450
  }).catch((error) => {
16300
16451
  console.warn("Error fetching form data:", error);
16301
- }).finally(() => setIsLoading(false));
16302
- }, [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
+ ]);
16303
16467
  const allGroups = useMemo14(() => {
16304
16468
  var _a3, _b;
16305
16469
  if (!formDataResponse) return [];
@@ -20448,6 +20612,7 @@ function PrivacyCenterContent({ onBack }) {
20448
20612
  }
20449
20613
  function RedactoPrivacyCenter({
20450
20614
  baseUrl,
20615
+ ledgerBaseUrl,
20451
20616
  slug,
20452
20617
  // `theme` is accepted for backwards compatibility but no longer used — the
20453
20618
  // Privacy Center themes itself from per-org branding.
@@ -20459,7 +20624,7 @@ function RedactoPrivacyCenter({
20459
20624
  initialCaseUuid,
20460
20625
  onBack
20461
20626
  }) {
20462
- return /* @__PURE__ */ jsx64(SafeAreaBoundary, { children: /* @__PURE__ */ jsx64(KeyboardProviderCompat, { children: /* @__PURE__ */ jsx64(QueryClientProvider, { client: privacyCenterQueryClient, children: /* @__PURE__ */ jsx64(I18nextProvider, { i18n: i18n_default, children: /* @__PURE__ */ jsx64(ThemeProvider, { slug, baseUrl, children: /* @__PURE__ */ jsx64(BaseUrlProvider, { baseUrl, children: /* @__PURE__ */ jsx64(
20627
+ return /* @__PURE__ */ jsx64(SafeAreaBoundary, { children: /* @__PURE__ */ jsx64(KeyboardProviderCompat, { children: /* @__PURE__ */ jsx64(QueryClientProvider, { client: privacyCenterQueryClient, children: /* @__PURE__ */ jsx64(I18nextProvider, { i18n: i18n_default, children: /* @__PURE__ */ jsx64(ThemeProvider, { slug, baseUrl, children: /* @__PURE__ */ jsx64(BaseUrlProvider, { baseUrl, ledgerBaseUrl, children: /* @__PURE__ */ jsx64(
20463
20628
  AuthProvider,
20464
20629
  {
20465
20630
  accessToken,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redacto.io/consent-sdk-react-native",
3
- "version": "4.0.1-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",
@@ -101,12 +101,14 @@
101
101
  "react-native": "0.76.9",
102
102
  "react-native-svg": "^15.0.0",
103
103
  "tsup": "^8.4.0",
104
- "typescript": "5.8.2"
104
+ "typescript": "5.8.2",
105
+ "vitest": "^4.1.8"
105
106
  },
106
107
  "scripts": {
107
108
  "build": "tsup",
108
109
  "dev": "tsup --watch",
109
110
  "check:locales": "node scripts/check-locales.js",
110
- "release": "pnpm run build"
111
+ "release": "pnpm run build",
112
+ "test": "vitest"
111
113
  }
112
114
  }