@redacto.io/consent-sdk-react 5.2.0 → 5.3.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 +24 -1
- package/dist/index.d.ts +24 -1
- package/dist/index.js +149 -206
- package/dist/index.mjs +148 -206
- package/dist/privacy-center.d.mts +81 -2
- package/dist/privacy-center.d.ts +81 -2
- package/dist/privacy-center.js +2823 -1272
- package/dist/privacy-center.mjs +2813 -1263
- package/package.json +1 -1
- package/src/RedactoNoticeConsent/RedactoNoticeConsent.tsx +16 -20
- package/src/RedactoNoticeConsent/api/index.ts +21 -103
- package/src/RedactoNoticeConsentInline/RedactoNoticeConsentInline.tsx +5 -4
- package/src/RedactoNoticeConsentInline/api/index.ts +64 -117
- package/src/RedactoPrivacyCenter/NotificationsPage.tsx +174 -0
- package/src/RedactoPrivacyCenter/PrivacyCenterHomePage.tsx +3 -0
- package/src/RedactoPrivacyCenter/api/actions.ts +84 -0
- package/src/RedactoPrivacyCenter/api/client.ts +118 -0
- package/src/RedactoPrivacyCenter/components/DataRequest/PurposeSelectionSection.tsx +15 -0
- package/src/RedactoPrivacyCenter/components/NotificationHub/NotificationBell.tsx +160 -0
- package/src/RedactoPrivacyCenter/components/NotificationHub/NotificationCTAButton.tsx +18 -0
- package/src/RedactoPrivacyCenter/components/NotificationHub/NotificationDropdown.tsx +54 -0
- package/src/RedactoPrivacyCenter/components/NotificationHub/NotificationDropdownItem.tsx +57 -0
- package/src/RedactoPrivacyCenter/components/NotificationHub/NotificationIcon.tsx +19 -0
- package/src/RedactoPrivacyCenter/components/NotificationHub/NotificationListPanel.tsx +92 -0
- package/src/RedactoPrivacyCenter/components/NotificationHub/NotificationRefreshButton.tsx +20 -0
- package/src/RedactoPrivacyCenter/components/NotificationHub/NotificationRow.tsx +58 -0
- package/src/RedactoPrivacyCenter/components/NotificationHub/StatCard.tsx +10 -0
- package/src/RedactoPrivacyCenter/components/NotificationHub/useNotificationActions.ts +112 -0
- package/src/RedactoPrivacyCenter/components/NotificationHub/utils.ts +27 -0
- package/src/RedactoPrivacyCenter/components/PrivacyCenterLayout.tsx +3 -2
- package/src/RedactoPrivacyCenter/context/NavigationContext.tsx +2 -2
- package/src/RedactoPrivacyCenter/lib/api.ts +77 -0
- package/src/RedactoPrivacyCenter/lib/constants.ts +78 -1
- package/src/RedactoPrivacyCenter/lib/types.ts +148 -0
- package/src/RedactoPrivacyCenter/lib/utils.ts +3 -0
- package/src/RedactoPrivacyCenter/locales/en/translation.json +27 -1
- package/src/RedactoPrivacyCenter/styles/injectStyles.ts +505 -0
- package/src/RedactoPrivacyCenter/ui/PCPopover.tsx +23 -1
- package/src/index.ts +5 -0
- package/src/privacy-center.ts +1 -0
- package/src/shared/api-errors.ts +171 -0
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared API error handling for the consent-sdk-react package.
|
|
3
|
+
*
|
|
4
|
+
* The backend (consent-server) owns the user-facing copy. Each error
|
|
5
|
+
* response carries:
|
|
6
|
+
* - `message`: user-safe text the SDK displays verbatim
|
|
7
|
+
* - `error_code`: stable identifier for programmatic branching
|
|
8
|
+
*
|
|
9
|
+
* The SDK never re-translates a code into a message. To change wording,
|
|
10
|
+
* update the server titles registry -- no SDK release required.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export type ApiErrorCode =
|
|
14
|
+
| "INVALID_TOKEN"
|
|
15
|
+
| "TOKEN_TENANT_MISMATCH"
|
|
16
|
+
| "UNAUTHORIZED"
|
|
17
|
+
| "USER_NOT_FOUND"
|
|
18
|
+
| "USER_EMAIL_MISMATCH"
|
|
19
|
+
| "USER_MOBILE_MISMATCH"
|
|
20
|
+
| "USER_CONTACT_MISMATCH"
|
|
21
|
+
| "NO_IDENTIFIER_PROVIDED"
|
|
22
|
+
| "INVALID_PAYLOAD"
|
|
23
|
+
| "VALIDATION_ERROR"
|
|
24
|
+
| "NOTICE_NOT_FOUND"
|
|
25
|
+
| "NOTICE_CONFIGURATION_NOT_FOUND"
|
|
26
|
+
| "NOTICE_STILL_IN_DRAFT"
|
|
27
|
+
| "CONSENT_ALREADY_PROVIDED"
|
|
28
|
+
| "PURPOSE_NOT_FOUND"
|
|
29
|
+
| "INVALID_PURPOSE"
|
|
30
|
+
| "INVALID_DATA_ELEMENTS"
|
|
31
|
+
| "MISSING_PURPOSE"
|
|
32
|
+
| "MISSING_DATA_ELEMENTS"
|
|
33
|
+
| "REQUIRED_DATA_ELEMENT_NOT_ENABLED"
|
|
34
|
+
| "AUDIO_NOT_FOUND"
|
|
35
|
+
| "INVALID_LANGUAGE"
|
|
36
|
+
| "VERIFICATION_FAILED"
|
|
37
|
+
| "VERIFICATION_EXPIRED"
|
|
38
|
+
| "GUARDIAN_INFO_NOT_FOUND"
|
|
39
|
+
| "GUARDIAN_SESSION_INVALID"
|
|
40
|
+
| "SESSION_EXPIRED"
|
|
41
|
+
| "INTERNAL_ERROR";
|
|
42
|
+
|
|
43
|
+
interface ApiErrorDetail {
|
|
44
|
+
message?: string;
|
|
45
|
+
error_code?: string | null;
|
|
46
|
+
// Ninja's auth-pipeline auto-401 returns `{detail: {detail: "Unauthorized"}}`
|
|
47
|
+
// -- the inner `detail` is a string. Captured here so the parser can use
|
|
48
|
+
// it as a last-resort message extraction.
|
|
49
|
+
detail?: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
interface ApiErrorBody {
|
|
53
|
+
message?: string;
|
|
54
|
+
error_code?: string | null;
|
|
55
|
+
// `detail` may arrive as:
|
|
56
|
+
// { message: "...", error_code: "..." } -- our MessageResponse via Ninja
|
|
57
|
+
// { detail: "..." } -- Ninja auto-401
|
|
58
|
+
// "..." -- direct string from some endpoints
|
|
59
|
+
// [...] -- Ninja auto-422 Pydantic errors
|
|
60
|
+
detail?: ApiErrorDetail | string | unknown[];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// `ApiErrorCode | (string & {})` keeps literal autocomplete on `code` while
|
|
64
|
+
// still permitting unknown strings from forward-compatible server versions.
|
|
65
|
+
// A bare `ApiErrorCode | string` would widen to `string` and lose the
|
|
66
|
+
// literal suggestions.
|
|
67
|
+
export type ApiErrorCodeLike = ApiErrorCode | (string & {});
|
|
68
|
+
|
|
69
|
+
export class ApiError extends Error {
|
|
70
|
+
readonly status: number;
|
|
71
|
+
readonly code?: ApiErrorCodeLike;
|
|
72
|
+
|
|
73
|
+
constructor(opts: {
|
|
74
|
+
status: number;
|
|
75
|
+
code?: ApiErrorCodeLike;
|
|
76
|
+
message: string;
|
|
77
|
+
}) {
|
|
78
|
+
super(opts.message);
|
|
79
|
+
this.name = "ApiError";
|
|
80
|
+
this.status = opts.status;
|
|
81
|
+
this.code = opts.code;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Tier-3 fallbacks for failure modes where the server did not produce
|
|
86
|
+
// a parseable body with `message`. Common case: Ninja's auth pipeline
|
|
87
|
+
// rejects a request before reaching our handler, returning
|
|
88
|
+
// `{detail: {detail: "Unauthorized"}}` with no message field. The
|
|
89
|
+
// fallback is a static, user-safe hint -- never an override of a
|
|
90
|
+
// server-supplied message.
|
|
91
|
+
function statusFallback(status: number): string | undefined {
|
|
92
|
+
if (status === 401) {
|
|
93
|
+
return "Your session has expired. Please request a new access link.";
|
|
94
|
+
}
|
|
95
|
+
if (status === 403) {
|
|
96
|
+
return "You do not have access to this resource.";
|
|
97
|
+
}
|
|
98
|
+
if (status >= 500) {
|
|
99
|
+
return "Something went wrong on our end. Please try again later.";
|
|
100
|
+
}
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Build an ApiError from a non-OK fetch Response.
|
|
106
|
+
*
|
|
107
|
+
* Order of preference for the user-facing message:
|
|
108
|
+
* 1. `message` field from the backend response body (preferred -- the
|
|
109
|
+
* SDK-consumed endpoints always populate this with user-safe copy).
|
|
110
|
+
* 2. Status-based fallback (only when the server omitted a message,
|
|
111
|
+
* e.g. Ninja's auto-401 for an expired JWT).
|
|
112
|
+
* 3. The caller-supplied fallback (used when the body is missing or
|
|
113
|
+
* unparseable -- network error, non-JSON proxy page).
|
|
114
|
+
*
|
|
115
|
+
* The `code` field is exposed as-is for programmatic branching. Consumers
|
|
116
|
+
* that want compile-time safety should narrow against `ApiErrorCode`.
|
|
117
|
+
*/
|
|
118
|
+
function extractFromDetail(
|
|
119
|
+
detail: ApiErrorBody["detail"]
|
|
120
|
+
): { message?: string; code?: string | null } {
|
|
121
|
+
// String detail (e.g. some endpoints return `{detail: "..."}` directly).
|
|
122
|
+
if (typeof detail === "string") {
|
|
123
|
+
return { message: detail };
|
|
124
|
+
}
|
|
125
|
+
// Array detail (Ninja's auto-422 Pydantic validation errors): no usable
|
|
126
|
+
// single message; let the caller fall back to status/generic copy.
|
|
127
|
+
if (Array.isArray(detail)) {
|
|
128
|
+
return {};
|
|
129
|
+
}
|
|
130
|
+
// Object detail covers both shapes:
|
|
131
|
+
// our MessageResponse: { message, error_code }
|
|
132
|
+
// Ninja auto-401: { detail: "Unauthorized" }
|
|
133
|
+
if (detail && typeof detail === "object") {
|
|
134
|
+
const obj = detail as ApiErrorDetail;
|
|
135
|
+
return { message: obj.message ?? obj.detail, code: obj.error_code };
|
|
136
|
+
}
|
|
137
|
+
return {};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export async function parseApiError(
|
|
141
|
+
response: Response,
|
|
142
|
+
fallbackMessage: string
|
|
143
|
+
): Promise<ApiError> {
|
|
144
|
+
let body: ApiErrorBody | undefined;
|
|
145
|
+
let parsedBody = false;
|
|
146
|
+
try {
|
|
147
|
+
body = (await response.json()) as ApiErrorBody;
|
|
148
|
+
parsedBody = true;
|
|
149
|
+
} catch {
|
|
150
|
+
body = undefined;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const fromDetail = extractFromDetail(body?.detail);
|
|
154
|
+
const serverMessage = body?.message ?? fromDetail.message;
|
|
155
|
+
// Status fallback only applies when the body parsed but had no message
|
|
156
|
+
// (e.g. Ninja auto-401's `{detail: {detail: "Unauthorized"}}`). When the
|
|
157
|
+
// body is unparseable (proxy HTML 502, empty body, network jank) the
|
|
158
|
+
// caller's per-endpoint fallback is the more accurate hint -- a 401 from
|
|
159
|
+
// a misbehaving proxy is not necessarily an expired session.
|
|
160
|
+
const message =
|
|
161
|
+
serverMessage ??
|
|
162
|
+
(parsedBody ? statusFallback(response.status) : undefined) ??
|
|
163
|
+
fallbackMessage;
|
|
164
|
+
const code = body?.error_code ?? fromDetail.code ?? undefined;
|
|
165
|
+
|
|
166
|
+
return new ApiError({
|
|
167
|
+
status: response.status,
|
|
168
|
+
code,
|
|
169
|
+
message,
|
|
170
|
+
});
|
|
171
|
+
}
|