@crelora/mark 0.1.1 → 0.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.
- package/README.md +73 -1
- package/dist/browser.es.js +100 -34
- package/dist/browser.es.js.map +1 -1
- package/dist/browser.umd.js +1 -1
- package/dist/browser.umd.js.map +1 -1
- package/dist/node.cjs +1 -1
- package/dist/node.cjs.map +1 -1
- package/dist/node.es.js +65 -12
- package/dist/node.es.js.map +1 -1
- package/dist/types/browser/BrowserStorage.d.ts +2 -0
- package/dist/types/browser/Mark.d.ts +16 -0
- package/dist/types/core/MarkCore.d.ts +24 -0
- package/dist/types/core/adapters.d.ts +2 -0
- package/dist/types/node/StatelessStorage.d.ts +2 -0
- package/dist/types/node/index.d.ts +6 -0
- package/dist/types/types.d.ts +12 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Mark is Crelora's lightweight attribution SDK for capturing user journeys, conversions, and consent across browsers and server-side runtimes. The npm package includes both browser and Node entry points so you can send consistent data from any surface.
|
|
4
4
|
|
|
5
|
-
Use it to feed **OneLence** with first‑party behavioral and conversion data so you can build **analytics**, **insights**, **signals**, and **decisions** on top of a unified event stream.
|
|
5
|
+
Use it to feed [**OneLence**](https://onelence.com) with first‑party behavioral and conversion data so you can build **analytics**, **insights**, **signals**, and **decisions** on top of a unified event stream.
|
|
6
6
|
|
|
7
7
|
### API keys and documentation
|
|
8
8
|
|
|
@@ -104,6 +104,7 @@ The Node factory accepts optional custom storage or transport adapters so you ca
|
|
|
104
104
|
- `flush()` – flushes queued/persisted delivery items.
|
|
105
105
|
- `reset()` – clears user/session/attribution state and rotates visitor identity for logout flows.
|
|
106
106
|
- `getStats()` – returns runtime delivery stats `{ queued, sent, failed, dropped }`.
|
|
107
|
+
- `setInternal(value)` / `getInternal()` – flags the current visitor as internal traffic (QA, staff, smoke tests). While set, every outgoing event is stamped with `is_internal: true` so the backend can exclude it from customer-facing reports by default. See [Flagging internal traffic](#flagging-internal-traffic).
|
|
107
108
|
|
|
108
109
|
Reserved SDK fields (for example `event_name`, `user_id`, `consent_state`, and internal identity metadata) are sanitized from user payloads/traits and cannot override SDK-managed values.
|
|
109
110
|
|
|
@@ -223,6 +224,77 @@ Mark.identify('user_123', {
|
|
|
223
224
|
- Autocapture (optional): set `autocapture: { pageview: true }` (and optionally `track_route_changes: true`) to emit on first load and SPA route changes. If consent is required and not yet granted, initial pageview is deferred and emitted once consent is granted.
|
|
224
225
|
- All SDK config and event fields use snake_case (`site_id`, `site_host`, etc.) and map directly to stored payloads and database columns.
|
|
225
226
|
|
|
227
|
+
## Extended autocapture (browser)
|
|
228
|
+
|
|
229
|
+
All of the following are **opt-in** under `autocapture`. They respect the same consent, DNT/GPC, and sampling rules as `track()` (see `sample_rate`: conversions are exempt; these modes are **not** conversion events). They are registered when `Mark.init()` runs.
|
|
230
|
+
|
|
231
|
+
| Flag | Event name(s) | Behavior |
|
|
232
|
+
| --- | --- | --- |
|
|
233
|
+
| `click` | `data-mark-event` value, or `click` | Listens for clicks. **Default:** only elements matching `[data-mark-event]`; set `click: { selector: '.my-tracked' }` to use a custom selector. Sends `element_id`, `element_classes`, truncated `text`, and `href` when applicable. |
|
|
234
|
+
| `form_submit` | `form_submit` | Listens for `submit`; sends `form_id`, `form_name`, `action`. |
|
|
235
|
+
| `outbound_link` | `outbound_link_click` | On click, if the link target is another origin, sends `href`. Uses `preferBeacon: true` so the event is more likely to fire before navigation. |
|
|
236
|
+
| `scroll_depth` | `scroll_depth` | Fires at **25%, 50%, 75%, and 100%** of vertical scroll depth (once each per page load). Payload includes `percent`. |
|
|
237
|
+
| `web_vitals` | `web_vital` | Lazy-loads the `web-vitals` dependency and reports **LCP, CLS, INP, and TTFB** with `metric`, `value`, optional `rating`, and `metric_id`. If the module fails to load, only `debug: true` logs a warning. |
|
|
238
|
+
|
|
239
|
+
Example:
|
|
240
|
+
|
|
241
|
+
```ts
|
|
242
|
+
Mark.init({
|
|
243
|
+
key: 'pk_xxxxx',
|
|
244
|
+
require_consent: 'auto',
|
|
245
|
+
autocapture: {
|
|
246
|
+
pageview: true,
|
|
247
|
+
click: true,
|
|
248
|
+
// or: click: { selector: '[data-analytics]' },
|
|
249
|
+
form_submit: true,
|
|
250
|
+
outbound_link: true,
|
|
251
|
+
scroll_depth: true,
|
|
252
|
+
web_vitals: true,
|
|
253
|
+
},
|
|
254
|
+
});
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
## Flagging internal traffic
|
|
258
|
+
|
|
259
|
+
Use the `is_internal` marker to keep QA sessions, staff testing, or automated smoke tests out of customer-facing reports without dropping them on the client. The SDK supports it in two complementary ways:
|
|
260
|
+
|
|
261
|
+
**1. Per-event field.** Any `track` / `conversion` call can carry `is_internal: true`:
|
|
262
|
+
|
|
263
|
+
```ts
|
|
264
|
+
Mark.track('checkout_started', { value: 1299, is_internal: true });
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
**2. Persistent visitor flag.** Set it once and every subsequent event is stamped automatically:
|
|
268
|
+
|
|
269
|
+
```ts
|
|
270
|
+
Mark.setInternal(true); // stamps is_internal: true on all future events
|
|
271
|
+
Mark.setInternal(false); // stops stamping
|
|
272
|
+
Mark.getInternal(); // boolean
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
The persistent flag is stored in the SDK's browser storage so it survives reloads. It is cleared automatically by `Mark.reset()` and by `Mark.setConsent('denied')`.
|
|
276
|
+
|
|
277
|
+
**Design note.** The SDK intentionally does *not* read a magic URL parameter or write to a well-known `localStorage` key on its own — that would let anyone opt themselves out of your analytics just by visiting a URL, and it would bake a specific policy into every integration. Instead, your app decides when to flip the flag. A common pattern is a URL query parameter for staff onboarding:
|
|
278
|
+
|
|
279
|
+
```ts
|
|
280
|
+
// After Mark.init(...)
|
|
281
|
+
const params = new URLSearchParams(window.location.search);
|
|
282
|
+
if (params.get('onelence_internal') === '1') Mark.setInternal(true);
|
|
283
|
+
if (params.get('onelence_internal') === '0') Mark.setInternal(false);
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
Other reasonable triggers: an authenticated admin/staff role, an internal IP range detected server-side, a feature flag, or an explicit toggle in your own settings UI.
|
|
287
|
+
|
|
288
|
+
**Server-side** the same API is available on `NodeMark`:
|
|
289
|
+
|
|
290
|
+
```ts
|
|
291
|
+
const mark = createNodeMark({ key: process.env.MARK_SECRET_KEY! });
|
|
292
|
+
mark.setInternal(true);
|
|
293
|
+
mark.track('backoffice_action'); // is_internal: true
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
`is_internal` is a first-class field on the backend: events are still ingested (so you can audit and debug integrations), but excluded from customer-facing reports by default.
|
|
297
|
+
|
|
226
298
|
## Support
|
|
227
299
|
|
|
228
300
|
Need help? Reach out through your account team or file a ticket via the OneLence dashboard. Please include the SDK version, runtime (browser or Node), and any reproduction steps so we can assist quickly.
|
package/dist/browser.es.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const
|
|
1
|
+
const I = "https://ingest.onelence.com";
|
|
2
2
|
class h extends Error {
|
|
3
3
|
status;
|
|
4
4
|
retryAfterMs;
|
|
@@ -6,7 +6,7 @@ class h extends Error {
|
|
|
6
6
|
super(t), this.name = "TransportError", this.status = e.status, this.retryAfterMs = e.retryAfterMs;
|
|
7
7
|
}
|
|
8
8
|
}
|
|
9
|
-
function
|
|
9
|
+
function S(c) {
|
|
10
10
|
return !(typeof c != "number" || c < 400 || c >= 500 || c === 408 || c === 429);
|
|
11
11
|
}
|
|
12
12
|
function E(c) {
|
|
@@ -22,10 +22,10 @@ function E(c) {
|
|
|
22
22
|
return s > 0 ? s : 0;
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
|
-
const x = 5, q = 300, T = 15e3,
|
|
26
|
-
class
|
|
25
|
+
const x = 5, q = 300, T = 15e3, D = 2880 * 60 * 1e3;
|
|
26
|
+
class C {
|
|
27
27
|
constructor(t, e = {}) {
|
|
28
|
-
this.transport = t, this.maxAttempts = e.maxAttempts ?? x, this.baseBackoffMs = e.baseBackoffMs ?? q, this.maxBackoffMs = e.maxBackoffMs ?? T, this.maxItemAgeMs = e.maxItemAgeMs ??
|
|
28
|
+
this.transport = t, this.maxAttempts = e.maxAttempts ?? x, this.baseBackoffMs = e.baseBackoffMs ?? q, this.maxBackoffMs = e.maxBackoffMs ?? T, this.maxItemAgeMs = e.maxItemAgeMs ?? D, this.debug = e.debug ?? !1, this.loadPersisted = e.loadPersisted, this.savePersisted = e.savePersisted, this.onError = e.onError;
|
|
29
29
|
const i = this.loadPersisted?.() ?? [];
|
|
30
30
|
if (i.length > 0) {
|
|
31
31
|
const s = Date.now();
|
|
@@ -118,7 +118,7 @@ class D {
|
|
|
118
118
|
} catch (i) {
|
|
119
119
|
this.failed += 1, this.onError?.(i, e.data);
|
|
120
120
|
const s = i instanceof h ? i.status : void 0;
|
|
121
|
-
if (
|
|
121
|
+
if (S(s)) {
|
|
122
122
|
this.queue.shift(), this.dropped += 1, this.persist(), this.debug && console.error("[Mark] Dropping event after non-retriable status", s, e.path);
|
|
123
123
|
continue;
|
|
124
124
|
}
|
|
@@ -147,7 +147,7 @@ class D {
|
|
|
147
147
|
}
|
|
148
148
|
}
|
|
149
149
|
}
|
|
150
|
-
const
|
|
150
|
+
const U = /* @__PURE__ */ new Set(["event_name", "user_id", "consent_state", "source", "is_conversion"]), k = /* @__PURE__ */ new Set([
|
|
151
151
|
"user_id",
|
|
152
152
|
"visitor_id",
|
|
153
153
|
"click_id",
|
|
@@ -159,10 +159,10 @@ const C = /* @__PURE__ */ new Set(["event_name", "user_id", "consent_state", "so
|
|
|
159
159
|
class L {
|
|
160
160
|
constructor(t, e) {
|
|
161
161
|
this.deps = e, this.validateConfig(t), this.config = {
|
|
162
|
-
endpoint: t.endpoint ??
|
|
162
|
+
endpoint: t.endpoint ?? I,
|
|
163
163
|
...t,
|
|
164
164
|
include_page_context: t.include_page_context ?? !0
|
|
165
|
-
}, this.consentRequirement = t.require_consent ?? !1, this.siteId = t.site_id, this.siteHost = t.site_host, this.sessionTimeoutMs = t.session_timeout_ms ?? 1800 * 1e3, this.queue = new
|
|
165
|
+
}, this.consentRequirement = t.require_consent ?? !1, this.siteId = t.site_id, this.siteHost = t.site_host, this.sessionTimeoutMs = t.session_timeout_ms ?? 1800 * 1e3, this.queue = new C(this.deps.transport, {
|
|
166
166
|
debug: this.config.debug,
|
|
167
167
|
loadPersisted: () => this.deps.storage.getOutbox?.() ?? [],
|
|
168
168
|
savePersisted: (i) => this.deps.storage.setOutbox?.(i),
|
|
@@ -214,7 +214,7 @@ class L {
|
|
|
214
214
|
};
|
|
215
215
|
i && (a.is_conversion = !0);
|
|
216
216
|
const l = r.site_id ?? this.siteId, f = r.site_host ?? this.siteHost;
|
|
217
|
-
l && (a.site_id = l), f && (a.site_host = f), this.config.include_page_context && typeof window < "u" && (this.applyPageContext(a), !f && a.site && (a.site_host = a.site));
|
|
217
|
+
l && (a.site_id = l), f && (a.site_host = f), this.config.include_page_context && typeof window < "u" && (this.applyPageContext(a), !f && a.site && (a.site_host = a.site)), this.applyInternalFlag(a, r.is_internal);
|
|
218
218
|
const d = this.config.before_send ? this.config.before_send(a) : a;
|
|
219
219
|
return d ? (this.ensureSession(), this.config.batching?.enabled && !i && !s?.preferBeacon ? (this.enqueueBatch(d), !0) : (this.queue.enqueue("/event", { ...d, __prefer_beacon: s?.preferBeacon === !0 }), !0)) : !0;
|
|
220
220
|
}
|
|
@@ -234,7 +234,7 @@ class L {
|
|
|
234
234
|
...this.sanitizeIdentifyTraits(e),
|
|
235
235
|
...this.getIdentityFields()
|
|
236
236
|
};
|
|
237
|
-
this.siteId && (i.site_id = this.siteId), this.siteHost && (i.site_host = this.siteHost);
|
|
237
|
+
this.siteId && (i.site_id = this.siteId), this.siteHost && (i.site_host = this.siteHost), this.applyInternalFlag(i);
|
|
238
238
|
const s = this.config.before_send ? this.config.before_send(i) : i;
|
|
239
239
|
s && this.queue.enqueue("/identify", s);
|
|
240
240
|
}
|
|
@@ -253,14 +253,14 @@ class L {
|
|
|
253
253
|
}
|
|
254
254
|
setConsent(t) {
|
|
255
255
|
const e = this.deps.storage.getConsentStatus();
|
|
256
|
-
this.deps.storage.setConsentStatus(t), t === "denied" ? (this.deps.storage.clearAttribution?.(), this.deps.storage.clearCookieVisitorId?.()) : t === "granted" && e === "denied" && this.config.rotate_visitor_on_consent_change && this.deps.storage.rotateVisitorId?.();
|
|
256
|
+
this.deps.storage.setConsentStatus(t), t === "denied" ? (this.deps.storage.clearAttribution?.(), this.deps.storage.clearCookieVisitorId?.(), this.deps.storage.setInternal?.(!1)) : t === "granted" && e === "denied" && this.config.rotate_visitor_on_consent_change && this.deps.storage.rotateVisitorId?.();
|
|
257
257
|
const i = {
|
|
258
258
|
visitor_id: this.deps.storage.getVisitorId(),
|
|
259
259
|
consent_state: t,
|
|
260
260
|
source: "sdk",
|
|
261
261
|
message_id: this.createMessageId()
|
|
262
262
|
};
|
|
263
|
-
this.siteId && (i.site_id = this.siteId), this.siteHost && (i.site_host = this.siteHost);
|
|
263
|
+
this.siteId && (i.site_id = this.siteId), this.siteHost && (i.site_host = this.siteHost), this.applyInternalFlag(i);
|
|
264
264
|
const s = this.config.before_send ? this.config.before_send(i) : i;
|
|
265
265
|
s && this.queue.enqueue("/consent", s);
|
|
266
266
|
}
|
|
@@ -272,15 +272,51 @@ class L {
|
|
|
272
272
|
query_params: void 0,
|
|
273
273
|
session_id: void 0,
|
|
274
274
|
session_started_at: void 0,
|
|
275
|
-
last_activity_at: void 0
|
|
275
|
+
last_activity_at: void 0,
|
|
276
|
+
is_internal: void 0
|
|
276
277
|
}), this.deps.storage.rotateVisitorId?.();
|
|
277
278
|
}
|
|
279
|
+
/**
|
|
280
|
+
* Marks or unmarks the current visitor as internal traffic. While set, every
|
|
281
|
+
* subsequent event (track/identify/conversion/consent) is stamped with
|
|
282
|
+
* `is_internal: true`, so the backend can exclude it from customer-facing
|
|
283
|
+
* reports by default.
|
|
284
|
+
*
|
|
285
|
+
* The flag is persisted via the storage adapter (browser: localStorage) so
|
|
286
|
+
* it survives reloads, and is cleared by `reset()` and by
|
|
287
|
+
* `setConsent('denied')`.
|
|
288
|
+
*/
|
|
289
|
+
setInternal(t) {
|
|
290
|
+
this.deps.storage.setInternal?.(!!t);
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Returns the currently persisted internal-traffic flag, if any.
|
|
294
|
+
*/
|
|
295
|
+
getInternal() {
|
|
296
|
+
return !!this.deps.storage.getInternal?.();
|
|
297
|
+
}
|
|
278
298
|
flush() {
|
|
279
299
|
return this.flushBatch(), this.queue.flush();
|
|
280
300
|
}
|
|
281
301
|
getStats() {
|
|
282
302
|
return this.queue.getStats();
|
|
283
303
|
}
|
|
304
|
+
/**
|
|
305
|
+
* Stamps `is_internal: true` on the payload when either:
|
|
306
|
+
* - the persistent visitor flag is set (via setInternal), or
|
|
307
|
+
* - the caller passed `is_internal: true` on this specific event.
|
|
308
|
+
*
|
|
309
|
+
* Explicit `is_internal: false` on a single event wins over the visitor flag
|
|
310
|
+
* so individual calls can opt out.
|
|
311
|
+
*/
|
|
312
|
+
applyInternalFlag(t, e) {
|
|
313
|
+
if (e === !1) {
|
|
314
|
+
delete t.is_internal;
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
const i = this.deps.storage.getInternal?.() === !0;
|
|
318
|
+
e === !0 || i ? t.is_internal = !0 : delete t.is_internal;
|
|
319
|
+
}
|
|
284
320
|
getIdentityFields(t) {
|
|
285
321
|
const e = t?.visitor_id ?? this.deps.storage.getVisitorId(), i = t?.user_id ?? this.deps.storage.getUserId?.(), s = t?.click_id ?? this.deps.storage.getLastClickId(), r = t?.campaign_id ?? this.deps.storage.getCampaignId(), o = t?.session_id ?? this.deps.storage.getSessionId?.(), a = this.deps.storage.getQueryParams() ?? {}, l = t?.query ?? {}, f = { ...a, ...l }, d = {};
|
|
286
322
|
return e && (d.visitor_id = e), i && (d.user_id = i), s && (d.click_id = s), r && (d.campaign_id = r), o && (d.session_id = o), Object.keys(f).length > 0 && (d.query = f), d;
|
|
@@ -296,13 +332,13 @@ class L {
|
|
|
296
332
|
sanitizeTrackData(t) {
|
|
297
333
|
const e = {};
|
|
298
334
|
for (const [i, s] of Object.entries(t))
|
|
299
|
-
|
|
335
|
+
U.has(i) || (e[i] = s);
|
|
300
336
|
return e;
|
|
301
337
|
}
|
|
302
338
|
sanitizeIdentifyTraits(t) {
|
|
303
339
|
const e = {};
|
|
304
340
|
for (const [i, s] of Object.entries(t))
|
|
305
|
-
|
|
341
|
+
k.has(i) || (e[i] = s);
|
|
306
342
|
return e;
|
|
307
343
|
}
|
|
308
344
|
validateConfig(t) {
|
|
@@ -426,7 +462,7 @@ class P {
|
|
|
426
462
|
endpoint;
|
|
427
463
|
pending = /* @__PURE__ */ new Set();
|
|
428
464
|
constructor(t) {
|
|
429
|
-
this.config = t, this.endpoint = t.endpoint ??
|
|
465
|
+
this.config = t, this.endpoint = t.endpoint ?? I;
|
|
430
466
|
}
|
|
431
467
|
async send(t, e, i) {
|
|
432
468
|
const s = this.sendInternal(t, e, i);
|
|
@@ -456,7 +492,7 @@ class P {
|
|
|
456
492
|
throw this.config.debug && console.error("[Mark] Global fetch is not available in this runtime."), new h("[Mark] Global fetch is not available in this runtime.");
|
|
457
493
|
const l = this.config.request_timeout_ms ?? 1e4, f = new AbortController();
|
|
458
494
|
let d = !1;
|
|
459
|
-
const
|
|
495
|
+
const g = setTimeout(() => {
|
|
460
496
|
d = !0, f.abort();
|
|
461
497
|
}, l);
|
|
462
498
|
try {
|
|
@@ -468,16 +504,16 @@ class P {
|
|
|
468
504
|
signal: f.signal
|
|
469
505
|
});
|
|
470
506
|
if (!u.ok) {
|
|
471
|
-
const
|
|
507
|
+
const p = await this.readErrorSnippet(u), w = E(u.headers.get("Retry-After"));
|
|
472
508
|
throw this.config.debug && console.error("[Mark] Request rejected", {
|
|
473
509
|
url: s,
|
|
474
510
|
status: u.status,
|
|
475
511
|
statusText: u.statusText,
|
|
476
|
-
body:
|
|
477
|
-
retryAfterMs:
|
|
512
|
+
body: p,
|
|
513
|
+
retryAfterMs: w
|
|
478
514
|
}), new h(
|
|
479
|
-
`[Mark] Request rejected with status ${u.status}: ${
|
|
480
|
-
{ status: u.status, retryAfterMs:
|
|
515
|
+
`[Mark] Request rejected with status ${u.status}: ${p}`,
|
|
516
|
+
{ status: u.status, retryAfterMs: w }
|
|
481
517
|
);
|
|
482
518
|
}
|
|
483
519
|
} catch (u) {
|
|
@@ -485,10 +521,10 @@ class P {
|
|
|
485
521
|
throw u;
|
|
486
522
|
if (d)
|
|
487
523
|
throw new h(`[Mark] Request timed out after ${l}ms`, { status: 408 });
|
|
488
|
-
const
|
|
489
|
-
throw new h(`[Mark] Network error: ${
|
|
524
|
+
const p = u instanceof Error ? u.message : String(u);
|
|
525
|
+
throw new h(`[Mark] Network error: ${p}`);
|
|
490
526
|
} finally {
|
|
491
|
-
clearTimeout(
|
|
527
|
+
clearTimeout(g);
|
|
492
528
|
}
|
|
493
529
|
}
|
|
494
530
|
joinUrl(t, e) {
|
|
@@ -503,7 +539,7 @@ class P {
|
|
|
503
539
|
}
|
|
504
540
|
}
|
|
505
541
|
}
|
|
506
|
-
const R = "crelora_mark_data", B = "crelora_mark_outbox",
|
|
542
|
+
const R = "crelora_mark_data", B = "crelora_mark_outbox", y = "crelora_mark_vid";
|
|
507
543
|
class F {
|
|
508
544
|
data;
|
|
509
545
|
storageKey;
|
|
@@ -546,6 +582,12 @@ class F {
|
|
|
546
582
|
getLastActivityAt() {
|
|
547
583
|
return this.data.last_activity_at;
|
|
548
584
|
}
|
|
585
|
+
getInternal() {
|
|
586
|
+
return this.data.is_internal;
|
|
587
|
+
}
|
|
588
|
+
setInternal(t) {
|
|
589
|
+
t ? this.update({ is_internal: !0 }) : this.update({ is_internal: void 0 });
|
|
590
|
+
}
|
|
549
591
|
update(t) {
|
|
550
592
|
this.data = { ...this.data, ...t }, this.save();
|
|
551
593
|
}
|
|
@@ -560,7 +602,7 @@ class F {
|
|
|
560
602
|
});
|
|
561
603
|
}
|
|
562
604
|
clearCookieVisitorId() {
|
|
563
|
-
this.setCookie(
|
|
605
|
+
this.setCookie(y, "", -1);
|
|
564
606
|
}
|
|
565
607
|
rotateVisitorId() {
|
|
566
608
|
this.update({ visitor_id: this.generateUUID() });
|
|
@@ -592,7 +634,7 @@ class F {
|
|
|
592
634
|
return JSON.parse(e);
|
|
593
635
|
} catch {
|
|
594
636
|
}
|
|
595
|
-
const t = this.getCookie(
|
|
637
|
+
const t = this.getCookie(y);
|
|
596
638
|
return t ? { visitor_id: t } : {};
|
|
597
639
|
}
|
|
598
640
|
save() {
|
|
@@ -601,7 +643,7 @@ class F {
|
|
|
601
643
|
localStorage.setItem(this.storageKey, JSON.stringify(this.data));
|
|
602
644
|
} catch {
|
|
603
645
|
}
|
|
604
|
-
this.data.visitor_id && this.isCookieEnabled() && (this.setCookie(
|
|
646
|
+
this.data.visitor_id && this.isCookieEnabled() && (this.setCookie(y, this.data.visitor_id, 365), this.options.bridge?.url && this.bridgeReady && this.postBridgeMessage({
|
|
605
647
|
type: "MARK_SYNC_UPDATE",
|
|
606
648
|
visitorId: this.data.visitor_id
|
|
607
649
|
}));
|
|
@@ -756,14 +798,14 @@ function j(c, t) {
|
|
|
756
798
|
const d = m(l);
|
|
757
799
|
if (!d)
|
|
758
800
|
continue;
|
|
759
|
-
const
|
|
760
|
-
if (i.has(d) || i.has(
|
|
801
|
+
const g = K[d] ?? d;
|
|
802
|
+
if (i.has(d) || i.has(g) || a && !a.has(d))
|
|
761
803
|
continue;
|
|
762
804
|
const u = f.trim();
|
|
763
805
|
if (u) {
|
|
764
|
-
if (!(
|
|
806
|
+
if (!(g in e) && Object.keys(e).length >= s)
|
|
765
807
|
break;
|
|
766
|
-
e[
|
|
808
|
+
e[g] = u.slice(0, r);
|
|
767
809
|
}
|
|
768
810
|
}
|
|
769
811
|
return e;
|
|
@@ -877,6 +919,30 @@ class n {
|
|
|
877
919
|
static flush() {
|
|
878
920
|
return n.client ? n.client.flush() : Promise.resolve();
|
|
879
921
|
}
|
|
922
|
+
/**
|
|
923
|
+
* Flags (or un-flags) the current visitor as internal traffic. Persists
|
|
924
|
+
* across reloads via the SDK's browser storage. Cleared by `reset()` and by
|
|
925
|
+
* `setConsent('denied')`.
|
|
926
|
+
*
|
|
927
|
+
* The policy for deciding *when* to call this (URL query parameter, auth
|
|
928
|
+
* role, IP, feature flag, etc.) is intentionally left to the host app.
|
|
929
|
+
*
|
|
930
|
+
* @example
|
|
931
|
+
* // URL-based opt-in: call once during app bootstrap, after Mark.init()
|
|
932
|
+
* const params = new URLSearchParams(window.location.search);
|
|
933
|
+
* if (params.get('onelence_internal') === '1') Mark.setInternal(true);
|
|
934
|
+
* if (params.get('onelence_internal') === '0') Mark.setInternal(false);
|
|
935
|
+
*/
|
|
936
|
+
static setInternal(t) {
|
|
937
|
+
if (!n.client) {
|
|
938
|
+
n.config?.debug && console.warn("[Mark] Not initialized. Call init() first.");
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
941
|
+
n.client.setInternal(t);
|
|
942
|
+
}
|
|
943
|
+
static getInternal() {
|
|
944
|
+
return n.client?.getInternal() ?? !1;
|
|
945
|
+
}
|
|
880
946
|
static reset() {
|
|
881
947
|
n.client?.reset(), n.pendingAttribution = {}, n.lastPageviewHref = null;
|
|
882
948
|
}
|