@crelora/mark 0.0.16

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,517 @@
1
+ const w = "https://ingest.onelence.com", v = /* @__PURE__ */ new Set(["event_name", "user_id", "consent_state", "source", "is_conversion"]), A = /* @__PURE__ */ new Set([
2
+ "user_id",
3
+ "visitor_id",
4
+ "click_id",
5
+ "campaign_id",
6
+ "query",
7
+ "consent_state",
8
+ "source"
9
+ ]);
10
+ class I {
11
+ constructor(t, e) {
12
+ this.deps = e, this.validateConfig(t), this.config = {
13
+ endpoint: t.endpoint ?? w,
14
+ ...t,
15
+ include_page_context: t.include_page_context ?? !0
16
+ }, this.consentRequirement = t.require_consent ?? !1, this.siteId = t.site_id, this.siteHost = t.site_host;
17
+ }
18
+ config;
19
+ consentRequirement;
20
+ siteId;
21
+ siteHost;
22
+ track(t, e = {}) {
23
+ return this.trackInternal(t, e, !1);
24
+ }
25
+ trackInternal(t, e = {}, i = !1) {
26
+ if (!t)
27
+ return this.config.debug && console.warn("[Mark] track called without event name"), !1;
28
+ if (!this.hasConsent())
29
+ return this.config.debug && console.warn("[Mark] Tracking blocked due to consent requirement."), !1;
30
+ const s = this.sanitizeTrackData(e), r = { ...s };
31
+ "query" in r && delete r.query, "site_id" in r && delete r.site_id, "site_host" in r && delete r.site_host;
32
+ const o = {
33
+ event_name: t,
34
+ ...this.getIdentityFields(s),
35
+ ...r
36
+ };
37
+ i && (o.is_conversion = !0);
38
+ const a = s.site_id ?? this.siteId, c = s.site_host ?? this.siteHost;
39
+ return a && (o.site_id = a), c && (o.site_host = c), this.config.include_page_context && typeof window < "u" && (this.applyPageContext(o), !c && o.site && (o.site_host = o.site)), this.deps.transport.send("/event", o), !0;
40
+ }
41
+ identify(t, e = {}) {
42
+ if (!t) {
43
+ this.config.debug && console.warn("[Mark] identify called without userId");
44
+ return;
45
+ }
46
+ if (!this.hasConsent()) {
47
+ this.config.debug && console.warn("[Mark] Identify blocked due to consent requirement.");
48
+ return;
49
+ }
50
+ const i = {
51
+ user_id: t,
52
+ ...this.sanitizeIdentifyTraits(e),
53
+ ...this.getIdentityFields()
54
+ };
55
+ this.siteId && (i.site_id = this.siteId), this.siteHost && (i.site_host = this.siteHost), this.deps.transport.send("/identify", i);
56
+ }
57
+ conversion(t, e = {}) {
58
+ return this.trackInternal(t, e, !0);
59
+ }
60
+ /**
61
+ * Returns the current visitor ID from storage, if any.
62
+ * Used by browser/Node wrappers to expose a stable pseudonymous ID for server-side attribution.
63
+ */
64
+ getVisitorId() {
65
+ return this.deps.storage.getVisitorId();
66
+ }
67
+ setConsent(t) {
68
+ this.deps.storage.setConsentStatus(t);
69
+ const e = {
70
+ visitor_id: this.deps.storage.getVisitorId(),
71
+ consent_state: t,
72
+ source: "sdk"
73
+ };
74
+ this.siteId && (e.site_id = this.siteId), this.siteHost && (e.site_host = this.siteHost), this.deps.transport.send("/consent", e);
75
+ }
76
+ getIdentityFields(t) {
77
+ const e = t?.visitor_id ?? this.deps.storage.getVisitorId(), i = t?.click_id ?? this.deps.storage.getLastClickId(), s = t?.campaign_id ?? this.deps.storage.getCampaignId(), r = this.deps.storage.getQueryParams() ?? {}, o = t?.query ?? {}, a = { ...r, ...o }, c = {};
78
+ return e && (c.visitor_id = e), i && (c.click_id = i), s && (c.campaign_id = s), Object.keys(a).length > 0 && (c.query = a), c;
79
+ }
80
+ hasConsent() {
81
+ if (!this.consentRequirement)
82
+ return !0;
83
+ const e = this.deps.storage.getConsentStatus();
84
+ return this.consentRequirement === "auto", e === "granted";
85
+ }
86
+ sanitizeTrackData(t) {
87
+ const e = {};
88
+ for (const [i, s] of Object.entries(t))
89
+ v.has(i) || (e[i] = s);
90
+ return e;
91
+ }
92
+ sanitizeIdentifyTraits(t) {
93
+ const e = {};
94
+ for (const [i, s] of Object.entries(t))
95
+ A.has(i) || (e[i] = s);
96
+ return e;
97
+ }
98
+ validateConfig(t) {
99
+ if (!t.key || !t.key.trim())
100
+ throw new Error("[Mark] `key` must be a non-empty string.");
101
+ if (t.endpoint)
102
+ try {
103
+ new URL(t.endpoint);
104
+ } catch {
105
+ throw new Error("[Mark] `endpoint` must be a valid absolute URL.");
106
+ }
107
+ if (typeof t.site_id == "string" && !t.site_id.trim())
108
+ throw new Error("[Mark] `site_id` cannot be an empty string.");
109
+ if (typeof t.site_host == "string" && !t.site_host.trim())
110
+ throw new Error("[Mark] `site_host` cannot be an empty string.");
111
+ }
112
+ applyPageContext(t) {
113
+ t.page || t.title || t.referrer || t.site || (t.site = window.location.host, t.page = window.location.pathname, t.title = document.title, document.referrer && (t.referrer = document.referrer));
114
+ }
115
+ }
116
+ class E {
117
+ config;
118
+ endpoint;
119
+ constructor(t) {
120
+ this.validateConfig(t), this.config = t, this.endpoint = t.endpoint ?? w;
121
+ }
122
+ async send(t, e) {
123
+ const i = this.joinUrl(this.endpoint, t), s = this.config.key, r = {
124
+ "Content-Type": "application/json",
125
+ [s.startsWith("sk_") ? "x-secret-key" : "x-publishable-key"]: s
126
+ };
127
+ if (this.config.debug && console.log("[Mark] Sending", i, e), typeof fetch != "function") {
128
+ this.config.debug && console.error("[Mark] Global fetch is not available in this runtime.");
129
+ return;
130
+ }
131
+ try {
132
+ const o = await fetch(i, {
133
+ method: "POST",
134
+ headers: r,
135
+ body: JSON.stringify(e),
136
+ keepalive: !0
137
+ });
138
+ if (!o.ok && this.config.debug) {
139
+ const a = await this.readErrorSnippet(o);
140
+ console.error("[Mark] Request rejected", {
141
+ url: i,
142
+ status: o.status,
143
+ statusText: o.statusText,
144
+ body: a
145
+ });
146
+ }
147
+ } catch (o) {
148
+ this.config.debug && console.error("[Mark] Failed to send", i, o);
149
+ }
150
+ }
151
+ joinUrl(t, e) {
152
+ const i = t.replace(/\/+$/, ""), s = e.replace(/^\/+/, "");
153
+ return `${i}/${s}`;
154
+ }
155
+ async readErrorSnippet(t) {
156
+ try {
157
+ return (await t.text()).slice(0, 300);
158
+ } catch {
159
+ return "";
160
+ }
161
+ }
162
+ validateConfig(t) {
163
+ if (!t.key || !t.key.trim())
164
+ throw new Error("[Mark] `key` must be a non-empty string.");
165
+ if (t.endpoint)
166
+ try {
167
+ new URL(t.endpoint);
168
+ } catch {
169
+ throw new Error("[Mark] `endpoint` must be a valid absolute URL.");
170
+ }
171
+ }
172
+ }
173
+ const k = "crelora_mark_data", g = "crelora_mark_vid";
174
+ class x {
175
+ data;
176
+ storageKey;
177
+ options;
178
+ bridgeFrame;
179
+ bridgeReady = !1;
180
+ bridgeOrigin;
181
+ constructor(t) {
182
+ this.options = t ?? {}, this.storageKey = this.options.storageKey ?? k, this.data = this.load(), this.data.visitor_id || (this.data.visitor_id = this.generateUUID(), this.save()), typeof window < "u" && this.options.bridge?.url && this.setupBridge(this.options.bridge);
183
+ }
184
+ getVisitorId() {
185
+ return this.data.visitor_id;
186
+ }
187
+ getLastClickId() {
188
+ return this.data.last_click_id;
189
+ }
190
+ getCampaignId() {
191
+ return this.data.campaign_id;
192
+ }
193
+ getQueryParams() {
194
+ return this.data.query_params;
195
+ }
196
+ getConsentStatus() {
197
+ return this.data.consent_status;
198
+ }
199
+ update(t) {
200
+ this.data = { ...this.data, ...t }, this.save();
201
+ }
202
+ setConsentStatus(t) {
203
+ this.data.consent_status = t, this.save();
204
+ }
205
+ load() {
206
+ try {
207
+ const e = localStorage.getItem(this.storageKey);
208
+ if (e)
209
+ return JSON.parse(e);
210
+ } catch {
211
+ }
212
+ const t = this.getCookie(g);
213
+ return t ? { visitor_id: t } : {};
214
+ }
215
+ save() {
216
+ try {
217
+ localStorage.setItem(this.storageKey, JSON.stringify(this.data));
218
+ } catch {
219
+ }
220
+ this.data.visitor_id && this.isCookieEnabled() && (this.setCookie(g, this.data.visitor_id, 365), this.options.bridge?.url && this.bridgeReady && this.postBridgeMessage({
221
+ type: "MARK_SYNC_UPDATE",
222
+ visitorId: this.data.visitor_id
223
+ }));
224
+ }
225
+ getCookie(t) {
226
+ try {
227
+ const e = document.cookie.match(new RegExp(`(^| )${t}=([^;]+)`));
228
+ if (e) return e[2];
229
+ } catch {
230
+ }
231
+ return null;
232
+ }
233
+ setCookie(t, e, i) {
234
+ try {
235
+ const s = new Date(Date.now() + i * 24 * 60 * 60 * 1e3), r = this.options.cookie_domain ? `;domain=${this.options.cookie_domain}` : "";
236
+ document.cookie = `${t}=${e};expires=${s.toUTCString()};path=/;SameSite=Lax${r}`;
237
+ } catch {
238
+ }
239
+ }
240
+ generateUUID() {
241
+ return typeof crypto < "u" && crypto.randomUUID ? crypto.randomUUID() : "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (t) => {
242
+ const e = Math.random() * 16 | 0;
243
+ return (t === "x" ? e : e & 3 | 8).toString(16);
244
+ });
245
+ }
246
+ isCookieEnabled() {
247
+ return this.options.mode ? this.options.mode === "single" || this.options.mode === "subdomain" : !0;
248
+ }
249
+ setupBridge(t) {
250
+ if (typeof document > "u") return;
251
+ let e;
252
+ try {
253
+ e = new URL(t.url, window.location.href);
254
+ } catch {
255
+ return;
256
+ }
257
+ this.bridgeOrigin = e.origin, this.bridgeFrame = document.createElement("iframe"), this.bridgeFrame.style.display = "none", this.bridgeFrame.setAttribute("aria-hidden", "true"), this.bridgeFrame.src = e.toString();
258
+ const i = t.timeout_ms ?? 3e3, s = setTimeout(() => {
259
+ window.removeEventListener("message", this.handleBridgeMessage);
260
+ }, i);
261
+ window.addEventListener("message", this.handleBridgeMessage);
262
+ const r = () => {
263
+ this.bridgeFrame && document.body && document.body.appendChild(this.bridgeFrame);
264
+ };
265
+ document.body ? r() : document.addEventListener("DOMContentLoaded", r, { once: !0 }), this.bridgeFrame.addEventListener("load", () => {
266
+ clearTimeout(s), this.bridgeReady = !0, this.postBridgeMessage({
267
+ type: "MARK_SYNC_REQUEST",
268
+ visitorId: this.data.visitor_id
269
+ });
270
+ });
271
+ }
272
+ handleBridgeMessage = (t) => {
273
+ if (!this.bridgeFrame?.contentWindow || t.source !== this.bridgeFrame.contentWindow || !this.isDomainAllowed(t.origin))
274
+ return;
275
+ const e = t.data;
276
+ !e || !e.type || e.type === "MARK_SYNC_RESPONSE" && e.visitorId && e.visitorId !== this.data.visitor_id && (this.data.visitor_id = e.visitorId, this.save());
277
+ };
278
+ postBridgeMessage(t) {
279
+ this.bridgeFrame?.contentWindow && this.bridgeFrame.contentWindow.postMessage(t, this.bridgeOrigin ?? "*");
280
+ }
281
+ isDomainAllowed(t) {
282
+ try {
283
+ const e = new URL(t);
284
+ if (this.options.allowed_domains?.length)
285
+ return this.options.allowed_domains.some(
286
+ (s) => e.hostname === s || e.hostname.endsWith(`.${s}`)
287
+ );
288
+ if (!this.bridgeOrigin)
289
+ return !1;
290
+ const i = new URL(this.bridgeOrigin);
291
+ return e.hostname === i.hostname;
292
+ } catch {
293
+ return !1;
294
+ }
295
+ }
296
+ }
297
+ const S = [
298
+ "click_id",
299
+ "ch_click_id",
300
+ "gclid",
301
+ "gbraid",
302
+ "wbraid",
303
+ "dclid",
304
+ "msclkid",
305
+ "fbclid",
306
+ "ttclid",
307
+ "twclid",
308
+ "li_fat_id"
309
+ ], C = ["cid", "campaign_id"], R = [
310
+ "utm_source",
311
+ "utm_medium",
312
+ "utm_campaign",
313
+ "utm_term",
314
+ "utm_content",
315
+ "ref",
316
+ "referral",
317
+ "affiliate_id",
318
+ "click_id",
319
+ "ch_click_id",
320
+ "cid",
321
+ "campaign_id",
322
+ "gclid",
323
+ "gbraid",
324
+ "wbraid",
325
+ "dclid",
326
+ "msclkid",
327
+ "fbclid",
328
+ "ttclid",
329
+ "twclid",
330
+ "li_fat_id"
331
+ ], U = {
332
+ referral: "ref",
333
+ affiliate_id: "ref",
334
+ ch_click_id: "click_id",
335
+ cid: "campaign_id"
336
+ }, P = ["email", "phone", "token", "auth", "password", "code"], T = 30, q = 256;
337
+ function p(d, t = {}) {
338
+ const e = new URLSearchParams(d), i = L(e), s = m(i, S), r = m(i, C), o = D(e, t), a = {};
339
+ return s && (a.last_click_id = s), r && (a.campaign_id = r), Object.keys(o).length > 0 && (a.query_params = o), a;
340
+ }
341
+ function D(d, t) {
342
+ const e = {}, i = new Set(y(t.query_param_denylist, P)), s = _(
343
+ t.max_captured_query_params,
344
+ T
345
+ ), r = _(
346
+ t.max_query_param_value_length,
347
+ q
348
+ ), a = t.capture_all_query_params ?? !1 ? null : /* @__PURE__ */ new Set([
349
+ ...R.map(l),
350
+ ...y(t.capture_query_params, [])
351
+ ]);
352
+ for (const [c, b] of d.entries()) {
353
+ const u = l(c);
354
+ if (!u)
355
+ continue;
356
+ const h = U[u] ?? u;
357
+ if (i.has(u) || i.has(h) || a && !a.has(u))
358
+ continue;
359
+ const f = b.trim();
360
+ if (f) {
361
+ if (!(h in e) && Object.keys(e).length >= s)
362
+ break;
363
+ e[h] = f.slice(0, r);
364
+ }
365
+ }
366
+ return e;
367
+ }
368
+ function m(d, t) {
369
+ for (const e of t) {
370
+ const i = l(e), s = d[i]?.trim();
371
+ if (s)
372
+ return s;
373
+ }
374
+ }
375
+ function L(d) {
376
+ const t = {};
377
+ for (const [e, i] of d.entries()) {
378
+ const s = l(e);
379
+ !s || s in t || (t[s] = i);
380
+ }
381
+ return t;
382
+ }
383
+ function l(d) {
384
+ return d.trim().toLowerCase();
385
+ }
386
+ function y(d, t) {
387
+ const e = d ?? t, i = /* @__PURE__ */ new Set();
388
+ for (const s of e) {
389
+ if (typeof s != "string") continue;
390
+ const r = l(s);
391
+ r && i.add(r);
392
+ }
393
+ return Array.from(i);
394
+ }
395
+ function _(d, t) {
396
+ if (typeof d != "number" || !Number.isFinite(d))
397
+ return t;
398
+ const e = Math.floor(d);
399
+ return e < 0 ? t : e;
400
+ }
401
+ class n {
402
+ static client = null;
403
+ static storage = null;
404
+ static config = null;
405
+ static pageviewTrackerInstalled = !1;
406
+ static lastPageviewHref = null;
407
+ static emitAutoPageview = null;
408
+ static pendingAttribution = {};
409
+ static init(t) {
410
+ if (!n.client) {
411
+ const e = new x(t.cross_domain), i = new E(t);
412
+ n.client = new I(t, { storage: e, transport: i }), n.storage = e, n.config = t, n.refreshAttribution(e, t), typeof window < "u" && t.autocapture?.pageview && n.installPageviewTracking(t, e);
413
+ }
414
+ return n.client;
415
+ }
416
+ static track(t, e = {}) {
417
+ if (!n.client) {
418
+ console.warn("[Mark] Not initialized. Call init() first.");
419
+ return;
420
+ }
421
+ n.client.track(t, e);
422
+ }
423
+ static identify(t, e = {}) {
424
+ if (!n.client) {
425
+ console.warn("[Mark] Not initialized. Call init() first.");
426
+ return;
427
+ }
428
+ n.client.identify(t, e);
429
+ }
430
+ static conversion(t, e = {}) {
431
+ if (!n.client) {
432
+ console.warn("[Mark] Not initialized. Call init() first.");
433
+ return;
434
+ }
435
+ n.client.conversion(t, e);
436
+ }
437
+ static setConsent(t) {
438
+ if (!n.client) {
439
+ console.warn("[Mark] Not initialized. Call init() first.");
440
+ return;
441
+ }
442
+ n.client.setConsent(t), t === "granted" && (n.flushPendingAttribution(), n.emitAutoPageview?.());
443
+ }
444
+ /**
445
+ * Returns the current visitor ID when available.
446
+ * When consent is required (`require_consent: true` or `'auto'`), returns `undefined` until consent is granted.
447
+ * Use this to pass the visitor ID to your backend (e.g. in a header or request body) for server-side attribution
448
+ * when you do not have an authenticated user ID.
449
+ */
450
+ static getVisitorId() {
451
+ if (!(!n.client || !n.storage || !n.config || (n.config.require_consent ?? !1) && n.storage.getConsentStatus() !== "granted"))
452
+ return n.client.getVisitorId();
453
+ }
454
+ static installPageviewTracking(t, e) {
455
+ if (n.pageviewTrackerInstalled) return;
456
+ n.pageviewTrackerInstalled = !0;
457
+ const i = () => {
458
+ if (!n.client) return;
459
+ n.refreshAttribution(e, t);
460
+ const a = window.location.href;
461
+ if (a === n.lastPageviewHref) return;
462
+ n.client.track("page_view", {
463
+ site: window.location.host,
464
+ page: window.location.pathname,
465
+ title: document.title,
466
+ referrer: document.referrer || void 0
467
+ }) && (n.lastPageviewHref = a);
468
+ };
469
+ if (n.emitAutoPageview = i, i(), !(t.track_route_changes ?? !0)) return;
470
+ const r = history.pushState, o = history.replaceState;
471
+ history.pushState = function(...a) {
472
+ const c = r.apply(this, a);
473
+ return i(), c;
474
+ }, history.replaceState = function(...a) {
475
+ const c = o.apply(this, a);
476
+ return i(), c;
477
+ }, window.addEventListener("popstate", i);
478
+ }
479
+ static refreshAttribution(t, e) {
480
+ if (typeof window > "u")
481
+ return;
482
+ const i = p(window.location.search, e);
483
+ if (Object.keys(i).length !== 0) {
484
+ if (n.shouldPersistAttribution(t, e)) {
485
+ const s = n.mergeAttributionUpdates(n.pendingAttribution, i);
486
+ t.update(s), n.pendingAttribution = {};
487
+ return;
488
+ }
489
+ n.pendingAttribution = n.mergeAttributionUpdates(n.pendingAttribution, i);
490
+ }
491
+ }
492
+ static flushPendingAttribution() {
493
+ const t = n.storage, e = n.config;
494
+ if (!t || !e || typeof window > "u")
495
+ return;
496
+ const i = p(window.location.search, e), s = n.mergeAttributionUpdates(n.pendingAttribution, i);
497
+ Object.keys(s).length > 0 && t.update(s), n.pendingAttribution = {};
498
+ }
499
+ static shouldPersistAttribution(t, e) {
500
+ return e.require_consent ?? !1 ? t.getConsentStatus() === "granted" : !0;
501
+ }
502
+ static mergeAttributionUpdates(t, e) {
503
+ return {
504
+ ...t,
505
+ ...e,
506
+ query_params: {
507
+ ...t.query_params ?? {},
508
+ ...e.query_params ?? {}
509
+ }
510
+ };
511
+ }
512
+ }
513
+ typeof window < "u" && (window.Mark = n);
514
+ export {
515
+ n as Mark,
516
+ n as default
517
+ };
@@ -0,0 +1 @@
1
+ (function(u,l){typeof exports=="object"&&typeof module<"u"?l(exports):typeof define=="function"&&define.amd?define(["exports"],l):(u=typeof globalThis<"u"?globalThis:u||self,l(u.Mark={}))})(this,(function(u){"use strict";const l="https://ingest.onelence.com",v=new Set(["event_name","user_id","consent_state","source","is_conversion"]),A=new Set(["user_id","visitor_id","click_id","campaign_id","query","consent_state","source"]);class I{constructor(t,e){this.deps=e,this.validateConfig(t),this.config={endpoint:t.endpoint??l,...t,include_page_context:t.include_page_context??!0},this.consentRequirement=t.require_consent??!1,this.siteId=t.site_id,this.siteHost=t.site_host}config;consentRequirement;siteId;siteHost;track(t,e={}){return this.trackInternal(t,e,!1)}trackInternal(t,e={},i=!1){if(!t)return this.config.debug&&console.warn("[Mark] track called without event name"),!1;if(!this.hasConsent())return this.config.debug&&console.warn("[Mark] Tracking blocked due to consent requirement."),!1;const s=this.sanitizeTrackData(e),r={...s};"query"in r&&delete r.query,"site_id"in r&&delete r.site_id,"site_host"in r&&delete r.site_host;const o={event_name:t,...this.getIdentityFields(s),...r};i&&(o.is_conversion=!0);const a=s.site_id??this.siteId,c=s.site_host??this.siteHost;return a&&(o.site_id=a),c&&(o.site_host=c),this.config.include_page_context&&typeof window<"u"&&(this.applyPageContext(o),!c&&o.site&&(o.site_host=o.site)),this.deps.transport.send("/event",o),!0}identify(t,e={}){if(!t){this.config.debug&&console.warn("[Mark] identify called without userId");return}if(!this.hasConsent()){this.config.debug&&console.warn("[Mark] Identify blocked due to consent requirement.");return}const i={user_id:t,...this.sanitizeIdentifyTraits(e),...this.getIdentityFields()};this.siteId&&(i.site_id=this.siteId),this.siteHost&&(i.site_host=this.siteHost),this.deps.transport.send("/identify",i)}conversion(t,e={}){return this.trackInternal(t,e,!0)}getVisitorId(){return this.deps.storage.getVisitorId()}setConsent(t){this.deps.storage.setConsentStatus(t);const e={visitor_id:this.deps.storage.getVisitorId(),consent_state:t,source:"sdk"};this.siteId&&(e.site_id=this.siteId),this.siteHost&&(e.site_host=this.siteHost),this.deps.transport.send("/consent",e)}getIdentityFields(t){const e=t?.visitor_id??this.deps.storage.getVisitorId(),i=t?.click_id??this.deps.storage.getLastClickId(),s=t?.campaign_id??this.deps.storage.getCampaignId(),r=this.deps.storage.getQueryParams()??{},o=t?.query??{},a={...r,...o},c={};return e&&(c.visitor_id=e),i&&(c.click_id=i),s&&(c.campaign_id=s),Object.keys(a).length>0&&(c.query=a),c}hasConsent(){if(!this.consentRequirement)return!0;const e=this.deps.storage.getConsentStatus();return this.consentRequirement==="auto",e==="granted"}sanitizeTrackData(t){const e={};for(const[i,s]of Object.entries(t))v.has(i)||(e[i]=s);return e}sanitizeIdentifyTraits(t){const e={};for(const[i,s]of Object.entries(t))A.has(i)||(e[i]=s);return e}validateConfig(t){if(!t.key||!t.key.trim())throw new Error("[Mark] `key` must be a non-empty string.");if(t.endpoint)try{new URL(t.endpoint)}catch{throw new Error("[Mark] `endpoint` must be a valid absolute URL.")}if(typeof t.site_id=="string"&&!t.site_id.trim())throw new Error("[Mark] `site_id` cannot be an empty string.");if(typeof t.site_host=="string"&&!t.site_host.trim())throw new Error("[Mark] `site_host` cannot be an empty string.")}applyPageContext(t){t.page||t.title||t.referrer||t.site||(t.site=window.location.host,t.page=window.location.pathname,t.title=document.title,document.referrer&&(t.referrer=document.referrer))}}class k{config;endpoint;constructor(t){this.validateConfig(t),this.config=t,this.endpoint=t.endpoint??l}async send(t,e){const i=this.joinUrl(this.endpoint,t),s=this.config.key,r={"Content-Type":"application/json",[s.startsWith("sk_")?"x-secret-key":"x-publishable-key"]:s};if(this.config.debug&&console.log("[Mark] Sending",i,e),typeof fetch!="function"){this.config.debug&&console.error("[Mark] Global fetch is not available in this runtime.");return}try{const o=await fetch(i,{method:"POST",headers:r,body:JSON.stringify(e),keepalive:!0});if(!o.ok&&this.config.debug){const a=await this.readErrorSnippet(o);console.error("[Mark] Request rejected",{url:i,status:o.status,statusText:o.statusText,body:a})}}catch(o){this.config.debug&&console.error("[Mark] Failed to send",i,o)}}joinUrl(t,e){const i=t.replace(/\/+$/,""),s=e.replace(/^\/+/,"");return`${i}/${s}`}async readErrorSnippet(t){try{return(await t.text()).slice(0,300)}catch{return""}}validateConfig(t){if(!t.key||!t.key.trim())throw new Error("[Mark] `key` must be a non-empty string.");if(t.endpoint)try{new URL(t.endpoint)}catch{throw new Error("[Mark] `endpoint` must be a valid absolute URL.")}}}const E="crelora_mark_data",p="crelora_mark_vid";class S{data;storageKey;options;bridgeFrame;bridgeReady=!1;bridgeOrigin;constructor(t){this.options=t??{},this.storageKey=this.options.storageKey??E,this.data=this.load(),this.data.visitor_id||(this.data.visitor_id=this.generateUUID(),this.save()),typeof window<"u"&&this.options.bridge?.url&&this.setupBridge(this.options.bridge)}getVisitorId(){return this.data.visitor_id}getLastClickId(){return this.data.last_click_id}getCampaignId(){return this.data.campaign_id}getQueryParams(){return this.data.query_params}getConsentStatus(){return this.data.consent_status}update(t){this.data={...this.data,...t},this.save()}setConsentStatus(t){this.data.consent_status=t,this.save()}load(){try{const e=localStorage.getItem(this.storageKey);if(e)return JSON.parse(e)}catch{}const t=this.getCookie(p);return t?{visitor_id:t}:{}}save(){try{localStorage.setItem(this.storageKey,JSON.stringify(this.data))}catch{}this.data.visitor_id&&this.isCookieEnabled()&&(this.setCookie(p,this.data.visitor_id,365),this.options.bridge?.url&&this.bridgeReady&&this.postBridgeMessage({type:"MARK_SYNC_UPDATE",visitorId:this.data.visitor_id}))}getCookie(t){try{const e=document.cookie.match(new RegExp(`(^| )${t}=([^;]+)`));if(e)return e[2]}catch{}return null}setCookie(t,e,i){try{const s=new Date(Date.now()+i*24*60*60*1e3),r=this.options.cookie_domain?`;domain=${this.options.cookie_domain}`:"";document.cookie=`${t}=${e};expires=${s.toUTCString()};path=/;SameSite=Lax${r}`}catch{}}generateUUID(){return typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,t=>{const e=Math.random()*16|0;return(t==="x"?e:e&3|8).toString(16)})}isCookieEnabled(){return this.options.mode?this.options.mode==="single"||this.options.mode==="subdomain":!0}setupBridge(t){if(typeof document>"u")return;let e;try{e=new URL(t.url,window.location.href)}catch{return}this.bridgeOrigin=e.origin,this.bridgeFrame=document.createElement("iframe"),this.bridgeFrame.style.display="none",this.bridgeFrame.setAttribute("aria-hidden","true"),this.bridgeFrame.src=e.toString();const i=t.timeout_ms??3e3,s=setTimeout(()=>{window.removeEventListener("message",this.handleBridgeMessage)},i);window.addEventListener("message",this.handleBridgeMessage);const r=()=>{this.bridgeFrame&&document.body&&document.body.appendChild(this.bridgeFrame)};document.body?r():document.addEventListener("DOMContentLoaded",r,{once:!0}),this.bridgeFrame.addEventListener("load",()=>{clearTimeout(s),this.bridgeReady=!0,this.postBridgeMessage({type:"MARK_SYNC_REQUEST",visitorId:this.data.visitor_id})})}handleBridgeMessage=t=>{if(!this.bridgeFrame?.contentWindow||t.source!==this.bridgeFrame.contentWindow||!this.isDomainAllowed(t.origin))return;const e=t.data;!e||!e.type||e.type==="MARK_SYNC_RESPONSE"&&e.visitorId&&e.visitorId!==this.data.visitor_id&&(this.data.visitor_id=e.visitorId,this.save())};postBridgeMessage(t){this.bridgeFrame?.contentWindow&&this.bridgeFrame.contentWindow.postMessage(t,this.bridgeOrigin??"*")}isDomainAllowed(t){try{const e=new URL(t);if(this.options.allowed_domains?.length)return this.options.allowed_domains.some(s=>e.hostname===s||e.hostname.endsWith(`.${s}`));if(!this.bridgeOrigin)return!1;const i=new URL(this.bridgeOrigin);return e.hostname===i.hostname}catch{return!1}}}const x=["click_id","ch_click_id","gclid","gbraid","wbraid","dclid","msclkid","fbclid","ttclid","twclid","li_fat_id"],C=["cid","campaign_id"],R=["utm_source","utm_medium","utm_campaign","utm_term","utm_content","ref","referral","affiliate_id","click_id","ch_click_id","cid","campaign_id","gclid","gbraid","wbraid","dclid","msclkid","fbclid","ttclid","twclid","li_fat_id"],U={referral:"ref",affiliate_id:"ref",ch_click_id:"click_id",cid:"campaign_id"},P=["email","phone","token","auth","password","code"],T=30,q=256;function m(d,t={}){const e=new URLSearchParams(d),i=L(e),s=y(i,x),r=y(i,C),o=D(e,t),a={};return s&&(a.last_click_id=s),r&&(a.campaign_id=r),Object.keys(o).length>0&&(a.query_params=o),a}function D(d,t){const e={},i=new Set(_(t.query_param_denylist,P)),s=w(t.max_captured_query_params,T),r=w(t.max_query_param_value_length,q),a=t.capture_all_query_params??!1?null:new Set([...R.map(h),..._(t.capture_query_params,[])]);for(const[c,F]of d.entries()){const f=h(c);if(!f)continue;const g=U[f]??f;if(i.has(f)||i.has(g)||a&&!a.has(f))continue;const b=F.trim();if(b){if(!(g in e)&&Object.keys(e).length>=s)break;e[g]=b.slice(0,r)}}return e}function y(d,t){for(const e of t){const i=h(e),s=d[i]?.trim();if(s)return s}}function L(d){const t={};for(const[e,i]of d.entries()){const s=h(e);!s||s in t||(t[s]=i)}return t}function h(d){return d.trim().toLowerCase()}function _(d,t){const e=d??t,i=new Set;for(const s of e){if(typeof s!="string")continue;const r=h(s);r&&i.add(r)}return Array.from(i)}function w(d,t){if(typeof d!="number"||!Number.isFinite(d))return t;const e=Math.floor(d);return e<0?t:e}class n{static client=null;static storage=null;static config=null;static pageviewTrackerInstalled=!1;static lastPageviewHref=null;static emitAutoPageview=null;static pendingAttribution={};static init(t){if(!n.client){const e=new S(t.cross_domain),i=new k(t);n.client=new I(t,{storage:e,transport:i}),n.storage=e,n.config=t,n.refreshAttribution(e,t),typeof window<"u"&&t.autocapture?.pageview&&n.installPageviewTracking(t,e)}return n.client}static track(t,e={}){if(!n.client){console.warn("[Mark] Not initialized. Call init() first.");return}n.client.track(t,e)}static identify(t,e={}){if(!n.client){console.warn("[Mark] Not initialized. Call init() first.");return}n.client.identify(t,e)}static conversion(t,e={}){if(!n.client){console.warn("[Mark] Not initialized. Call init() first.");return}n.client.conversion(t,e)}static setConsent(t){if(!n.client){console.warn("[Mark] Not initialized. Call init() first.");return}n.client.setConsent(t),t==="granted"&&(n.flushPendingAttribution(),n.emitAutoPageview?.())}static getVisitorId(){if(!(!n.client||!n.storage||!n.config||(n.config.require_consent??!1)&&n.storage.getConsentStatus()!=="granted"))return n.client.getVisitorId()}static installPageviewTracking(t,e){if(n.pageviewTrackerInstalled)return;n.pageviewTrackerInstalled=!0;const i=()=>{if(!n.client)return;n.refreshAttribution(e,t);const a=window.location.href;if(a===n.lastPageviewHref)return;n.client.track("page_view",{site:window.location.host,page:window.location.pathname,title:document.title,referrer:document.referrer||void 0})&&(n.lastPageviewHref=a)};if(n.emitAutoPageview=i,i(),!(t.track_route_changes??!0))return;const r=history.pushState,o=history.replaceState;history.pushState=function(...a){const c=r.apply(this,a);return i(),c},history.replaceState=function(...a){const c=o.apply(this,a);return i(),c},window.addEventListener("popstate",i)}static refreshAttribution(t,e){if(typeof window>"u")return;const i=m(window.location.search,e);if(Object.keys(i).length!==0){if(n.shouldPersistAttribution(t,e)){const s=n.mergeAttributionUpdates(n.pendingAttribution,i);t.update(s),n.pendingAttribution={};return}n.pendingAttribution=n.mergeAttributionUpdates(n.pendingAttribution,i)}}static flushPendingAttribution(){const t=n.storage,e=n.config;if(!t||!e||typeof window>"u")return;const i=m(window.location.search,e),s=n.mergeAttributionUpdates(n.pendingAttribution,i);Object.keys(s).length>0&&t.update(s),n.pendingAttribution={}}static shouldPersistAttribution(t,e){return e.require_consent??!1?t.getConsentStatus()==="granted":!0}static mergeAttributionUpdates(t,e){return{...t,...e,query_params:{...t.query_params??{},...e.query_params??{}}}}}typeof window<"u"&&(window.Mark=n),u.Mark=n,u.default=n,Object.defineProperties(u,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})}));
package/dist/node.cjs ADDED
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const c="https://ingest.onelence.com",l=new Set(["event_name","user_id","consent_state","source","is_conversion"]),f=new Set(["user_id","visitor_id","click_id","campaign_id","query","consent_state","source"]);class g{constructor(t,e){this.deps=e,this.validateConfig(t),this.config={endpoint:t.endpoint??c,...t,include_page_context:t.include_page_context??!0},this.consentRequirement=t.require_consent??!1,this.siteId=t.site_id,this.siteHost=t.site_host}config;consentRequirement;siteId;siteHost;track(t,e={}){return this.trackInternal(t,e,!1)}trackInternal(t,e={},s=!1){if(!t)return this.config.debug&&console.warn("[Mark] track called without event name"),!1;if(!this.hasConsent())return this.config.debug&&console.warn("[Mark] Tracking blocked due to consent requirement."),!1;const i=this.sanitizeTrackData(e),r={...i};"query"in r&&delete r.query,"site_id"in r&&delete r.site_id,"site_host"in r&&delete r.site_host;const n={event_name:t,...this.getIdentityFields(i),...r};s&&(n.is_conversion=!0);const a=i.site_id??this.siteId,o=i.site_host??this.siteHost;return a&&(n.site_id=a),o&&(n.site_host=o),this.config.include_page_context&&typeof window<"u"&&(this.applyPageContext(n),!o&&n.site&&(n.site_host=n.site)),this.deps.transport.send("/event",n),!0}identify(t,e={}){if(!t){this.config.debug&&console.warn("[Mark] identify called without userId");return}if(!this.hasConsent()){this.config.debug&&console.warn("[Mark] Identify blocked due to consent requirement.");return}const s={user_id:t,...this.sanitizeIdentifyTraits(e),...this.getIdentityFields()};this.siteId&&(s.site_id=this.siteId),this.siteHost&&(s.site_host=this.siteHost),this.deps.transport.send("/identify",s)}conversion(t,e={}){return this.trackInternal(t,e,!0)}getVisitorId(){return this.deps.storage.getVisitorId()}setConsent(t){this.deps.storage.setConsentStatus(t);const e={visitor_id:this.deps.storage.getVisitorId(),consent_state:t,source:"sdk"};this.siteId&&(e.site_id=this.siteId),this.siteHost&&(e.site_host=this.siteHost),this.deps.transport.send("/consent",e)}getIdentityFields(t){const e=t?.visitor_id??this.deps.storage.getVisitorId(),s=t?.click_id??this.deps.storage.getLastClickId(),i=t?.campaign_id??this.deps.storage.getCampaignId(),r=this.deps.storage.getQueryParams()??{},n=t?.query??{},a={...r,...n},o={};return e&&(o.visitor_id=e),s&&(o.click_id=s),i&&(o.campaign_id=i),Object.keys(a).length>0&&(o.query=a),o}hasConsent(){if(!this.consentRequirement)return!0;const e=this.deps.storage.getConsentStatus();return this.consentRequirement==="auto",e==="granted"}sanitizeTrackData(t){const e={};for(const[s,i]of Object.entries(t))l.has(s)||(e[s]=i);return e}sanitizeIdentifyTraits(t){const e={};for(const[s,i]of Object.entries(t))f.has(s)||(e[s]=i);return e}validateConfig(t){if(!t.key||!t.key.trim())throw new Error("[Mark] `key` must be a non-empty string.");if(t.endpoint)try{new URL(t.endpoint)}catch{throw new Error("[Mark] `endpoint` must be a valid absolute URL.")}if(typeof t.site_id=="string"&&!t.site_id.trim())throw new Error("[Mark] `site_id` cannot be an empty string.");if(typeof t.site_host=="string"&&!t.site_host.trim())throw new Error("[Mark] `site_host` cannot be an empty string.")}applyPageContext(t){t.page||t.title||t.referrer||t.site||(t.site=window.location.host,t.page=window.location.pathname,t.title=document.title,document.referrer&&(t.referrer=document.referrer))}}class _{config;endpoint;constructor(t){this.validateConfig(t),this.config=t,this.endpoint=t.endpoint??c}async send(t,e){const s=this.joinUrl(this.endpoint,t),i=this.config.key,r={"Content-Type":"application/json",[i.startsWith("sk_")?"x-secret-key":"x-publishable-key"]:i};if(this.config.debug&&console.log("[Mark] Sending",s,e),typeof fetch!="function"){this.config.debug&&console.error("[Mark] Global fetch is not available in this runtime.");return}try{const n=await fetch(s,{method:"POST",headers:r,body:JSON.stringify(e),keepalive:!0});if(!n.ok&&this.config.debug){const a=await this.readErrorSnippet(n);console.error("[Mark] Request rejected",{url:s,status:n.status,statusText:n.statusText,body:a})}}catch(n){this.config.debug&&console.error("[Mark] Failed to send",s,n)}}joinUrl(t,e){const s=t.replace(/\/+$/,""),i=e.replace(/^\/+/,"");return`${s}/${i}`}async readErrorSnippet(t){try{return(await t.text()).slice(0,300)}catch{return""}}validateConfig(t){if(!t.key||!t.key.trim())throw new Error("[Mark] `key` must be a non-empty string.");if(t.endpoint)try{new URL(t.endpoint)}catch{throw new Error("[Mark] `endpoint` must be a valid absolute URL.")}}}class u{constructor(t={}){this.defaults=t}getVisitorId(){return this.defaults.visitor_id}getLastClickId(){return this.defaults.last_click_id}getCampaignId(){return this.defaults.campaign_id}getQueryParams(){return this.defaults.query_params}getConsentStatus(){return this.defaults.consent_status}update(){}setConsentStatus(){}}class h{constructor(t){this.client=t}track(t,e={}){this.client.track(t,e)}conversion(t,e={}){this.client.conversion(t,e)}identify(t,e={}){this.client.identify(t,e)}setConsent(t){this.client.setConsent(t)}getVisitorId(){return this.client.getVisitorId()}}const p=(d,t={})=>{const e=t.storage??new u({visitor_id:t.storageDefaults?.visitor_id,last_click_id:t.storageDefaults?.last_click_id,campaign_id:t.storageDefaults?.campaign_id,query_params:t.storageDefaults?.query_params,consent_status:t.storageDefaults?.consent_status}),s=t.transport??new _(d),i=new g(d,{storage:e,transport:s});return new h(i)};exports.NodeMark=h;exports.StatelessStorage=u;exports.createNodeMark=p;