@redacto.io/consent-sdk-react 10.2.0-beta.0 → 10.2.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.
@@ -0,0 +1,93 @@
1
+ // src/shared/idempotency.ts
2
+ var HEADER = "Idempotency-Key";
3
+ var pending = /* @__PURE__ */ new Map();
4
+ var MAX_PENDING = 16;
5
+ var mintKey = () => {
6
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
7
+ return crypto.randomUUID();
8
+ }
9
+ return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 14)}-${Math.random().toString(36).slice(2, 14)}`;
10
+ };
11
+ var statusOf = (outcome) => {
12
+ if (typeof Response !== "undefined" && outcome instanceof Response) {
13
+ return outcome.status;
14
+ }
15
+ if (outcome && typeof outcome === "object" && "status" in outcome) {
16
+ const status = outcome.status;
17
+ if (typeof status === "number") return status;
18
+ }
19
+ return void 0;
20
+ };
21
+ var settledOnResolve = (result) => {
22
+ const status = statusOf(result);
23
+ return status === void 0 || status < 500;
24
+ };
25
+ var settledOnReject = (error) => {
26
+ const status = statusOf(error);
27
+ return status !== void 0 && status < 500;
28
+ };
29
+ var withIdempotencyKey = async (url, body, send) => {
30
+ const signature = `${url}
31
+ ${body}`;
32
+ let entry = pending.get(signature);
33
+ if (!entry) {
34
+ entry = { key: mintKey(), inFlight: 0, keep: false };
35
+ if (pending.size >= MAX_PENDING) {
36
+ const oldest = pending.keys().next().value;
37
+ if (oldest !== void 0) pending.delete(oldest);
38
+ }
39
+ pending.set(signature, entry);
40
+ } else if (entry.inFlight === 0) {
41
+ entry.keep = false;
42
+ }
43
+ entry.inFlight += 1;
44
+ const settle = (settled) => {
45
+ entry.inFlight -= 1;
46
+ if (!settled) entry.keep = true;
47
+ if (entry.inFlight === 0 && !entry.keep) pending.delete(signature);
48
+ };
49
+ try {
50
+ const result = await send({ [HEADER]: entry.key });
51
+ settle(settledOnResolve(result));
52
+ return result;
53
+ } catch (error) {
54
+ settle(settledOnReject(error));
55
+ throw error;
56
+ }
57
+ };
58
+
59
+ // src/RedactoNoticeConsent/useMediaQuery.ts
60
+ import { useState, useEffect } from "react";
61
+ var useMediaQuery = (query) => {
62
+ const getMatches = () => typeof window !== "undefined" ? window.matchMedia(query).matches : false;
63
+ const [matches, setMatches] = useState(getMatches);
64
+ useEffect(() => {
65
+ if (typeof window === "undefined") return;
66
+ const media = window.matchMedia(query);
67
+ const listener = (e) => {
68
+ setMatches(e.matches);
69
+ };
70
+ if (media.addEventListener) {
71
+ media.addEventListener("change", listener);
72
+ } else {
73
+ media.addListener(listener);
74
+ }
75
+ return () => {
76
+ if (media.removeEventListener) {
77
+ media.removeEventListener("change", listener);
78
+ } else {
79
+ media.removeListener(listener);
80
+ }
81
+ };
82
+ }, [query]);
83
+ return matches;
84
+ };
85
+
86
+ // src/shared/sandbox.ts
87
+ var SANDBOX_TOKEN_HEADER = "X-Consent-Token";
88
+
89
+ export {
90
+ SANDBOX_TOKEN_HEADER,
91
+ withIdempotencyKey,
92
+ useMediaQuery
93
+ };
package/dist/index.js CHANGED
@@ -219,6 +219,64 @@ var sandboxBodyIdentity = (sandbox) => {
219
219
  };
220
220
  var sandboxQueryIdentity = (sandbox) => sandboxBodyIdentity(sandbox);
221
221
 
222
+ // src/shared/idempotency.ts
223
+ var HEADER = "Idempotency-Key";
224
+ var pending = /* @__PURE__ */ new Map();
225
+ var MAX_PENDING = 16;
226
+ var mintKey = () => {
227
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
228
+ return crypto.randomUUID();
229
+ }
230
+ return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 14)}-${Math.random().toString(36).slice(2, 14)}`;
231
+ };
232
+ var statusOf = (outcome) => {
233
+ if (typeof Response !== "undefined" && outcome instanceof Response) {
234
+ return outcome.status;
235
+ }
236
+ if (outcome && typeof outcome === "object" && "status" in outcome) {
237
+ const status = outcome.status;
238
+ if (typeof status === "number") return status;
239
+ }
240
+ return void 0;
241
+ };
242
+ var settledOnResolve = (result) => {
243
+ const status = statusOf(result);
244
+ return status === void 0 || status < 500;
245
+ };
246
+ var settledOnReject = (error) => {
247
+ const status = statusOf(error);
248
+ return status !== void 0 && status < 500;
249
+ };
250
+ var withIdempotencyKey = async (url, body, send) => {
251
+ const signature = `${url}
252
+ ${body}`;
253
+ let entry = pending.get(signature);
254
+ if (!entry) {
255
+ entry = { key: mintKey(), inFlight: 0, keep: false };
256
+ if (pending.size >= MAX_PENDING) {
257
+ const oldest = pending.keys().next().value;
258
+ if (oldest !== void 0) pending.delete(oldest);
259
+ }
260
+ pending.set(signature, entry);
261
+ } else if (entry.inFlight === 0) {
262
+ entry.keep = false;
263
+ }
264
+ entry.inFlight += 1;
265
+ const settle = (settled) => {
266
+ entry.inFlight -= 1;
267
+ if (!settled) entry.keep = true;
268
+ if (entry.inFlight === 0 && !entry.keep) pending.delete(signature);
269
+ };
270
+ try {
271
+ const result = await send({ [HEADER]: entry.key });
272
+ settle(settledOnResolve(result));
273
+ return result;
274
+ } catch (error) {
275
+ settle(settledOnReject(error));
276
+ throw error;
277
+ }
278
+ };
279
+
222
280
  // src/shared/notice-consent-status.ts
223
281
  var import_jwt_decode = require("jwt-decode");
224
282
  var decodeNoticeConsentToken = (token) => {
@@ -439,17 +497,21 @@ var submitConsentEvent = async ({
439
497
  self_declared_adult: selfDeclaredAdult
440
498
  };
441
499
  const requestBody = sandbox ? { ...payload, ...sandboxBodyIdentity(sandbox) } : payload;
442
- const response = await fetch(
443
- `${apiBaseUrl}/public/organisations/${ORGANISATION_UUID}/workspaces/${WORKSPACE_UUID}/submit-consent`,
444
- {
500
+ const url = `${apiBaseUrl}/public/organisations/${ORGANISATION_UUID}/workspaces/${WORKSPACE_UUID}/submit-consent`;
501
+ const serialisedBody = JSON.stringify(requestBody);
502
+ const response = await withIdempotencyKey(
503
+ url,
504
+ serialisedBody,
505
+ (idempotencyHeaders) => fetch(url, {
445
506
  method: "POST",
446
507
  headers: {
447
508
  ...buildAuthHeaders(accessToken, sandbox),
448
- "Content-Type": "application/json"
509
+ "Content-Type": "application/json",
510
+ ...idempotencyHeaders
449
511
  },
450
- body: JSON.stringify(requestBody),
512
+ body: serialisedBody,
451
513
  signal
452
- }
514
+ })
453
515
  );
454
516
  if (!response.ok) {
455
517
  throw await parseApiError(
@@ -912,6 +974,15 @@ var styles = {
912
974
  padding: 0,
913
975
  boxSizing: "border-box"
914
976
  },
977
+ // The purposes belonging to one product. Indented so the product chevron and
978
+ // the purpose chevrons below it do not sit on the same vertical line, which
979
+ // read as one flat list rather than a heading and its contents. The trailing
980
+ // margin separates a section from the next product heading.
981
+ sectionBody: {
982
+ paddingLeft: "18px",
983
+ margin: "0 0 14px",
984
+ boxSizing: "border-box"
985
+ },
915
986
  // No colour token: the header always overrides it with the notice's
916
987
  // headingColor, so one here would imply a default that never applies.
917
988
  sectionTitle: {
@@ -1067,10 +1138,10 @@ var styles = {
1067
1138
  color: "#ffffff"
1068
1139
  },
1069
1140
  /**
1070
- * The accept-all shortcut. Tinted rather than filled so the primary
1071
- * "accept selected" action stays visually dominant, and outlined-but-coloured
1072
- * so it still reads as an accept next to the neutral decline button.
1073
- */
1141
+ * The accept-all shortcut. Tinted rather than filled so the primary
1142
+ * "accept selected" action stays visually dominant, and outlined-but-coloured
1143
+ * so it still reads as an accept next to the neutral decline button.
1144
+ */
1074
1145
  acceptSecondaryButton: {
1075
1146
  backgroundColor: "#ffffff",
1076
1147
  border: "1px solid #4f87ff",
@@ -1478,6 +1549,7 @@ var injectCheckboxStyles = () => {
1478
1549
  cursor: pointer !important;
1479
1550
  position: relative !important;
1480
1551
  box-sizing: border-box !important;
1552
+ flex-shrink: 0 !important;
1481
1553
  margin: 0 !important;
1482
1554
  padding: 4px !important;
1483
1555
  }
@@ -1494,6 +1566,7 @@ var injectCheckboxStyles = () => {
1494
1566
  cursor: pointer !important;
1495
1567
  position: relative !important;
1496
1568
  box-sizing: border-box !important;
1569
+ flex-shrink: 0 !important;
1497
1570
  margin: 0 !important;
1498
1571
  padding: 4px !important;
1499
1572
  }
@@ -1822,9 +1895,9 @@ var toFontFaceCss = (entry, sources) => sources.map(
1822
1895
  ).join("\n\n");
1823
1896
  var fontSourceLoads = /* @__PURE__ */ new Map();
1824
1897
  var loadFontSources = (entry, importSources) => {
1825
- const pending = fontSourceLoads.get(entry.id);
1826
- if (pending) {
1827
- return pending;
1898
+ const pending2 = fontSourceLoads.get(entry.id);
1899
+ if (pending2) {
1900
+ return pending2;
1828
1901
  }
1829
1902
  const load = importSources().then((module2) => module2.NOTICE_FONT_SOURCES).catch((error) => {
1830
1903
  fontSourceLoads.delete(entry.id);
@@ -3762,7 +3835,19 @@ var RedactoNoticeConsent = ({
3762
3835
  {}
3763
3836
  );
3764
3837
  setCollapsedPurposes(initialCollapsedState);
3765
- setCollapsedProducts({});
3838
+ setCollapsedProducts(
3839
+ groupPurposesByProduct(
3840
+ activeConfig.products,
3841
+ activeConfig.purposes,
3842
+ activeConfig.product_purpose_order
3843
+ ).reduce(
3844
+ (acc, group, index) => ({
3845
+ ...acc,
3846
+ [group.product.uuid]: index > 0
3847
+ }),
3848
+ {}
3849
+ )
3850
+ );
3766
3851
  const isReconsentRequired = consentContentData.detail.reconsent_required;
3767
3852
  const purposeSelections = consentContentData.detail.purpose_selections;
3768
3853
  const newCategorizedPurposes = isReconsentRequired ? {
@@ -5646,6 +5731,7 @@ var RedactoNoticeConsent = ({
5646
5731
  "div",
5647
5732
  {
5648
5733
  id: `product-${group.product.uuid}`,
5734
+ style: styles.sectionBody,
5649
5735
  hidden: collapsedProducts[group.product.uuid] || false,
5650
5736
  children: group.purposes.map((purpose) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
5651
5737
  PurposeItem,
@@ -5936,16 +6022,20 @@ var submitConsentEvent2 = async ({
5936
6022
  declined,
5937
6023
  meta_data
5938
6024
  };
5939
- const response = await fetch(
5940
- `${apiBaseUrl}/public/organisations/${org_uuid}/workspaces/${workspace_uuid}/submit-consent`,
5941
- {
6025
+ const url = `${apiBaseUrl}/public/organisations/${org_uuid}/workspaces/${workspace_uuid}/submit-consent`;
6026
+ const serialisedBody = JSON.stringify(payload);
6027
+ const response = await withIdempotencyKey(
6028
+ url,
6029
+ serialisedBody,
6030
+ (idempotencyHeaders) => fetch(url, {
5942
6031
  method: "POST",
5943
6032
  headers: {
5944
6033
  Authorization: `Bearer ${accessToken}`,
5945
- "Content-Type": "application/json"
6034
+ "Content-Type": "application/json",
6035
+ ...idempotencyHeaders
5946
6036
  },
5947
- body: JSON.stringify(payload)
5948
- }
6037
+ body: serialisedBody
6038
+ })
5949
6039
  );
5950
6040
  if (!response.ok) {
5951
6041
  throw await parseApiError(
@@ -8115,16 +8205,22 @@ var submitAssistedConsent = async ({
8115
8205
  language: language || void 0
8116
8206
  };
8117
8207
  const url = `${publicScope(ledgerBaseUrl || baseUrl, organisationUuid, workspaceUuid)}/submit-consent`;
8118
- const response = await fetch(url, {
8119
- method: "POST",
8120
- headers: {
8121
- accept: "application/json",
8122
- Authorization: `Bearer ${accessToken}`,
8123
- "Content-Type": "application/json"
8124
- },
8125
- body: JSON.stringify(payload),
8126
- signal
8127
- });
8208
+ const serialisedBody = JSON.stringify(payload);
8209
+ const response = await withIdempotencyKey(
8210
+ url,
8211
+ serialisedBody,
8212
+ (idempotencyHeaders) => fetch(url, {
8213
+ method: "POST",
8214
+ headers: {
8215
+ accept: "application/json",
8216
+ Authorization: `Bearer ${accessToken}`,
8217
+ "Content-Type": "application/json",
8218
+ ...idempotencyHeaders
8219
+ },
8220
+ body: serialisedBody,
8221
+ signal
8222
+ })
8223
+ );
8128
8224
  if (!response.ok) {
8129
8225
  throw await parseApiError(
8130
8226
  response,
package/dist/index.mjs CHANGED
@@ -1,7 +1,8 @@
1
1
  import {
2
2
  SANDBOX_TOKEN_HEADER,
3
- useMediaQuery
4
- } from "./chunk-OGL6OC3A.mjs";
3
+ useMediaQuery,
4
+ withIdempotencyKey
5
+ } from "./chunk-6EET4YOY.mjs";
5
6
  import {
6
7
  FONT_FAMILY_SEPARATOR,
7
8
  FONT_NAME_QUOTE_CHARACTERS,
@@ -372,17 +373,21 @@ var submitConsentEvent = async ({
372
373
  self_declared_adult: selfDeclaredAdult
373
374
  };
374
375
  const requestBody = sandbox ? { ...payload, ...sandboxBodyIdentity(sandbox) } : payload;
375
- const response = await fetch(
376
- `${apiBaseUrl}/public/organisations/${ORGANISATION_UUID}/workspaces/${WORKSPACE_UUID}/submit-consent`,
377
- {
376
+ const url = `${apiBaseUrl}/public/organisations/${ORGANISATION_UUID}/workspaces/${WORKSPACE_UUID}/submit-consent`;
377
+ const serialisedBody = JSON.stringify(requestBody);
378
+ const response = await withIdempotencyKey(
379
+ url,
380
+ serialisedBody,
381
+ (idempotencyHeaders) => fetch(url, {
378
382
  method: "POST",
379
383
  headers: {
380
384
  ...buildAuthHeaders(accessToken, sandbox),
381
- "Content-Type": "application/json"
385
+ "Content-Type": "application/json",
386
+ ...idempotencyHeaders
382
387
  },
383
- body: JSON.stringify(requestBody),
388
+ body: serialisedBody,
384
389
  signal
385
- }
390
+ })
386
391
  );
387
392
  if (!response.ok) {
388
393
  throw await parseApiError(
@@ -845,6 +850,15 @@ var styles = {
845
850
  padding: 0,
846
851
  boxSizing: "border-box"
847
852
  },
853
+ // The purposes belonging to one product. Indented so the product chevron and
854
+ // the purpose chevrons below it do not sit on the same vertical line, which
855
+ // read as one flat list rather than a heading and its contents. The trailing
856
+ // margin separates a section from the next product heading.
857
+ sectionBody: {
858
+ paddingLeft: "18px",
859
+ margin: "0 0 14px",
860
+ boxSizing: "border-box"
861
+ },
848
862
  // No colour token: the header always overrides it with the notice's
849
863
  // headingColor, so one here would imply a default that never applies.
850
864
  sectionTitle: {
@@ -1000,10 +1014,10 @@ var styles = {
1000
1014
  color: "#ffffff"
1001
1015
  },
1002
1016
  /**
1003
- * The accept-all shortcut. Tinted rather than filled so the primary
1004
- * "accept selected" action stays visually dominant, and outlined-but-coloured
1005
- * so it still reads as an accept next to the neutral decline button.
1006
- */
1017
+ * The accept-all shortcut. Tinted rather than filled so the primary
1018
+ * "accept selected" action stays visually dominant, and outlined-but-coloured
1019
+ * so it still reads as an accept next to the neutral decline button.
1020
+ */
1007
1021
  acceptSecondaryButton: {
1008
1022
  backgroundColor: "#ffffff",
1009
1023
  border: "1px solid #4f87ff",
@@ -1411,6 +1425,7 @@ var injectCheckboxStyles = () => {
1411
1425
  cursor: pointer !important;
1412
1426
  position: relative !important;
1413
1427
  box-sizing: border-box !important;
1428
+ flex-shrink: 0 !important;
1414
1429
  margin: 0 !important;
1415
1430
  padding: 4px !important;
1416
1431
  }
@@ -1427,6 +1442,7 @@ var injectCheckboxStyles = () => {
1427
1442
  cursor: pointer !important;
1428
1443
  position: relative !important;
1429
1444
  box-sizing: border-box !important;
1445
+ flex-shrink: 0 !important;
1430
1446
  margin: 0 !important;
1431
1447
  padding: 4px !important;
1432
1448
  }
@@ -3494,7 +3510,19 @@ var RedactoNoticeConsent = ({
3494
3510
  {}
3495
3511
  );
3496
3512
  setCollapsedPurposes(initialCollapsedState);
3497
- setCollapsedProducts({});
3513
+ setCollapsedProducts(
3514
+ groupPurposesByProduct(
3515
+ activeConfig.products,
3516
+ activeConfig.purposes,
3517
+ activeConfig.product_purpose_order
3518
+ ).reduce(
3519
+ (acc, group, index) => ({
3520
+ ...acc,
3521
+ [group.product.uuid]: index > 0
3522
+ }),
3523
+ {}
3524
+ )
3525
+ );
3498
3526
  const isReconsentRequired = consentContentData.detail.reconsent_required;
3499
3527
  const purposeSelections = consentContentData.detail.purpose_selections;
3500
3528
  const newCategorizedPurposes = isReconsentRequired ? {
@@ -5378,6 +5406,7 @@ var RedactoNoticeConsent = ({
5378
5406
  "div",
5379
5407
  {
5380
5408
  id: `product-${group.product.uuid}`,
5409
+ style: styles.sectionBody,
5381
5410
  hidden: collapsedProducts[group.product.uuid] || false,
5382
5411
  children: group.purposes.map((purpose) => /* @__PURE__ */ jsx3(
5383
5412
  PurposeItem,
@@ -5668,16 +5697,20 @@ var submitConsentEvent2 = async ({
5668
5697
  declined,
5669
5698
  meta_data
5670
5699
  };
5671
- const response = await fetch(
5672
- `${apiBaseUrl}/public/organisations/${org_uuid}/workspaces/${workspace_uuid}/submit-consent`,
5673
- {
5700
+ const url = `${apiBaseUrl}/public/organisations/${org_uuid}/workspaces/${workspace_uuid}/submit-consent`;
5701
+ const serialisedBody = JSON.stringify(payload);
5702
+ const response = await withIdempotencyKey(
5703
+ url,
5704
+ serialisedBody,
5705
+ (idempotencyHeaders) => fetch(url, {
5674
5706
  method: "POST",
5675
5707
  headers: {
5676
5708
  Authorization: `Bearer ${accessToken}`,
5677
- "Content-Type": "application/json"
5709
+ "Content-Type": "application/json",
5710
+ ...idempotencyHeaders
5678
5711
  },
5679
- body: JSON.stringify(payload)
5680
- }
5712
+ body: serialisedBody
5713
+ })
5681
5714
  );
5682
5715
  if (!response.ok) {
5683
5716
  throw await parseApiError(
@@ -7847,16 +7880,22 @@ var submitAssistedConsent = async ({
7847
7880
  language: language || void 0
7848
7881
  };
7849
7882
  const url = `${publicScope(ledgerBaseUrl || baseUrl, organisationUuid, workspaceUuid)}/submit-consent`;
7850
- const response = await fetch(url, {
7851
- method: "POST",
7852
- headers: {
7853
- accept: "application/json",
7854
- Authorization: `Bearer ${accessToken}`,
7855
- "Content-Type": "application/json"
7856
- },
7857
- body: JSON.stringify(payload),
7858
- signal
7859
- });
7883
+ const serialisedBody = JSON.stringify(payload);
7884
+ const response = await withIdempotencyKey(
7885
+ url,
7886
+ serialisedBody,
7887
+ (idempotencyHeaders) => fetch(url, {
7888
+ method: "POST",
7889
+ headers: {
7890
+ accept: "application/json",
7891
+ Authorization: `Bearer ${accessToken}`,
7892
+ "Content-Type": "application/json",
7893
+ ...idempotencyHeaders
7894
+ },
7895
+ body: serialisedBody,
7896
+ signal
7897
+ })
7898
+ );
7860
7899
  if (!response.ok) {
7861
7900
  throw await parseApiError(
7862
7901
  response,
@@ -10032,6 +10032,64 @@ var useAuth = () => {
10032
10032
  // src/RedactoPrivacyCenter/context/BaseUrlContext.tsx
10033
10033
  var import_react4 = require("react");
10034
10034
 
10035
+ // src/shared/idempotency.ts
10036
+ var HEADER = "Idempotency-Key";
10037
+ var pending = /* @__PURE__ */ new Map();
10038
+ var MAX_PENDING = 16;
10039
+ var mintKey = () => {
10040
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
10041
+ return crypto.randomUUID();
10042
+ }
10043
+ return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 14)}-${Math.random().toString(36).slice(2, 14)}`;
10044
+ };
10045
+ var statusOf = (outcome) => {
10046
+ if (typeof Response !== "undefined" && outcome instanceof Response) {
10047
+ return outcome.status;
10048
+ }
10049
+ if (outcome && typeof outcome === "object" && "status" in outcome) {
10050
+ const status = outcome.status;
10051
+ if (typeof status === "number") return status;
10052
+ }
10053
+ return void 0;
10054
+ };
10055
+ var settledOnResolve = (result) => {
10056
+ const status = statusOf(result);
10057
+ return status === void 0 || status < 500;
10058
+ };
10059
+ var settledOnReject = (error) => {
10060
+ const status = statusOf(error);
10061
+ return status !== void 0 && status < 500;
10062
+ };
10063
+ var withIdempotencyKey = async (url, body, send) => {
10064
+ const signature = `${url}
10065
+ ${body}`;
10066
+ let entry = pending.get(signature);
10067
+ if (!entry) {
10068
+ entry = { key: mintKey(), inFlight: 0, keep: false };
10069
+ if (pending.size >= MAX_PENDING) {
10070
+ const oldest = pending.keys().next().value;
10071
+ if (oldest !== void 0) pending.delete(oldest);
10072
+ }
10073
+ pending.set(signature, entry);
10074
+ } else if (entry.inFlight === 0) {
10075
+ entry.keep = false;
10076
+ }
10077
+ entry.inFlight += 1;
10078
+ const settle = (settled) => {
10079
+ entry.inFlight -= 1;
10080
+ if (!settled) entry.keep = true;
10081
+ if (entry.inFlight === 0 && !entry.keep) pending.delete(signature);
10082
+ };
10083
+ try {
10084
+ const result = await send({ [HEADER]: entry.key });
10085
+ settle(settledOnResolve(result));
10086
+ return result;
10087
+ } catch (error) {
10088
+ settle(settledOnReject(error));
10089
+ throw error;
10090
+ }
10091
+ };
10092
+
10035
10093
  // src/RedactoPrivacyCenter/api/client.ts
10036
10094
  function createConsentClient(baseUrl, ledgerBaseUrl) {
10037
10095
  const ledgerConfigured = Boolean(ledgerBaseUrl);
@@ -10453,11 +10511,19 @@ function createConsentClient(baseUrl, ledgerBaseUrl) {
10453
10511
  ...language ? { language } : {}
10454
10512
  };
10455
10513
  const url = ledgerUrl(baseRequest, "/submit-consent");
10456
- return privacyCenterApiFetch(url, {
10457
- ...options,
10458
- method: "POST",
10459
- body: JSON.stringify(body),
10460
- authToken: resolveAuthToken(authToken, baseRequest)
10514
+ const serialisedBody = JSON.stringify(body);
10515
+ return withIdempotencyKey(url, serialisedBody, (idempotencyHeaders) => {
10516
+ const headers = new Headers(options == null ? void 0 : options.headers);
10517
+ Object.entries(idempotencyHeaders).forEach(
10518
+ ([name, value]) => headers.set(name, value)
10519
+ );
10520
+ return privacyCenterApiFetch(url, {
10521
+ ...options,
10522
+ method: "POST",
10523
+ body: serialisedBody,
10524
+ headers,
10525
+ authToken: resolveAuthToken(authToken, baseRequest)
10526
+ });
10461
10527
  });
10462
10528
  },
10463
10529
  /**
@@ -1,7 +1,8 @@
1
1
  import {
2
2
  SANDBOX_TOKEN_HEADER,
3
- useMediaQuery
4
- } from "./chunk-OGL6OC3A.mjs";
3
+ useMediaQuery,
4
+ withIdempotencyKey
5
+ } from "./chunk-6EET4YOY.mjs";
5
6
 
6
7
  // src/RedactoPrivacyCenter/RedactoPrivacyCenter.tsx
7
8
  import { I18nextProvider } from "react-i18next";
@@ -10385,11 +10386,19 @@ function createConsentClient(baseUrl, ledgerBaseUrl) {
10385
10386
  ...language ? { language } : {}
10386
10387
  };
10387
10388
  const url = ledgerUrl(baseRequest, "/submit-consent");
10388
- return privacyCenterApiFetch(url, {
10389
- ...options,
10390
- method: "POST",
10391
- body: JSON.stringify(body),
10392
- authToken: resolveAuthToken(authToken, baseRequest)
10389
+ const serialisedBody = JSON.stringify(body);
10390
+ return withIdempotencyKey(url, serialisedBody, (idempotencyHeaders) => {
10391
+ const headers = new Headers(options == null ? void 0 : options.headers);
10392
+ Object.entries(idempotencyHeaders).forEach(
10393
+ ([name, value]) => headers.set(name, value)
10394
+ );
10395
+ return privacyCenterApiFetch(url, {
10396
+ ...options,
10397
+ method: "POST",
10398
+ body: serialisedBody,
10399
+ headers,
10400
+ authToken: resolveAuthToken(authToken, baseRequest)
10401
+ });
10393
10402
  });
10394
10403
  },
10395
10404
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redacto.io/consent-sdk-react",
3
- "version": "10.2.0-beta.0",
3
+ "version": "10.2.0",
4
4
  "main": "dist/index.cjs",
5
5
  "module": "dist/index.mjs",
6
6
  "types": "dist/index.d.ts",
@@ -1,34 +0,0 @@
1
- // src/RedactoNoticeConsent/useMediaQuery.ts
2
- import { useState, useEffect } from "react";
3
- var useMediaQuery = (query) => {
4
- const getMatches = () => typeof window !== "undefined" ? window.matchMedia(query).matches : false;
5
- const [matches, setMatches] = useState(getMatches);
6
- useEffect(() => {
7
- if (typeof window === "undefined") return;
8
- const media = window.matchMedia(query);
9
- const listener = (e) => {
10
- setMatches(e.matches);
11
- };
12
- if (media.addEventListener) {
13
- media.addEventListener("change", listener);
14
- } else {
15
- media.addListener(listener);
16
- }
17
- return () => {
18
- if (media.removeEventListener) {
19
- media.removeEventListener("change", listener);
20
- } else {
21
- media.removeListener(listener);
22
- }
23
- };
24
- }, [query]);
25
- return matches;
26
- };
27
-
28
- // src/shared/sandbox.ts
29
- var SANDBOX_TOKEN_HEADER = "X-Consent-Token";
30
-
31
- export {
32
- SANDBOX_TOKEN_HEADER,
33
- useMediaQuery
34
- };