@gurulu/web 1.0.2 → 1.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/dist/activate-runtime.d.ts +15 -0
- package/dist/activate-runtime.d.ts.map +1 -0
- package/dist/activate-runtime.js +237 -0
- package/dist/activation.d.ts +106 -0
- package/dist/activation.d.ts.map +1 -0
- package/dist/autocapture/error.d.ts +14 -0
- package/dist/autocapture/error.d.ts.map +1 -0
- package/dist/autocapture/index.d.ts +2 -0
- package/dist/autocapture/index.d.ts.map +1 -1
- package/dist/core.d.ts +3 -0
- package/dist/core.d.ts.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +182 -1
- package/dist/react.d.ts +10 -0
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +1611 -0
- package/dist/t.js +7 -7
- package/dist/t.js.map +8 -6
- package/package.json +22 -2
package/dist/index.js
CHANGED
|
@@ -1,3 +1,132 @@
|
|
|
1
|
+
// src/activation.ts
|
|
2
|
+
function fnv1a32(str) {
|
|
3
|
+
let h = 2166136261;
|
|
4
|
+
for (let i = 0;i < str.length; i++) {
|
|
5
|
+
h ^= str.charCodeAt(i);
|
|
6
|
+
h = Math.imul(h, 16777619);
|
|
7
|
+
}
|
|
8
|
+
return h >>> 0;
|
|
9
|
+
}
|
|
10
|
+
function activationBucket(key, uid) {
|
|
11
|
+
return fnv1a32(`${key}:${uid}`) % 1e4 / 1e4;
|
|
12
|
+
}
|
|
13
|
+
var EMPTY = {
|
|
14
|
+
workspace_id: "",
|
|
15
|
+
popups: [],
|
|
16
|
+
tours: [],
|
|
17
|
+
personalizations: [],
|
|
18
|
+
experiments: []
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
class GuruluActivation {
|
|
22
|
+
cfg;
|
|
23
|
+
data = null;
|
|
24
|
+
pending = null;
|
|
25
|
+
exposed = new Set;
|
|
26
|
+
served = new Set;
|
|
27
|
+
constructor(cfg) {
|
|
28
|
+
this.cfg = cfg;
|
|
29
|
+
}
|
|
30
|
+
async refresh(opts) {
|
|
31
|
+
if (this.pending)
|
|
32
|
+
return this.pending;
|
|
33
|
+
const f = this.cfg.fetchImpl ?? (typeof fetch !== "undefined" ? fetch.bind(globalThis) : null);
|
|
34
|
+
if (!f)
|
|
35
|
+
return EMPTY;
|
|
36
|
+
const uid = this.cfg.getUid();
|
|
37
|
+
const sp = new URLSearchParams;
|
|
38
|
+
if (uid)
|
|
39
|
+
sp.set("uid", uid);
|
|
40
|
+
const winHref = typeof window !== "undefined" && window.location ? window.location.href : "";
|
|
41
|
+
const url = opts?.url ?? winHref;
|
|
42
|
+
if (url)
|
|
43
|
+
sp.set("url", url);
|
|
44
|
+
const ua = typeof navigator !== "undefined" && navigator.userAgent ? navigator.userAgent : "";
|
|
45
|
+
const device = opts?.device ?? (/Mobi|Android/i.test(ua) ? "mobile" : "desktop");
|
|
46
|
+
sp.set("device", device);
|
|
47
|
+
this.pending = (async () => {
|
|
48
|
+
try {
|
|
49
|
+
const res = await f(`${this.cfg.endpoint}/v1/activate?${sp.toString()}`, {
|
|
50
|
+
method: "GET",
|
|
51
|
+
headers: { Authorization: `Bearer ${this.cfg.workspaceKey}` },
|
|
52
|
+
credentials: "omit"
|
|
53
|
+
});
|
|
54
|
+
if (!res.ok)
|
|
55
|
+
return this.data ?? EMPTY;
|
|
56
|
+
this.data = await res.json();
|
|
57
|
+
return this.data;
|
|
58
|
+
} catch {
|
|
59
|
+
return this.data ?? EMPTY;
|
|
60
|
+
} finally {
|
|
61
|
+
this.pending = null;
|
|
62
|
+
}
|
|
63
|
+
})();
|
|
64
|
+
return this.pending;
|
|
65
|
+
}
|
|
66
|
+
getPopups() {
|
|
67
|
+
return this.data?.popups ?? [];
|
|
68
|
+
}
|
|
69
|
+
getTours() {
|
|
70
|
+
return this.data?.tours ?? [];
|
|
71
|
+
}
|
|
72
|
+
getContent(slot) {
|
|
73
|
+
const p = (this.data?.personalizations ?? []).find((x) => x.slot_key === slot);
|
|
74
|
+
if (!p)
|
|
75
|
+
return null;
|
|
76
|
+
if (!this.served.has(p.key)) {
|
|
77
|
+
this.served.add(p.key);
|
|
78
|
+
this.cfg.track("personalization_served", {
|
|
79
|
+
personalization_key: p.key,
|
|
80
|
+
variant_key: p.variant_key,
|
|
81
|
+
is_holdout: p.is_holdout
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
return p.content;
|
|
85
|
+
}
|
|
86
|
+
getVariant(experimentKey) {
|
|
87
|
+
const e = (this.data?.experiments ?? []).find((x) => x.key === experimentKey);
|
|
88
|
+
if (!e)
|
|
89
|
+
return null;
|
|
90
|
+
const variant = e.assigned_variant ?? assignLocalVariant(experimentKey, this.cfg.getUid(), e.variants);
|
|
91
|
+
if (variant && !this.exposed.has(experimentKey)) {
|
|
92
|
+
this.exposed.add(experimentKey);
|
|
93
|
+
this.cfg.track("experiment_exposed", {
|
|
94
|
+
experiment_key: experimentKey,
|
|
95
|
+
variant_key: variant
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
return variant;
|
|
99
|
+
}
|
|
100
|
+
trackPopup(key, action) {
|
|
101
|
+
this.cfg.track(`popup_${action}`, { popup_key: key });
|
|
102
|
+
}
|
|
103
|
+
trackTour(key, action, stepIndex) {
|
|
104
|
+
this.cfg.track(`tour_${action}`, {
|
|
105
|
+
tour_key: key,
|
|
106
|
+
...stepIndex !== undefined ? { step_index: stepIndex } : {}
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
async saveTourProgress(tourKey, body) {}
|
|
110
|
+
snapshot() {
|
|
111
|
+
return this.data ?? EMPTY;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
function assignLocalVariant(key, uid, variants) {
|
|
115
|
+
const valid = variants.filter((v) => v.weight > 0 && v.key.length > 0);
|
|
116
|
+
if (valid.length === 0 || !uid)
|
|
117
|
+
return null;
|
|
118
|
+
const total = valid.reduce((s, v) => s + v.weight, 0);
|
|
119
|
+
if (total <= 0)
|
|
120
|
+
return null;
|
|
121
|
+
const bucket = activationBucket(key, uid);
|
|
122
|
+
let cum = 0;
|
|
123
|
+
for (const v of valid) {
|
|
124
|
+
cum += v.weight / total;
|
|
125
|
+
if (bucket < cum)
|
|
126
|
+
return v.key;
|
|
127
|
+
}
|
|
128
|
+
return valid[valid.length - 1].key;
|
|
129
|
+
}
|
|
1
130
|
// src/consent.ts
|
|
2
131
|
var DEFAULT_API_URL = "https://api.gurulu.io";
|
|
3
132
|
var STORAGE_PREFIX = "gurulu_consent_";
|
|
@@ -351,6 +480,44 @@ function startClickAutocapture(track) {
|
|
|
351
480
|
};
|
|
352
481
|
}
|
|
353
482
|
|
|
483
|
+
// src/autocapture/error.ts
|
|
484
|
+
var MAX_STACK = 2000;
|
|
485
|
+
var MAX_MESSAGE = 1000;
|
|
486
|
+
function startErrorAutocapture(track) {
|
|
487
|
+
if (typeof window === "undefined")
|
|
488
|
+
return { stop: () => {
|
|
489
|
+
return;
|
|
490
|
+
} };
|
|
491
|
+
const onError = (ev) => {
|
|
492
|
+
const err = ev.error;
|
|
493
|
+
track({
|
|
494
|
+
message: String(ev.message ?? err?.message ?? "Error").slice(0, MAX_MESSAGE),
|
|
495
|
+
error_type: err?.name ?? "Error",
|
|
496
|
+
...ev.filename ? { source: ev.filename } : {},
|
|
497
|
+
...typeof ev.lineno === "number" ? { lineno: ev.lineno } : {},
|
|
498
|
+
...typeof ev.colno === "number" ? { colno: ev.colno } : {},
|
|
499
|
+
...err?.stack ? { stack: String(err.stack).slice(0, MAX_STACK) } : {}
|
|
500
|
+
});
|
|
501
|
+
};
|
|
502
|
+
const onRejection = (ev) => {
|
|
503
|
+
const reason = ev.reason;
|
|
504
|
+
const isErr = reason instanceof Error;
|
|
505
|
+
track({
|
|
506
|
+
message: String(isErr ? reason.message : reason).slice(0, MAX_MESSAGE),
|
|
507
|
+
error_type: isErr ? reason.name : "UnhandledRejection",
|
|
508
|
+
...isErr && reason.stack ? { stack: String(reason.stack).slice(0, MAX_STACK) } : {}
|
|
509
|
+
});
|
|
510
|
+
};
|
|
511
|
+
window.addEventListener("error", onError);
|
|
512
|
+
window.addEventListener("unhandledrejection", onRejection);
|
|
513
|
+
return {
|
|
514
|
+
stop() {
|
|
515
|
+
window.removeEventListener("error", onError);
|
|
516
|
+
window.removeEventListener("unhandledrejection", onRejection);
|
|
517
|
+
}
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
|
|
354
521
|
// src/autocapture/form.ts
|
|
355
522
|
var SENSITIVE_INPUT_TYPES = new Set(["password", "tel"]);
|
|
356
523
|
function isSensitiveField(el) {
|
|
@@ -701,6 +868,8 @@ function startAutocapture(cfg, sinks) {
|
|
|
701
868
|
handles.push(startScrollAutocapture(sinks.scrollDepth));
|
|
702
869
|
if (merged.web_vitals)
|
|
703
870
|
handles.push(startWebVitalsAutocapture(sinks.webVital));
|
|
871
|
+
if (merged.js_error)
|
|
872
|
+
handles.push(startErrorAutocapture(sinks.jsError));
|
|
704
873
|
return {
|
|
705
874
|
stopAll() {
|
|
706
875
|
for (const h of handles) {
|
|
@@ -1173,6 +1342,7 @@ class Gurulu {
|
|
|
1173
1342
|
autocaptureHandle = null;
|
|
1174
1343
|
testMode = false;
|
|
1175
1344
|
consent;
|
|
1345
|
+
activation;
|
|
1176
1346
|
init(opts) {
|
|
1177
1347
|
if (this.initialized) {
|
|
1178
1348
|
if (opts.debug && typeof console !== "undefined") {
|
|
@@ -1193,6 +1363,12 @@ class Gurulu {
|
|
|
1193
1363
|
autoBanner: opts.consent_mode === "banner_required"
|
|
1194
1364
|
});
|
|
1195
1365
|
this.consent.init();
|
|
1366
|
+
this.activation = new GuruluActivation({
|
|
1367
|
+
endpoint: opts.endpoint ?? DEFAULT_ENDPOINT,
|
|
1368
|
+
workspaceKey: opts.workspaceKey,
|
|
1369
|
+
getUid: () => getPersonId() ?? this.anonymousId ?? "",
|
|
1370
|
+
track: (key, props) => this.track(key, props)
|
|
1371
|
+
});
|
|
1196
1372
|
const transport = {
|
|
1197
1373
|
endpoint: opts.endpoint ?? DEFAULT_ENDPOINT,
|
|
1198
1374
|
workspaceKey: opts.workspaceKey,
|
|
@@ -1234,7 +1410,8 @@ class Gurulu {
|
|
|
1234
1410
|
formStarted: (p) => this.queueEvent("form_started", "interaction", p),
|
|
1235
1411
|
formSubmitted: (p) => this.queueEvent("form_submitted", "interaction", p),
|
|
1236
1412
|
scrollDepth: (p) => this.queueEvent("scroll_depth", "interaction", p),
|
|
1237
|
-
webVital: (p) => this.queueEvent("web_vital", "interaction", p)
|
|
1413
|
+
webVital: (p) => this.queueEvent("web_vital", "interaction", p),
|
|
1414
|
+
jsError: (p) => this.queueEvent("js_error", "interaction", p)
|
|
1238
1415
|
});
|
|
1239
1416
|
this.initialized = true;
|
|
1240
1417
|
}
|
|
@@ -1380,6 +1557,9 @@ var publicSdk = {
|
|
|
1380
1557
|
get consent() {
|
|
1381
1558
|
return singleton.consent;
|
|
1382
1559
|
},
|
|
1560
|
+
get activation() {
|
|
1561
|
+
return singleton.activation;
|
|
1562
|
+
},
|
|
1383
1563
|
VERSION
|
|
1384
1564
|
};
|
|
1385
1565
|
function autoBootstrap() {
|
|
@@ -1424,6 +1604,7 @@ export {
|
|
|
1424
1604
|
NetworkError,
|
|
1425
1605
|
InitError,
|
|
1426
1606
|
GuruluConsent,
|
|
1607
|
+
GuruluActivation,
|
|
1427
1608
|
Gurulu,
|
|
1428
1609
|
ConsentBlockedError
|
|
1429
1610
|
};
|
package/dist/react.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import type { GuruluPublicSDK, InitOptions } from './index.ts';
|
|
3
|
+
export type GuruluProviderProps = InitOptions & {
|
|
4
|
+
children: ReactNode;
|
|
5
|
+
};
|
|
6
|
+
/** Uygulama kökünü sar — mount'ta gurulu.init (idempotent). */
|
|
7
|
+
export declare function GuruluProvider({ children, ...opts }: GuruluProviderProps): ReactNode;
|
|
8
|
+
/** Singleton SDK — track/identify/page/... handler'larında kullan. */
|
|
9
|
+
export declare function useGurulu(): GuruluPublicSDK;
|
|
10
|
+
//# sourceMappingURL=react.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,SAAS,EAAa,MAAM,OAAO,CAAC;AAElD,OAAO,KAAK,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE/D,MAAM,MAAM,mBAAmB,GAAG,WAAW,GAAG;IAAE,QAAQ,EAAE,SAAS,CAAA;CAAE,CAAC;AAExE,+DAA+D;AAC/D,wBAAgB,cAAc,CAAC,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,EAAE,mBAAmB,GAAG,SAAS,CAOpF;AAED,sEAAsE;AACtE,wBAAgB,SAAS,IAAI,eAAe,CAE3C"}
|