@ecomconsult/consentkit 0.5.7 → 0.5.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/README.md +43 -1
- package/npm/core.cjs +3 -0
- package/npm/core.mjs +4 -0
- package/npm/index.cjs +3 -0
- package/npm/index.d.ts +67 -1
- package/npm/index.mjs +4 -0
- package/npm/internal-stub.mjs +11 -1
- package/npm/react.mjs +10 -1
- package/package.json +3 -2
- package/src/ck-core.js +384 -12
- package/src/ck-debug.js +273 -3
- package/src/ck-locales.js +6 -0
- package/src/ck-saas.js +33 -0
- package/src/ck-ui.js +545 -42
package/src/ck-debug.js
CHANGED
|
@@ -108,6 +108,92 @@
|
|
|
108
108
|
return out;
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
+
// report `why` -> STRINGS key. A map rather than a switch so the report stays
|
|
112
|
+
// language-neutral: the JSON carries 'early', the panel renders the sentence.
|
|
113
|
+
var WHY_KEY = {
|
|
114
|
+
early: 'whyEarly',
|
|
115
|
+
gcm: 'whyGcm',
|
|
116
|
+
held: 'whyHeld',
|
|
117
|
+
dead: 'whyDead'
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
/* SPEC V1.12 §3 — «каждая строка "до согласия" получает пометку словами».
|
|
121
|
+
|
|
122
|
+
A request that left before the decision is not automatically a problem, and
|
|
123
|
+
the four cases the owner needs told apart are:
|
|
124
|
+
|
|
125
|
+
'held' — the engine caught it: this is the banner working. The host
|
|
126
|
+
appears in `blocked`, so the request the browser recorded is
|
|
127
|
+
the interception, not a leak.
|
|
128
|
+
'dead' — caught, the category was later granted, and it still never came
|
|
129
|
+
back: the tag is almost always missing type="text/plain".
|
|
130
|
+
'gcm' — Consent Mode was already denied when it fired, so the vendor
|
|
131
|
+
was told not to write cookies. Google's own tags do this.
|
|
132
|
+
'early' — none of the above: the tag ran BEFORE the banner line in the
|
|
133
|
+
markup, so nothing could have held it. This is the one that
|
|
134
|
+
needs a fix on the site, and it is the one this note exists for.
|
|
135
|
+
|
|
136
|
+
Additive: every existing field of a request row is untouched, so a caller
|
|
137
|
+
reading the JSON report keeps working and `why` is simply new. Rows AFTER
|
|
138
|
+
consent get no note — there is nothing to explain about a request the
|
|
139
|
+
visitor agreed to. */
|
|
140
|
+
function explainRequests(requests, blocked, consentMode) {
|
|
141
|
+
// Consent Mode was told 'denied' at page load: our own gcmDefault() pushes
|
|
142
|
+
// exactly that before any tag can run.
|
|
143
|
+
var gcmDenied = false;
|
|
144
|
+
try {
|
|
145
|
+
for (var m = 0; m < consentMode.length; m++) {
|
|
146
|
+
var c = consentMode[m];
|
|
147
|
+
// noteDataLayer() records the gtag call verbatim: 'gtag consent default'.
|
|
148
|
+
if (!c || String(c.type || '').indexOf('consent default') === -1 || !c.signals) { continue; }
|
|
149
|
+
var sig = c.signals;
|
|
150
|
+
gcmDenied = sig.analytics_storage === 'denied' || sig.ad_storage === 'denied';
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
} catch (e) { gcmDenied = false; }
|
|
154
|
+
|
|
155
|
+
// host -> the interception record, so a request can be matched to what the
|
|
156
|
+
// engine did with that host.
|
|
157
|
+
var held = {};
|
|
158
|
+
try {
|
|
159
|
+
for (var b = 0; b < blocked.length; b++) {
|
|
160
|
+
var rec = blocked[b];
|
|
161
|
+
if (!rec || !rec.host) { continue; }
|
|
162
|
+
// A host held more than once keeps its WORST outcome: one dead tag on a
|
|
163
|
+
// host is the fact worth surfacing.
|
|
164
|
+
if (held[rec.host] && held[rec.host].revived === false) { continue; }
|
|
165
|
+
held[rec.host] = rec;
|
|
166
|
+
}
|
|
167
|
+
} catch (e2) { /* noop */ }
|
|
168
|
+
|
|
169
|
+
var out = [];
|
|
170
|
+
for (var i = 0; i < requests.length; i++) {
|
|
171
|
+
var q = requests[i];
|
|
172
|
+
if (q.when !== 'before') { out.push(q); continue; }
|
|
173
|
+
/* A `necessary` host is never held — allowed('necessary') is always true —
|
|
174
|
+
so it appears in no `blocked` record, and every note below would be a
|
|
175
|
+
lie about it: «раньше строки баннера» (we never wanted to hold it) or
|
|
176
|
+
«Consent Mode: без cookie» (it is not a Consent Mode decision). §4 files
|
|
177
|
+
necessary under `ok`, and §3's four notes have no slot for it, so it
|
|
178
|
+
gets what an after-consent row gets: nothing. Sending the owner off to
|
|
179
|
+
chase __cf_bm is exactly the noise §3 exists to remove. */
|
|
180
|
+
if (q.category === 'necessary') { out.push(q); continue; }
|
|
181
|
+
var h = held[q.host];
|
|
182
|
+
var why;
|
|
183
|
+
if (h && h.revived === false) { why = 'dead'; }
|
|
184
|
+
else if (h) { why = 'held'; }
|
|
185
|
+
else if (gcmDenied) { why = 'gcm'; }
|
|
186
|
+
else { why = 'early'; }
|
|
187
|
+
var copy = {};
|
|
188
|
+
for (var k in q) {
|
|
189
|
+
if (Object.prototype.hasOwnProperty.call(q, k)) { copy[k] = q[k]; }
|
|
190
|
+
}
|
|
191
|
+
copy.why = why;
|
|
192
|
+
out.push(copy);
|
|
193
|
+
}
|
|
194
|
+
return out;
|
|
195
|
+
}
|
|
196
|
+
|
|
111
197
|
function buildReport(input) {
|
|
112
198
|
var d = input || {};
|
|
113
199
|
var st = d.state || {};
|
|
@@ -148,7 +234,11 @@
|
|
|
148
234
|
revived: b.revived !== false
|
|
149
235
|
};
|
|
150
236
|
}),
|
|
151
|
-
requests:
|
|
237
|
+
requests: explainRequests(
|
|
238
|
+
buildRequests(d.entries, d.consentAtMs, d.classify),
|
|
239
|
+
d.blocked || [],
|
|
240
|
+
d.consentMode || []
|
|
241
|
+
),
|
|
152
242
|
consentMode: (d.consentMode || []).slice(),
|
|
153
243
|
// Names only — a consent debug panel must never leak cookie contents.
|
|
154
244
|
cookieNames: (d.cookieNames || []).slice(),
|
|
@@ -161,6 +251,8 @@
|
|
|
161
251
|
'такие теги размечают вручную.';
|
|
162
252
|
var NOTE_EN = 'Requests that left before ConsentKit loaded (a plain <script src> ' +
|
|
163
253
|
'written into the HTML) show up here but cannot be blocked — mark such tags up manually.';
|
|
254
|
+
var NOTE_RO = 'Cererile plecate înainte de încărcarea ConsentKit (un <script src> ' +
|
|
255
|
+
'obișnuit scris în HTML) apar aici, dar nu pot fi blocate — astfel de etichete se marchează manual.';
|
|
164
256
|
|
|
165
257
|
// ---------------------------------------------------------------------------
|
|
166
258
|
// Panel language (pure; the JSON report stays language-neutral either way)
|
|
@@ -191,6 +283,13 @@
|
|
|
191
283
|
markup: ' (разметка)',
|
|
192
284
|
strict: 'strict',
|
|
193
285
|
notRevived: ' — не ожил после согласия',
|
|
286
|
+
// SPEC V1.12 §3 — пометка словами на каждой строке «до согласия»:
|
|
287
|
+
// что именно случилось и можно ли было это остановить.
|
|
288
|
+
whyEarly: 'раньше строки баннера — задержать не можем',
|
|
289
|
+
whyGcm: 'Consent Mode: без cookie',
|
|
290
|
+
whyHeld: 'задержан баннером',
|
|
291
|
+
whyDead: 'не ожил после согласия — проверьте, что тег помечен type="text/plain"',
|
|
292
|
+
cabinet: 'Что с этим делать — в кабинете',
|
|
194
293
|
secRequests: 'Запросы к трекерам',
|
|
195
294
|
noRequests: 'запросов к известным трекерам не было',
|
|
196
295
|
after: 'после согласия',
|
|
@@ -252,6 +351,11 @@
|
|
|
252
351
|
markup: ' (markup)',
|
|
253
352
|
strict: 'strict',
|
|
254
353
|
notRevived: ' — did not come back after consent',
|
|
354
|
+
whyEarly: 'loaded before the banner line — we cannot hold it',
|
|
355
|
+
whyGcm: 'Consent Mode: no cookies',
|
|
356
|
+
whyHeld: 'held back by the banner',
|
|
357
|
+
whyDead: 'did not come back after consent — check the tag is marked type="text/plain"',
|
|
358
|
+
cabinet: 'What to do about it — in your account',
|
|
255
359
|
secRequests: 'Tracker requests',
|
|
256
360
|
noRequests: 'no requests to known trackers',
|
|
257
361
|
after: 'after consent',
|
|
@@ -291,6 +395,77 @@
|
|
|
291
395
|
copied: 'Copied',
|
|
292
396
|
copyFailed: 'Failed',
|
|
293
397
|
footer: 'This panel is visible in this browser only. To turn it off, add ?ck_debug=0 to the URL.'
|
|
398
|
+
},
|
|
399
|
+
/* SPEC V1.12 §3 asks for the «до согласия» notes in ru/ro/en, so ro joins
|
|
400
|
+
the panel here. It was deliberately absent before — the comment above
|
|
401
|
+
still holds for every OTHER language: this is an internal diagnostic
|
|
402
|
+
surface and a half-translated one is worse than an English one. ro is
|
|
403
|
+
whole. */
|
|
404
|
+
ro: {
|
|
405
|
+
regionLabel: 'ConsentKit — mod de depanare',
|
|
406
|
+
collapse: 'Restrânge',
|
|
407
|
+
expand: 'Extinde',
|
|
408
|
+
closeLabel: 'Închide și oprește modul de depanare',
|
|
409
|
+
secClient: 'Client',
|
|
410
|
+
version: 'versiune',
|
|
411
|
+
source: 'sursă',
|
|
412
|
+
srcSaas: 'SaaS',
|
|
413
|
+
srcInline: 'inline',
|
|
414
|
+
secConsent: 'Consimțământ',
|
|
415
|
+
decidedAt: 'decizie',
|
|
416
|
+
method: 'mod',
|
|
417
|
+
ttl: 'durata cookie-ului',
|
|
418
|
+
days: ' zile',
|
|
419
|
+
status: { none: 'fără decizie', accepted: 'acceptat', rejected: 'respins', partial: 'parțial' },
|
|
420
|
+
secBlocked: 'Blocat până la consimțământ',
|
|
421
|
+
noBlocked: 'nimic interceptat',
|
|
422
|
+
markup: ' (marcaj)',
|
|
423
|
+
strict: 'strict',
|
|
424
|
+
notRevived: ' — nu a repornit după consimțământ',
|
|
425
|
+
whyEarly: 'încărcat înaintea liniei bannerului — nu îl putem opri',
|
|
426
|
+
whyGcm: 'Consent Mode: fără cookie-uri',
|
|
427
|
+
whyHeld: 'reținut de banner',
|
|
428
|
+
whyDead: 'nu a repornit după consimțământ — verificați că eticheta are type="text/plain"',
|
|
429
|
+
cabinet: 'Ce este de făcut — în contul dumneavoastră',
|
|
430
|
+
secRequests: 'Cereri către urmăritori',
|
|
431
|
+
noRequests: 'nu au fost cereri către urmăritori cunoscuți',
|
|
432
|
+
after: 'după consimțământ',
|
|
433
|
+
before: 'înainte de consimțământ',
|
|
434
|
+
ms: ' ms',
|
|
435
|
+
note: NOTE_RO,
|
|
436
|
+
secConsentMode: 'Consent Mode / dataLayer',
|
|
437
|
+
noEvents: 'niciun eveniment',
|
|
438
|
+
secTheme: 'Aspect',
|
|
439
|
+
themeMode: 'temă',
|
|
440
|
+
themeModeLight: 'deschisă',
|
|
441
|
+
themeModeDark: 'închisă',
|
|
442
|
+
themeFont: 'Font',
|
|
443
|
+
themeFontInherit: 'moștenit',
|
|
444
|
+
themeFontSystem: 'de sistem',
|
|
445
|
+
themeFontPage: 'din pagină',
|
|
446
|
+
themeFontTry: 'încercarea',
|
|
447
|
+
themeRadius: 'colțuri',
|
|
448
|
+
themeCard: 'card',
|
|
449
|
+
themeBtn: 'butoane',
|
|
450
|
+
themeLink: 'Linkuri',
|
|
451
|
+
btnAccept: 'Acceptă tot',
|
|
452
|
+
btnReject: 'Respinge tot',
|
|
453
|
+
btnSettings: 'Personalizează',
|
|
454
|
+
btnFilled: 'plin',
|
|
455
|
+
btnOutline: 'contur',
|
|
456
|
+
btnText: 'text',
|
|
457
|
+
btnBorder: 'contur',
|
|
458
|
+
btnOn: 'pe',
|
|
459
|
+
btnAdjusted: 'corectat automat',
|
|
460
|
+
btnOk: 'AA',
|
|
461
|
+
btnFail: 'sub AA',
|
|
462
|
+
secActions: 'Acțiuni',
|
|
463
|
+
reset: 'Resetează consimțământul',
|
|
464
|
+
showPrefs: 'Arată setările',
|
|
465
|
+
copy: 'Copiază raportul',
|
|
466
|
+
copied: 'Copiat',
|
|
467
|
+
copyFailed: 'Nu a mers',
|
|
468
|
+
footer: 'Acest panou este vizibil doar în acest browser. Pentru a-l opri, adăugați ?ck_debug=0 la adresă.'
|
|
294
469
|
}
|
|
295
470
|
};
|
|
296
471
|
|
|
@@ -299,7 +474,11 @@
|
|
|
299
474
|
function pickLang(cfgLang, navLang) {
|
|
300
475
|
var raw = String(cfgLang || '').toLowerCase();
|
|
301
476
|
if (!raw || raw === 'auto') { raw = String(navLang || '').toLowerCase(); }
|
|
302
|
-
|
|
477
|
+
var two = raw.slice(0, 2);
|
|
478
|
+
if (two === 'ru') { return 'ru'; }
|
|
479
|
+
// 'mo' is the legacy Moldovan tag some browsers still send for Romanian.
|
|
480
|
+
if (two === 'ro' || two === 'mo') { return 'ro'; }
|
|
481
|
+
return 'en';
|
|
303
482
|
}
|
|
304
483
|
|
|
305
484
|
// Testable surface. Published before the activation check so the inactive
|
|
@@ -311,6 +490,17 @@
|
|
|
311
490
|
stripUrl: stripUrl,
|
|
312
491
|
pickLang: pickLang,
|
|
313
492
|
strings: STRINGS,
|
|
493
|
+
// SPEC V1.12 §3 — the per-row «почему» rule, pure and testable: given the
|
|
494
|
+
// requests, what the engine intercepted and the Consent Mode signals, which
|
|
495
|
+
// of the four notes does each «до согласия» row get?
|
|
496
|
+
explainRequests: explainRequests,
|
|
497
|
+
whyKeys: WHY_KEY,
|
|
498
|
+
// SPEC V1.12 §3 — the key render() compares to decide whether the DOM
|
|
499
|
+
// needs rebuilding at all. Exported so the rule is testable: two reports
|
|
500
|
+
// that differ only in `generatedAt` must produce the SAME key (that field
|
|
501
|
+
// is a fresh timestamp on every tick and would otherwise defeat the check
|
|
502
|
+
// entirely), and any real change must produce a different one.
|
|
503
|
+
reportKey: reportKey,
|
|
314
504
|
active: false
|
|
315
505
|
};
|
|
316
506
|
try {
|
|
@@ -505,6 +695,21 @@
|
|
|
505
695
|
return out;
|
|
506
696
|
}
|
|
507
697
|
|
|
698
|
+
/* Where the owner's account lives, when this build knows.
|
|
699
|
+
|
|
700
|
+
`texts.cabinetUrl` is server-owned and injected by the SaaS config, exactly
|
|
701
|
+
like `declarationUrl` in ck-ui.js — the client only reads it and never
|
|
702
|
+
invents one. http(s) only, because it becomes an href. An inline build has
|
|
703
|
+
none and gets the plain sentence instead. */
|
|
704
|
+
function cabinetUrl() {
|
|
705
|
+
try {
|
|
706
|
+
var t = CK && CK.config && CK.config.texts;
|
|
707
|
+
var u = t && t.cabinetUrl;
|
|
708
|
+
if (typeof u === 'string' && /^https?:\/\//i.test(u.trim())) { return u.trim(); }
|
|
709
|
+
} catch (e) { /* noop */ }
|
|
710
|
+
return null;
|
|
711
|
+
}
|
|
712
|
+
|
|
508
713
|
function reportInput() {
|
|
509
714
|
var s = saasInfo();
|
|
510
715
|
var cfg = (CK && CK.config) || {};
|
|
@@ -560,6 +765,11 @@
|
|
|
560
765
|
'.t.off{background:#3a1a1d;color:#ff9aa2}',
|
|
561
766
|
'.mut{color:#8d96ab}',
|
|
562
767
|
'.note{margin:6px 0 0;color:#8d96ab;font-size:11px;line-height:1.4}',
|
|
768
|
+
/* SPEC V1.12 §3 — the plain-words note under a «до согласия» row. Indented
|
|
769
|
+
to the width of the two tags above it, so it reads as belonging to that
|
|
770
|
+
row rather than as a new entry. */
|
|
771
|
+
'.why{margin:2px 0 0;color:#c2b078;font-size:11px;line-height:1.4}',
|
|
772
|
+
'a.note{display:inline-block;color:#7aa2ff}',
|
|
563
773
|
'.row{display:flex;gap:6px;flex-wrap:wrap;margin-top:8px}',
|
|
564
774
|
'@media (prefers-reduced-motion:no-preference){button{transition:background-color .12s ease}}'
|
|
565
775
|
].join('');
|
|
@@ -638,10 +848,49 @@
|
|
|
638
848
|
return T;
|
|
639
849
|
}
|
|
640
850
|
|
|
641
|
-
|
|
851
|
+
/* SPEC V1.12 §3 — the two render() fixes.
|
|
852
|
+
|
|
853
|
+
THE JUMP: render() empties `body` and rebuilds it, which resets
|
|
854
|
+
`body.scrollTop` to 0. On a page that keeps producing requests the 3s
|
|
855
|
+
scheduler then yanks the panel back to the top every three seconds, and
|
|
856
|
+
reading anything past the fold is impossible. Saved before the clear,
|
|
857
|
+
restored after the rebuild.
|
|
858
|
+
|
|
859
|
+
THE REDRAW: with the report unchanged, rebuilding the DOM is pure churn —
|
|
860
|
+
it kills text selection, closes nothing the user opened, and is what makes
|
|
861
|
+
the jump above happen at all. `renderKey` is the serialised report with
|
|
862
|
+
`generatedAt` dropped: that field is a fresh `new Date().toISOString()` on
|
|
863
|
+
every tick, so comparing the report as-is would never match and would
|
|
864
|
+
suppress nothing. `null` until the first render, so the first one always
|
|
865
|
+
happens. */
|
|
866
|
+
var renderKey = null;
|
|
867
|
+
|
|
868
|
+
function reportKey(r) {
|
|
869
|
+
try {
|
|
870
|
+
var copy = {};
|
|
871
|
+
for (var k in r) {
|
|
872
|
+
if (Object.prototype.hasOwnProperty.call(r, k) && k !== 'generatedAt') { copy[k] = r[k]; }
|
|
873
|
+
}
|
|
874
|
+
return JSON.stringify(copy);
|
|
875
|
+
} catch (e) { return null; }
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
function render(force) {
|
|
642
879
|
if (!body) { return; }
|
|
880
|
+
var langBefore = T;
|
|
643
881
|
refreshLang();
|
|
644
882
|
var r = buildReport(reportInput());
|
|
883
|
+
|
|
884
|
+
var key = reportKey(r);
|
|
885
|
+
// A language switch changes every string without changing the report, so it
|
|
886
|
+
// is its own reason to redraw.
|
|
887
|
+
if (!force && key !== null && key === renderKey && langBefore === T) { return; }
|
|
888
|
+
renderKey = key;
|
|
889
|
+
|
|
890
|
+
// Saved BEFORE the clear: an emptied body has no scroll height and reports 0.
|
|
891
|
+
var scrollTop = 0;
|
|
892
|
+
try { scrollTop = body.scrollTop || 0; } catch (e) { scrollTop = 0; }
|
|
893
|
+
|
|
645
894
|
body.textContent = '';
|
|
646
895
|
|
|
647
896
|
// 1. Client
|
|
@@ -707,11 +956,28 @@
|
|
|
707
956
|
li.appendChild(tag(q.category));
|
|
708
957
|
li.appendChild(doc.createTextNode(q.host + q.path + ' · ' + q.at + T.ms +
|
|
709
958
|
(q.count > 1 ? ' ×' + q.count : '')));
|
|
959
|
+
// SPEC V1.12 §3 — the plain-words note, on its own line so it reads as a
|
|
960
|
+
// sentence rather than as another tag. Only «до согласия» rows have one.
|
|
961
|
+
if (q.why && WHY_KEY[q.why]) {
|
|
962
|
+
li.appendChild(el('div', { class: 'why' }, T[WHY_KEY[q.why]]));
|
|
963
|
+
}
|
|
710
964
|
u4.appendChild(li);
|
|
711
965
|
});
|
|
712
966
|
s4.appendChild(u4);
|
|
713
967
|
}
|
|
714
968
|
s4.appendChild(el('p', { class: 'note' }, T.note));
|
|
969
|
+
/* «Что с этим делать — в кабинете» (§3). A LINK when this build knows the
|
|
970
|
+
cabinet's address and plain text otherwise — §3 says no URL is needed if
|
|
971
|
+
none is known, and a dead link would be worse than a sentence. The
|
|
972
|
+
address is server-owned, exactly like `declarationUrl`: the SaaS config
|
|
973
|
+
injects it and the client only reads it, http(s) only. */
|
|
974
|
+
var cab = cabinetUrl();
|
|
975
|
+
if (cab) {
|
|
976
|
+
var a = el('a', { href: cab, target: '_blank', rel: 'noopener noreferrer', class: 'note' }, T.cabinet);
|
|
977
|
+
s4.appendChild(a);
|
|
978
|
+
} else {
|
|
979
|
+
s4.appendChild(el('p', { class: 'note' }, T.cabinet));
|
|
980
|
+
}
|
|
715
981
|
body.appendChild(s4);
|
|
716
982
|
|
|
717
983
|
// 5. Consent Mode
|
|
@@ -821,6 +1087,10 @@
|
|
|
821
1087
|
s6.appendChild(row);
|
|
822
1088
|
s6.appendChild(el('p', { class: 'note' }, T.footer));
|
|
823
1089
|
body.appendChild(s6);
|
|
1090
|
+
|
|
1091
|
+
// Put the reader back where they were (§3). Wrapped: a body detached
|
|
1092
|
+
// between the save and here has no scrollTop to write.
|
|
1093
|
+
try { if (scrollTop) { body.scrollTop = scrollTop; } } catch (e) { /* noop */ }
|
|
824
1094
|
}
|
|
825
1095
|
|
|
826
1096
|
function schedule() {
|
package/src/ck-locales.js
CHANGED
|
@@ -578,6 +578,12 @@
|
|
|
578
578
|
phAllow: 'Permite și arată',
|
|
579
579
|
phSettings: 'Setări cookie-uri',
|
|
580
580
|
phLabel: 'Conținut blocat',
|
|
581
|
+
// SPEC V1.12 §3 — servicii în grupul categoriei. Româna are trei forme:
|
|
582
|
+
// 1 serviciu, 2 servicii, 20 de servicii (peste 19 cere «de»).
|
|
583
|
+
svcCount: ['{n} serviciu', '{n} servicii', '{n} de servicii'],
|
|
584
|
+
ckCount: ['{n} cookie', '{n} cookie-uri', '{n} de cookie-uri'],
|
|
585
|
+
svcPolicy: 'Politica',
|
|
586
|
+
svcCookies: 'Ce cookie-uri pune',
|
|
581
587
|
cat: {
|
|
582
588
|
necessary: { title: 'Necesare', desc: 'Sunt necesare pentru funcționarea site-ului: autentificare, securitate, reținerea alegerii dumneavoastră. Nu pot fi dezactivate.' },
|
|
583
589
|
functional: { title: 'Funcționale', desc: 'Rețin alegerile dumneavoastră: limba, coșul, chatul.' },
|
package/src/ck-saas.js
CHANGED
|
@@ -298,9 +298,42 @@
|
|
|
298
298
|
if (lang) { body.lang = lang; }
|
|
299
299
|
var layout = resolvedLayout();
|
|
300
300
|
if (layout) { body.layout = layout; }
|
|
301
|
+
|
|
302
|
+
/* SPEC V1.12 §3 — the optional `services` field: the ids the visitor
|
|
303
|
+
switched off, «только при method: 'custom'».
|
|
304
|
+
|
|
305
|
+
Three guards, all of them load-bearing against a 400 from a closed
|
|
306
|
+
schema: only on a 'custom' decision (an accept_all or reject_all has no
|
|
307
|
+
per-service refusals to report and a withdraw is not a choice about
|
|
308
|
+
services at all), only when the list is non-empty (an empty array is a
|
|
309
|
+
field the server did not need to receive), and capped at 50 ids of at
|
|
310
|
+
most 64 characters each — the same bounds §2 puts on the config. */
|
|
311
|
+
if (body.method === 'custom') {
|
|
312
|
+
var denied = deniedServices();
|
|
313
|
+
if (denied.length) { body.services = denied; }
|
|
314
|
+
}
|
|
301
315
|
return body;
|
|
302
316
|
}
|
|
303
317
|
|
|
318
|
+
// Read from the core, which normalised and bounded the ids already; re-checked
|
|
319
|
+
// here anyway, because this is the last place before the wire.
|
|
320
|
+
function deniedServices() {
|
|
321
|
+
var out = [];
|
|
322
|
+
try {
|
|
323
|
+
var ck = global.ConsentKit;
|
|
324
|
+
if (!ck || typeof ck._deniedServices !== 'function') { return out; }
|
|
325
|
+
var list = ck._deniedServices();
|
|
326
|
+
if (!list || typeof list.length !== 'number') { return out; }
|
|
327
|
+
for (var i = 0; i < list.length && out.length < 50; i++) {
|
|
328
|
+
var id = list[i];
|
|
329
|
+
if (typeof id === 'string' && id && id.length <= 64 && out.indexOf(id) === -1) {
|
|
330
|
+
out.push(id);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
} catch (e) { /* noop */ }
|
|
334
|
+
return out;
|
|
335
|
+
}
|
|
336
|
+
|
|
304
337
|
function drop(payload) {
|
|
305
338
|
var i = pending.indexOf(payload);
|
|
306
339
|
if (i > -1) { pending.splice(i, 1); }
|