@redacto.io/consent-sdk-react-native 5.0.0-beta.6 → 5.0.0-beta.8

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/dist/index.mjs CHANGED
@@ -1,6 +1,7 @@
1
1
  import {
2
+ SANDBOX_TOKEN_HEADER,
2
3
  toLanguageCode
3
- } from "./chunk-UVRSRLYW.mjs";
4
+ } from "./chunk-B2TXWC2C.mjs";
4
5
 
5
6
  // src/RedactoNoticeConsent/RedactoNoticeConsent.tsx
6
7
  import {
@@ -29,6 +30,64 @@ import Svg3, { Path as Path3, Rect, SvgUri } from "react-native-svg";
29
30
  // src/RedactoNoticeConsent/api/index.ts
30
31
  import { jwtDecode } from "jwt-decode";
31
32
 
33
+ // src/RedactoNoticeConsent/api/sandbox.ts
34
+ var resolveSandboxAuth = (params) => {
35
+ var _a, _b, _c, _d, _e;
36
+ const token = (_a = params.token) == null ? void 0 : _a.trim();
37
+ if (!token) {
38
+ return null;
39
+ }
40
+ const organisationUuid = (_b = params.organisationUuid) == null ? void 0 : _b.trim();
41
+ const workspaceUuid = (_c = params.workspaceUuid) == null ? void 0 : _c.trim();
42
+ const ucic = (_d = params.sandboxUcic) == null ? void 0 : _d.trim();
43
+ const subject = (_e = params.sandboxSubject) == null ? void 0 : _e.trim();
44
+ if (!organisationUuid || !workspaceUuid) {
45
+ throw new Error(
46
+ "Sandbox mode requires organisationUuid and workspaceUuid props"
47
+ );
48
+ }
49
+ if (!ucic && !subject) {
50
+ throw new Error("Sandbox mode requires a UCIC, email, or mobile");
51
+ }
52
+ return { token, ucic, subject: subject != null ? subject : "", organisationUuid, workspaceUuid };
53
+ };
54
+ var resolveRequestAuth = (accessToken, sandboxParams, decodeTokenSafely2) => {
55
+ const sandbox = resolveSandboxAuth(sandboxParams);
56
+ if (sandbox) {
57
+ return {
58
+ organisationUuid: sandbox.organisationUuid,
59
+ workspaceUuid: sandbox.workspaceUuid,
60
+ sandbox
61
+ };
62
+ }
63
+ const decodedToken = accessToken ? decodeTokenSafely2(accessToken) : null;
64
+ if (!decodedToken) {
65
+ throw new Error("Invalid access token");
66
+ }
67
+ const { organisation_uuid, workspace_uuid } = decodedToken;
68
+ if (!organisation_uuid || !workspace_uuid) {
69
+ throw new Error("Invalid token: missing organization or workspace UUID");
70
+ }
71
+ return {
72
+ organisationUuid: organisation_uuid,
73
+ workspaceUuid: workspace_uuid,
74
+ sandbox: null
75
+ };
76
+ };
77
+ var buildAuthHeaders = (accessToken, sandbox) => {
78
+ if (sandbox) {
79
+ return { [SANDBOX_TOKEN_HEADER]: sandbox.token };
80
+ }
81
+ return { Authorization: `Bearer ${accessToken}` };
82
+ };
83
+ var sandboxBodyIdentity = (sandbox) => {
84
+ if (sandbox.ucic) {
85
+ return { org_user_id: sandbox.ucic };
86
+ }
87
+ return sandbox.subject.includes("@") ? { primary_email: sandbox.subject } : { primary_mobile: sandbox.subject };
88
+ };
89
+ var sandboxQueryIdentity = (sandbox) => sandboxBodyIdentity(sandbox);
90
+
32
91
  // src/constants.ts
33
92
  var DEFAULT_CONSENT_API_BASE_URL = "https://api.redacto.io/consent";
34
93
 
@@ -67,21 +126,19 @@ var fetchConsentContent = async ({
67
126
  specific_uuid,
68
127
  validate_against = "all",
69
128
  include_fully_consented_data = false,
70
- signal
129
+ signal,
130
+ ...sandboxParams
71
131
  }) => {
72
- if (!noticeId || !accessToken || !validate_against) {
73
- throw new Error("noticeId and accessToken are required");
74
- }
75
- const decodedToken = decodeTokenSafely(accessToken);
76
- if (!decodedToken) {
77
- throw new Error("Invalid access token");
132
+ if (!noticeId || !validate_against) {
133
+ throw new Error("noticeId is required");
78
134
  }
79
- const { organisation_uuid: ORGANISATION_UUID, workspace_uuid: WORKSPACE_UUID } = decodedToken;
80
- if (!ORGANISATION_UUID || !WORKSPACE_UUID) {
81
- throw new Error("Invalid token: missing organization or workspace UUID");
135
+ if (!accessToken && !sandboxParams.token) {
136
+ throw new Error("accessToken or token is required");
82
137
  }
138
+ const { organisationUuid: ORGANISATION_UUID, workspaceUuid: WORKSPACE_UUID, sandbox } = resolveRequestAuth(accessToken, sandboxParams, decodeTokenSafely);
83
139
  const apiBaseUrl = baseUrl || DEFAULT_CONSENT_API_BASE_URL;
84
- const cacheKey = `${accessToken}-${noticeId}-${validate_against}-${language}-${specific_uuid || ""}-${include_fully_consented_data}`;
140
+ const cacheIdentity = sandbox ? `sandbox:${sandbox.organisationUuid}:${sandbox.workspaceUuid}:${sandbox.ucic || sandbox.subject}` : accessToken;
141
+ const cacheKey = `${cacheIdentity}-${noticeId}-${validate_against}-${language}-${specific_uuid || ""}-${include_fully_consented_data}`;
85
142
  if (validate_against === "all" && !specific_uuid) {
86
143
  const cachedData = getCachedData(cacheKey);
87
144
  if (cachedData) {
@@ -98,10 +155,15 @@ var fetchConsentContent = async ({
98
155
  if (include_fully_consented_data) {
99
156
  url.searchParams.append("include_fully_consented_data", "true");
100
157
  }
158
+ if (sandbox) {
159
+ for (const [key, value] of Object.entries(sandboxQueryIdentity(sandbox))) {
160
+ url.searchParams.append(key, value);
161
+ }
162
+ }
101
163
  const response = await fetch(url.toString(), {
102
164
  method: "GET",
103
165
  headers: {
104
- Authorization: `Bearer ${accessToken}`,
166
+ ...buildAuthHeaders(accessToken, sandbox),
105
167
  "Accept-Language": language,
106
168
  "Content-Type": "application/json"
107
169
  },
@@ -141,20 +203,17 @@ var submitConsentEvent = async ({
141
203
  guardianVerificationReference,
142
204
  selfDeclaredAdult,
143
205
  language,
144
- signal
206
+ signal,
207
+ ...sandboxParams
145
208
  }) => {
146
- if (!noticeUuid || !accessToken || !Array.isArray(purposes)) {
147
- throw new Error("noticeUuid, accessToken, and purposes array are required");
209
+ if (!noticeUuid || !Array.isArray(purposes)) {
210
+ throw new Error("noticeUuid and purposes array are required");
148
211
  }
149
- const decodedToken = decodeTokenSafely(accessToken);
150
- if (!decodedToken) {
151
- throw new Error("Invalid access token");
152
- }
153
- const { organisation_uuid: ORGANISATION_UUID, workspace_uuid: WORKSPACE_UUID } = decodedToken;
154
- if (!ORGANISATION_UUID || !WORKSPACE_UUID) {
155
- throw new Error("Invalid token: missing organization or workspace UUID");
212
+ if (!accessToken && !sandboxParams.token) {
213
+ throw new Error("accessToken or token is required");
156
214
  }
157
- const apiBaseUrl = ledgerBaseUrl || baseUrl || DEFAULT_CONSENT_API_BASE_URL;
215
+ const { organisationUuid: ORGANISATION_UUID, workspaceUuid: WORKSPACE_UUID, sandbox } = resolveRequestAuth(accessToken, sandboxParams, decodeTokenSafely);
216
+ const apiBaseUrl = (sandbox ? null : ledgerBaseUrl) || baseUrl || DEFAULT_CONSENT_API_BASE_URL;
158
217
  const validatedPurposes = purposes.map((purpose) => {
159
218
  if (!purpose.uuid) {
160
219
  throw new Error("Invalid purpose data: missing uuid");
@@ -180,15 +239,16 @@ var submitConsentEvent = async ({
180
239
  self_declared_adult: selfDeclaredAdult,
181
240
  language: toLanguageCode(language)
182
241
  };
242
+ const requestBody = sandbox ? { ...payload, ...sandboxBodyIdentity(sandbox) } : payload;
183
243
  const response = await fetch(
184
244
  `${apiBaseUrl}/public/organisations/${ORGANISATION_UUID}/workspaces/${WORKSPACE_UUID}/submit-consent`,
185
245
  {
186
246
  method: "POST",
187
247
  headers: {
188
- Authorization: `Bearer ${accessToken}`,
248
+ ...buildAuthHeaders(accessToken, sandbox),
189
249
  "Content-Type": "application/json"
190
250
  },
191
- body: JSON.stringify(payload),
251
+ body: JSON.stringify(requestBody),
192
252
  signal
193
253
  }
194
254
  );
@@ -214,28 +274,30 @@ var submitConsentEvent = async ({
214
274
  var fetchTTSAudioUrls = async ({
215
275
  accessToken,
216
276
  baseUrl,
277
+ ledgerBaseUrl,
217
278
  noticeUuid,
218
279
  language,
219
- signal
280
+ signal,
281
+ ...sandboxParams
220
282
  }) => {
221
- if (!accessToken || !noticeUuid || !language) {
222
- throw new Error("accessToken, noticeUuid, and language are required");
223
- }
224
- const decodedToken = decodeTokenSafely(accessToken);
225
- if (!decodedToken) {
226
- throw new Error("Invalid access token");
227
- }
228
- const { organisation_uuid: ORGANISATION_UUID, workspace_uuid: WORKSPACE_UUID } = decodedToken;
229
- if (!ORGANISATION_UUID || !WORKSPACE_UUID) {
230
- throw new Error("Invalid token: missing organization or workspace UUID");
283
+ if (!accessToken && !sandboxParams.token || !noticeUuid || !language) {
284
+ throw new Error("accessToken (or token), noticeUuid, and language are required");
231
285
  }
232
- const apiBaseUrl = baseUrl || DEFAULT_CONSENT_API_BASE_URL;
286
+ const { organisationUuid: ORGANISATION_UUID, workspaceUuid: WORKSPACE_UUID, sandbox } = resolveRequestAuth(accessToken, sandboxParams, decodeTokenSafely);
287
+ const apiBaseUrl = (sandbox ? null : ledgerBaseUrl) || baseUrl || DEFAULT_CONSENT_API_BASE_URL;
233
288
  const normalizedLanguage = language === "en" || language === "EN" ? "English" : language;
234
- const url = `${apiBaseUrl}/public/organisations/${ORGANISATION_UUID}/workspaces/${WORKSPACE_UUID}/notices/${noticeUuid}/audio/${encodeURIComponent(normalizedLanguage)}`;
235
- const response = await fetch(url, {
289
+ const url = new URL(
290
+ `${apiBaseUrl}/public/organisations/${ORGANISATION_UUID}/workspaces/${WORKSPACE_UUID}/notices/${noticeUuid}/audio/${encodeURIComponent(normalizedLanguage)}`
291
+ );
292
+ if (sandbox) {
293
+ for (const [key, value] of Object.entries(sandboxQueryIdentity(sandbox))) {
294
+ url.searchParams.append(key, value);
295
+ }
296
+ }
297
+ const response = await fetch(url.toString(), {
236
298
  method: "GET",
237
299
  headers: {
238
- Authorization: `Bearer ${accessToken}`,
300
+ ...buildAuthHeaders(accessToken, sandbox),
239
301
  Accept: "application/json",
240
302
  "Content-Type": "application/json"
241
303
  },
@@ -1749,6 +1811,12 @@ var RedactoNoticeConsent = ({
1749
1811
  noticeId,
1750
1812
  accessToken,
1751
1813
  refreshToken: _refreshToken,
1814
+ token,
1815
+ email,
1816
+ mobile,
1817
+ ucic,
1818
+ organisationUuid,
1819
+ workspaceUuid,
1752
1820
  baseUrl,
1753
1821
  ledgerBaseUrl,
1754
1822
  settings,
@@ -1764,6 +1832,16 @@ var RedactoNoticeConsent = ({
1764
1832
  }) => {
1765
1833
  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u;
1766
1834
  const { isMobile } = useResponsive();
1835
+ const sandboxParams = useMemo(
1836
+ () => ({
1837
+ token,
1838
+ sandboxUcic: ucic,
1839
+ sandboxSubject: email || mobile,
1840
+ organisationUuid,
1841
+ workspaceUuid
1842
+ }),
1843
+ [token, ucic, email, mobile, organisationUuid, workspaceUuid]
1844
+ );
1767
1845
  const [content, setContent] = useState(null);
1768
1846
  const [isLoading, setIsLoading] = useState(true);
1769
1847
  const [isSubmitting, setIsSubmitting] = useState(false);
@@ -1818,20 +1896,22 @@ var RedactoNoticeConsent = ({
1818
1896
  setIsLoading(true);
1819
1897
  setErrorMessage(null);
1820
1898
  try {
1899
+ const noticeBaseUrl = ledgerBaseUrl && !applicationId && !token ? ledgerBaseUrl : baseUrl;
1821
1900
  const data = await fetchConsentContent({
1822
1901
  noticeId,
1823
1902
  accessToken,
1824
- baseUrl,
1903
+ baseUrl: noticeBaseUrl,
1825
1904
  language: "English",
1826
1905
  validate_against: validateAgainst,
1827
1906
  include_fully_consented_data: includeFullyConsentedData,
1828
1907
  specific_uuid: applicationId,
1829
- signal: abortControllerRef.current.signal
1908
+ signal: abortControllerRef.current.signal,
1909
+ ...sandboxParams
1830
1910
  });
1831
1911
  const activeConfig2 = data.detail.active_config;
1832
1912
  let purposeSelections = data.detail.purpose_selections;
1833
1913
  let reconsentRequired = data.detail.reconsent_required || false;
1834
- if (ledgerBaseUrl && !applicationId) {
1914
+ if (accessToken && ledgerBaseUrl && !applicationId && !token) {
1835
1915
  const ledgerStatus = await fetchNoticeConsentStatus({
1836
1916
  accessToken,
1837
1917
  ledgerBaseUrl,
@@ -1908,7 +1988,7 @@ var RedactoNoticeConsent = ({
1908
1988
  } finally {
1909
1989
  setIsLoading(false);
1910
1990
  }
1911
- }, [noticeId, accessToken, baseUrl, ledgerBaseUrl, validateAgainst, includeFullyConsentedData, applicationId, onAccept, onError]);
1991
+ }, [noticeId, accessToken, sandboxParams, token, baseUrl, ledgerBaseUrl, validateAgainst, includeFullyConsentedData, applicationId, onAccept, onError]);
1912
1992
  useEffect2(() => {
1913
1993
  hasBootstrappedLanguageRef.current = false;
1914
1994
  }, [noticeId, accessToken]);
@@ -1929,8 +2009,10 @@ var RedactoNoticeConsent = ({
1929
2009
  const data = await fetchTTSAudioUrls({
1930
2010
  accessToken,
1931
2011
  baseUrl,
2012
+ ledgerBaseUrl,
1932
2013
  noticeUuid: content.detail.active_config.notice_uuid,
1933
- language: selectedLanguage
2014
+ language: selectedLanguage,
2015
+ ...sandboxParams
1934
2016
  });
1935
2017
  const urls = {
1936
2018
  notice_text: data.detail.notice_text_audio_url,
@@ -1984,7 +2066,7 @@ var RedactoNoticeConsent = ({
1984
2066
  setTtsAudioUrls({});
1985
2067
  setIsTTSAvailable(false);
1986
2068
  }
1987
- }, [content, accessToken, baseUrl, selectedLanguage]);
2069
+ }, [content, accessToken, sandboxParams, baseUrl, ledgerBaseUrl, selectedLanguage]);
1988
2070
  const clearPlaybackWatchers = useCallback(() => {
1989
2071
  if (playbackSubscriptionRef.current) {
1990
2072
  playbackSubscriptionRef.current.remove();
@@ -2325,7 +2407,8 @@ var RedactoNoticeConsent = ({
2325
2407
  meta_data: applicationId ? { specific_uuid: applicationId } : void 0,
2326
2408
  guardianVerificationReference: verificationReferenceRef.current || void 0,
2327
2409
  selfDeclaredAdult: selfDeclaredAdult || void 0,
2328
- language: selectedLanguage
2410
+ language: selectedLanguage,
2411
+ ...sandboxParams
2329
2412
  });
2330
2413
  await stopAudio();
2331
2414
  clearApiCache();
@@ -2340,7 +2423,7 @@ var RedactoNoticeConsent = ({
2340
2423
  } finally {
2341
2424
  setIsSubmitting(false);
2342
2425
  }
2343
- }, [content, accessToken, baseUrl, ledgerBaseUrl, applicationId, selectedPurposes, selectedDataElements, selectedLanguage, stopAudio, onAccept, onError]);
2426
+ }, [content, accessToken, sandboxParams, baseUrl, ledgerBaseUrl, applicationId, selectedPurposes, selectedDataElements, selectedLanguage, selfDeclaredAdult, stopAudio, onAccept, onError]);
2344
2427
  const handleDecline = useCallback(() => {
2345
2428
  stopAudio();
2346
2429
  stopPolling();
@@ -2955,14 +3038,16 @@ var fetchConsentContent2 = async ({
2955
3038
  org_uuid,
2956
3039
  workspace_uuid,
2957
3040
  notice_uuid,
3041
+ accessToken,
2958
3042
  baseUrl,
2959
3043
  language = "en",
2960
3044
  specific_uuid,
2961
3045
  signal
2962
3046
  }) => {
2963
3047
  const apiBaseUrl = baseUrl || DEFAULT_CONSENT_API_BASE_URL;
3048
+ const noticesUrl = `${apiBaseUrl}/public/organisations/${org_uuid}/workspaces/${workspace_uuid}/notices`;
2964
3049
  const url = new URL(
2965
- `${apiBaseUrl}/public/organisations/${org_uuid}/workspaces/${workspace_uuid}/notices/get-notice/${notice_uuid}`
3050
+ accessToken ? `${noticesUrl}/${notice_uuid}` : `${noticesUrl}/get-notice/${notice_uuid}`
2966
3051
  );
2967
3052
  if (specific_uuid) {
2968
3053
  url.searchParams.append("specific_uuid", specific_uuid);
@@ -2971,7 +3056,8 @@ var fetchConsentContent2 = async ({
2971
3056
  method: "GET",
2972
3057
  headers: {
2973
3058
  "Content-Type": "application/json",
2974
- "Accept-Language": language
3059
+ "Accept-Language": language,
3060
+ ...accessToken ? { Authorization: `Bearer ${accessToken}` } : {}
2975
3061
  },
2976
3062
  signal
2977
3063
  });
@@ -3412,11 +3498,13 @@ var RedactoConsentInlineComponent = ({
3412
3498
  const fetchNotice = useCallback2(async (signal) => {
3413
3499
  setIsLoading(true);
3414
3500
  setFetchError(null);
3501
+ setHasAlreadyConsented(false);
3415
3502
  try {
3416
3503
  const consentContentData = await fetchConsentContent2({
3417
3504
  org_uuid,
3418
3505
  workspace_uuid,
3419
3506
  notice_uuid,
3507
+ accessToken,
3420
3508
  baseUrl,
3421
3509
  language,
3422
3510
  specific_uuid: applicationId,
@@ -3466,7 +3554,7 @@ var RedactoConsentInlineComponent = ({
3466
3554
  setIsLoading(false);
3467
3555
  }
3468
3556
  }
3469
- }, [org_uuid, workspace_uuid, notice_uuid, baseUrl, language, applicationId, onError]);
3557
+ }, [org_uuid, workspace_uuid, notice_uuid, accessToken, baseUrl, language, applicationId, onError]);
3470
3558
  useEffect3(() => {
3471
3559
  const controller = new AbortController();
3472
3560
  void fetchNotice(controller.signal);
@@ -301,6 +301,7 @@ type UserConsent = {
301
301
  product_name?: string;
302
302
  product_description?: string | null;
303
303
  nominator_info?: NominatorInfo;
304
+ revoke_warning_message?: string | null;
304
305
  notice_uuid?: string | null;
305
306
  };
306
307
  type NominatorInfo = {
@@ -333,6 +334,7 @@ type PurposeItem = {
333
334
  method: string;
334
335
  data_elements: ConsentDataElement[];
335
336
  link_reason: string | null;
337
+ revoke_warning_message?: string | null;
336
338
  notice_uuid?: string | null;
337
339
  };
338
340
  type ConsentGroup = {
@@ -484,6 +486,29 @@ type CaseDetailsTab = Readonly<{
484
486
  id: TabId;
485
487
  label: string;
486
488
  }>;
489
+ /**
490
+ * One data principal sharing this session's contact.
491
+ *
492
+ * `org_user_id` is unique per principal within the org/workspace, so it is the
493
+ * reliable way to tell profiles apart when the email and mobile are identical —
494
+ * which is the whole point of a shared contact.
495
+ */
496
+ type IdentityCandidate = Readonly<{
497
+ uuid: string;
498
+ org_user_id?: string | null;
499
+ primary_email?: string | null;
500
+ primary_mobile?: string | null;
501
+ created_at?: string | null;
502
+ }>;
503
+ type IdentityListResponse = Readonly<{
504
+ identities: IdentityCandidate[];
505
+ }>;
506
+ /** Fresh tokens scoped to the chosen principal. */
507
+ type SelectPrincipalResponse = Readonly<{
508
+ token: string;
509
+ refresh_token: string;
510
+ expires_at?: string;
511
+ }>;
487
512
 
488
513
  type InternalPage = 'home' | 'consent-manager' | 'form' | 'activity' | 'receipts' | 'case-details' | 'notifications';
489
514
  interface NavigationContextType {
@@ -512,15 +537,38 @@ interface RedactoPrivacyCenterProps {
512
537
  * Accepted for backwards compatibility but ignored.
513
538
  */
514
539
  theme?: 'light' | 'dark';
515
- accessToken: string;
516
- refreshToken: string;
540
+ /** JWT access token. Optional so sandbox mode (see `token`) can omit it. */
541
+ accessToken?: string;
542
+ /** JWT refresh token. Optional; used by the OTP-refresh loop in the JWT flow. */
543
+ refreshToken?: string;
544
+ /**
545
+ * Sandbox PoC: a pasted static token. When set, the Privacy Center runs
546
+ * against the sandbox with no JWT — it sends the single `X-Consent-Token`
547
+ * header, carries the acting identity (`email` || `mobile`) in each request's
548
+ * own payload, takes org/workspace from `organisationUuid`/`workspaceUuid`,
549
+ * and never calls `/otp/refresh`.
550
+ */
551
+ token?: string;
552
+ /** Sandbox test subject email (carried in the request payload when set). */
553
+ email?: string;
554
+ /** Sandbox test subject mobile (fallback subject when `email` is absent). */
555
+ mobile?: string;
556
+ /**
557
+ * Sandbox UCIC: the client's own user id / org_user_id, e.g. a UUID or
558
+ * customer code; optional third identifier alongside email/mobile.
559
+ */
560
+ ucic?: string;
561
+ /** Sandbox org UUID (replaces the JWT-decoded org in sandbox mode). */
562
+ organisationUuid?: string;
563
+ /** Sandbox workspace UUID (replaces the JWT-decoded workspace in sandbox mode). */
564
+ workspaceUuid?: string;
517
565
  onError: (error: Error) => boolean;
518
566
  initialPage?: InternalPage;
519
567
  /** Optional deep-link: open a specific case (use with initialPage='case-details'). */
520
568
  initialCaseUuid?: string;
521
569
  onBack?: 'back' | 'signout';
522
570
  }
523
- declare function RedactoPrivacyCenter({ baseUrl, ledgerBaseUrl, slug, theme: _theme, accessToken, refreshToken, onError, initialPage, initialCaseUuid, onBack, }: RedactoPrivacyCenterProps): React.JSX.Element;
571
+ declare function RedactoPrivacyCenter({ baseUrl, ledgerBaseUrl, slug, theme: _theme, accessToken, refreshToken, token, email, mobile, ucic, organisationUuid, workspaceUuid, onError, initialPage, initialCaseUuid, onBack, }: RedactoPrivacyCenterProps): React.JSX.Element;
524
572
 
525
573
  declare function ConsentManager(): React__default.JSX.Element;
526
574
 
@@ -704,6 +752,18 @@ declare function createConsentClient(baseUrl: string, ledgerBaseUrl?: string): {
704
752
  * Get activities (privacy center)
705
753
  */
706
754
  getActivities(baseRequest: BaseRequest, offset?: number, limit?: number, language?: string, authToken?: string, options?: Omit<ApiFetchOptions, "getAuthToken">): Promise<unknown>;
755
+ /**
756
+ * List all data principals that share this session's contact
757
+ * (shared-contact profile picker). Identity is derived server-side from the
758
+ * JWT — the caller sends nothing but its bearer token.
759
+ */
760
+ listIdentities(baseRequest: BaseRequest, authToken?: string, options?: Omit<ApiFetchOptions, "getAuthToken">): Promise<IdentityListResponse>;
761
+ /**
762
+ * Re-scope the session to a chosen principal uuid (must be one of the
763
+ * principals sharing this session's contact). Returns fresh access/refresh
764
+ * tokens scoped to that principal.
765
+ */
766
+ selectPrincipal(baseRequest: BaseRequest, principalUuid: string, authToken?: string, options?: Omit<ApiFetchOptions, "getAuthToken">): Promise<SelectPrincipalResponse>;
707
767
  /**
708
768
  * Get receipts (privacy center)
709
769
  */
@@ -876,9 +936,26 @@ interface AuthProviderProps {
876
936
  children: ReactNode;
877
937
  accessToken: string;
878
938
  refreshToken: string;
939
+ /**
940
+ * Sandbox PoC: when set, the Privacy Center skips the JWT entirely — it seeds
941
+ * org/workspace/subject from the props below, sends the single
942
+ * `X-Consent-Token` header, carries the acting identity in each request's own
943
+ * payload, and never calls `/otp/refresh`.
944
+ */
945
+ token?: string;
946
+ /** Sandbox UCIC — the client's own user id (`org_user_id`); takes precedence over the subject. */
947
+ sandboxUcic?: string;
948
+ /** Sandbox subject (email or mobile) — carried in the request payload. */
949
+ sandboxSubject?: string;
950
+ /** Sandbox org UUID (replaces the JWT-decoded org). */
951
+ organisationUuid?: string;
952
+ /** Sandbox workspace UUID (replaces the JWT-decoded workspace). */
953
+ workspaceUuid?: string;
954
+ /** Organisation slug — used to seed the sandbox config. */
955
+ slug?: string;
879
956
  onError: (error: Error) => boolean;
880
957
  }
881
- declare function AuthProvider({ children, accessToken: initialAccessToken, refreshToken: initialRefreshToken, onError }: AuthProviderProps): React.JSX.Element;
958
+ declare function AuthProvider(props: AuthProviderProps): React.JSX.Element;
882
959
  declare function useAuth(): AuthContextType;
883
960
 
884
961
  /**
@@ -301,6 +301,7 @@ type UserConsent = {
301
301
  product_name?: string;
302
302
  product_description?: string | null;
303
303
  nominator_info?: NominatorInfo;
304
+ revoke_warning_message?: string | null;
304
305
  notice_uuid?: string | null;
305
306
  };
306
307
  type NominatorInfo = {
@@ -333,6 +334,7 @@ type PurposeItem = {
333
334
  method: string;
334
335
  data_elements: ConsentDataElement[];
335
336
  link_reason: string | null;
337
+ revoke_warning_message?: string | null;
336
338
  notice_uuid?: string | null;
337
339
  };
338
340
  type ConsentGroup = {
@@ -484,6 +486,29 @@ type CaseDetailsTab = Readonly<{
484
486
  id: TabId;
485
487
  label: string;
486
488
  }>;
489
+ /**
490
+ * One data principal sharing this session's contact.
491
+ *
492
+ * `org_user_id` is unique per principal within the org/workspace, so it is the
493
+ * reliable way to tell profiles apart when the email and mobile are identical —
494
+ * which is the whole point of a shared contact.
495
+ */
496
+ type IdentityCandidate = Readonly<{
497
+ uuid: string;
498
+ org_user_id?: string | null;
499
+ primary_email?: string | null;
500
+ primary_mobile?: string | null;
501
+ created_at?: string | null;
502
+ }>;
503
+ type IdentityListResponse = Readonly<{
504
+ identities: IdentityCandidate[];
505
+ }>;
506
+ /** Fresh tokens scoped to the chosen principal. */
507
+ type SelectPrincipalResponse = Readonly<{
508
+ token: string;
509
+ refresh_token: string;
510
+ expires_at?: string;
511
+ }>;
487
512
 
488
513
  type InternalPage = 'home' | 'consent-manager' | 'form' | 'activity' | 'receipts' | 'case-details' | 'notifications';
489
514
  interface NavigationContextType {
@@ -512,15 +537,38 @@ interface RedactoPrivacyCenterProps {
512
537
  * Accepted for backwards compatibility but ignored.
513
538
  */
514
539
  theme?: 'light' | 'dark';
515
- accessToken: string;
516
- refreshToken: string;
540
+ /** JWT access token. Optional so sandbox mode (see `token`) can omit it. */
541
+ accessToken?: string;
542
+ /** JWT refresh token. Optional; used by the OTP-refresh loop in the JWT flow. */
543
+ refreshToken?: string;
544
+ /**
545
+ * Sandbox PoC: a pasted static token. When set, the Privacy Center runs
546
+ * against the sandbox with no JWT — it sends the single `X-Consent-Token`
547
+ * header, carries the acting identity (`email` || `mobile`) in each request's
548
+ * own payload, takes org/workspace from `organisationUuid`/`workspaceUuid`,
549
+ * and never calls `/otp/refresh`.
550
+ */
551
+ token?: string;
552
+ /** Sandbox test subject email (carried in the request payload when set). */
553
+ email?: string;
554
+ /** Sandbox test subject mobile (fallback subject when `email` is absent). */
555
+ mobile?: string;
556
+ /**
557
+ * Sandbox UCIC: the client's own user id / org_user_id, e.g. a UUID or
558
+ * customer code; optional third identifier alongside email/mobile.
559
+ */
560
+ ucic?: string;
561
+ /** Sandbox org UUID (replaces the JWT-decoded org in sandbox mode). */
562
+ organisationUuid?: string;
563
+ /** Sandbox workspace UUID (replaces the JWT-decoded workspace in sandbox mode). */
564
+ workspaceUuid?: string;
517
565
  onError: (error: Error) => boolean;
518
566
  initialPage?: InternalPage;
519
567
  /** Optional deep-link: open a specific case (use with initialPage='case-details'). */
520
568
  initialCaseUuid?: string;
521
569
  onBack?: 'back' | 'signout';
522
570
  }
523
- declare function RedactoPrivacyCenter({ baseUrl, ledgerBaseUrl, slug, theme: _theme, accessToken, refreshToken, onError, initialPage, initialCaseUuid, onBack, }: RedactoPrivacyCenterProps): React.JSX.Element;
571
+ declare function RedactoPrivacyCenter({ baseUrl, ledgerBaseUrl, slug, theme: _theme, accessToken, refreshToken, token, email, mobile, ucic, organisationUuid, workspaceUuid, onError, initialPage, initialCaseUuid, onBack, }: RedactoPrivacyCenterProps): React.JSX.Element;
524
572
 
525
573
  declare function ConsentManager(): React__default.JSX.Element;
526
574
 
@@ -704,6 +752,18 @@ declare function createConsentClient(baseUrl: string, ledgerBaseUrl?: string): {
704
752
  * Get activities (privacy center)
705
753
  */
706
754
  getActivities(baseRequest: BaseRequest, offset?: number, limit?: number, language?: string, authToken?: string, options?: Omit<ApiFetchOptions, "getAuthToken">): Promise<unknown>;
755
+ /**
756
+ * List all data principals that share this session's contact
757
+ * (shared-contact profile picker). Identity is derived server-side from the
758
+ * JWT — the caller sends nothing but its bearer token.
759
+ */
760
+ listIdentities(baseRequest: BaseRequest, authToken?: string, options?: Omit<ApiFetchOptions, "getAuthToken">): Promise<IdentityListResponse>;
761
+ /**
762
+ * Re-scope the session to a chosen principal uuid (must be one of the
763
+ * principals sharing this session's contact). Returns fresh access/refresh
764
+ * tokens scoped to that principal.
765
+ */
766
+ selectPrincipal(baseRequest: BaseRequest, principalUuid: string, authToken?: string, options?: Omit<ApiFetchOptions, "getAuthToken">): Promise<SelectPrincipalResponse>;
707
767
  /**
708
768
  * Get receipts (privacy center)
709
769
  */
@@ -876,9 +936,26 @@ interface AuthProviderProps {
876
936
  children: ReactNode;
877
937
  accessToken: string;
878
938
  refreshToken: string;
939
+ /**
940
+ * Sandbox PoC: when set, the Privacy Center skips the JWT entirely — it seeds
941
+ * org/workspace/subject from the props below, sends the single
942
+ * `X-Consent-Token` header, carries the acting identity in each request's own
943
+ * payload, and never calls `/otp/refresh`.
944
+ */
945
+ token?: string;
946
+ /** Sandbox UCIC — the client's own user id (`org_user_id`); takes precedence over the subject. */
947
+ sandboxUcic?: string;
948
+ /** Sandbox subject (email or mobile) — carried in the request payload. */
949
+ sandboxSubject?: string;
950
+ /** Sandbox org UUID (replaces the JWT-decoded org). */
951
+ organisationUuid?: string;
952
+ /** Sandbox workspace UUID (replaces the JWT-decoded workspace). */
953
+ workspaceUuid?: string;
954
+ /** Organisation slug — used to seed the sandbox config. */
955
+ slug?: string;
879
956
  onError: (error: Error) => boolean;
880
957
  }
881
- declare function AuthProvider({ children, accessToken: initialAccessToken, refreshToken: initialRefreshToken, onError }: AuthProviderProps): React.JSX.Element;
958
+ declare function AuthProvider(props: AuthProviderProps): React.JSX.Element;
882
959
  declare function useAuth(): AuthContextType;
883
960
 
884
961
  /**