@tracelog/capture-web 1.3.1 → 2.0.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 +17 -0
- package/README.md +18 -6
- package/dist/tracelog.esm.js +76 -13
- package/dist/tracelog.iife.js +76 -13
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -18,6 +18,23 @@ patch, and neither requires reading your code.
|
|
|
18
18
|
Entries after 1.0.0 are drafted from the commits that touched this package and
|
|
19
19
|
edited before release.
|
|
20
20
|
|
|
21
|
+
## [2.0.0](https://github.com/nacorga/tracelog-sdk/compare/capture-web@1.3.1...capture-web@2.0.0) (2026-10-01)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
### ⚠ BREAKING CHANGES
|
|
25
|
+
|
|
26
|
+
* **capture-core:** call TraceLog.consent.grant() on every load while the visitor's consent stands, not only when they accept.
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
|
|
30
|
+
* **capture-web:** calls before init are held ([b8d4fe5](https://github.com/nacorga/tracelog-sdk/commit/b8d4fe5e4bb104c2343aeceeb1b17f70d8cc697a))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
### Fixed
|
|
34
|
+
|
|
35
|
+
* **capture-core:** a grant is not remembered across loads ([75d4cac](https://github.com/nacorga/tracelog-sdk/commit/75d4cac28055007553e36b97d4eef0dbabf27103))
|
|
36
|
+
* **capture-web:** the verification mark follows its tab ([9653074](https://github.com/nacorga/tracelog-sdk/commit/9653074fb57ef5523a43a7fa6bc681f8b8a8814f))
|
|
37
|
+
|
|
21
38
|
## [1.3.1](https://github.com/nacorga/tracelog-sdk/compare/capture-web@1.3.0...capture-web@1.3.1) (2026-09-30)
|
|
22
39
|
|
|
23
40
|
|
package/README.md
CHANGED
|
@@ -22,13 +22,13 @@ as they are served.
|
|
|
22
22
|
<!-- x-release-please-start-version -->
|
|
23
23
|
|
|
24
24
|
```bash
|
|
25
|
-
npm install --save-exact @tracelog/capture-web@
|
|
25
|
+
npm install --save-exact @tracelog/capture-web@2.0.0
|
|
26
26
|
```
|
|
27
27
|
|
|
28
28
|
Without a build step, the same runtime as a script tag:
|
|
29
29
|
|
|
30
30
|
```html
|
|
31
|
-
<script src="https://cdn.tracelog.io/v/
|
|
31
|
+
<script src="https://cdn.tracelog.io/v/2.0.0/tracelog.js"></script>
|
|
32
32
|
```
|
|
33
33
|
|
|
34
34
|
<!-- x-release-please-end -->
|
|
@@ -42,7 +42,7 @@ the floor before a release.
|
|
|
42
42
|
Before consent is granted the runtime creates no identifiers, writes no storage,
|
|
43
43
|
and sends no network traffic — none, not a reduced set. Consent is a state
|
|
44
44
|
machine you drive; until it reaches `granted`, capture is inert. Denying keeps it
|
|
45
|
-
inert without breaking the page.
|
|
45
|
+
inert without breaking the page, and is remembered; a grant is not.
|
|
46
46
|
|
|
47
47
|
```js
|
|
48
48
|
import TraceLog from "@tracelog/capture-web";
|
|
@@ -52,10 +52,16 @@ TraceLog.init({
|
|
|
52
52
|
endpoint: "https://api.tracelog.io/v1/events",
|
|
53
53
|
});
|
|
54
54
|
|
|
55
|
-
//
|
|
55
|
+
// When your consent surface says yes, and on every later load while
|
|
56
|
+
// that consent stands:
|
|
56
57
|
TraceLog.consent.grant();
|
|
57
58
|
```
|
|
58
59
|
|
|
60
|
+
A grant lasts the page it was given on. Your consent surface keeps the
|
|
61
|
+
visitor's answer and its expiry, so call `grant()` on each load where that
|
|
62
|
+
answer is still yes; a grant that outlived it would capture a visitor whose
|
|
63
|
+
consent has lapsed.
|
|
64
|
+
|
|
59
65
|
A site that never calls `consent.grant()` captures nothing and costs its
|
|
60
66
|
visitors nothing.
|
|
61
67
|
|
|
@@ -74,11 +80,17 @@ That is all of it, and it is what the major version protects.
|
|
|
74
80
|
|
|
75
81
|
`init` also accepts `mode: "verification"`, which a distributed platform artifact
|
|
76
82
|
declares for its platform's own test order. A site's own snippet never sets it —
|
|
77
|
-
the runtime derives verification mode from the window that opened the page
|
|
83
|
+
the runtime derives verification mode from the window that opened the page, and
|
|
84
|
+
once consent is granted there the tab keeps it (`sessionStorage`, `__tl.v`) for
|
|
85
|
+
the site's next pages and the return from a payment taken elsewhere, to the same
|
|
86
|
+
origin. A denial forgets it.
|
|
78
87
|
|
|
79
88
|
Call `init` once per page load. The runtime is built on the first call and
|
|
80
89
|
kept; a later call re-reads the key and the endpoint and rebuilds nothing else,
|
|
81
|
-
so a mode or an acquisition the first call decided stands for the page.
|
|
90
|
+
so a mode or an acquisition the first call decided stands for the page. A
|
|
91
|
+
call made before it — a grant, a step, a conversion — is held, up to 100, and
|
|
92
|
+
made once it has run. The runtime can hold only what reaches it: a page that
|
|
93
|
+
imports it lazily holds what it calls before the import resolves.
|
|
82
94
|
|
|
83
95
|
The application generates the exact calls your declared plan needs, so you never
|
|
84
96
|
type a name TraceLog already knows.
|
package/dist/tracelog.esm.js
CHANGED
|
@@ -550,22 +550,26 @@ function createCaptureEngine(ports, acquisition) {
|
|
|
550
550
|
endpoint: options.endpoint ?? "/v1/events",
|
|
551
551
|
};
|
|
552
552
|
initialized = true;
|
|
553
|
+
/**
|
|
554
|
+
* Only a denial is remembered; a grant is the integrator's on every load
|
|
555
|
+
* while consent stands, so it never outlives the banner that gave it
|
|
556
|
+
* ([spec/capture.md] § Consent first). A grant an earlier runtime
|
|
557
|
+
* stored is deleted. A second init keeps the decision this page made.
|
|
558
|
+
*/
|
|
559
|
+
if (consentState !== "unknown")
|
|
560
|
+
return;
|
|
553
561
|
const storedConsent = safeGet(ports.storage, CONSENT_KEY);
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
if (consentState === "granted") {
|
|
559
|
-
ports.sightings?.start();
|
|
560
|
-
scheduleFlush(FLUSH_INTERVAL_MS);
|
|
561
|
-
}
|
|
562
|
+
if (storedConsent === "denied")
|
|
563
|
+
consentState = "denied";
|
|
564
|
+
else if (storedConsent !== null)
|
|
565
|
+
safeRemove(ports.storage, CONSENT_KEY);
|
|
562
566
|
},
|
|
563
567
|
consent: {
|
|
564
568
|
grant() {
|
|
565
569
|
if (!initialized)
|
|
566
570
|
return;
|
|
567
571
|
consentState = "granted";
|
|
568
|
-
|
|
572
|
+
safeRemove(ports.storage, CONSENT_KEY);
|
|
569
573
|
ports.sightings?.start();
|
|
570
574
|
const buffered = pending;
|
|
571
575
|
pending = [];
|
|
@@ -918,6 +922,8 @@ function createTagSightingPort() {
|
|
|
918
922
|
};
|
|
919
923
|
}
|
|
920
924
|
const WEB_PUBLIC_KEY_PATTERN = /^tl_pk_[a-z2-7]{26}$/;
|
|
925
|
+
/** The tab's verification nonce, in `sessionStorage`, written at the grant. */
|
|
926
|
+
const VERIFICATION_KEY = "__tl.v";
|
|
921
927
|
class MemoryStorage {
|
|
922
928
|
constructor() {
|
|
923
929
|
this.values = new Map();
|
|
@@ -938,6 +944,20 @@ let engine;
|
|
|
938
944
|
let verification;
|
|
939
945
|
let configValid = false;
|
|
940
946
|
let listenersInstalled = false;
|
|
947
|
+
/**
|
|
948
|
+
* Calls made before `init`, made in order once it has run, up to the same 100
|
|
949
|
+
* the engine holds before consent; past that they are dropped
|
|
950
|
+
* ([spec/capture.md] § Consent first).
|
|
951
|
+
*/
|
|
952
|
+
let early = [];
|
|
953
|
+
/** True when there is no engine yet, and the call was held, or dropped. */
|
|
954
|
+
function heldForInit(call) {
|
|
955
|
+
if (engine !== undefined)
|
|
956
|
+
return false;
|
|
957
|
+
if (early.length < PRE_CONSENT_CAP)
|
|
958
|
+
early.push(call);
|
|
959
|
+
return true;
|
|
960
|
+
}
|
|
941
961
|
function browserStorage() {
|
|
942
962
|
try {
|
|
943
963
|
return window.localStorage;
|
|
@@ -946,11 +966,39 @@ function browserStorage() {
|
|
|
946
966
|
return new MemoryStorage();
|
|
947
967
|
}
|
|
948
968
|
}
|
|
969
|
+
/**
|
|
970
|
+
* A page opened from a verification session carries the marker and has an
|
|
971
|
+
* opener. Once consent is granted there, the tab keeps the nonce, so the mark
|
|
972
|
+
* follows it to the site's next pages and back from a payment taken
|
|
973
|
+
* elsewhere, to the same origin ([spec/capture.md] § Verification mode).
|
|
974
|
+
*/
|
|
949
975
|
function verificationMode() {
|
|
950
976
|
const nonce = new URL(window.location.href).searchParams.get("__tl_verify");
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
977
|
+
if (nonce !== null && nonce.length > 0 && window.opener !== null) {
|
|
978
|
+
return { nonce, opener: window.opener };
|
|
979
|
+
}
|
|
980
|
+
let kept = null;
|
|
981
|
+
try {
|
|
982
|
+
kept = window.sessionStorage.getItem(VERIFICATION_KEY);
|
|
983
|
+
}
|
|
984
|
+
catch {
|
|
985
|
+
// An unreadable tab storage keeps no mark.
|
|
986
|
+
}
|
|
987
|
+
return kept === null || kept.length === 0
|
|
988
|
+
? undefined
|
|
989
|
+
: { nonce: kept, opener: window.opener };
|
|
990
|
+
}
|
|
991
|
+
function keepVerification(keep) {
|
|
992
|
+
try {
|
|
993
|
+
if (!keep)
|
|
994
|
+
window.sessionStorage.removeItem(VERIFICATION_KEY);
|
|
995
|
+
else if (verification !== undefined) {
|
|
996
|
+
window.sessionStorage.setItem(VERIFICATION_KEY, verification.nonce);
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
catch {
|
|
1000
|
+
// A tab that cannot keep it marks this page only.
|
|
1001
|
+
}
|
|
954
1002
|
}
|
|
955
1003
|
function landingPage() {
|
|
956
1004
|
const url = new URL(window.location.href);
|
|
@@ -1005,7 +1053,7 @@ function isEndpointValid(endpoint) {
|
|
|
1005
1053
|
}
|
|
1006
1054
|
}
|
|
1007
1055
|
function reportDiagnostic() {
|
|
1008
|
-
if (verification === undefined)
|
|
1056
|
+
if (verification === undefined || verification.opener === null)
|
|
1009
1057
|
return;
|
|
1010
1058
|
verification.opener.postMessage({
|
|
1011
1059
|
type: "tracelog:diag",
|
|
@@ -1102,17 +1150,28 @@ function init(options) {
|
|
|
1102
1150
|
...(options.endpoint === undefined ? {} : { endpoint }),
|
|
1103
1151
|
});
|
|
1104
1152
|
installListeners();
|
|
1153
|
+
const held = early;
|
|
1154
|
+
early = [];
|
|
1155
|
+
for (const call of held)
|
|
1156
|
+
call();
|
|
1105
1157
|
reportDiagnostic();
|
|
1106
1158
|
}
|
|
1107
1159
|
const TraceLog = {
|
|
1108
1160
|
init,
|
|
1109
1161
|
consent: {
|
|
1110
1162
|
grant() {
|
|
1163
|
+
if (heldForInit(() => TraceLog.consent.grant()))
|
|
1164
|
+
return;
|
|
1111
1165
|
engine?.consent.grant();
|
|
1166
|
+
if (engine?.consent.state() === "granted")
|
|
1167
|
+
keepVerification(true);
|
|
1112
1168
|
reportDiagnostic();
|
|
1113
1169
|
},
|
|
1114
1170
|
deny() {
|
|
1171
|
+
if (heldForInit(() => TraceLog.consent.deny()))
|
|
1172
|
+
return;
|
|
1115
1173
|
engine?.consent.deny();
|
|
1174
|
+
keepVerification(false);
|
|
1116
1175
|
reportDiagnostic();
|
|
1117
1176
|
},
|
|
1118
1177
|
state() {
|
|
@@ -1120,10 +1179,14 @@ const TraceLog = {
|
|
|
1120
1179
|
},
|
|
1121
1180
|
},
|
|
1122
1181
|
step(name, context) {
|
|
1182
|
+
if (heldForInit(() => TraceLog.step(name, context)))
|
|
1183
|
+
return;
|
|
1123
1184
|
engine?.step(name, context);
|
|
1124
1185
|
reportDiagnostic();
|
|
1125
1186
|
},
|
|
1126
1187
|
conversion(name, options) {
|
|
1188
|
+
if (heldForInit(() => TraceLog.conversion(name, options)))
|
|
1189
|
+
return;
|
|
1127
1190
|
engine?.conversion(name, options);
|
|
1128
1191
|
reportDiagnostic();
|
|
1129
1192
|
},
|
package/dist/tracelog.iife.js
CHANGED
|
@@ -551,22 +551,26 @@ function createCaptureEngine(ports, acquisition) {
|
|
|
551
551
|
endpoint: options.endpoint ?? "/v1/events",
|
|
552
552
|
};
|
|
553
553
|
initialized = true;
|
|
554
|
+
/**
|
|
555
|
+
* Only a denial is remembered; a grant is the integrator's on every load
|
|
556
|
+
* while consent stands, so it never outlives the banner that gave it
|
|
557
|
+
* ([spec/capture.md] § Consent first). A grant an earlier runtime
|
|
558
|
+
* stored is deleted. A second init keeps the decision this page made.
|
|
559
|
+
*/
|
|
560
|
+
if (consentState !== "unknown")
|
|
561
|
+
return;
|
|
554
562
|
const storedConsent = safeGet(ports.storage, CONSENT_KEY);
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
if (consentState === "granted") {
|
|
560
|
-
ports.sightings?.start();
|
|
561
|
-
scheduleFlush(FLUSH_INTERVAL_MS);
|
|
562
|
-
}
|
|
563
|
+
if (storedConsent === "denied")
|
|
564
|
+
consentState = "denied";
|
|
565
|
+
else if (storedConsent !== null)
|
|
566
|
+
safeRemove(ports.storage, CONSENT_KEY);
|
|
563
567
|
},
|
|
564
568
|
consent: {
|
|
565
569
|
grant() {
|
|
566
570
|
if (!initialized)
|
|
567
571
|
return;
|
|
568
572
|
consentState = "granted";
|
|
569
|
-
|
|
573
|
+
safeRemove(ports.storage, CONSENT_KEY);
|
|
570
574
|
ports.sightings?.start();
|
|
571
575
|
const buffered = pending;
|
|
572
576
|
pending = [];
|
|
@@ -919,6 +923,8 @@ function createTagSightingPort() {
|
|
|
919
923
|
};
|
|
920
924
|
}
|
|
921
925
|
const WEB_PUBLIC_KEY_PATTERN = /^tl_pk_[a-z2-7]{26}$/;
|
|
926
|
+
/** The tab's verification nonce, in `sessionStorage`, written at the grant. */
|
|
927
|
+
const VERIFICATION_KEY = "__tl.v";
|
|
922
928
|
class MemoryStorage {
|
|
923
929
|
constructor() {
|
|
924
930
|
this.values = new Map();
|
|
@@ -939,6 +945,20 @@ let engine;
|
|
|
939
945
|
let verification;
|
|
940
946
|
let configValid = false;
|
|
941
947
|
let listenersInstalled = false;
|
|
948
|
+
/**
|
|
949
|
+
* Calls made before `init`, made in order once it has run, up to the same 100
|
|
950
|
+
* the engine holds before consent; past that they are dropped
|
|
951
|
+
* ([spec/capture.md] § Consent first).
|
|
952
|
+
*/
|
|
953
|
+
let early = [];
|
|
954
|
+
/** True when there is no engine yet, and the call was held, or dropped. */
|
|
955
|
+
function heldForInit(call) {
|
|
956
|
+
if (engine !== undefined)
|
|
957
|
+
return false;
|
|
958
|
+
if (early.length < PRE_CONSENT_CAP)
|
|
959
|
+
early.push(call);
|
|
960
|
+
return true;
|
|
961
|
+
}
|
|
942
962
|
function browserStorage() {
|
|
943
963
|
try {
|
|
944
964
|
return window.localStorage;
|
|
@@ -947,11 +967,39 @@ function browserStorage() {
|
|
|
947
967
|
return new MemoryStorage();
|
|
948
968
|
}
|
|
949
969
|
}
|
|
970
|
+
/**
|
|
971
|
+
* A page opened from a verification session carries the marker and has an
|
|
972
|
+
* opener. Once consent is granted there, the tab keeps the nonce, so the mark
|
|
973
|
+
* follows it to the site's next pages and back from a payment taken
|
|
974
|
+
* elsewhere, to the same origin ([spec/capture.md] § Verification mode).
|
|
975
|
+
*/
|
|
950
976
|
function verificationMode() {
|
|
951
977
|
const nonce = new URL(window.location.href).searchParams.get("__tl_verify");
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
978
|
+
if (nonce !== null && nonce.length > 0 && window.opener !== null) {
|
|
979
|
+
return { nonce, opener: window.opener };
|
|
980
|
+
}
|
|
981
|
+
let kept = null;
|
|
982
|
+
try {
|
|
983
|
+
kept = window.sessionStorage.getItem(VERIFICATION_KEY);
|
|
984
|
+
}
|
|
985
|
+
catch {
|
|
986
|
+
// An unreadable tab storage keeps no mark.
|
|
987
|
+
}
|
|
988
|
+
return kept === null || kept.length === 0
|
|
989
|
+
? undefined
|
|
990
|
+
: { nonce: kept, opener: window.opener };
|
|
991
|
+
}
|
|
992
|
+
function keepVerification(keep) {
|
|
993
|
+
try {
|
|
994
|
+
if (!keep)
|
|
995
|
+
window.sessionStorage.removeItem(VERIFICATION_KEY);
|
|
996
|
+
else if (verification !== undefined) {
|
|
997
|
+
window.sessionStorage.setItem(VERIFICATION_KEY, verification.nonce);
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
catch {
|
|
1001
|
+
// A tab that cannot keep it marks this page only.
|
|
1002
|
+
}
|
|
955
1003
|
}
|
|
956
1004
|
function landingPage() {
|
|
957
1005
|
const url = new URL(window.location.href);
|
|
@@ -1006,7 +1054,7 @@ function isEndpointValid(endpoint) {
|
|
|
1006
1054
|
}
|
|
1007
1055
|
}
|
|
1008
1056
|
function reportDiagnostic() {
|
|
1009
|
-
if (verification === undefined)
|
|
1057
|
+
if (verification === undefined || verification.opener === null)
|
|
1010
1058
|
return;
|
|
1011
1059
|
verification.opener.postMessage({
|
|
1012
1060
|
type: "tracelog:diag",
|
|
@@ -1103,17 +1151,28 @@ function init(options) {
|
|
|
1103
1151
|
...(options.endpoint === undefined ? {} : { endpoint }),
|
|
1104
1152
|
});
|
|
1105
1153
|
installListeners();
|
|
1154
|
+
const held = early;
|
|
1155
|
+
early = [];
|
|
1156
|
+
for (const call of held)
|
|
1157
|
+
call();
|
|
1106
1158
|
reportDiagnostic();
|
|
1107
1159
|
}
|
|
1108
1160
|
const TraceLog = {
|
|
1109
1161
|
init,
|
|
1110
1162
|
consent: {
|
|
1111
1163
|
grant() {
|
|
1164
|
+
if (heldForInit(() => TraceLog.consent.grant()))
|
|
1165
|
+
return;
|
|
1112
1166
|
engine?.consent.grant();
|
|
1167
|
+
if (engine?.consent.state() === "granted")
|
|
1168
|
+
keepVerification(true);
|
|
1113
1169
|
reportDiagnostic();
|
|
1114
1170
|
},
|
|
1115
1171
|
deny() {
|
|
1172
|
+
if (heldForInit(() => TraceLog.consent.deny()))
|
|
1173
|
+
return;
|
|
1116
1174
|
engine?.consent.deny();
|
|
1175
|
+
keepVerification(false);
|
|
1117
1176
|
reportDiagnostic();
|
|
1118
1177
|
},
|
|
1119
1178
|
state() {
|
|
@@ -1121,10 +1180,14 @@ const TraceLog = {
|
|
|
1121
1180
|
},
|
|
1122
1181
|
},
|
|
1123
1182
|
step(name, context) {
|
|
1183
|
+
if (heldForInit(() => TraceLog.step(name, context)))
|
|
1184
|
+
return;
|
|
1124
1185
|
engine?.step(name, context);
|
|
1125
1186
|
reportDiagnostic();
|
|
1126
1187
|
},
|
|
1127
1188
|
conversion(name, options) {
|
|
1189
|
+
if (heldForInit(() => TraceLog.conversion(name, options)))
|
|
1190
|
+
return;
|
|
1128
1191
|
engine?.conversion(name, options);
|
|
1129
1192
|
reportDiagnostic();
|
|
1130
1193
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tracelog/capture-web",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "The TraceLog browser capture runtime: consent-first, pinned per version.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"conversion",
|
|
@@ -45,8 +45,8 @@
|
|
|
45
45
|
"access": "public"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
|
-
"@tracelog/capture-core": "
|
|
49
|
-
"@tracelog/event-contract": "
|
|
48
|
+
"@tracelog/capture-core": "2.0.0",
|
|
49
|
+
"@tracelog/event-contract": "2.0.0"
|
|
50
50
|
},
|
|
51
51
|
"scripts": {
|
|
52
52
|
"build": "tsc -p tsconfig.build.json && node build.mjs",
|