react-lgpd-consent 0.1.13 → 0.2.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.
@@ -11,7 +11,7 @@ import Typography2 from "@mui/material/Typography";
11
11
  import { useEffect as useEffect2, useState as useState2 } from "react";
12
12
 
13
13
  // src/context/ConsentContext.tsx
14
- import * as React from "react";
14
+ import * as React2 from "react";
15
15
  import { ThemeProvider } from "@mui/material/styles";
16
16
 
17
17
  // src/utils/cookieUtils.ts
@@ -23,20 +23,61 @@ var DEFAULT_COOKIE_OPTS = {
23
23
  secure: typeof window !== "undefined" ? window.location.protocol === "https:" : false,
24
24
  path: "/"
25
25
  };
26
+ var COOKIE_SCHEMA_VERSION = "1.0";
26
27
  function readConsentCookie(name = DEFAULT_COOKIE_OPTS.name) {
27
28
  if (typeof document === "undefined") return null;
28
29
  const raw = Cookies.get(name);
29
30
  if (!raw) return null;
30
31
  try {
31
- return JSON.parse(raw);
32
+ const data = JSON.parse(raw);
33
+ if (!data.version) {
34
+ return migrateLegacyCookie(data);
35
+ }
36
+ if (data.version !== COOKIE_SCHEMA_VERSION) {
37
+ console.warn(
38
+ `[react-lgpd-consent] Cookie version mismatch: ${data.version} != ${COOKIE_SCHEMA_VERSION}`
39
+ );
40
+ return null;
41
+ }
42
+ return data;
43
+ } catch {
44
+ return null;
45
+ }
46
+ }
47
+ function migrateLegacyCookie(legacyData) {
48
+ try {
49
+ const now = (/* @__PURE__ */ new Date()).toISOString();
50
+ return {
51
+ version: COOKIE_SCHEMA_VERSION,
52
+ consented: legacyData.consented || false,
53
+ preferences: legacyData.preferences || { necessary: true },
54
+ consentDate: now,
55
+ // Não temos o original, usar data atual
56
+ lastUpdate: now,
57
+ source: "banner",
58
+ // Assumir origem banner
59
+ isModalOpen: false
60
+ // Nunca persistir estado de UI
61
+ };
32
62
  } catch {
33
63
  return null;
34
64
  }
35
65
  }
36
- function writeConsentCookie(state, opts) {
66
+ function writeConsentCookie(state, source = "banner", opts) {
37
67
  if (typeof document === "undefined") return;
68
+ const now = (/* @__PURE__ */ new Date()).toISOString();
38
69
  const o = { ...DEFAULT_COOKIE_OPTS, ...opts };
39
- Cookies.set(o.name, JSON.stringify(state), {
70
+ const cookieData = {
71
+ version: COOKIE_SCHEMA_VERSION,
72
+ consented: state.consented,
73
+ preferences: state.preferences,
74
+ consentDate: state.consentDate || now,
75
+ // Preservar data original ou usar atual
76
+ lastUpdate: now,
77
+ source
78
+ // isModalOpen NÃO é persistido (campo de UI apenas)
79
+ };
80
+ Cookies.set(o.name, JSON.stringify(cookieData), {
40
81
  expires: o.maxAgeDays,
41
82
  sameSite: o.sameSite,
42
83
  secure: o.secure,
@@ -117,18 +158,105 @@ var defaultConsentTheme = createTheme({
117
158
  }
118
159
  });
119
160
 
161
+ // src/context/CategoriesContext.tsx
162
+ import * as React from "react";
163
+ import { jsx } from "react/jsx-runtime";
164
+ var CategoriesCtx = React.createContext([]);
165
+ function CategoriesProvider({
166
+ categories,
167
+ children
168
+ }) {
169
+ const value = React.useMemo(() => categories || [], [categories]);
170
+ return /* @__PURE__ */ jsx(CategoriesCtx.Provider, { value, children });
171
+ }
172
+ function useCustomCategories() {
173
+ return React.useContext(CategoriesCtx);
174
+ }
175
+ function useAllCategories() {
176
+ const customCategories = useCustomCategories();
177
+ return React.useMemo(() => {
178
+ const defaultCategories = [
179
+ {
180
+ id: "necessary",
181
+ name: "Cookies Necess\xE1rios",
182
+ description: "Essenciais para o funcionamento b\xE1sico do site. Incluem cookies de sess\xE3o, autentica\xE7\xE3o e seguran\xE7a.",
183
+ essential: true,
184
+ cookies: ["PHPSESSID", "JSESSIONID", "cookieConsent", "csrf_token"]
185
+ },
186
+ {
187
+ id: "analytics",
188
+ name: "Analytics e Estat\xEDsticas",
189
+ description: "Permitem medir audi\xEAncia e desempenho, gerando estat\xEDsticas an\xF4nimas de uso.",
190
+ essential: false,
191
+ cookies: ["_ga", "_ga_*", "_gid", "_gat", "gtag"]
192
+ },
193
+ {
194
+ id: "functional",
195
+ name: "Cookies Funcionais",
196
+ description: "Melhoram a experi\xEAncia do usu\xE1rio, lembrando prefer\xEAncias e configura\xE7\xF5es.",
197
+ essential: false,
198
+ cookies: ["language", "theme", "timezone", "preferences"]
199
+ },
200
+ {
201
+ id: "marketing",
202
+ name: "Marketing e Publicidade",
203
+ description: "Utilizados para publicidade direcionada e medi\xE7\xE3o de campanhas publicit\xE1rias.",
204
+ essential: false,
205
+ cookies: ["_fbp", "fr", "tr", "ads_*", "doubleclick"]
206
+ },
207
+ {
208
+ id: "social",
209
+ name: "Redes Sociais",
210
+ description: "Permitem compartilhamento e integra\xE7\xE3o com redes sociais como Facebook, YouTube, etc.",
211
+ essential: false,
212
+ cookies: ["__Secure-*", "sb", "datr", "c_user", "social_*"]
213
+ },
214
+ {
215
+ id: "personalization",
216
+ name: "Personaliza\xE7\xE3o",
217
+ description: "Adaptam o conte\xFAdo e interface \xE0s prefer\xEAncias individuais do usu\xE1rio.",
218
+ essential: false,
219
+ cookies: ["personalization_*", "content_*", "layout_*"]
220
+ }
221
+ ];
222
+ return [...defaultCategories, ...customCategories];
223
+ }, [customCategories]);
224
+ }
225
+
120
226
  // src/context/ConsentContext.tsx
121
- import { jsx, jsxs } from "react/jsx-runtime";
122
- var PreferencesModal = React.lazy(
123
- () => import("./PreferencesModal-2V2W3262.js").then((m) => ({
227
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
228
+ var PreferencesModal = React2.lazy(
229
+ () => import("./PreferencesModal-4WHKT67T.js").then((m) => ({
124
230
  default: m.PreferencesModal
125
231
  }))
126
232
  );
127
233
  var DEFAULT_PREFERENCES = {
128
- analytics: false,
129
- marketing: false
234
+ necessary: true
235
+ // Sempre ativo (essencial)
130
236
  };
237
+ function createInitialPreferences(customCategories) {
238
+ const prefs = { ...DEFAULT_PREFERENCES };
239
+ if (customCategories) {
240
+ customCategories.forEach((category) => {
241
+ prefs[category.id] = category.essential === true;
242
+ });
243
+ }
244
+ return prefs;
245
+ }
246
+ function createFullConsentState(consented, preferences, source, isModalOpen = false, existingState) {
247
+ const now = (/* @__PURE__ */ new Date()).toISOString();
248
+ return {
249
+ version: "1.0",
250
+ consented,
251
+ preferences,
252
+ consentDate: existingState?.consentDate || now,
253
+ lastUpdate: now,
254
+ source,
255
+ isModalOpen
256
+ };
257
+ }
131
258
  var DEFAULT_TEXTS = {
259
+ // Textos básicos
132
260
  bannerMessage: "Utilizamos cookies para melhorar sua experi\xEAncia.",
133
261
  acceptAll: "Aceitar todos",
134
262
  declineAll: "Recusar",
@@ -137,62 +265,97 @@ var DEFAULT_TEXTS = {
137
265
  modalTitle: "Prefer\xEAncias de Cookies",
138
266
  modalIntro: "Ajuste as categorias de cookies. Cookies necess\xE1rios s\xE3o sempre utilizados para funcionalidades b\xE1sicas.",
139
267
  save: "Salvar prefer\xEAncias",
140
- necessaryAlwaysOn: "Cookies necess\xE1rios (sempre ativos)"
268
+ necessaryAlwaysOn: "Cookies necess\xE1rios (sempre ativos)",
269
+ // Textos ANPD expandidos (opcionais)
270
+ controllerInfo: void 0,
271
+ // Exibido se definido
272
+ dataTypes: void 0,
273
+ // Exibido se definido
274
+ thirdPartySharing: void 0,
275
+ // Exibido se definido
276
+ userRights: void 0,
277
+ // Exibido se definido
278
+ contactInfo: void 0,
279
+ // Exibido se definido
280
+ retentionPeriod: void 0,
281
+ // Exibido se definido
282
+ lawfulBasis: void 0,
283
+ // Exibido se definido
284
+ transferCountries: void 0
285
+ // Exibido se definido
141
286
  };
142
287
  function reducer(state, action) {
143
288
  switch (action.type) {
144
- case "ACCEPT_ALL":
145
- return {
146
- consented: true,
147
- preferences: { analytics: true, marketing: true },
148
- isModalOpen: false
149
- };
150
- case "REJECT_ALL":
151
- return {
152
- consented: true,
153
- preferences: { analytics: false, marketing: false },
154
- isModalOpen: false
155
- };
289
+ case "ACCEPT_ALL": {
290
+ const prefs = createInitialPreferences(action.customCategories);
291
+ Object.keys(prefs).forEach((key) => {
292
+ prefs[key] = true;
293
+ });
294
+ return createFullConsentState(true, prefs, "banner", false, state);
295
+ }
296
+ case "REJECT_ALL": {
297
+ const prefs = createInitialPreferences(action.customCategories);
298
+ if (action.customCategories) {
299
+ action.customCategories.forEach((category) => {
300
+ if (category.essential) {
301
+ prefs[category.id] = true;
302
+ }
303
+ });
304
+ }
305
+ return createFullConsentState(true, prefs, "banner", false, state);
306
+ }
156
307
  case "SET_CATEGORY":
157
308
  return {
158
309
  ...state,
159
310
  preferences: {
160
311
  ...state.preferences,
161
312
  [action.category]: action.value
162
- }
313
+ },
314
+ lastUpdate: (/* @__PURE__ */ new Date()).toISOString()
163
315
  };
164
316
  case "SET_PREFERENCES":
165
- return {
166
- ...state,
167
- consented: true,
168
- preferences: action.preferences,
169
- isModalOpen: false
170
- };
317
+ return createFullConsentState(
318
+ true,
319
+ action.preferences,
320
+ "modal",
321
+ false,
322
+ state
323
+ );
171
324
  case "OPEN_MODAL":
172
325
  return { ...state, isModalOpen: true };
173
326
  case "CLOSE_MODAL":
174
- return { ...state, isModalOpen: false, consented: true };
175
- // houve interação
176
- case "RESET":
177
- return {
178
- consented: false,
179
- preferences: { ...DEFAULT_PREFERENCES },
180
- isModalOpen: false
181
- };
327
+ return createFullConsentState(
328
+ true,
329
+ state.preferences,
330
+ "modal",
331
+ false,
332
+ state
333
+ );
334
+ case "RESET": {
335
+ return createFullConsentState(
336
+ false,
337
+ createInitialPreferences(action.customCategories),
338
+ "programmatic",
339
+ false
340
+ );
341
+ }
182
342
  case "HYDRATE":
183
- return { ...action.state };
343
+ return { ...action.state, isModalOpen: false };
344
+ // Nunca hidratar com modal aberto
184
345
  default:
185
346
  return state;
186
347
  }
187
348
  }
188
- var StateCtx = React.createContext(null);
189
- var ActionsCtx = React.createContext(null);
190
- var TextsCtx = React.createContext(DEFAULT_TEXTS);
191
- var HydrationCtx = React.createContext(false);
349
+ var StateCtx = React2.createContext(null);
350
+ var ActionsCtx = React2.createContext(null);
351
+ var TextsCtx = React2.createContext(DEFAULT_TEXTS);
352
+ var HydrationCtx = React2.createContext(false);
192
353
  function ConsentProvider({
193
354
  initialState,
194
355
  texts: textsProp,
195
356
  theme,
357
+ customCategories,
358
+ scriptIntegrations,
196
359
  PreferencesModalComponent,
197
360
  preferencesModalProps = {},
198
361
  disableAutomaticModal = false,
@@ -202,29 +365,30 @@ function ConsentProvider({
202
365
  cookie: cookieOpts,
203
366
  children
204
367
  }) {
205
- const texts = React.useMemo(
368
+ const texts = React2.useMemo(
206
369
  () => ({ ...DEFAULT_TEXTS, ...textsProp ?? {} }),
207
370
  [textsProp]
208
371
  );
209
- const cookie = React.useMemo(
372
+ const cookie = React2.useMemo(
210
373
  () => ({ ...DEFAULT_COOKIE_OPTS, ...cookieOpts ?? {} }),
211
374
  [cookieOpts]
212
375
  );
213
- const appliedTheme = React.useMemo(
376
+ const appliedTheme = React2.useMemo(
214
377
  () => theme || defaultConsentTheme,
215
378
  [theme]
216
379
  );
217
- const boot = React.useMemo(() => {
380
+ const boot = React2.useMemo(() => {
218
381
  if (initialState) return { ...initialState, isModalOpen: false };
219
- return {
220
- consented: false,
221
- preferences: { ...DEFAULT_PREFERENCES },
222
- isModalOpen: false
223
- };
224
- }, [initialState]);
225
- const [state, dispatch] = React.useReducer(reducer, boot);
226
- const [isHydrated, setIsHydrated] = React.useState(false);
227
- React.useEffect(() => {
382
+ return createFullConsentState(
383
+ false,
384
+ createInitialPreferences(customCategories),
385
+ "banner",
386
+ false
387
+ );
388
+ }, [initialState, customCategories]);
389
+ const [state, dispatch] = React2.useReducer(reducer, boot);
390
+ const [isHydrated, setIsHydrated] = React2.useState(false);
391
+ React2.useEffect(() => {
228
392
  if (!initialState) {
229
393
  const saved = readConsentCookie(cookie.name);
230
394
  if (saved?.consented) {
@@ -234,33 +398,33 @@ function ConsentProvider({
234
398
  }
235
399
  setIsHydrated(true);
236
400
  }, [cookie.name, initialState]);
237
- React.useEffect(() => {
238
- if (state.consented) writeConsentCookie(state, cookie);
401
+ React2.useEffect(() => {
402
+ if (state.consented) writeConsentCookie(state, state.source, cookie);
239
403
  }, [state, cookie]);
240
- const prevConsented = React.useRef(state.consented);
241
- React.useEffect(() => {
404
+ const prevConsented = React2.useRef(state.consented);
405
+ React2.useEffect(() => {
242
406
  if (!prevConsented.current && state.consented && onConsentGiven) {
243
407
  setTimeout(() => onConsentGiven(state), 150);
244
408
  }
245
409
  prevConsented.current = state.consented;
246
410
  }, [state, onConsentGiven]);
247
- const prevPrefs = React.useRef(state.preferences);
248
- React.useEffect(() => {
411
+ const prevPrefs = React2.useRef(state.preferences);
412
+ React2.useEffect(() => {
249
413
  if (state.consented && onPreferencesSaved && prevPrefs.current !== state.preferences) {
250
414
  setTimeout(() => onPreferencesSaved(state.preferences), 150);
251
415
  prevPrefs.current = state.preferences;
252
416
  }
253
417
  }, [state, onPreferencesSaved]);
254
- const api = React.useMemo(() => {
255
- const acceptAll = () => dispatch({ type: "ACCEPT_ALL" });
256
- const rejectAll = () => dispatch({ type: "REJECT_ALL" });
418
+ const api = React2.useMemo(() => {
419
+ const acceptAll = () => dispatch({ type: "ACCEPT_ALL", customCategories });
420
+ const rejectAll = () => dispatch({ type: "REJECT_ALL", customCategories });
257
421
  const setPreference = (category, value) => dispatch({ type: "SET_CATEGORY", category, value });
258
422
  const setPreferences = (preferences) => dispatch({ type: "SET_PREFERENCES", preferences });
259
423
  const openPreferences = () => dispatch({ type: "OPEN_MODAL" });
260
424
  const closePreferences = () => dispatch({ type: "CLOSE_MODAL" });
261
425
  const resetConsent = () => {
262
426
  removeConsentCookie(cookie);
263
- dispatch({ type: "RESET" });
427
+ dispatch({ type: "RESET", customCategories });
264
428
  };
265
429
  return {
266
430
  consented: !!state.consented,
@@ -274,30 +438,30 @@ function ConsentProvider({
274
438
  closePreferences,
275
439
  resetConsent
276
440
  };
277
- }, [state, cookie]);
278
- return /* @__PURE__ */ jsx(ThemeProvider, { theme: appliedTheme, children: /* @__PURE__ */ jsx(StateCtx.Provider, { value: state, children: /* @__PURE__ */ jsx(ActionsCtx.Provider, { value: api, children: /* @__PURE__ */ jsx(TextsCtx.Provider, { value: texts, children: /* @__PURE__ */ jsxs(HydrationCtx.Provider, { value: isHydrated, children: [
441
+ }, [state, cookie, customCategories]);
442
+ return /* @__PURE__ */ jsx2(ThemeProvider, { theme: appliedTheme, children: /* @__PURE__ */ jsx2(StateCtx.Provider, { value: state, children: /* @__PURE__ */ jsx2(ActionsCtx.Provider, { value: api, children: /* @__PURE__ */ jsx2(TextsCtx.Provider, { value: texts, children: /* @__PURE__ */ jsx2(HydrationCtx.Provider, { value: isHydrated, children: /* @__PURE__ */ jsxs(CategoriesProvider, { categories: customCategories, children: [
279
443
  children,
280
- !disableAutomaticModal && /* @__PURE__ */ jsx(React.Suspense, { fallback: null, children: PreferencesModalComponent ? /* @__PURE__ */ jsx(PreferencesModalComponent, { ...preferencesModalProps }) : /* @__PURE__ */ jsx(PreferencesModal, { hideBranding }) })
281
- ] }) }) }) }) });
444
+ !disableAutomaticModal && /* @__PURE__ */ jsx2(React2.Suspense, { fallback: null, children: PreferencesModalComponent ? /* @__PURE__ */ jsx2(PreferencesModalComponent, { ...preferencesModalProps }) : /* @__PURE__ */ jsx2(PreferencesModal, { hideBranding }) })
445
+ ] }) }) }) }) }) });
282
446
  }
283
447
  function useConsentStateInternal() {
284
- const ctx = React.useContext(StateCtx);
448
+ const ctx = React2.useContext(StateCtx);
285
449
  if (!ctx)
286
450
  throw new Error("useConsentState must be used within ConsentProvider");
287
451
  return ctx;
288
452
  }
289
453
  function useConsentActionsInternal() {
290
- const ctx = React.useContext(ActionsCtx);
454
+ const ctx = React2.useContext(ActionsCtx);
291
455
  if (!ctx)
292
456
  throw new Error("useConsentActions must be used within ConsentProvider");
293
457
  return ctx;
294
458
  }
295
459
  function useConsentTextsInternal() {
296
- const ctx = React.useContext(TextsCtx);
460
+ const ctx = React2.useContext(TextsCtx);
297
461
  return ctx;
298
462
  }
299
463
  function useConsentHydrationInternal() {
300
- return React.useContext(HydrationCtx);
464
+ return React2.useContext(HydrationCtx);
301
465
  }
302
466
 
303
467
  // src/hooks/useConsent.ts
@@ -327,7 +491,7 @@ function useConsentHydration() {
327
491
  // src/components/Branding.tsx
328
492
  import Link from "@mui/material/Link";
329
493
  import Typography from "@mui/material/Typography";
330
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
494
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
331
495
  var brandingStyles = {
332
496
  banner: {
333
497
  fontSize: "0.65rem",
@@ -365,7 +529,7 @@ function Branding({ variant, hidden = false }) {
365
529
  children: [
366
530
  "fornecido por",
367
531
  " ",
368
- /* @__PURE__ */ jsx2(
532
+ /* @__PURE__ */ jsx3(
369
533
  Link,
370
534
  {
371
535
  href: "https://www.ledipo.eti.br",
@@ -384,7 +548,7 @@ function Branding({ variant, hidden = false }) {
384
548
  }
385
549
 
386
550
  // src/components/PreferencesModal.tsx
387
- import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
551
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
388
552
  function PreferencesModal2({
389
553
  DialogProps: DialogProps2,
390
554
  hideBranding = false
@@ -413,14 +577,14 @@ function PreferencesModal2({
413
577
  onClose: handleCancel,
414
578
  ...DialogProps2,
415
579
  children: [
416
- /* @__PURE__ */ jsx3(DialogTitle, { id: "cookie-pref-title", children: texts.modalTitle }),
580
+ /* @__PURE__ */ jsx4(DialogTitle, { id: "cookie-pref-title", children: texts.modalTitle }),
417
581
  /* @__PURE__ */ jsxs3(DialogContent, { dividers: true, children: [
418
- /* @__PURE__ */ jsx3(Typography2, { variant: "body2", sx: { mb: 2 }, children: texts.modalIntro }),
582
+ /* @__PURE__ */ jsx4(Typography2, { variant: "body2", sx: { mb: 2 }, children: texts.modalIntro }),
419
583
  /* @__PURE__ */ jsxs3(FormGroup, { children: [
420
- /* @__PURE__ */ jsx3(
584
+ /* @__PURE__ */ jsx4(
421
585
  FormControlLabel,
422
586
  {
423
- control: /* @__PURE__ */ jsx3(
587
+ control: /* @__PURE__ */ jsx4(
424
588
  Switch,
425
589
  {
426
590
  checked: tempPreferences.analytics,
@@ -433,10 +597,10 @@ function PreferencesModal2({
433
597
  label: "Cookies Anal\xEDticos (medem uso do site)"
434
598
  }
435
599
  ),
436
- /* @__PURE__ */ jsx3(
600
+ /* @__PURE__ */ jsx4(
437
601
  FormControlLabel,
438
602
  {
439
- control: /* @__PURE__ */ jsx3(
603
+ control: /* @__PURE__ */ jsx4(
440
604
  Switch,
441
605
  {
442
606
  checked: tempPreferences.marketing,
@@ -449,19 +613,19 @@ function PreferencesModal2({
449
613
  label: "Cookies de Marketing/Publicidade"
450
614
  }
451
615
  ),
452
- /* @__PURE__ */ jsx3(
616
+ /* @__PURE__ */ jsx4(
453
617
  FormControlLabel,
454
618
  {
455
- control: /* @__PURE__ */ jsx3(Switch, { checked: true, disabled: true }),
619
+ control: /* @__PURE__ */ jsx4(Switch, { checked: true, disabled: true }),
456
620
  label: texts.necessaryAlwaysOn
457
621
  }
458
622
  )
459
623
  ] })
460
624
  ] }),
461
- !hideBranding && /* @__PURE__ */ jsx3(Branding, { variant: "modal" }),
625
+ !hideBranding && /* @__PURE__ */ jsx4(Branding, { variant: "modal" }),
462
626
  /* @__PURE__ */ jsxs3(DialogActions, { children: [
463
- /* @__PURE__ */ jsx3(Button, { variant: "outlined", onClick: handleCancel, children: "Cancelar" }),
464
- /* @__PURE__ */ jsx3(Button, { variant: "contained", onClick: handleSave, children: texts.save })
627
+ /* @__PURE__ */ jsx4(Button, { variant: "outlined", onClick: handleCancel, children: "Cancelar" }),
628
+ /* @__PURE__ */ jsx4(Button, { variant: "contained", onClick: handleSave, children: texts.save })
465
629
  ] })
466
630
  ]
467
631
  }
@@ -470,6 +634,8 @@ function PreferencesModal2({
470
634
 
471
635
  export {
472
636
  defaultConsentTheme,
637
+ useCustomCategories,
638
+ useAllCategories,
473
639
  Branding,
474
640
  PreferencesModal2 as PreferencesModal,
475
641
  ConsentProvider,