@redacto.io/consent-sdk-react 9.4.0 → 10.0.0-beta.0

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.
Files changed (36) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/index.d.mts +4 -2
  3. package/dist/index.d.ts +4 -2
  4. package/dist/index.js +122 -15
  5. package/dist/index.mjs +122 -15
  6. package/dist/privacy-center.d.mts +31 -7
  7. package/dist/privacy-center.d.ts +31 -7
  8. package/dist/privacy-center.js +187 -44
  9. package/dist/privacy-center.mjs +187 -44
  10. package/package.json +1 -1
  11. package/src/RedactoNoticeConsent/RedactoNoticeConsent.test.tsx +5 -0
  12. package/src/RedactoNoticeConsent/RedactoNoticeConsent.tsx +29 -4
  13. package/src/RedactoNoticeConsent/api/index.ts +131 -2
  14. package/src/RedactoNoticeConsent/api/types.ts +33 -1
  15. package/src/RedactoNoticeConsent/types.ts +1 -0
  16. package/src/RedactoNoticeConsentInline/RedactoNoticeConsentInline.tsx +3 -1
  17. package/src/RedactoNoticeConsentInline/api/index.ts +5 -2
  18. package/src/RedactoNoticeConsentInline/api/types.ts +1 -1
  19. package/src/RedactoNoticeConsentInline/types.ts +1 -0
  20. package/src/RedactoPrivacyCenter/PrivacyCenterLayout.tsx +6 -4
  21. package/src/RedactoPrivacyCenter/RedactoPrivacyCenter.tsx +3 -1
  22. package/src/RedactoPrivacyCenter/api/actions.ts +10 -3
  23. package/src/RedactoPrivacyCenter/api/client.ts +67 -21
  24. package/src/RedactoPrivacyCenter/components/ConsentManager.tsx +12 -1
  25. package/src/RedactoPrivacyCenter/components/Form.tsx +58 -6
  26. package/src/RedactoPrivacyCenter/components/groupHelpers.ts +64 -1
  27. package/src/RedactoPrivacyCenter/context/AuthContext.tsx +7 -5
  28. package/src/RedactoPrivacyCenter/context/BaseUrlContext.tsx +8 -2
  29. package/src/RedactoPrivacyCenter/context/types.ts +1 -0
  30. package/src/RedactoPrivacyCenter/lib/api.ts +8 -0
  31. package/src/RedactoPrivacyCenter/lib/constants.ts +9 -0
  32. package/src/RedactoPrivacyCenter/lib/types.ts +11 -1
  33. package/src/RedactoPrivacyCenter/lib/utils.ts +28 -0
  34. package/tests/Form.test.tsx +5 -0
  35. package/tests/mocks.ts +1 -0
  36. package/tests/resolveContactFromJwt.test.ts +44 -0
@@ -196,6 +196,15 @@ export const MAX_REFRESH_BACKOFF_MS = 300_000; // 5m
196
196
  export const REFRESH_TOKEN_STORAGE_KEY = 'redacto_pc_refresh_token';
197
197
  export const LEGACY_REFRESH_TOKEN_COOKIE = 'refresh_token';
198
198
 
199
+ // ─── DSAR request form ─────────────────────────────────────────────────────
200
+
201
+ // Max product groups requested from the Go ledger's user-consents when
202
+ // sourcing the DSAR request-form product picker (Approach A). The ledger caps
203
+ // user-consents page size at 100; the picker needs all of a principal's
204
+ // groups, so we request the max. A principal with >100 product groups would
205
+ // need group-level pagination (follow-up).
206
+ export const FORM_PICKER_GROUP_LIMIT = 100;
207
+
199
208
  // ─── Notification Hub ──────────────────────────────────────────────────────
200
209
 
201
210
  export const POLL_INTERVAL = 30_000;
@@ -256,6 +256,10 @@ export type PurposeItem = {
256
256
  method: string;
257
257
  data_elements: ConsentDataElement[];
258
258
  link_reason: string | null;
259
+ // UUID of the notice version this purpose was consented under. Served by the
260
+ // Go ledger's user-consents response; required to revoke/regrant via the
261
+ // ledger's submit-consent endpoint (which has no manage-consent route).
262
+ notice_uuid: string | null;
259
263
  };
260
264
 
261
265
  export type ConsentGroup = {
@@ -405,9 +409,15 @@ export type Receipt = {
405
409
  created_at: string;
406
410
  };
407
411
 
412
+ // Mirrors the Go ledger's `ConsentReceiptListResponse` (offset-paginated page
413
+ // of the principal's receipts): flat `total`/`skip`/`limit`, not a nested
414
+ // `pagination` object. `normalizeReceipts` in ReceiptsList tolerates either
415
+ // envelope, but this type documents the ledger contract the SDK now calls.
408
416
  export type ReceiptListResponse = {
409
417
  data: Receipt[];
410
- pagination: Pagination;
418
+ total: number;
419
+ skip: number;
420
+ limit: number;
411
421
  };
412
422
 
413
423
  export type ActivityResponse = {
@@ -244,6 +244,34 @@ export const decodeJwtToken = (token: string): { organisation_uuid?: string; org
244
244
  }
245
245
  };
246
246
 
247
+ /**
248
+ * Resolve the user's contact identifier from a decoded JWT.
249
+ *
250
+ * Identifiers live under `user_data` (primary_email / primary_mobile /
251
+ * org_user_id); some token shapes also expose `contact`/`primary_email` at the
252
+ * top level. Email is preferred, but a mobile-only login must still resolve to
253
+ * the phone (or org_user_id) so case/grievance and consent-management actions —
254
+ * which key off this contact — work without an email. The backend matches
255
+ * identifiers type-aware (phone-to-phone), so storing the mobile here is safe.
256
+ */
257
+ export const resolveContactFromJwt = (
258
+ decoded: { [key: string]: any } | null,
259
+ ): string | null => {
260
+ if (!decoded) {
261
+ return null;
262
+ }
263
+ const userData = (decoded.user_data as Record<string, unknown>) || {};
264
+ const candidate =
265
+ decoded.contact ||
266
+ decoded.primary_email ||
267
+ userData.primary_email ||
268
+ userData.primary_mobile ||
269
+ userData.org_user_id;
270
+ return typeof candidate === 'string' && candidate.trim() !== ''
271
+ ? candidate
272
+ : null;
273
+ };
274
+
247
275
  /**
248
276
  * Decode JWT payload and check if token is expired or will expire soon
249
277
  * @param token - JWT token string
@@ -98,9 +98,14 @@ vi.mock('../src/RedactoPrivacyCenter/lib/i18n', () => ({
98
98
  // 1-form -> 1-case: only `createCaseRequest` is consumed by Form.tsx now.
99
99
  const createCaseRequest = vi.fn();
100
100
  const getFormData = vi.fn();
101
+ const fetchUserConsents = vi.fn();
101
102
  vi.mock('../src/RedactoPrivacyCenter/lib/api', () => ({
102
103
  createCaseRequest: (...args: unknown[]) => createCaseRequest(...args),
103
104
  getFormData: (...args: unknown[]) => getFormData(...args),
105
+ // Approach A sources the product picker from the ledger's user-consents.
106
+ // Default to no ledger result so the form falls back to the getFormData
107
+ // product groups these tests set up.
108
+ fetchUserConsents: (...args: unknown[]) => fetchUserConsents(...args),
104
109
  }));
105
110
 
106
111
  // ─── upload stub ─────────────────────────────────────────────────────────────
package/tests/mocks.ts CHANGED
@@ -235,6 +235,7 @@ export const defaultProps = {
235
235
  org_uuid: "org-123",
236
236
  workspace_uuid: "workspace-123",
237
237
  notice_uuid: "notice-123",
238
+ ledgerBaseUrl: "https://ledger.test/consent-ledger",
238
239
  accessToken: "mock-token",
239
240
  refreshToken: "mock-refresh-token",
240
241
  language: "en",
@@ -0,0 +1,44 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { resolveContactFromJwt } from '../src/RedactoPrivacyCenter/lib/utils';
4
+
5
+ describe('resolveContactFromJwt', () => {
6
+ it('prefers the email when present in user_data', () => {
7
+ const decoded = {
8
+ user_data: {
9
+ primary_email: 'user@example.com',
10
+ primary_mobile: '+918955610253',
11
+ org_user_id: '+918955610253',
12
+ },
13
+ };
14
+ expect(resolveContactFromJwt(decoded)).toBe('user@example.com');
15
+ });
16
+
17
+ it('falls back to the mobile for a mobile-only login (no email)', () => {
18
+ const decoded = {
19
+ user_data: {
20
+ primary_email: null,
21
+ primary_mobile: '+918955610253',
22
+ org_user_id: '+918955610253',
23
+ },
24
+ };
25
+ expect(resolveContactFromJwt(decoded)).toBe('+918955610253');
26
+ });
27
+
28
+ it('falls back to org_user_id when neither email nor mobile is present', () => {
29
+ const decoded = { user_data: { org_user_id: 'ucid-123' } };
30
+ expect(resolveContactFromJwt(decoded)).toBe('ucid-123');
31
+ });
32
+
33
+ it('reads top-level contact / primary_email when not nested', () => {
34
+ expect(resolveContactFromJwt({ contact: 'a@b.com' })).toBe('a@b.com');
35
+ expect(resolveContactFromJwt({ primary_email: 'c@d.com' })).toBe('c@d.com');
36
+ });
37
+
38
+ it('returns null for missing, empty, or whitespace identifiers', () => {
39
+ expect(resolveContactFromJwt(null)).toBeNull();
40
+ expect(resolveContactFromJwt({})).toBeNull();
41
+ expect(resolveContactFromJwt({ user_data: {} })).toBeNull();
42
+ expect(resolveContactFromJwt({ user_data: { primary_mobile: ' ' } })).toBeNull();
43
+ });
44
+ });