@shellapps/experience 1.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/dist/index.d.mts +105 -0
- package/dist/index.d.ts +105 -0
- package/dist/index.js +480 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +453 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +41 -0
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
interface ExperienceConfig {
|
|
2
|
+
appId: string;
|
|
3
|
+
apiKey: string;
|
|
4
|
+
endpoint?: string;
|
|
5
|
+
enableTracking?: boolean;
|
|
6
|
+
enableErrorCapture?: boolean;
|
|
7
|
+
enableHeatmaps?: boolean;
|
|
8
|
+
enableBreadcrumbs?: boolean;
|
|
9
|
+
sampleRate?: number;
|
|
10
|
+
batchIntervalMs?: number;
|
|
11
|
+
maxBatchSize?: number;
|
|
12
|
+
debug?: boolean;
|
|
13
|
+
}
|
|
14
|
+
interface PageContext {
|
|
15
|
+
path: string;
|
|
16
|
+
title: string;
|
|
17
|
+
referrer: string;
|
|
18
|
+
viewportWidth: number;
|
|
19
|
+
viewportHeight: number;
|
|
20
|
+
}
|
|
21
|
+
interface ElementContext {
|
|
22
|
+
tag: string;
|
|
23
|
+
text: string;
|
|
24
|
+
x: number;
|
|
25
|
+
y: number;
|
|
26
|
+
}
|
|
27
|
+
interface ExperienceEvent {
|
|
28
|
+
eventId: string;
|
|
29
|
+
appId: string;
|
|
30
|
+
profileId: string;
|
|
31
|
+
sessionId: string;
|
|
32
|
+
timestampMs: number;
|
|
33
|
+
eventType: string;
|
|
34
|
+
pageUrl: string;
|
|
35
|
+
elementTid: string;
|
|
36
|
+
metadata: Record<string, unknown>;
|
|
37
|
+
pageContext: PageContext;
|
|
38
|
+
elementContext: ElementContext | null;
|
|
39
|
+
}
|
|
40
|
+
interface ErrorReport {
|
|
41
|
+
eventId: string;
|
|
42
|
+
appId: string;
|
|
43
|
+
profileId: string;
|
|
44
|
+
sessionId: string;
|
|
45
|
+
timestampMs: number;
|
|
46
|
+
message: string;
|
|
47
|
+
stack: string;
|
|
48
|
+
severity: string;
|
|
49
|
+
extra: Record<string, unknown>;
|
|
50
|
+
pageContext: PageContext;
|
|
51
|
+
breadcrumbs: Breadcrumb[];
|
|
52
|
+
}
|
|
53
|
+
interface Breadcrumb {
|
|
54
|
+
type: string;
|
|
55
|
+
category: string;
|
|
56
|
+
message: string;
|
|
57
|
+
timestampMs: number;
|
|
58
|
+
data?: Record<string, unknown>;
|
|
59
|
+
}
|
|
60
|
+
interface HeatmapEntry {
|
|
61
|
+
x: number;
|
|
62
|
+
y: number;
|
|
63
|
+
type: 'move' | 'click';
|
|
64
|
+
timestampMs: number;
|
|
65
|
+
viewportWidth: number;
|
|
66
|
+
viewportHeight: number;
|
|
67
|
+
pageUrl: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
declare class Experience {
|
|
71
|
+
private config;
|
|
72
|
+
private sessionId;
|
|
73
|
+
private profileId;
|
|
74
|
+
private eventQueue;
|
|
75
|
+
private errorQueue;
|
|
76
|
+
private batchTimer;
|
|
77
|
+
private breadcrumbs;
|
|
78
|
+
private flags;
|
|
79
|
+
private flagsFetched;
|
|
80
|
+
private teardownFns;
|
|
81
|
+
private destroyed;
|
|
82
|
+
private constructor();
|
|
83
|
+
static init(config: ExperienceConfig): Experience;
|
|
84
|
+
private log;
|
|
85
|
+
private setup;
|
|
86
|
+
track(eventName: string, metadata?: Record<string, unknown>): void;
|
|
87
|
+
trackPageView(): void;
|
|
88
|
+
captureError(error: Error, extra?: Record<string, unknown>): void;
|
|
89
|
+
captureMessage(msg: string, severity?: string): void;
|
|
90
|
+
getFlag<T>(name: string, defaultValue: T): T;
|
|
91
|
+
identify(profileId: string): void;
|
|
92
|
+
shutdown(): Promise<void>;
|
|
93
|
+
private enqueueEvent;
|
|
94
|
+
private enqueueError;
|
|
95
|
+
private flush;
|
|
96
|
+
private flushSync;
|
|
97
|
+
private sendOrQueue;
|
|
98
|
+
private sendRequest;
|
|
99
|
+
private fetchFlags;
|
|
100
|
+
private setupErrorCapture;
|
|
101
|
+
private setupAutoPageViews;
|
|
102
|
+
private setupHeatmaps;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export { type Breadcrumb, type ElementContext, type ErrorReport, Experience, type ExperienceConfig, type ExperienceEvent, type HeatmapEntry, type PageContext };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
interface ExperienceConfig {
|
|
2
|
+
appId: string;
|
|
3
|
+
apiKey: string;
|
|
4
|
+
endpoint?: string;
|
|
5
|
+
enableTracking?: boolean;
|
|
6
|
+
enableErrorCapture?: boolean;
|
|
7
|
+
enableHeatmaps?: boolean;
|
|
8
|
+
enableBreadcrumbs?: boolean;
|
|
9
|
+
sampleRate?: number;
|
|
10
|
+
batchIntervalMs?: number;
|
|
11
|
+
maxBatchSize?: number;
|
|
12
|
+
debug?: boolean;
|
|
13
|
+
}
|
|
14
|
+
interface PageContext {
|
|
15
|
+
path: string;
|
|
16
|
+
title: string;
|
|
17
|
+
referrer: string;
|
|
18
|
+
viewportWidth: number;
|
|
19
|
+
viewportHeight: number;
|
|
20
|
+
}
|
|
21
|
+
interface ElementContext {
|
|
22
|
+
tag: string;
|
|
23
|
+
text: string;
|
|
24
|
+
x: number;
|
|
25
|
+
y: number;
|
|
26
|
+
}
|
|
27
|
+
interface ExperienceEvent {
|
|
28
|
+
eventId: string;
|
|
29
|
+
appId: string;
|
|
30
|
+
profileId: string;
|
|
31
|
+
sessionId: string;
|
|
32
|
+
timestampMs: number;
|
|
33
|
+
eventType: string;
|
|
34
|
+
pageUrl: string;
|
|
35
|
+
elementTid: string;
|
|
36
|
+
metadata: Record<string, unknown>;
|
|
37
|
+
pageContext: PageContext;
|
|
38
|
+
elementContext: ElementContext | null;
|
|
39
|
+
}
|
|
40
|
+
interface ErrorReport {
|
|
41
|
+
eventId: string;
|
|
42
|
+
appId: string;
|
|
43
|
+
profileId: string;
|
|
44
|
+
sessionId: string;
|
|
45
|
+
timestampMs: number;
|
|
46
|
+
message: string;
|
|
47
|
+
stack: string;
|
|
48
|
+
severity: string;
|
|
49
|
+
extra: Record<string, unknown>;
|
|
50
|
+
pageContext: PageContext;
|
|
51
|
+
breadcrumbs: Breadcrumb[];
|
|
52
|
+
}
|
|
53
|
+
interface Breadcrumb {
|
|
54
|
+
type: string;
|
|
55
|
+
category: string;
|
|
56
|
+
message: string;
|
|
57
|
+
timestampMs: number;
|
|
58
|
+
data?: Record<string, unknown>;
|
|
59
|
+
}
|
|
60
|
+
interface HeatmapEntry {
|
|
61
|
+
x: number;
|
|
62
|
+
y: number;
|
|
63
|
+
type: 'move' | 'click';
|
|
64
|
+
timestampMs: number;
|
|
65
|
+
viewportWidth: number;
|
|
66
|
+
viewportHeight: number;
|
|
67
|
+
pageUrl: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
declare class Experience {
|
|
71
|
+
private config;
|
|
72
|
+
private sessionId;
|
|
73
|
+
private profileId;
|
|
74
|
+
private eventQueue;
|
|
75
|
+
private errorQueue;
|
|
76
|
+
private batchTimer;
|
|
77
|
+
private breadcrumbs;
|
|
78
|
+
private flags;
|
|
79
|
+
private flagsFetched;
|
|
80
|
+
private teardownFns;
|
|
81
|
+
private destroyed;
|
|
82
|
+
private constructor();
|
|
83
|
+
static init(config: ExperienceConfig): Experience;
|
|
84
|
+
private log;
|
|
85
|
+
private setup;
|
|
86
|
+
track(eventName: string, metadata?: Record<string, unknown>): void;
|
|
87
|
+
trackPageView(): void;
|
|
88
|
+
captureError(error: Error, extra?: Record<string, unknown>): void;
|
|
89
|
+
captureMessage(msg: string, severity?: string): void;
|
|
90
|
+
getFlag<T>(name: string, defaultValue: T): T;
|
|
91
|
+
identify(profileId: string): void;
|
|
92
|
+
shutdown(): Promise<void>;
|
|
93
|
+
private enqueueEvent;
|
|
94
|
+
private enqueueError;
|
|
95
|
+
private flush;
|
|
96
|
+
private flushSync;
|
|
97
|
+
private sendOrQueue;
|
|
98
|
+
private sendRequest;
|
|
99
|
+
private fetchFlags;
|
|
100
|
+
private setupErrorCapture;
|
|
101
|
+
private setupAutoPageViews;
|
|
102
|
+
private setupHeatmaps;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export { type Breadcrumb, type ElementContext, type ErrorReport, Experience, type ExperienceConfig, type ExperienceEvent, type HeatmapEntry, type PageContext };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
Experience: () => Experience
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(index_exports);
|
|
26
|
+
|
|
27
|
+
// src/breadcrumbs.ts
|
|
28
|
+
var MAX_BREADCRUMBS = 50;
|
|
29
|
+
var BreadcrumbManager = class {
|
|
30
|
+
constructor() {
|
|
31
|
+
this.buffer = [];
|
|
32
|
+
this.originalFetch = null;
|
|
33
|
+
this.originalXhrOpen = null;
|
|
34
|
+
this.clickHandler = null;
|
|
35
|
+
this.originalConsole = {
|
|
36
|
+
log: console.log,
|
|
37
|
+
warn: console.warn,
|
|
38
|
+
error: console.error
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
add(breadcrumb) {
|
|
42
|
+
this.buffer.push({ ...breadcrumb, timestampMs: Date.now() });
|
|
43
|
+
if (this.buffer.length > MAX_BREADCRUMBS) {
|
|
44
|
+
this.buffer.shift();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
getAll() {
|
|
48
|
+
return [...this.buffer];
|
|
49
|
+
}
|
|
50
|
+
install() {
|
|
51
|
+
["log", "warn", "error"].forEach((level) => {
|
|
52
|
+
const original = this.originalConsole[level];
|
|
53
|
+
console[level] = (...args) => {
|
|
54
|
+
this.add({
|
|
55
|
+
type: "console",
|
|
56
|
+
category: level,
|
|
57
|
+
message: args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ")
|
|
58
|
+
});
|
|
59
|
+
original.apply(console, args);
|
|
60
|
+
};
|
|
61
|
+
});
|
|
62
|
+
if (typeof fetch !== "undefined") {
|
|
63
|
+
this.originalFetch = fetch;
|
|
64
|
+
const self = this;
|
|
65
|
+
window.fetch = function(input, init) {
|
|
66
|
+
const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
|
|
67
|
+
const method = init?.method || "GET";
|
|
68
|
+
self.add({ type: "http", category: "fetch", message: `${method} ${url}` });
|
|
69
|
+
return self.originalFetch.call(window, input, init);
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
if (typeof XMLHttpRequest !== "undefined") {
|
|
73
|
+
this.originalXhrOpen = XMLHttpRequest.prototype.open;
|
|
74
|
+
const self = this;
|
|
75
|
+
XMLHttpRequest.prototype.open = function(method, url) {
|
|
76
|
+
self.add({ type: "http", category: "xhr", message: `${method} ${url}` });
|
|
77
|
+
return self.originalXhrOpen.apply(this, arguments);
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
this.clickHandler = (e) => {
|
|
81
|
+
const target = e.target;
|
|
82
|
+
if (!target) return;
|
|
83
|
+
const tag = target.tagName?.toLowerCase() || "";
|
|
84
|
+
const text = (target.textContent || "").slice(0, 100);
|
|
85
|
+
this.add({ type: "ui", category: "click", message: `${tag}: ${text}` });
|
|
86
|
+
};
|
|
87
|
+
document.addEventListener("click", this.clickHandler, true);
|
|
88
|
+
}
|
|
89
|
+
teardown() {
|
|
90
|
+
console.log = this.originalConsole.log;
|
|
91
|
+
console.warn = this.originalConsole.warn;
|
|
92
|
+
console.error = this.originalConsole.error;
|
|
93
|
+
if (this.originalFetch) {
|
|
94
|
+
window.fetch = this.originalFetch;
|
|
95
|
+
}
|
|
96
|
+
if (this.originalXhrOpen) {
|
|
97
|
+
XMLHttpRequest.prototype.open = this.originalXhrOpen;
|
|
98
|
+
}
|
|
99
|
+
if (this.clickHandler) {
|
|
100
|
+
document.removeEventListener("click", this.clickHandler, true);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// src/offline.ts
|
|
106
|
+
var DB_NAME = "shellapps_experience";
|
|
107
|
+
var STORE_NAME = "offline_queue";
|
|
108
|
+
var DB_VERSION = 1;
|
|
109
|
+
function openDB() {
|
|
110
|
+
return new Promise((resolve, reject) => {
|
|
111
|
+
const request = indexedDB.open(DB_NAME, DB_VERSION);
|
|
112
|
+
request.onupgradeneeded = () => {
|
|
113
|
+
const db = request.result;
|
|
114
|
+
if (!db.objectStoreNames.contains(STORE_NAME)) {
|
|
115
|
+
db.createObjectStore(STORE_NAME, { autoIncrement: true });
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
request.onsuccess = () => resolve(request.result);
|
|
119
|
+
request.onerror = () => reject(request.error);
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
async function enqueueOffline(data) {
|
|
123
|
+
const db = await openDB();
|
|
124
|
+
const tx = db.transaction(STORE_NAME, "readwrite");
|
|
125
|
+
tx.objectStore(STORE_NAME).add(data);
|
|
126
|
+
await new Promise((resolve, reject) => {
|
|
127
|
+
tx.oncomplete = () => resolve();
|
|
128
|
+
tx.onerror = () => reject(tx.error);
|
|
129
|
+
});
|
|
130
|
+
db.close();
|
|
131
|
+
}
|
|
132
|
+
async function drainOfflineQueue(sendFn) {
|
|
133
|
+
const db = await openDB();
|
|
134
|
+
const tx = db.transaction(STORE_NAME, "readwrite");
|
|
135
|
+
const store = tx.objectStore(STORE_NAME);
|
|
136
|
+
const request = store.getAll();
|
|
137
|
+
await new Promise((resolve, reject) => {
|
|
138
|
+
request.onsuccess = async () => {
|
|
139
|
+
const items = request.result;
|
|
140
|
+
for (const item of items) {
|
|
141
|
+
try {
|
|
142
|
+
await sendFn(item.url, item.body);
|
|
143
|
+
} catch {
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
store.clear();
|
|
148
|
+
resolve();
|
|
149
|
+
};
|
|
150
|
+
request.onerror = () => reject(request.error);
|
|
151
|
+
});
|
|
152
|
+
db.close();
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// src/utils.ts
|
|
156
|
+
function generateUUID() {
|
|
157
|
+
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
158
|
+
return crypto.randomUUID();
|
|
159
|
+
}
|
|
160
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
161
|
+
const r = Math.random() * 16 | 0;
|
|
162
|
+
const v = c === "x" ? r : r & 3 | 8;
|
|
163
|
+
return v.toString(16);
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
function getPageContext() {
|
|
167
|
+
return {
|
|
168
|
+
path: location.pathname,
|
|
169
|
+
title: document.title,
|
|
170
|
+
referrer: document.referrer,
|
|
171
|
+
viewportWidth: window.innerWidth,
|
|
172
|
+
viewportHeight: window.innerHeight
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
function throttle(fn, ms) {
|
|
176
|
+
let last = 0;
|
|
177
|
+
return ((...args) => {
|
|
178
|
+
const now = Date.now();
|
|
179
|
+
if (now - last >= ms) {
|
|
180
|
+
last = now;
|
|
181
|
+
fn(...args);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// src/experience.ts
|
|
187
|
+
var DEFAULT_CONFIG = {
|
|
188
|
+
endpoint: "",
|
|
189
|
+
enableTracking: true,
|
|
190
|
+
enableErrorCapture: true,
|
|
191
|
+
enableHeatmaps: false,
|
|
192
|
+
enableBreadcrumbs: true,
|
|
193
|
+
sampleRate: 1,
|
|
194
|
+
batchIntervalMs: 5e3,
|
|
195
|
+
maxBatchSize: 50,
|
|
196
|
+
debug: false
|
|
197
|
+
};
|
|
198
|
+
var Experience = class _Experience {
|
|
199
|
+
constructor(config) {
|
|
200
|
+
this.profileId = "";
|
|
201
|
+
this.eventQueue = [];
|
|
202
|
+
this.errorQueue = [];
|
|
203
|
+
this.batchTimer = null;
|
|
204
|
+
this.flags = {};
|
|
205
|
+
this.flagsFetched = false;
|
|
206
|
+
this.teardownFns = [];
|
|
207
|
+
this.destroyed = false;
|
|
208
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
209
|
+
this.breadcrumbs = new BreadcrumbManager();
|
|
210
|
+
const stored = typeof sessionStorage !== "undefined" ? sessionStorage.getItem("exp_session_id") : null;
|
|
211
|
+
if (stored) {
|
|
212
|
+
this.sessionId = stored;
|
|
213
|
+
} else {
|
|
214
|
+
this.sessionId = generateUUID();
|
|
215
|
+
if (typeof sessionStorage !== "undefined") {
|
|
216
|
+
sessionStorage.setItem("exp_session_id", this.sessionId);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
static init(config) {
|
|
221
|
+
const instance = new _Experience(config);
|
|
222
|
+
instance.setup();
|
|
223
|
+
return instance;
|
|
224
|
+
}
|
|
225
|
+
log(...args) {
|
|
226
|
+
if (this.config.debug) {
|
|
227
|
+
console.log("[Experience]", ...args);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
setup() {
|
|
231
|
+
if (Math.random() > this.config.sampleRate) {
|
|
232
|
+
this.log("Sampled out");
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
if (this.config.enableBreadcrumbs) {
|
|
236
|
+
this.breadcrumbs.install();
|
|
237
|
+
}
|
|
238
|
+
if (this.config.enableErrorCapture) {
|
|
239
|
+
this.setupErrorCapture();
|
|
240
|
+
}
|
|
241
|
+
if (this.config.enableTracking) {
|
|
242
|
+
this.setupAutoPageViews();
|
|
243
|
+
}
|
|
244
|
+
if (this.config.enableHeatmaps) {
|
|
245
|
+
this.setupHeatmaps();
|
|
246
|
+
}
|
|
247
|
+
this.batchTimer = setInterval(() => this.flush(), this.config.batchIntervalMs);
|
|
248
|
+
const beforeUnload = () => this.flushSync();
|
|
249
|
+
window.addEventListener("beforeunload", beforeUnload);
|
|
250
|
+
this.teardownFns.push(() => window.removeEventListener("beforeunload", beforeUnload));
|
|
251
|
+
const onlineHandler = () => {
|
|
252
|
+
this.log("Back online, draining queue");
|
|
253
|
+
drainOfflineQueue((url, body) => this.sendRequest(url, body)).catch(() => {
|
|
254
|
+
});
|
|
255
|
+
};
|
|
256
|
+
window.addEventListener("online", onlineHandler);
|
|
257
|
+
this.teardownFns.push(() => window.removeEventListener("online", onlineHandler));
|
|
258
|
+
this.fetchFlags().catch(() => {
|
|
259
|
+
});
|
|
260
|
+
this.log("Initialized", this.config.appId);
|
|
261
|
+
}
|
|
262
|
+
// --- Public API ---
|
|
263
|
+
track(eventName, metadata) {
|
|
264
|
+
if (this.destroyed) return;
|
|
265
|
+
this.enqueueEvent("custom", eventName, metadata);
|
|
266
|
+
}
|
|
267
|
+
trackPageView() {
|
|
268
|
+
if (this.destroyed) return;
|
|
269
|
+
this.enqueueEvent("page_view", "page_view");
|
|
270
|
+
this.breadcrumbs.add({ type: "navigation", category: "page_view", message: location.href });
|
|
271
|
+
}
|
|
272
|
+
captureError(error, extra) {
|
|
273
|
+
if (this.destroyed) return;
|
|
274
|
+
this.enqueueError(error.message, error.stack || "", "error", extra);
|
|
275
|
+
}
|
|
276
|
+
captureMessage(msg, severity = "info") {
|
|
277
|
+
if (this.destroyed) return;
|
|
278
|
+
this.enqueueError(msg, "", severity);
|
|
279
|
+
}
|
|
280
|
+
getFlag(name, defaultValue) {
|
|
281
|
+
if (!this.flagsFetched || !(name in this.flags)) return defaultValue;
|
|
282
|
+
return this.flags[name];
|
|
283
|
+
}
|
|
284
|
+
identify(profileId) {
|
|
285
|
+
this.profileId = profileId;
|
|
286
|
+
this.log("Identified", profileId);
|
|
287
|
+
}
|
|
288
|
+
async shutdown() {
|
|
289
|
+
if (this.destroyed) return;
|
|
290
|
+
this.destroyed = true;
|
|
291
|
+
if (this.batchTimer) {
|
|
292
|
+
clearInterval(this.batchTimer);
|
|
293
|
+
this.batchTimer = null;
|
|
294
|
+
}
|
|
295
|
+
await this.flush();
|
|
296
|
+
this.breadcrumbs.teardown();
|
|
297
|
+
this.teardownFns.forEach((fn) => fn());
|
|
298
|
+
this.teardownFns = [];
|
|
299
|
+
this.log("Shutdown complete");
|
|
300
|
+
}
|
|
301
|
+
// --- Internal ---
|
|
302
|
+
enqueueEvent(eventType, eventName, metadata) {
|
|
303
|
+
const event = {
|
|
304
|
+
eventId: generateUUID(),
|
|
305
|
+
appId: this.config.appId,
|
|
306
|
+
profileId: this.profileId,
|
|
307
|
+
sessionId: this.sessionId,
|
|
308
|
+
timestampMs: Date.now(),
|
|
309
|
+
eventType,
|
|
310
|
+
pageUrl: location.href,
|
|
311
|
+
elementTid: metadata?.elementTid || "",
|
|
312
|
+
metadata: { eventName, ...metadata },
|
|
313
|
+
pageContext: getPageContext(),
|
|
314
|
+
elementContext: null
|
|
315
|
+
};
|
|
316
|
+
this.eventQueue.push(event);
|
|
317
|
+
if (this.eventQueue.length >= this.config.maxBatchSize) {
|
|
318
|
+
this.flush().catch(() => {
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
enqueueError(message, stack, severity, extra) {
|
|
323
|
+
const report = {
|
|
324
|
+
eventId: generateUUID(),
|
|
325
|
+
appId: this.config.appId,
|
|
326
|
+
profileId: this.profileId,
|
|
327
|
+
sessionId: this.sessionId,
|
|
328
|
+
timestampMs: Date.now(),
|
|
329
|
+
message,
|
|
330
|
+
stack,
|
|
331
|
+
severity,
|
|
332
|
+
extra: extra || {},
|
|
333
|
+
pageContext: getPageContext(),
|
|
334
|
+
breadcrumbs: this.breadcrumbs.getAll()
|
|
335
|
+
};
|
|
336
|
+
this.errorQueue.push(report);
|
|
337
|
+
}
|
|
338
|
+
async flush() {
|
|
339
|
+
if (this.eventQueue.length > 0) {
|
|
340
|
+
const events = this.eventQueue.splice(0);
|
|
341
|
+
const url = `${this.config.endpoint}/api/v1/ingest/events`;
|
|
342
|
+
const body = JSON.stringify({ events });
|
|
343
|
+
await this.sendOrQueue(url, body);
|
|
344
|
+
}
|
|
345
|
+
if (this.errorQueue.length > 0) {
|
|
346
|
+
const errors = this.errorQueue.splice(0);
|
|
347
|
+
const url = `${this.config.endpoint}/api/v1/ingest/errors`;
|
|
348
|
+
const body = JSON.stringify({ errors });
|
|
349
|
+
await this.sendOrQueue(url, body);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
flushSync() {
|
|
353
|
+
if (this.eventQueue.length > 0) {
|
|
354
|
+
const url = `${this.config.endpoint}/api/v1/ingest/events`;
|
|
355
|
+
const body = JSON.stringify({ events: this.eventQueue.splice(0) });
|
|
356
|
+
if (typeof navigator.sendBeacon === "function") {
|
|
357
|
+
navigator.sendBeacon(url, body);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
if (this.errorQueue.length > 0) {
|
|
361
|
+
const url = `${this.config.endpoint}/api/v1/ingest/errors`;
|
|
362
|
+
const body = JSON.stringify({ errors: this.errorQueue.splice(0) });
|
|
363
|
+
if (typeof navigator.sendBeacon === "function") {
|
|
364
|
+
navigator.sendBeacon(url, body);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
async sendOrQueue(url, body) {
|
|
369
|
+
if (typeof navigator !== "undefined" && !navigator.onLine) {
|
|
370
|
+
await enqueueOffline({ url, body });
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
try {
|
|
374
|
+
await this.sendRequest(url, body);
|
|
375
|
+
} catch {
|
|
376
|
+
await enqueueOffline({ url, body }).catch(() => {
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
async sendRequest(url, body) {
|
|
381
|
+
const response = await fetch(url, {
|
|
382
|
+
method: "POST",
|
|
383
|
+
headers: {
|
|
384
|
+
"Content-Type": "application/json",
|
|
385
|
+
"Authorization": `Bearer ${this.config.apiKey}`
|
|
386
|
+
},
|
|
387
|
+
body
|
|
388
|
+
});
|
|
389
|
+
if (!response.ok) {
|
|
390
|
+
throw new Error(`HTTP ${response.status}`);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
async fetchFlags() {
|
|
394
|
+
try {
|
|
395
|
+
const url = `${this.config.endpoint}/api/v1/flags/${this.config.appId}`;
|
|
396
|
+
const response = await fetch(url, {
|
|
397
|
+
headers: { "Authorization": `Bearer ${this.config.apiKey}` }
|
|
398
|
+
});
|
|
399
|
+
if (response.ok) {
|
|
400
|
+
this.flags = await response.json();
|
|
401
|
+
this.flagsFetched = true;
|
|
402
|
+
this.log("Flags loaded", this.flags);
|
|
403
|
+
}
|
|
404
|
+
} catch {
|
|
405
|
+
this.log("Failed to fetch flags");
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
// --- Auto features ---
|
|
409
|
+
setupErrorCapture() {
|
|
410
|
+
const onError = (event) => {
|
|
411
|
+
this.enqueueError(
|
|
412
|
+
event.message,
|
|
413
|
+
event.error?.stack || "",
|
|
414
|
+
"error",
|
|
415
|
+
{ filename: event.filename, lineno: event.lineno, colno: event.colno }
|
|
416
|
+
);
|
|
417
|
+
};
|
|
418
|
+
window.addEventListener("error", onError);
|
|
419
|
+
this.teardownFns.push(() => window.removeEventListener("error", onError));
|
|
420
|
+
const onUnhandledRejection = (event) => {
|
|
421
|
+
const msg = event.reason instanceof Error ? event.reason.message : String(event.reason);
|
|
422
|
+
const stack = event.reason instanceof Error ? event.reason.stack || "" : "";
|
|
423
|
+
this.enqueueError(msg, stack, "error", { type: "unhandledrejection" });
|
|
424
|
+
};
|
|
425
|
+
window.addEventListener("unhandledrejection", onUnhandledRejection);
|
|
426
|
+
this.teardownFns.push(() => window.removeEventListener("unhandledrejection", onUnhandledRejection));
|
|
427
|
+
}
|
|
428
|
+
setupAutoPageViews() {
|
|
429
|
+
const originalPushState = history.pushState.bind(history);
|
|
430
|
+
const originalReplaceState = history.replaceState.bind(history);
|
|
431
|
+
history.pushState = (...args) => {
|
|
432
|
+
originalPushState(...args);
|
|
433
|
+
this.trackPageView();
|
|
434
|
+
};
|
|
435
|
+
history.replaceState = (...args) => {
|
|
436
|
+
originalReplaceState(...args);
|
|
437
|
+
this.trackPageView();
|
|
438
|
+
};
|
|
439
|
+
this.teardownFns.push(() => {
|
|
440
|
+
history.pushState = originalPushState;
|
|
441
|
+
history.replaceState = originalReplaceState;
|
|
442
|
+
});
|
|
443
|
+
const onPopState = () => this.trackPageView();
|
|
444
|
+
window.addEventListener("popstate", onPopState);
|
|
445
|
+
this.teardownFns.push(() => window.removeEventListener("popstate", onPopState));
|
|
446
|
+
this.trackPageView();
|
|
447
|
+
}
|
|
448
|
+
setupHeatmaps() {
|
|
449
|
+
const handleMove = throttle((e) => {
|
|
450
|
+
this.enqueueEvent("heatmap_move", "mousemove", {
|
|
451
|
+
x: e.clientX,
|
|
452
|
+
y: e.clientY,
|
|
453
|
+
viewportWidth: window.innerWidth,
|
|
454
|
+
viewportHeight: window.innerHeight
|
|
455
|
+
});
|
|
456
|
+
}, 100);
|
|
457
|
+
const handleClick = (e) => {
|
|
458
|
+
const target = e.target;
|
|
459
|
+
this.enqueueEvent("heatmap_click", "click", {
|
|
460
|
+
x: e.clientX,
|
|
461
|
+
y: e.clientY,
|
|
462
|
+
viewportWidth: window.innerWidth,
|
|
463
|
+
viewportHeight: window.innerHeight,
|
|
464
|
+
tag: target?.tagName?.toLowerCase() || "",
|
|
465
|
+
text: (target?.textContent || "").slice(0, 100)
|
|
466
|
+
});
|
|
467
|
+
};
|
|
468
|
+
document.addEventListener("mousemove", handleMove);
|
|
469
|
+
document.addEventListener("click", handleClick);
|
|
470
|
+
this.teardownFns.push(() => {
|
|
471
|
+
document.removeEventListener("mousemove", handleMove);
|
|
472
|
+
document.removeEventListener("click", handleClick);
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
};
|
|
476
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
477
|
+
0 && (module.exports = {
|
|
478
|
+
Experience
|
|
479
|
+
});
|
|
480
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/breadcrumbs.ts","../src/offline.ts","../src/utils.ts","../src/experience.ts"],"sourcesContent":["export { Experience } from './experience';\nexport type {\n ExperienceConfig,\n ExperienceEvent,\n ErrorReport,\n Breadcrumb,\n PageContext,\n ElementContext,\n HeatmapEntry,\n} from './types';\n","import type { Breadcrumb } from './types';\n\nconst MAX_BREADCRUMBS = 50;\n\nexport class BreadcrumbManager {\n private buffer: Breadcrumb[] = [];\n private originalConsole: Pick<Console, 'log' | 'warn' | 'error'>;\n private originalFetch: typeof fetch | null = null;\n private originalXhrOpen: typeof XMLHttpRequest.prototype.open | null = null;\n private clickHandler: ((e: MouseEvent) => void) | null = null;\n\n constructor() {\n this.originalConsole = {\n log: console.log,\n warn: console.warn,\n error: console.error,\n };\n }\n\n add(breadcrumb: Omit<Breadcrumb, 'timestampMs'>): void {\n this.buffer.push({ ...breadcrumb, timestampMs: Date.now() });\n if (this.buffer.length > MAX_BREADCRUMBS) {\n this.buffer.shift();\n }\n }\n\n getAll(): Breadcrumb[] {\n return [...this.buffer];\n }\n\n install(): void {\n (['log', 'warn', 'error'] as const).forEach((level) => {\n const original = this.originalConsole[level];\n console[level] = (...args: unknown[]) => {\n this.add({\n type: 'console',\n category: level,\n message: args.map((a) => (typeof a === 'string' ? a : JSON.stringify(a))).join(' '),\n });\n original.apply(console, args);\n };\n });\n\n if (typeof fetch !== 'undefined') {\n this.originalFetch = fetch;\n const self = this;\n (window as unknown as Record<string, unknown>).fetch = function (input: RequestInfo | URL, init?: RequestInit) {\n const url = typeof input === 'string' ? input : input instanceof URL ? input.toString() : input.url;\n const method = init?.method || 'GET';\n self.add({ type: 'http', category: 'fetch', message: `${method} ${url}` });\n return self.originalFetch!.call(window, input, init);\n };\n }\n\n if (typeof XMLHttpRequest !== 'undefined') {\n this.originalXhrOpen = XMLHttpRequest.prototype.open;\n const self = this;\n XMLHttpRequest.prototype.open = function (method: string, url: string | URL) {\n self.add({ type: 'http', category: 'xhr', message: `${method} ${url}` });\n return self.originalXhrOpen!.apply(this, arguments as unknown as Parameters<typeof XMLHttpRequest.prototype.open>);\n };\n }\n\n this.clickHandler = (e: MouseEvent) => {\n const target = e.target as HTMLElement;\n if (!target) return;\n const tag = target.tagName?.toLowerCase() || '';\n const text = (target.textContent || '').slice(0, 100);\n this.add({ type: 'ui', category: 'click', message: `${tag}: ${text}` });\n };\n document.addEventListener('click', this.clickHandler, true);\n }\n\n teardown(): void {\n console.log = this.originalConsole.log;\n console.warn = this.originalConsole.warn;\n console.error = this.originalConsole.error;\n\n if (this.originalFetch) {\n (window as unknown as Record<string, unknown>).fetch = this.originalFetch;\n }\n if (this.originalXhrOpen) {\n XMLHttpRequest.prototype.open = this.originalXhrOpen;\n }\n if (this.clickHandler) {\n document.removeEventListener('click', this.clickHandler, true);\n }\n }\n}\n","const DB_NAME = 'shellapps_experience';\nconst STORE_NAME = 'offline_queue';\nconst DB_VERSION = 1;\n\nfunction openDB(): Promise<IDBDatabase> {\n return new Promise((resolve, reject) => {\n const request = indexedDB.open(DB_NAME, DB_VERSION);\n request.onupgradeneeded = () => {\n const db = request.result;\n if (!db.objectStoreNames.contains(STORE_NAME)) {\n db.createObjectStore(STORE_NAME, { autoIncrement: true });\n }\n };\n request.onsuccess = () => resolve(request.result);\n request.onerror = () => reject(request.error);\n });\n}\n\nexport async function enqueueOffline(data: { url: string; body: string }): Promise<void> {\n const db = await openDB();\n const tx = db.transaction(STORE_NAME, 'readwrite');\n tx.objectStore(STORE_NAME).add(data);\n await new Promise<void>((resolve, reject) => {\n tx.oncomplete = () => resolve();\n tx.onerror = () => reject(tx.error);\n });\n db.close();\n}\n\nexport async function drainOfflineQueue(sendFn: (url: string, body: string) => Promise<void>): Promise<void> {\n const db = await openDB();\n const tx = db.transaction(STORE_NAME, 'readwrite');\n const store = tx.objectStore(STORE_NAME);\n const request = store.getAll();\n\n await new Promise<void>((resolve, reject) => {\n request.onsuccess = async () => {\n const items = request.result as { url: string; body: string }[];\n for (const item of items) {\n try {\n await sendFn(item.url, item.body);\n } catch {\n // Will retry next time\n break;\n }\n }\n store.clear();\n resolve();\n };\n request.onerror = () => reject(request.error);\n });\n\n db.close();\n}\n","export function generateUUID(): string {\n if (typeof crypto !== 'undefined' && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n const v = c === 'x' ? r : (r & 0x3) | 0x8;\n return v.toString(16);\n });\n}\n\nexport function getPageContext() {\n return {\n path: location.pathname,\n title: document.title,\n referrer: document.referrer,\n viewportWidth: window.innerWidth,\n viewportHeight: window.innerHeight,\n };\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function throttle<T extends (...args: any[]) => void>(fn: T, ms: number): T {\n let last = 0;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return ((...args: any[]) => {\n const now = Date.now();\n if (now - last >= ms) {\n last = now;\n fn(...args);\n }\n }) as T;\n}\n","import type { ExperienceConfig, ExperienceEvent, ErrorReport } from './types';\nimport { BreadcrumbManager } from './breadcrumbs';\nimport { enqueueOffline, drainOfflineQueue } from './offline';\nimport { generateUUID, getPageContext, throttle } from './utils';\n\nconst DEFAULT_CONFIG: Partial<ExperienceConfig> = {\n endpoint: '',\n enableTracking: true,\n enableErrorCapture: true,\n enableHeatmaps: false,\n enableBreadcrumbs: true,\n sampleRate: 1.0,\n batchIntervalMs: 5000,\n maxBatchSize: 50,\n debug: false,\n};\n\nexport class Experience {\n private config: Required<ExperienceConfig>;\n private sessionId: string;\n private profileId = '';\n private eventQueue: ExperienceEvent[] = [];\n private errorQueue: ErrorReport[] = [];\n private batchTimer: ReturnType<typeof setInterval> | null = null;\n private breadcrumbs: BreadcrumbManager;\n private flags: Record<string, unknown> = {};\n private flagsFetched = false;\n private teardownFns: Array<() => void> = [];\n private destroyed = false;\n\n private constructor(config: ExperienceConfig) {\n this.config = { ...DEFAULT_CONFIG, ...config } as Required<ExperienceConfig>;\n this.breadcrumbs = new BreadcrumbManager();\n\n // Session management\n const stored = typeof sessionStorage !== 'undefined' ? sessionStorage.getItem('exp_session_id') : null;\n if (stored) {\n this.sessionId = stored;\n } else {\n this.sessionId = generateUUID();\n if (typeof sessionStorage !== 'undefined') {\n sessionStorage.setItem('exp_session_id', this.sessionId);\n }\n }\n }\n\n static init(config: ExperienceConfig): Experience {\n const instance = new Experience(config);\n instance.setup();\n return instance;\n }\n\n private log(...args: unknown[]): void {\n if (this.config.debug) {\n console.log('[Experience]', ...args);\n }\n }\n\n private setup(): void {\n // Check sample rate\n if (Math.random() > this.config.sampleRate) {\n this.log('Sampled out');\n return;\n }\n\n // Breadcrumbs\n if (this.config.enableBreadcrumbs) {\n this.breadcrumbs.install();\n }\n\n // Auto error capture\n if (this.config.enableErrorCapture) {\n this.setupErrorCapture();\n }\n\n // Auto page views\n if (this.config.enableTracking) {\n this.setupAutoPageViews();\n }\n\n // Heatmaps\n if (this.config.enableHeatmaps) {\n this.setupHeatmaps();\n }\n\n // Batch timer\n this.batchTimer = setInterval(() => this.flush(), this.config.batchIntervalMs);\n\n // Page unload\n const beforeUnload = () => this.flushSync();\n window.addEventListener('beforeunload', beforeUnload);\n this.teardownFns.push(() => window.removeEventListener('beforeunload', beforeUnload));\n\n // Offline resilience\n const onlineHandler = () => {\n this.log('Back online, draining queue');\n drainOfflineQueue((url, body) => this.sendRequest(url, body)).catch(() => {});\n };\n window.addEventListener('online', onlineHandler);\n this.teardownFns.push(() => window.removeEventListener('online', onlineHandler));\n\n // Fetch flags\n this.fetchFlags().catch(() => {});\n\n this.log('Initialized', this.config.appId);\n }\n\n // --- Public API ---\n\n track(eventName: string, metadata?: Record<string, unknown>): void {\n if (this.destroyed) return;\n this.enqueueEvent('custom', eventName, metadata);\n }\n\n trackPageView(): void {\n if (this.destroyed) return;\n this.enqueueEvent('page_view', 'page_view');\n this.breadcrumbs.add({ type: 'navigation', category: 'page_view', message: location.href });\n }\n\n captureError(error: Error, extra?: Record<string, unknown>): void {\n if (this.destroyed) return;\n this.enqueueError(error.message, error.stack || '', 'error', extra);\n }\n\n captureMessage(msg: string, severity = 'info'): void {\n if (this.destroyed) return;\n this.enqueueError(msg, '', severity);\n }\n\n getFlag<T>(name: string, defaultValue: T): T {\n if (!this.flagsFetched || !(name in this.flags)) return defaultValue;\n return this.flags[name] as T;\n }\n\n identify(profileId: string): void {\n this.profileId = profileId;\n this.log('Identified', profileId);\n }\n\n async shutdown(): Promise<void> {\n if (this.destroyed) return;\n this.destroyed = true;\n if (this.batchTimer) {\n clearInterval(this.batchTimer);\n this.batchTimer = null;\n }\n await this.flush();\n this.breadcrumbs.teardown();\n this.teardownFns.forEach((fn) => fn());\n this.teardownFns = [];\n this.log('Shutdown complete');\n }\n\n // --- Internal ---\n\n private enqueueEvent(eventType: string, eventName: string, metadata?: Record<string, unknown>): void {\n const event: ExperienceEvent = {\n eventId: generateUUID(),\n appId: this.config.appId,\n profileId: this.profileId,\n sessionId: this.sessionId,\n timestampMs: Date.now(),\n eventType,\n pageUrl: location.href,\n elementTid: metadata?.elementTid as string || '',\n metadata: { eventName, ...metadata },\n pageContext: getPageContext(),\n elementContext: null,\n };\n this.eventQueue.push(event);\n if (this.eventQueue.length >= this.config.maxBatchSize) {\n this.flush().catch(() => {});\n }\n }\n\n private enqueueError(message: string, stack: string, severity: string, extra?: Record<string, unknown>): void {\n const report: ErrorReport = {\n eventId: generateUUID(),\n appId: this.config.appId,\n profileId: this.profileId,\n sessionId: this.sessionId,\n timestampMs: Date.now(),\n message,\n stack,\n severity,\n extra: extra || {},\n pageContext: getPageContext(),\n breadcrumbs: this.breadcrumbs.getAll(),\n };\n this.errorQueue.push(report);\n }\n\n private async flush(): Promise<void> {\n if (this.eventQueue.length > 0) {\n const events = this.eventQueue.splice(0);\n const url = `${this.config.endpoint}/api/v1/ingest/events`;\n const body = JSON.stringify({ events });\n await this.sendOrQueue(url, body);\n }\n if (this.errorQueue.length > 0) {\n const errors = this.errorQueue.splice(0);\n const url = `${this.config.endpoint}/api/v1/ingest/errors`;\n const body = JSON.stringify({ errors });\n await this.sendOrQueue(url, body);\n }\n }\n\n private flushSync(): void {\n if (this.eventQueue.length > 0) {\n const url = `${this.config.endpoint}/api/v1/ingest/events`;\n const body = JSON.stringify({ events: this.eventQueue.splice(0) });\n if (typeof navigator.sendBeacon === 'function') {\n navigator.sendBeacon(url, body);\n }\n }\n if (this.errorQueue.length > 0) {\n const url = `${this.config.endpoint}/api/v1/ingest/errors`;\n const body = JSON.stringify({ errors: this.errorQueue.splice(0) });\n if (typeof navigator.sendBeacon === 'function') {\n navigator.sendBeacon(url, body);\n }\n }\n }\n\n private async sendOrQueue(url: string, body: string): Promise<void> {\n if (typeof navigator !== 'undefined' && !navigator.onLine) {\n await enqueueOffline({ url, body });\n return;\n }\n try {\n await this.sendRequest(url, body);\n } catch {\n await enqueueOffline({ url, body }).catch(() => {});\n }\n }\n\n private async sendRequest(url: string, body: string): Promise<void> {\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.config.apiKey}`,\n },\n body,\n });\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`);\n }\n }\n\n private async fetchFlags(): Promise<void> {\n try {\n const url = `${this.config.endpoint}/api/v1/flags/${this.config.appId}`;\n const response = await fetch(url, {\n headers: { 'Authorization': `Bearer ${this.config.apiKey}` },\n });\n if (response.ok) {\n this.flags = await response.json();\n this.flagsFetched = true;\n this.log('Flags loaded', this.flags);\n }\n } catch {\n this.log('Failed to fetch flags');\n }\n }\n\n // --- Auto features ---\n\n private setupErrorCapture(): void {\n const onError = (event: ErrorEvent) => {\n this.enqueueError(\n event.message,\n event.error?.stack || '',\n 'error',\n { filename: event.filename, lineno: event.lineno, colno: event.colno },\n );\n };\n window.addEventListener('error', onError);\n this.teardownFns.push(() => window.removeEventListener('error', onError));\n\n const onUnhandledRejection = (event: PromiseRejectionEvent) => {\n const msg = event.reason instanceof Error ? event.reason.message : String(event.reason);\n const stack = event.reason instanceof Error ? event.reason.stack || '' : '';\n this.enqueueError(msg, stack, 'error', { type: 'unhandledrejection' });\n };\n window.addEventListener('unhandledrejection', onUnhandledRejection);\n this.teardownFns.push(() => window.removeEventListener('unhandledrejection', onUnhandledRejection));\n }\n\n private setupAutoPageViews(): void {\n // Intercept pushState / replaceState\n const originalPushState = history.pushState.bind(history);\n const originalReplaceState = history.replaceState.bind(history);\n\n history.pushState = (...args: Parameters<typeof history.pushState>) => {\n originalPushState(...args);\n this.trackPageView();\n };\n history.replaceState = (...args: Parameters<typeof history.replaceState>) => {\n originalReplaceState(...args);\n this.trackPageView();\n };\n\n this.teardownFns.push(() => {\n history.pushState = originalPushState;\n history.replaceState = originalReplaceState;\n });\n\n const onPopState = () => this.trackPageView();\n window.addEventListener('popstate', onPopState);\n this.teardownFns.push(() => window.removeEventListener('popstate', onPopState));\n\n // Track initial page view\n this.trackPageView();\n }\n\n private setupHeatmaps(): void {\n const handleMove = throttle((e: MouseEvent) => {\n this.enqueueEvent('heatmap_move', 'mousemove', {\n x: e.clientX,\n y: e.clientY,\n viewportWidth: window.innerWidth,\n viewportHeight: window.innerHeight,\n });\n }, 100);\n\n const handleClick = (e: MouseEvent) => {\n const target = e.target as HTMLElement;\n this.enqueueEvent('heatmap_click', 'click', {\n x: e.clientX,\n y: e.clientY,\n viewportWidth: window.innerWidth,\n viewportHeight: window.innerHeight,\n tag: target?.tagName?.toLowerCase() || '',\n text: (target?.textContent || '').slice(0, 100),\n });\n };\n\n document.addEventListener('mousemove', handleMove as EventListener);\n document.addEventListener('click', handleClick);\n this.teardownFns.push(() => {\n document.removeEventListener('mousemove', handleMove as EventListener);\n document.removeEventListener('click', handleClick);\n });\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
// src/breadcrumbs.ts
|
|
2
|
+
var MAX_BREADCRUMBS = 50;
|
|
3
|
+
var BreadcrumbManager = class {
|
|
4
|
+
constructor() {
|
|
5
|
+
this.buffer = [];
|
|
6
|
+
this.originalFetch = null;
|
|
7
|
+
this.originalXhrOpen = null;
|
|
8
|
+
this.clickHandler = null;
|
|
9
|
+
this.originalConsole = {
|
|
10
|
+
log: console.log,
|
|
11
|
+
warn: console.warn,
|
|
12
|
+
error: console.error
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
add(breadcrumb) {
|
|
16
|
+
this.buffer.push({ ...breadcrumb, timestampMs: Date.now() });
|
|
17
|
+
if (this.buffer.length > MAX_BREADCRUMBS) {
|
|
18
|
+
this.buffer.shift();
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
getAll() {
|
|
22
|
+
return [...this.buffer];
|
|
23
|
+
}
|
|
24
|
+
install() {
|
|
25
|
+
["log", "warn", "error"].forEach((level) => {
|
|
26
|
+
const original = this.originalConsole[level];
|
|
27
|
+
console[level] = (...args) => {
|
|
28
|
+
this.add({
|
|
29
|
+
type: "console",
|
|
30
|
+
category: level,
|
|
31
|
+
message: args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ")
|
|
32
|
+
});
|
|
33
|
+
original.apply(console, args);
|
|
34
|
+
};
|
|
35
|
+
});
|
|
36
|
+
if (typeof fetch !== "undefined") {
|
|
37
|
+
this.originalFetch = fetch;
|
|
38
|
+
const self = this;
|
|
39
|
+
window.fetch = function(input, init) {
|
|
40
|
+
const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
|
|
41
|
+
const method = init?.method || "GET";
|
|
42
|
+
self.add({ type: "http", category: "fetch", message: `${method} ${url}` });
|
|
43
|
+
return self.originalFetch.call(window, input, init);
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
if (typeof XMLHttpRequest !== "undefined") {
|
|
47
|
+
this.originalXhrOpen = XMLHttpRequest.prototype.open;
|
|
48
|
+
const self = this;
|
|
49
|
+
XMLHttpRequest.prototype.open = function(method, url) {
|
|
50
|
+
self.add({ type: "http", category: "xhr", message: `${method} ${url}` });
|
|
51
|
+
return self.originalXhrOpen.apply(this, arguments);
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
this.clickHandler = (e) => {
|
|
55
|
+
const target = e.target;
|
|
56
|
+
if (!target) return;
|
|
57
|
+
const tag = target.tagName?.toLowerCase() || "";
|
|
58
|
+
const text = (target.textContent || "").slice(0, 100);
|
|
59
|
+
this.add({ type: "ui", category: "click", message: `${tag}: ${text}` });
|
|
60
|
+
};
|
|
61
|
+
document.addEventListener("click", this.clickHandler, true);
|
|
62
|
+
}
|
|
63
|
+
teardown() {
|
|
64
|
+
console.log = this.originalConsole.log;
|
|
65
|
+
console.warn = this.originalConsole.warn;
|
|
66
|
+
console.error = this.originalConsole.error;
|
|
67
|
+
if (this.originalFetch) {
|
|
68
|
+
window.fetch = this.originalFetch;
|
|
69
|
+
}
|
|
70
|
+
if (this.originalXhrOpen) {
|
|
71
|
+
XMLHttpRequest.prototype.open = this.originalXhrOpen;
|
|
72
|
+
}
|
|
73
|
+
if (this.clickHandler) {
|
|
74
|
+
document.removeEventListener("click", this.clickHandler, true);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// src/offline.ts
|
|
80
|
+
var DB_NAME = "shellapps_experience";
|
|
81
|
+
var STORE_NAME = "offline_queue";
|
|
82
|
+
var DB_VERSION = 1;
|
|
83
|
+
function openDB() {
|
|
84
|
+
return new Promise((resolve, reject) => {
|
|
85
|
+
const request = indexedDB.open(DB_NAME, DB_VERSION);
|
|
86
|
+
request.onupgradeneeded = () => {
|
|
87
|
+
const db = request.result;
|
|
88
|
+
if (!db.objectStoreNames.contains(STORE_NAME)) {
|
|
89
|
+
db.createObjectStore(STORE_NAME, { autoIncrement: true });
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
request.onsuccess = () => resolve(request.result);
|
|
93
|
+
request.onerror = () => reject(request.error);
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
async function enqueueOffline(data) {
|
|
97
|
+
const db = await openDB();
|
|
98
|
+
const tx = db.transaction(STORE_NAME, "readwrite");
|
|
99
|
+
tx.objectStore(STORE_NAME).add(data);
|
|
100
|
+
await new Promise((resolve, reject) => {
|
|
101
|
+
tx.oncomplete = () => resolve();
|
|
102
|
+
tx.onerror = () => reject(tx.error);
|
|
103
|
+
});
|
|
104
|
+
db.close();
|
|
105
|
+
}
|
|
106
|
+
async function drainOfflineQueue(sendFn) {
|
|
107
|
+
const db = await openDB();
|
|
108
|
+
const tx = db.transaction(STORE_NAME, "readwrite");
|
|
109
|
+
const store = tx.objectStore(STORE_NAME);
|
|
110
|
+
const request = store.getAll();
|
|
111
|
+
await new Promise((resolve, reject) => {
|
|
112
|
+
request.onsuccess = async () => {
|
|
113
|
+
const items = request.result;
|
|
114
|
+
for (const item of items) {
|
|
115
|
+
try {
|
|
116
|
+
await sendFn(item.url, item.body);
|
|
117
|
+
} catch {
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
store.clear();
|
|
122
|
+
resolve();
|
|
123
|
+
};
|
|
124
|
+
request.onerror = () => reject(request.error);
|
|
125
|
+
});
|
|
126
|
+
db.close();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// src/utils.ts
|
|
130
|
+
function generateUUID() {
|
|
131
|
+
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
132
|
+
return crypto.randomUUID();
|
|
133
|
+
}
|
|
134
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
135
|
+
const r = Math.random() * 16 | 0;
|
|
136
|
+
const v = c === "x" ? r : r & 3 | 8;
|
|
137
|
+
return v.toString(16);
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
function getPageContext() {
|
|
141
|
+
return {
|
|
142
|
+
path: location.pathname,
|
|
143
|
+
title: document.title,
|
|
144
|
+
referrer: document.referrer,
|
|
145
|
+
viewportWidth: window.innerWidth,
|
|
146
|
+
viewportHeight: window.innerHeight
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
function throttle(fn, ms) {
|
|
150
|
+
let last = 0;
|
|
151
|
+
return ((...args) => {
|
|
152
|
+
const now = Date.now();
|
|
153
|
+
if (now - last >= ms) {
|
|
154
|
+
last = now;
|
|
155
|
+
fn(...args);
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// src/experience.ts
|
|
161
|
+
var DEFAULT_CONFIG = {
|
|
162
|
+
endpoint: "",
|
|
163
|
+
enableTracking: true,
|
|
164
|
+
enableErrorCapture: true,
|
|
165
|
+
enableHeatmaps: false,
|
|
166
|
+
enableBreadcrumbs: true,
|
|
167
|
+
sampleRate: 1,
|
|
168
|
+
batchIntervalMs: 5e3,
|
|
169
|
+
maxBatchSize: 50,
|
|
170
|
+
debug: false
|
|
171
|
+
};
|
|
172
|
+
var Experience = class _Experience {
|
|
173
|
+
constructor(config) {
|
|
174
|
+
this.profileId = "";
|
|
175
|
+
this.eventQueue = [];
|
|
176
|
+
this.errorQueue = [];
|
|
177
|
+
this.batchTimer = null;
|
|
178
|
+
this.flags = {};
|
|
179
|
+
this.flagsFetched = false;
|
|
180
|
+
this.teardownFns = [];
|
|
181
|
+
this.destroyed = false;
|
|
182
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
183
|
+
this.breadcrumbs = new BreadcrumbManager();
|
|
184
|
+
const stored = typeof sessionStorage !== "undefined" ? sessionStorage.getItem("exp_session_id") : null;
|
|
185
|
+
if (stored) {
|
|
186
|
+
this.sessionId = stored;
|
|
187
|
+
} else {
|
|
188
|
+
this.sessionId = generateUUID();
|
|
189
|
+
if (typeof sessionStorage !== "undefined") {
|
|
190
|
+
sessionStorage.setItem("exp_session_id", this.sessionId);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
static init(config) {
|
|
195
|
+
const instance = new _Experience(config);
|
|
196
|
+
instance.setup();
|
|
197
|
+
return instance;
|
|
198
|
+
}
|
|
199
|
+
log(...args) {
|
|
200
|
+
if (this.config.debug) {
|
|
201
|
+
console.log("[Experience]", ...args);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
setup() {
|
|
205
|
+
if (Math.random() > this.config.sampleRate) {
|
|
206
|
+
this.log("Sampled out");
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
if (this.config.enableBreadcrumbs) {
|
|
210
|
+
this.breadcrumbs.install();
|
|
211
|
+
}
|
|
212
|
+
if (this.config.enableErrorCapture) {
|
|
213
|
+
this.setupErrorCapture();
|
|
214
|
+
}
|
|
215
|
+
if (this.config.enableTracking) {
|
|
216
|
+
this.setupAutoPageViews();
|
|
217
|
+
}
|
|
218
|
+
if (this.config.enableHeatmaps) {
|
|
219
|
+
this.setupHeatmaps();
|
|
220
|
+
}
|
|
221
|
+
this.batchTimer = setInterval(() => this.flush(), this.config.batchIntervalMs);
|
|
222
|
+
const beforeUnload = () => this.flushSync();
|
|
223
|
+
window.addEventListener("beforeunload", beforeUnload);
|
|
224
|
+
this.teardownFns.push(() => window.removeEventListener("beforeunload", beforeUnload));
|
|
225
|
+
const onlineHandler = () => {
|
|
226
|
+
this.log("Back online, draining queue");
|
|
227
|
+
drainOfflineQueue((url, body) => this.sendRequest(url, body)).catch(() => {
|
|
228
|
+
});
|
|
229
|
+
};
|
|
230
|
+
window.addEventListener("online", onlineHandler);
|
|
231
|
+
this.teardownFns.push(() => window.removeEventListener("online", onlineHandler));
|
|
232
|
+
this.fetchFlags().catch(() => {
|
|
233
|
+
});
|
|
234
|
+
this.log("Initialized", this.config.appId);
|
|
235
|
+
}
|
|
236
|
+
// --- Public API ---
|
|
237
|
+
track(eventName, metadata) {
|
|
238
|
+
if (this.destroyed) return;
|
|
239
|
+
this.enqueueEvent("custom", eventName, metadata);
|
|
240
|
+
}
|
|
241
|
+
trackPageView() {
|
|
242
|
+
if (this.destroyed) return;
|
|
243
|
+
this.enqueueEvent("page_view", "page_view");
|
|
244
|
+
this.breadcrumbs.add({ type: "navigation", category: "page_view", message: location.href });
|
|
245
|
+
}
|
|
246
|
+
captureError(error, extra) {
|
|
247
|
+
if (this.destroyed) return;
|
|
248
|
+
this.enqueueError(error.message, error.stack || "", "error", extra);
|
|
249
|
+
}
|
|
250
|
+
captureMessage(msg, severity = "info") {
|
|
251
|
+
if (this.destroyed) return;
|
|
252
|
+
this.enqueueError(msg, "", severity);
|
|
253
|
+
}
|
|
254
|
+
getFlag(name, defaultValue) {
|
|
255
|
+
if (!this.flagsFetched || !(name in this.flags)) return defaultValue;
|
|
256
|
+
return this.flags[name];
|
|
257
|
+
}
|
|
258
|
+
identify(profileId) {
|
|
259
|
+
this.profileId = profileId;
|
|
260
|
+
this.log("Identified", profileId);
|
|
261
|
+
}
|
|
262
|
+
async shutdown() {
|
|
263
|
+
if (this.destroyed) return;
|
|
264
|
+
this.destroyed = true;
|
|
265
|
+
if (this.batchTimer) {
|
|
266
|
+
clearInterval(this.batchTimer);
|
|
267
|
+
this.batchTimer = null;
|
|
268
|
+
}
|
|
269
|
+
await this.flush();
|
|
270
|
+
this.breadcrumbs.teardown();
|
|
271
|
+
this.teardownFns.forEach((fn) => fn());
|
|
272
|
+
this.teardownFns = [];
|
|
273
|
+
this.log("Shutdown complete");
|
|
274
|
+
}
|
|
275
|
+
// --- Internal ---
|
|
276
|
+
enqueueEvent(eventType, eventName, metadata) {
|
|
277
|
+
const event = {
|
|
278
|
+
eventId: generateUUID(),
|
|
279
|
+
appId: this.config.appId,
|
|
280
|
+
profileId: this.profileId,
|
|
281
|
+
sessionId: this.sessionId,
|
|
282
|
+
timestampMs: Date.now(),
|
|
283
|
+
eventType,
|
|
284
|
+
pageUrl: location.href,
|
|
285
|
+
elementTid: metadata?.elementTid || "",
|
|
286
|
+
metadata: { eventName, ...metadata },
|
|
287
|
+
pageContext: getPageContext(),
|
|
288
|
+
elementContext: null
|
|
289
|
+
};
|
|
290
|
+
this.eventQueue.push(event);
|
|
291
|
+
if (this.eventQueue.length >= this.config.maxBatchSize) {
|
|
292
|
+
this.flush().catch(() => {
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
enqueueError(message, stack, severity, extra) {
|
|
297
|
+
const report = {
|
|
298
|
+
eventId: generateUUID(),
|
|
299
|
+
appId: this.config.appId,
|
|
300
|
+
profileId: this.profileId,
|
|
301
|
+
sessionId: this.sessionId,
|
|
302
|
+
timestampMs: Date.now(),
|
|
303
|
+
message,
|
|
304
|
+
stack,
|
|
305
|
+
severity,
|
|
306
|
+
extra: extra || {},
|
|
307
|
+
pageContext: getPageContext(),
|
|
308
|
+
breadcrumbs: this.breadcrumbs.getAll()
|
|
309
|
+
};
|
|
310
|
+
this.errorQueue.push(report);
|
|
311
|
+
}
|
|
312
|
+
async flush() {
|
|
313
|
+
if (this.eventQueue.length > 0) {
|
|
314
|
+
const events = this.eventQueue.splice(0);
|
|
315
|
+
const url = `${this.config.endpoint}/api/v1/ingest/events`;
|
|
316
|
+
const body = JSON.stringify({ events });
|
|
317
|
+
await this.sendOrQueue(url, body);
|
|
318
|
+
}
|
|
319
|
+
if (this.errorQueue.length > 0) {
|
|
320
|
+
const errors = this.errorQueue.splice(0);
|
|
321
|
+
const url = `${this.config.endpoint}/api/v1/ingest/errors`;
|
|
322
|
+
const body = JSON.stringify({ errors });
|
|
323
|
+
await this.sendOrQueue(url, body);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
flushSync() {
|
|
327
|
+
if (this.eventQueue.length > 0) {
|
|
328
|
+
const url = `${this.config.endpoint}/api/v1/ingest/events`;
|
|
329
|
+
const body = JSON.stringify({ events: this.eventQueue.splice(0) });
|
|
330
|
+
if (typeof navigator.sendBeacon === "function") {
|
|
331
|
+
navigator.sendBeacon(url, body);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
if (this.errorQueue.length > 0) {
|
|
335
|
+
const url = `${this.config.endpoint}/api/v1/ingest/errors`;
|
|
336
|
+
const body = JSON.stringify({ errors: this.errorQueue.splice(0) });
|
|
337
|
+
if (typeof navigator.sendBeacon === "function") {
|
|
338
|
+
navigator.sendBeacon(url, body);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
async sendOrQueue(url, body) {
|
|
343
|
+
if (typeof navigator !== "undefined" && !navigator.onLine) {
|
|
344
|
+
await enqueueOffline({ url, body });
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
try {
|
|
348
|
+
await this.sendRequest(url, body);
|
|
349
|
+
} catch {
|
|
350
|
+
await enqueueOffline({ url, body }).catch(() => {
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
async sendRequest(url, body) {
|
|
355
|
+
const response = await fetch(url, {
|
|
356
|
+
method: "POST",
|
|
357
|
+
headers: {
|
|
358
|
+
"Content-Type": "application/json",
|
|
359
|
+
"Authorization": `Bearer ${this.config.apiKey}`
|
|
360
|
+
},
|
|
361
|
+
body
|
|
362
|
+
});
|
|
363
|
+
if (!response.ok) {
|
|
364
|
+
throw new Error(`HTTP ${response.status}`);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
async fetchFlags() {
|
|
368
|
+
try {
|
|
369
|
+
const url = `${this.config.endpoint}/api/v1/flags/${this.config.appId}`;
|
|
370
|
+
const response = await fetch(url, {
|
|
371
|
+
headers: { "Authorization": `Bearer ${this.config.apiKey}` }
|
|
372
|
+
});
|
|
373
|
+
if (response.ok) {
|
|
374
|
+
this.flags = await response.json();
|
|
375
|
+
this.flagsFetched = true;
|
|
376
|
+
this.log("Flags loaded", this.flags);
|
|
377
|
+
}
|
|
378
|
+
} catch {
|
|
379
|
+
this.log("Failed to fetch flags");
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
// --- Auto features ---
|
|
383
|
+
setupErrorCapture() {
|
|
384
|
+
const onError = (event) => {
|
|
385
|
+
this.enqueueError(
|
|
386
|
+
event.message,
|
|
387
|
+
event.error?.stack || "",
|
|
388
|
+
"error",
|
|
389
|
+
{ filename: event.filename, lineno: event.lineno, colno: event.colno }
|
|
390
|
+
);
|
|
391
|
+
};
|
|
392
|
+
window.addEventListener("error", onError);
|
|
393
|
+
this.teardownFns.push(() => window.removeEventListener("error", onError));
|
|
394
|
+
const onUnhandledRejection = (event) => {
|
|
395
|
+
const msg = event.reason instanceof Error ? event.reason.message : String(event.reason);
|
|
396
|
+
const stack = event.reason instanceof Error ? event.reason.stack || "" : "";
|
|
397
|
+
this.enqueueError(msg, stack, "error", { type: "unhandledrejection" });
|
|
398
|
+
};
|
|
399
|
+
window.addEventListener("unhandledrejection", onUnhandledRejection);
|
|
400
|
+
this.teardownFns.push(() => window.removeEventListener("unhandledrejection", onUnhandledRejection));
|
|
401
|
+
}
|
|
402
|
+
setupAutoPageViews() {
|
|
403
|
+
const originalPushState = history.pushState.bind(history);
|
|
404
|
+
const originalReplaceState = history.replaceState.bind(history);
|
|
405
|
+
history.pushState = (...args) => {
|
|
406
|
+
originalPushState(...args);
|
|
407
|
+
this.trackPageView();
|
|
408
|
+
};
|
|
409
|
+
history.replaceState = (...args) => {
|
|
410
|
+
originalReplaceState(...args);
|
|
411
|
+
this.trackPageView();
|
|
412
|
+
};
|
|
413
|
+
this.teardownFns.push(() => {
|
|
414
|
+
history.pushState = originalPushState;
|
|
415
|
+
history.replaceState = originalReplaceState;
|
|
416
|
+
});
|
|
417
|
+
const onPopState = () => this.trackPageView();
|
|
418
|
+
window.addEventListener("popstate", onPopState);
|
|
419
|
+
this.teardownFns.push(() => window.removeEventListener("popstate", onPopState));
|
|
420
|
+
this.trackPageView();
|
|
421
|
+
}
|
|
422
|
+
setupHeatmaps() {
|
|
423
|
+
const handleMove = throttle((e) => {
|
|
424
|
+
this.enqueueEvent("heatmap_move", "mousemove", {
|
|
425
|
+
x: e.clientX,
|
|
426
|
+
y: e.clientY,
|
|
427
|
+
viewportWidth: window.innerWidth,
|
|
428
|
+
viewportHeight: window.innerHeight
|
|
429
|
+
});
|
|
430
|
+
}, 100);
|
|
431
|
+
const handleClick = (e) => {
|
|
432
|
+
const target = e.target;
|
|
433
|
+
this.enqueueEvent("heatmap_click", "click", {
|
|
434
|
+
x: e.clientX,
|
|
435
|
+
y: e.clientY,
|
|
436
|
+
viewportWidth: window.innerWidth,
|
|
437
|
+
viewportHeight: window.innerHeight,
|
|
438
|
+
tag: target?.tagName?.toLowerCase() || "",
|
|
439
|
+
text: (target?.textContent || "").slice(0, 100)
|
|
440
|
+
});
|
|
441
|
+
};
|
|
442
|
+
document.addEventListener("mousemove", handleMove);
|
|
443
|
+
document.addEventListener("click", handleClick);
|
|
444
|
+
this.teardownFns.push(() => {
|
|
445
|
+
document.removeEventListener("mousemove", handleMove);
|
|
446
|
+
document.removeEventListener("click", handleClick);
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
};
|
|
450
|
+
export {
|
|
451
|
+
Experience
|
|
452
|
+
};
|
|
453
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/breadcrumbs.ts","../src/offline.ts","../src/utils.ts","../src/experience.ts"],"sourcesContent":["import type { Breadcrumb } from './types';\n\nconst MAX_BREADCRUMBS = 50;\n\nexport class BreadcrumbManager {\n private buffer: Breadcrumb[] = [];\n private originalConsole: Pick<Console, 'log' | 'warn' | 'error'>;\n private originalFetch: typeof fetch | null = null;\n private originalXhrOpen: typeof XMLHttpRequest.prototype.open | null = null;\n private clickHandler: ((e: MouseEvent) => void) | null = null;\n\n constructor() {\n this.originalConsole = {\n log: console.log,\n warn: console.warn,\n error: console.error,\n };\n }\n\n add(breadcrumb: Omit<Breadcrumb, 'timestampMs'>): void {\n this.buffer.push({ ...breadcrumb, timestampMs: Date.now() });\n if (this.buffer.length > MAX_BREADCRUMBS) {\n this.buffer.shift();\n }\n }\n\n getAll(): Breadcrumb[] {\n return [...this.buffer];\n }\n\n install(): void {\n (['log', 'warn', 'error'] as const).forEach((level) => {\n const original = this.originalConsole[level];\n console[level] = (...args: unknown[]) => {\n this.add({\n type: 'console',\n category: level,\n message: args.map((a) => (typeof a === 'string' ? a : JSON.stringify(a))).join(' '),\n });\n original.apply(console, args);\n };\n });\n\n if (typeof fetch !== 'undefined') {\n this.originalFetch = fetch;\n const self = this;\n (window as unknown as Record<string, unknown>).fetch = function (input: RequestInfo | URL, init?: RequestInit) {\n const url = typeof input === 'string' ? input : input instanceof URL ? input.toString() : input.url;\n const method = init?.method || 'GET';\n self.add({ type: 'http', category: 'fetch', message: `${method} ${url}` });\n return self.originalFetch!.call(window, input, init);\n };\n }\n\n if (typeof XMLHttpRequest !== 'undefined') {\n this.originalXhrOpen = XMLHttpRequest.prototype.open;\n const self = this;\n XMLHttpRequest.prototype.open = function (method: string, url: string | URL) {\n self.add({ type: 'http', category: 'xhr', message: `${method} ${url}` });\n return self.originalXhrOpen!.apply(this, arguments as unknown as Parameters<typeof XMLHttpRequest.prototype.open>);\n };\n }\n\n this.clickHandler = (e: MouseEvent) => {\n const target = e.target as HTMLElement;\n if (!target) return;\n const tag = target.tagName?.toLowerCase() || '';\n const text = (target.textContent || '').slice(0, 100);\n this.add({ type: 'ui', category: 'click', message: `${tag}: ${text}` });\n };\n document.addEventListener('click', this.clickHandler, true);\n }\n\n teardown(): void {\n console.log = this.originalConsole.log;\n console.warn = this.originalConsole.warn;\n console.error = this.originalConsole.error;\n\n if (this.originalFetch) {\n (window as unknown as Record<string, unknown>).fetch = this.originalFetch;\n }\n if (this.originalXhrOpen) {\n XMLHttpRequest.prototype.open = this.originalXhrOpen;\n }\n if (this.clickHandler) {\n document.removeEventListener('click', this.clickHandler, true);\n }\n }\n}\n","const DB_NAME = 'shellapps_experience';\nconst STORE_NAME = 'offline_queue';\nconst DB_VERSION = 1;\n\nfunction openDB(): Promise<IDBDatabase> {\n return new Promise((resolve, reject) => {\n const request = indexedDB.open(DB_NAME, DB_VERSION);\n request.onupgradeneeded = () => {\n const db = request.result;\n if (!db.objectStoreNames.contains(STORE_NAME)) {\n db.createObjectStore(STORE_NAME, { autoIncrement: true });\n }\n };\n request.onsuccess = () => resolve(request.result);\n request.onerror = () => reject(request.error);\n });\n}\n\nexport async function enqueueOffline(data: { url: string; body: string }): Promise<void> {\n const db = await openDB();\n const tx = db.transaction(STORE_NAME, 'readwrite');\n tx.objectStore(STORE_NAME).add(data);\n await new Promise<void>((resolve, reject) => {\n tx.oncomplete = () => resolve();\n tx.onerror = () => reject(tx.error);\n });\n db.close();\n}\n\nexport async function drainOfflineQueue(sendFn: (url: string, body: string) => Promise<void>): Promise<void> {\n const db = await openDB();\n const tx = db.transaction(STORE_NAME, 'readwrite');\n const store = tx.objectStore(STORE_NAME);\n const request = store.getAll();\n\n await new Promise<void>((resolve, reject) => {\n request.onsuccess = async () => {\n const items = request.result as { url: string; body: string }[];\n for (const item of items) {\n try {\n await sendFn(item.url, item.body);\n } catch {\n // Will retry next time\n break;\n }\n }\n store.clear();\n resolve();\n };\n request.onerror = () => reject(request.error);\n });\n\n db.close();\n}\n","export function generateUUID(): string {\n if (typeof crypto !== 'undefined' && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n const v = c === 'x' ? r : (r & 0x3) | 0x8;\n return v.toString(16);\n });\n}\n\nexport function getPageContext() {\n return {\n path: location.pathname,\n title: document.title,\n referrer: document.referrer,\n viewportWidth: window.innerWidth,\n viewportHeight: window.innerHeight,\n };\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function throttle<T extends (...args: any[]) => void>(fn: T, ms: number): T {\n let last = 0;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return ((...args: any[]) => {\n const now = Date.now();\n if (now - last >= ms) {\n last = now;\n fn(...args);\n }\n }) as T;\n}\n","import type { ExperienceConfig, ExperienceEvent, ErrorReport } from './types';\nimport { BreadcrumbManager } from './breadcrumbs';\nimport { enqueueOffline, drainOfflineQueue } from './offline';\nimport { generateUUID, getPageContext, throttle } from './utils';\n\nconst DEFAULT_CONFIG: Partial<ExperienceConfig> = {\n endpoint: '',\n enableTracking: true,\n enableErrorCapture: true,\n enableHeatmaps: false,\n enableBreadcrumbs: true,\n sampleRate: 1.0,\n batchIntervalMs: 5000,\n maxBatchSize: 50,\n debug: false,\n};\n\nexport class Experience {\n private config: Required<ExperienceConfig>;\n private sessionId: string;\n private profileId = '';\n private eventQueue: ExperienceEvent[] = [];\n private errorQueue: ErrorReport[] = [];\n private batchTimer: ReturnType<typeof setInterval> | null = null;\n private breadcrumbs: BreadcrumbManager;\n private flags: Record<string, unknown> = {};\n private flagsFetched = false;\n private teardownFns: Array<() => void> = [];\n private destroyed = false;\n\n private constructor(config: ExperienceConfig) {\n this.config = { ...DEFAULT_CONFIG, ...config } as Required<ExperienceConfig>;\n this.breadcrumbs = new BreadcrumbManager();\n\n // Session management\n const stored = typeof sessionStorage !== 'undefined' ? sessionStorage.getItem('exp_session_id') : null;\n if (stored) {\n this.sessionId = stored;\n } else {\n this.sessionId = generateUUID();\n if (typeof sessionStorage !== 'undefined') {\n sessionStorage.setItem('exp_session_id', this.sessionId);\n }\n }\n }\n\n static init(config: ExperienceConfig): Experience {\n const instance = new Experience(config);\n instance.setup();\n return instance;\n }\n\n private log(...args: unknown[]): void {\n if (this.config.debug) {\n console.log('[Experience]', ...args);\n }\n }\n\n private setup(): void {\n // Check sample rate\n if (Math.random() > this.config.sampleRate) {\n this.log('Sampled out');\n return;\n }\n\n // Breadcrumbs\n if (this.config.enableBreadcrumbs) {\n this.breadcrumbs.install();\n }\n\n // Auto error capture\n if (this.config.enableErrorCapture) {\n this.setupErrorCapture();\n }\n\n // Auto page views\n if (this.config.enableTracking) {\n this.setupAutoPageViews();\n }\n\n // Heatmaps\n if (this.config.enableHeatmaps) {\n this.setupHeatmaps();\n }\n\n // Batch timer\n this.batchTimer = setInterval(() => this.flush(), this.config.batchIntervalMs);\n\n // Page unload\n const beforeUnload = () => this.flushSync();\n window.addEventListener('beforeunload', beforeUnload);\n this.teardownFns.push(() => window.removeEventListener('beforeunload', beforeUnload));\n\n // Offline resilience\n const onlineHandler = () => {\n this.log('Back online, draining queue');\n drainOfflineQueue((url, body) => this.sendRequest(url, body)).catch(() => {});\n };\n window.addEventListener('online', onlineHandler);\n this.teardownFns.push(() => window.removeEventListener('online', onlineHandler));\n\n // Fetch flags\n this.fetchFlags().catch(() => {});\n\n this.log('Initialized', this.config.appId);\n }\n\n // --- Public API ---\n\n track(eventName: string, metadata?: Record<string, unknown>): void {\n if (this.destroyed) return;\n this.enqueueEvent('custom', eventName, metadata);\n }\n\n trackPageView(): void {\n if (this.destroyed) return;\n this.enqueueEvent('page_view', 'page_view');\n this.breadcrumbs.add({ type: 'navigation', category: 'page_view', message: location.href });\n }\n\n captureError(error: Error, extra?: Record<string, unknown>): void {\n if (this.destroyed) return;\n this.enqueueError(error.message, error.stack || '', 'error', extra);\n }\n\n captureMessage(msg: string, severity = 'info'): void {\n if (this.destroyed) return;\n this.enqueueError(msg, '', severity);\n }\n\n getFlag<T>(name: string, defaultValue: T): T {\n if (!this.flagsFetched || !(name in this.flags)) return defaultValue;\n return this.flags[name] as T;\n }\n\n identify(profileId: string): void {\n this.profileId = profileId;\n this.log('Identified', profileId);\n }\n\n async shutdown(): Promise<void> {\n if (this.destroyed) return;\n this.destroyed = true;\n if (this.batchTimer) {\n clearInterval(this.batchTimer);\n this.batchTimer = null;\n }\n await this.flush();\n this.breadcrumbs.teardown();\n this.teardownFns.forEach((fn) => fn());\n this.teardownFns = [];\n this.log('Shutdown complete');\n }\n\n // --- Internal ---\n\n private enqueueEvent(eventType: string, eventName: string, metadata?: Record<string, unknown>): void {\n const event: ExperienceEvent = {\n eventId: generateUUID(),\n appId: this.config.appId,\n profileId: this.profileId,\n sessionId: this.sessionId,\n timestampMs: Date.now(),\n eventType,\n pageUrl: location.href,\n elementTid: metadata?.elementTid as string || '',\n metadata: { eventName, ...metadata },\n pageContext: getPageContext(),\n elementContext: null,\n };\n this.eventQueue.push(event);\n if (this.eventQueue.length >= this.config.maxBatchSize) {\n this.flush().catch(() => {});\n }\n }\n\n private enqueueError(message: string, stack: string, severity: string, extra?: Record<string, unknown>): void {\n const report: ErrorReport = {\n eventId: generateUUID(),\n appId: this.config.appId,\n profileId: this.profileId,\n sessionId: this.sessionId,\n timestampMs: Date.now(),\n message,\n stack,\n severity,\n extra: extra || {},\n pageContext: getPageContext(),\n breadcrumbs: this.breadcrumbs.getAll(),\n };\n this.errorQueue.push(report);\n }\n\n private async flush(): Promise<void> {\n if (this.eventQueue.length > 0) {\n const events = this.eventQueue.splice(0);\n const url = `${this.config.endpoint}/api/v1/ingest/events`;\n const body = JSON.stringify({ events });\n await this.sendOrQueue(url, body);\n }\n if (this.errorQueue.length > 0) {\n const errors = this.errorQueue.splice(0);\n const url = `${this.config.endpoint}/api/v1/ingest/errors`;\n const body = JSON.stringify({ errors });\n await this.sendOrQueue(url, body);\n }\n }\n\n private flushSync(): void {\n if (this.eventQueue.length > 0) {\n const url = `${this.config.endpoint}/api/v1/ingest/events`;\n const body = JSON.stringify({ events: this.eventQueue.splice(0) });\n if (typeof navigator.sendBeacon === 'function') {\n navigator.sendBeacon(url, body);\n }\n }\n if (this.errorQueue.length > 0) {\n const url = `${this.config.endpoint}/api/v1/ingest/errors`;\n const body = JSON.stringify({ errors: this.errorQueue.splice(0) });\n if (typeof navigator.sendBeacon === 'function') {\n navigator.sendBeacon(url, body);\n }\n }\n }\n\n private async sendOrQueue(url: string, body: string): Promise<void> {\n if (typeof navigator !== 'undefined' && !navigator.onLine) {\n await enqueueOffline({ url, body });\n return;\n }\n try {\n await this.sendRequest(url, body);\n } catch {\n await enqueueOffline({ url, body }).catch(() => {});\n }\n }\n\n private async sendRequest(url: string, body: string): Promise<void> {\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${this.config.apiKey}`,\n },\n body,\n });\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`);\n }\n }\n\n private async fetchFlags(): Promise<void> {\n try {\n const url = `${this.config.endpoint}/api/v1/flags/${this.config.appId}`;\n const response = await fetch(url, {\n headers: { 'Authorization': `Bearer ${this.config.apiKey}` },\n });\n if (response.ok) {\n this.flags = await response.json();\n this.flagsFetched = true;\n this.log('Flags loaded', this.flags);\n }\n } catch {\n this.log('Failed to fetch flags');\n }\n }\n\n // --- Auto features ---\n\n private setupErrorCapture(): void {\n const onError = (event: ErrorEvent) => {\n this.enqueueError(\n event.message,\n event.error?.stack || '',\n 'error',\n { filename: event.filename, lineno: event.lineno, colno: event.colno },\n );\n };\n window.addEventListener('error', onError);\n this.teardownFns.push(() => window.removeEventListener('error', onError));\n\n const onUnhandledRejection = (event: PromiseRejectionEvent) => {\n const msg = event.reason instanceof Error ? event.reason.message : String(event.reason);\n const stack = event.reason instanceof Error ? event.reason.stack || '' : '';\n this.enqueueError(msg, stack, 'error', { type: 'unhandledrejection' });\n };\n window.addEventListener('unhandledrejection', onUnhandledRejection);\n this.teardownFns.push(() => window.removeEventListener('unhandledrejection', onUnhandledRejection));\n }\n\n private setupAutoPageViews(): void {\n // Intercept pushState / replaceState\n const originalPushState = history.pushState.bind(history);\n const originalReplaceState = history.replaceState.bind(history);\n\n history.pushState = (...args: Parameters<typeof history.pushState>) => {\n originalPushState(...args);\n this.trackPageView();\n };\n history.replaceState = (...args: Parameters<typeof history.replaceState>) => {\n originalReplaceState(...args);\n this.trackPageView();\n };\n\n this.teardownFns.push(() => {\n history.pushState = originalPushState;\n history.replaceState = originalReplaceState;\n });\n\n const onPopState = () => this.trackPageView();\n window.addEventListener('popstate', onPopState);\n this.teardownFns.push(() => window.removeEventListener('popstate', onPopState));\n\n // Track initial page view\n this.trackPageView();\n }\n\n private setupHeatmaps(): void {\n const handleMove = throttle((e: MouseEvent) => {\n this.enqueueEvent('heatmap_move', 'mousemove', {\n x: e.clientX,\n y: e.clientY,\n viewportWidth: window.innerWidth,\n viewportHeight: window.innerHeight,\n });\n }, 100);\n\n const handleClick = (e: MouseEvent) => {\n const target = e.target as HTMLElement;\n this.enqueueEvent('heatmap_click', 'click', {\n x: e.clientX,\n y: e.clientY,\n viewportWidth: window.innerWidth,\n viewportHeight: window.innerHeight,\n tag: target?.tagName?.toLowerCase() || '',\n text: (target?.textContent || '').slice(0, 100),\n });\n };\n\n document.addEventListener('mousemove', handleMove as EventListener);\n document.addEventListener('click', handleClick);\n this.teardownFns.push(() => {\n document.removeEventListener('mousemove', handleMove as EventListener);\n document.removeEventListener('click', handleClick);\n });\n }\n}\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@shellapps/experience",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Browser event tracking, error capture, and feature flags SDK",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsup",
|
|
20
|
+
"dev": "tsup --watch",
|
|
21
|
+
"clean": "rm -rf dist"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"analytics",
|
|
25
|
+
"tracking",
|
|
26
|
+
"experience",
|
|
27
|
+
"shellapps"
|
|
28
|
+
],
|
|
29
|
+
"author": "Alex Hewitt-Procter <alexhp@hotmail.co.uk>",
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"tsup": "^8.0.1",
|
|
33
|
+
"typescript": "^5.3.3"
|
|
34
|
+
},
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "git+https://github.com/ShellTechnology/shellapps-js.git",
|
|
38
|
+
"directory": "packages/experience"
|
|
39
|
+
},
|
|
40
|
+
"gitHead": "97840ec6e98bfb5289b83abbe5f618ef3c4c663f"
|
|
41
|
+
}
|