@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.
- package/CHANGELOG.md +6 -0
- package/dist/index.d.mts +4 -2
- package/dist/index.d.ts +4 -2
- package/dist/index.js +122 -15
- package/dist/index.mjs +122 -15
- package/dist/privacy-center.d.mts +31 -7
- package/dist/privacy-center.d.ts +31 -7
- package/dist/privacy-center.js +187 -44
- package/dist/privacy-center.mjs +187 -44
- package/package.json +1 -1
- package/src/RedactoNoticeConsent/RedactoNoticeConsent.test.tsx +5 -0
- package/src/RedactoNoticeConsent/RedactoNoticeConsent.tsx +29 -4
- package/src/RedactoNoticeConsent/api/index.ts +131 -2
- package/src/RedactoNoticeConsent/api/types.ts +33 -1
- package/src/RedactoNoticeConsent/types.ts +1 -0
- package/src/RedactoNoticeConsentInline/RedactoNoticeConsentInline.tsx +3 -1
- package/src/RedactoNoticeConsentInline/api/index.ts +5 -2
- package/src/RedactoNoticeConsentInline/api/types.ts +1 -1
- package/src/RedactoNoticeConsentInline/types.ts +1 -0
- package/src/RedactoPrivacyCenter/PrivacyCenterLayout.tsx +6 -4
- package/src/RedactoPrivacyCenter/RedactoPrivacyCenter.tsx +3 -1
- package/src/RedactoPrivacyCenter/api/actions.ts +10 -3
- package/src/RedactoPrivacyCenter/api/client.ts +67 -21
- package/src/RedactoPrivacyCenter/components/ConsentManager.tsx +12 -1
- package/src/RedactoPrivacyCenter/components/Form.tsx +58 -6
- package/src/RedactoPrivacyCenter/components/groupHelpers.ts +64 -1
- package/src/RedactoPrivacyCenter/context/AuthContext.tsx +7 -5
- package/src/RedactoPrivacyCenter/context/BaseUrlContext.tsx +8 -2
- package/src/RedactoPrivacyCenter/context/types.ts +1 -0
- package/src/RedactoPrivacyCenter/lib/api.ts +8 -0
- package/src/RedactoPrivacyCenter/lib/constants.ts +9 -0
- package/src/RedactoPrivacyCenter/lib/types.ts +11 -1
- package/src/RedactoPrivacyCenter/lib/utils.ts +28 -0
- package/tests/Form.test.tsx +5 -0
- package/tests/mocks.ts +1 -0
- 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
|
-
|
|
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
|
package/tests/Form.test.tsx
CHANGED
|
@@ -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
|
+
});
|