@react-lgpd-consent/core 0.6.1 → 0.6.2
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 +17 -0
- package/dist/index.cjs +36 -17
- package/dist/index.d.cts +10 -4
- package/dist/index.d.ts +10 -4
- package/dist/index.js +36 -17
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog - @react-lgpd-consent/core
|
|
2
2
|
|
|
3
|
+
## 0.6.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#113](https://github.com/lucianoedipo/react-lgpd-consent/pull/113) [`b51e1f8`](https://github.com/lucianoedipo/react-lgpd-consent/commit/b51e1f8dcac35907e3a30471135da14160c95213) Thanks [@lucianoedipo](https://github.com/lucianoedipo)! - feat: Compatibilidade completa com React 19 StrictMode
|
|
8
|
+
- Implementado registro global `LOADING_SCRIPTS` em `scriptLoader.ts` para prevenir injeções duplicadas de scripts durante double-invoking de efeitos
|
|
9
|
+
- Adicionado `setTimeout` com cleanup adequado em `ConsentScriptLoader.tsx` para prevenir race conditions
|
|
10
|
+
- Scripts agora carregam apenas uma vez mesmo em desenvolvimento com StrictMode ativo
|
|
11
|
+
- Função `loadScript` é idempotente: múltiplas chamadas simultâneas retornam a mesma Promise
|
|
12
|
+
- **Correção crítica**: `loadScript` agora aguarda dinamicamente o consentimento em vez de rejeitar imediatamente, permitindo que scripts carreguem quando preferências mudarem
|
|
13
|
+
- Cleanup automático do registro ao completar/falhar carregamento
|
|
14
|
+
- Adicionados testes extensivos: `ConsentScriptLoader.strictmode.test.tsx` e `scriptLoader.strictmode.test.ts`
|
|
15
|
+
- Documentação completa em `docs/REACT19-STRICTMODE.md`
|
|
16
|
+
- Todos os 302 testes passando, incluindo 5 novos testes de StrictMode
|
|
17
|
+
|
|
18
|
+
**Breaking Changes:** Nenhuma - totalmente retrocompatível
|
|
19
|
+
|
|
3
20
|
## 0.6.1
|
|
4
21
|
|
|
5
22
|
### Patch Changes
|
package/dist/index.cjs
CHANGED
|
@@ -436,7 +436,7 @@ function removeConsentCookie(opts) {
|
|
|
436
436
|
}
|
|
437
437
|
|
|
438
438
|
// src/utils/dataLayerEvents.ts
|
|
439
|
-
var LIBRARY_VERSION = "0.6.
|
|
439
|
+
var LIBRARY_VERSION = "0.6.2";
|
|
440
440
|
function ensureDataLayer() {
|
|
441
441
|
if (typeof window === "undefined") return;
|
|
442
442
|
if (!window.dataLayer) {
|
|
@@ -2003,10 +2003,13 @@ function ConsentGate(props) {
|
|
|
2003
2003
|
}
|
|
2004
2004
|
|
|
2005
2005
|
// src/utils/scriptLoader.ts
|
|
2006
|
+
var LOADING_SCRIPTS = /* @__PURE__ */ new Map();
|
|
2006
2007
|
function loadScript(id, src, category = null, attrs = {}) {
|
|
2007
2008
|
if (typeof document === "undefined") return Promise.resolve();
|
|
2008
2009
|
if (document.getElementById(id)) return Promise.resolve();
|
|
2009
|
-
|
|
2010
|
+
const existingPromise = LOADING_SCRIPTS.get(id);
|
|
2011
|
+
if (existingPromise) return existingPromise;
|
|
2012
|
+
const promise = new Promise((resolve, reject) => {
|
|
2010
2013
|
const checkConsent = () => {
|
|
2011
2014
|
const consentCookie = document.cookie.split("; ").find((row) => row.startsWith("cookieConsent="))?.split("=")[1];
|
|
2012
2015
|
if (!consentCookie) {
|
|
@@ -2020,7 +2023,7 @@ function loadScript(id, src, category = null, attrs = {}) {
|
|
|
2020
2023
|
return;
|
|
2021
2024
|
}
|
|
2022
2025
|
if (category && !consent.preferences[category]) {
|
|
2023
|
-
|
|
2026
|
+
setTimeout(checkConsent, 100);
|
|
2024
2027
|
return;
|
|
2025
2028
|
}
|
|
2026
2029
|
const s = document.createElement("script");
|
|
@@ -2028,8 +2031,14 @@ function loadScript(id, src, category = null, attrs = {}) {
|
|
|
2028
2031
|
s.src = src;
|
|
2029
2032
|
s.async = true;
|
|
2030
2033
|
for (const [k, v] of Object.entries(attrs)) s.setAttribute(k, v);
|
|
2031
|
-
s.onload = () =>
|
|
2032
|
-
|
|
2034
|
+
s.onload = () => {
|
|
2035
|
+
LOADING_SCRIPTS.delete(id);
|
|
2036
|
+
resolve();
|
|
2037
|
+
};
|
|
2038
|
+
s.onerror = () => {
|
|
2039
|
+
LOADING_SCRIPTS.delete(id);
|
|
2040
|
+
reject(new Error(`Failed to load script: ${src}`));
|
|
2041
|
+
};
|
|
2033
2042
|
document.body.appendChild(s);
|
|
2034
2043
|
} catch {
|
|
2035
2044
|
setTimeout(checkConsent, 100);
|
|
@@ -2037,6 +2046,8 @@ function loadScript(id, src, category = null, attrs = {}) {
|
|
|
2037
2046
|
};
|
|
2038
2047
|
checkConsent();
|
|
2039
2048
|
});
|
|
2049
|
+
LOADING_SCRIPTS.set(id, promise);
|
|
2050
|
+
return promise;
|
|
2040
2051
|
}
|
|
2041
2052
|
|
|
2042
2053
|
// src/utils/autoConfigureCategories.ts
|
|
@@ -2264,21 +2275,29 @@ function ConsentScriptLoader({
|
|
|
2264
2275
|
}, [integrations, categories]);
|
|
2265
2276
|
React4__namespace.useEffect(() => {
|
|
2266
2277
|
if (!consented) return;
|
|
2267
|
-
|
|
2268
|
-
const
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2278
|
+
const timeoutId = setTimeout(async () => {
|
|
2279
|
+
for (const integration of integrations) {
|
|
2280
|
+
const shouldLoad = preferences[integration.category];
|
|
2281
|
+
const alreadyLoaded = loadedScripts.current.has(integration.id);
|
|
2282
|
+
if (shouldLoad && (!alreadyLoaded || reloadOnChange)) {
|
|
2283
|
+
try {
|
|
2284
|
+
await loadScript(
|
|
2285
|
+
integration.id,
|
|
2286
|
+
integration.src,
|
|
2287
|
+
integration.category,
|
|
2288
|
+
integration.attrs
|
|
2289
|
+
);
|
|
2290
|
+
if (integration.init) {
|
|
2291
|
+
integration.init();
|
|
2292
|
+
}
|
|
2293
|
+
loadedScripts.current.add(integration.id);
|
|
2294
|
+
} catch (error) {
|
|
2295
|
+
logger.error(`\u274C Failed to load script: ${integration.id}`, error);
|
|
2275
2296
|
}
|
|
2276
|
-
loadedScripts.current.add(integration.id);
|
|
2277
|
-
} catch (error) {
|
|
2278
|
-
logger.error(`\u274C Failed to load script: ${integration.id}`, error);
|
|
2279
2297
|
}
|
|
2280
2298
|
}
|
|
2281
|
-
});
|
|
2299
|
+
}, 0);
|
|
2300
|
+
return () => clearTimeout(timeoutId);
|
|
2282
2301
|
}, [preferences, consented, integrations, reloadOnChange]);
|
|
2283
2302
|
return null;
|
|
2284
2303
|
}
|
package/dist/index.d.cts
CHANGED
|
@@ -784,7 +784,8 @@ type BackdropConfig = boolean | string | {
|
|
|
784
784
|
*
|
|
785
785
|
* @category Types
|
|
786
786
|
* @since 0.1.3
|
|
787
|
-
* @
|
|
787
|
+
* @remarks
|
|
788
|
+
* **Histórico**: v0.4.1 - Expandido substancialmente com novos tokens
|
|
788
789
|
* @public
|
|
789
790
|
*
|
|
790
791
|
* @example Configuração básica
|
|
@@ -1475,8 +1476,8 @@ interface ConsentContextValue {
|
|
|
1475
1476
|
* @param cat - ID da categoria (predefinida ou customizada)
|
|
1476
1477
|
* @param value - Valor do consentimento para a categoria
|
|
1477
1478
|
*
|
|
1478
|
-
* @
|
|
1479
|
-
* **v0.4.1**: Parâmetro `cat` mudou de `Category` para `string` para suportar
|
|
1479
|
+
* @remarks
|
|
1480
|
+
* **Breaking Change (v0.4.1)**: Parâmetro `cat` mudou de `Category` para `string` para suportar
|
|
1480
1481
|
* categorias customizadas. O uso com strings literais continua funcionando.
|
|
1481
1482
|
*
|
|
1482
1483
|
* @example
|
|
@@ -2252,6 +2253,7 @@ declare function useCategoryStatus(categoryId: string): {
|
|
|
2252
2253
|
* }
|
|
2253
2254
|
* ```
|
|
2254
2255
|
*
|
|
2256
|
+
*
|
|
2255
2257
|
* @component
|
|
2256
2258
|
* @category Context
|
|
2257
2259
|
* @since 0.1.0
|
|
@@ -2361,6 +2363,9 @@ declare function buildConsentStorageKey(options?: {
|
|
|
2361
2363
|
* após o consentimento do usuário. Ela garante que o script só seja inserido na página
|
|
2362
2364
|
* se o consentimento for dado e o contexto estiver disponível.
|
|
2363
2365
|
*
|
|
2366
|
+
* **React 19 StrictMode**: A função é idempotente e mantém um registro global de scripts
|
|
2367
|
+
* em carregamento para evitar duplicações durante double-invoking de efeitos em desenvolvimento.
|
|
2368
|
+
*
|
|
2364
2369
|
* @param {string} id Um identificador único para o elemento `<script>` a ser criado.
|
|
2365
2370
|
* @param {string} src A URL do script externo a ser carregado.
|
|
2366
2371
|
* @param {string | null} [category=null] A categoria de consentimento exigida para o script. Suporta tanto categorias predefinidas quanto customizadas. Se o consentimento para esta categoria não for dado, o script não será carregado.
|
|
@@ -3321,7 +3326,8 @@ type LanguageTexts = Partial<Omit<ConsentTexts, 'i18n' | 'variants' | 'contexts'
|
|
|
3321
3326
|
*
|
|
3322
3327
|
* @category Types
|
|
3323
3328
|
* @since 0.4.1
|
|
3324
|
-
* @
|
|
3329
|
+
* @remarks
|
|
3330
|
+
* **Histórico**: v0.4.1 - Nova interface com suporte avançado a i18n e contextos
|
|
3325
3331
|
*
|
|
3326
3332
|
* @example Configuração multilíngue
|
|
3327
3333
|
* ```typescript
|
package/dist/index.d.ts
CHANGED
|
@@ -784,7 +784,8 @@ type BackdropConfig = boolean | string | {
|
|
|
784
784
|
*
|
|
785
785
|
* @category Types
|
|
786
786
|
* @since 0.1.3
|
|
787
|
-
* @
|
|
787
|
+
* @remarks
|
|
788
|
+
* **Histórico**: v0.4.1 - Expandido substancialmente com novos tokens
|
|
788
789
|
* @public
|
|
789
790
|
*
|
|
790
791
|
* @example Configuração básica
|
|
@@ -1475,8 +1476,8 @@ interface ConsentContextValue {
|
|
|
1475
1476
|
* @param cat - ID da categoria (predefinida ou customizada)
|
|
1476
1477
|
* @param value - Valor do consentimento para a categoria
|
|
1477
1478
|
*
|
|
1478
|
-
* @
|
|
1479
|
-
* **v0.4.1**: Parâmetro `cat` mudou de `Category` para `string` para suportar
|
|
1479
|
+
* @remarks
|
|
1480
|
+
* **Breaking Change (v0.4.1)**: Parâmetro `cat` mudou de `Category` para `string` para suportar
|
|
1480
1481
|
* categorias customizadas. O uso com strings literais continua funcionando.
|
|
1481
1482
|
*
|
|
1482
1483
|
* @example
|
|
@@ -2252,6 +2253,7 @@ declare function useCategoryStatus(categoryId: string): {
|
|
|
2252
2253
|
* }
|
|
2253
2254
|
* ```
|
|
2254
2255
|
*
|
|
2256
|
+
*
|
|
2255
2257
|
* @component
|
|
2256
2258
|
* @category Context
|
|
2257
2259
|
* @since 0.1.0
|
|
@@ -2361,6 +2363,9 @@ declare function buildConsentStorageKey(options?: {
|
|
|
2361
2363
|
* após o consentimento do usuário. Ela garante que o script só seja inserido na página
|
|
2362
2364
|
* se o consentimento for dado e o contexto estiver disponível.
|
|
2363
2365
|
*
|
|
2366
|
+
* **React 19 StrictMode**: A função é idempotente e mantém um registro global de scripts
|
|
2367
|
+
* em carregamento para evitar duplicações durante double-invoking de efeitos em desenvolvimento.
|
|
2368
|
+
*
|
|
2364
2369
|
* @param {string} id Um identificador único para o elemento `<script>` a ser criado.
|
|
2365
2370
|
* @param {string} src A URL do script externo a ser carregado.
|
|
2366
2371
|
* @param {string | null} [category=null] A categoria de consentimento exigida para o script. Suporta tanto categorias predefinidas quanto customizadas. Se o consentimento para esta categoria não for dado, o script não será carregado.
|
|
@@ -3321,7 +3326,8 @@ type LanguageTexts = Partial<Omit<ConsentTexts, 'i18n' | 'variants' | 'contexts'
|
|
|
3321
3326
|
*
|
|
3322
3327
|
* @category Types
|
|
3323
3328
|
* @since 0.4.1
|
|
3324
|
-
* @
|
|
3329
|
+
* @remarks
|
|
3330
|
+
* **Histórico**: v0.4.1 - Nova interface com suporte avançado a i18n e contextos
|
|
3325
3331
|
*
|
|
3326
3332
|
* @example Configuração multilíngue
|
|
3327
3333
|
* ```typescript
|
package/dist/index.js
CHANGED
|
@@ -412,7 +412,7 @@ function removeConsentCookie(opts) {
|
|
|
412
412
|
}
|
|
413
413
|
|
|
414
414
|
// src/utils/dataLayerEvents.ts
|
|
415
|
-
var LIBRARY_VERSION = "0.6.
|
|
415
|
+
var LIBRARY_VERSION = "0.6.2";
|
|
416
416
|
function ensureDataLayer() {
|
|
417
417
|
if (typeof window === "undefined") return;
|
|
418
418
|
if (!window.dataLayer) {
|
|
@@ -1979,10 +1979,13 @@ function ConsentGate(props) {
|
|
|
1979
1979
|
}
|
|
1980
1980
|
|
|
1981
1981
|
// src/utils/scriptLoader.ts
|
|
1982
|
+
var LOADING_SCRIPTS = /* @__PURE__ */ new Map();
|
|
1982
1983
|
function loadScript(id, src, category = null, attrs = {}) {
|
|
1983
1984
|
if (typeof document === "undefined") return Promise.resolve();
|
|
1984
1985
|
if (document.getElementById(id)) return Promise.resolve();
|
|
1985
|
-
|
|
1986
|
+
const existingPromise = LOADING_SCRIPTS.get(id);
|
|
1987
|
+
if (existingPromise) return existingPromise;
|
|
1988
|
+
const promise = new Promise((resolve, reject) => {
|
|
1986
1989
|
const checkConsent = () => {
|
|
1987
1990
|
const consentCookie = document.cookie.split("; ").find((row) => row.startsWith("cookieConsent="))?.split("=")[1];
|
|
1988
1991
|
if (!consentCookie) {
|
|
@@ -1996,7 +1999,7 @@ function loadScript(id, src, category = null, attrs = {}) {
|
|
|
1996
1999
|
return;
|
|
1997
2000
|
}
|
|
1998
2001
|
if (category && !consent.preferences[category]) {
|
|
1999
|
-
|
|
2002
|
+
setTimeout(checkConsent, 100);
|
|
2000
2003
|
return;
|
|
2001
2004
|
}
|
|
2002
2005
|
const s = document.createElement("script");
|
|
@@ -2004,8 +2007,14 @@ function loadScript(id, src, category = null, attrs = {}) {
|
|
|
2004
2007
|
s.src = src;
|
|
2005
2008
|
s.async = true;
|
|
2006
2009
|
for (const [k, v] of Object.entries(attrs)) s.setAttribute(k, v);
|
|
2007
|
-
s.onload = () =>
|
|
2008
|
-
|
|
2010
|
+
s.onload = () => {
|
|
2011
|
+
LOADING_SCRIPTS.delete(id);
|
|
2012
|
+
resolve();
|
|
2013
|
+
};
|
|
2014
|
+
s.onerror = () => {
|
|
2015
|
+
LOADING_SCRIPTS.delete(id);
|
|
2016
|
+
reject(new Error(`Failed to load script: ${src}`));
|
|
2017
|
+
};
|
|
2009
2018
|
document.body.appendChild(s);
|
|
2010
2019
|
} catch {
|
|
2011
2020
|
setTimeout(checkConsent, 100);
|
|
@@ -2013,6 +2022,8 @@ function loadScript(id, src, category = null, attrs = {}) {
|
|
|
2013
2022
|
};
|
|
2014
2023
|
checkConsent();
|
|
2015
2024
|
});
|
|
2025
|
+
LOADING_SCRIPTS.set(id, promise);
|
|
2026
|
+
return promise;
|
|
2016
2027
|
}
|
|
2017
2028
|
|
|
2018
2029
|
// src/utils/autoConfigureCategories.ts
|
|
@@ -2240,21 +2251,29 @@ function ConsentScriptLoader({
|
|
|
2240
2251
|
}, [integrations, categories]);
|
|
2241
2252
|
React4.useEffect(() => {
|
|
2242
2253
|
if (!consented) return;
|
|
2243
|
-
|
|
2244
|
-
const
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2254
|
+
const timeoutId = setTimeout(async () => {
|
|
2255
|
+
for (const integration of integrations) {
|
|
2256
|
+
const shouldLoad = preferences[integration.category];
|
|
2257
|
+
const alreadyLoaded = loadedScripts.current.has(integration.id);
|
|
2258
|
+
if (shouldLoad && (!alreadyLoaded || reloadOnChange)) {
|
|
2259
|
+
try {
|
|
2260
|
+
await loadScript(
|
|
2261
|
+
integration.id,
|
|
2262
|
+
integration.src,
|
|
2263
|
+
integration.category,
|
|
2264
|
+
integration.attrs
|
|
2265
|
+
);
|
|
2266
|
+
if (integration.init) {
|
|
2267
|
+
integration.init();
|
|
2268
|
+
}
|
|
2269
|
+
loadedScripts.current.add(integration.id);
|
|
2270
|
+
} catch (error) {
|
|
2271
|
+
logger.error(`\u274C Failed to load script: ${integration.id}`, error);
|
|
2251
2272
|
}
|
|
2252
|
-
loadedScripts.current.add(integration.id);
|
|
2253
|
-
} catch (error) {
|
|
2254
|
-
logger.error(`\u274C Failed to load script: ${integration.id}`, error);
|
|
2255
2273
|
}
|
|
2256
2274
|
}
|
|
2257
|
-
});
|
|
2275
|
+
}, 0);
|
|
2276
|
+
return () => clearTimeout(timeoutId);
|
|
2258
2277
|
}, [preferences, consented, integrations, reloadOnChange]);
|
|
2259
2278
|
return null;
|
|
2260
2279
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@react-lgpd-consent/core",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"description": "Núcleo da biblioteca de consentimento LGPD para React - Estado, hooks e utilitários sem dependências de UI",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"lgpd",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
55
|
"js-cookie": "^3.0.5",
|
|
56
|
-
"zod": "^4.1.
|
|
56
|
+
"zod": "^4.1.13"
|
|
57
57
|
},
|
|
58
58
|
"repository": {
|
|
59
59
|
"type": "git",
|