@mushi-mushi/core 1.27.2 → 1.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -1
- package/dist/index.cjs +726 -69
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +553 -8
- package/dist/index.d.ts +553 -8
- package/dist/index.js +689 -70
- package/dist/index.js.map +1 -1
- package/package.json +5 -13
- package/CODE_OF_CONDUCT.md +0 -51
- package/CONTRIBUTING.md +0 -226
- package/SECURITY.md +0 -359
package/dist/index.cjs
CHANGED
|
@@ -86,7 +86,92 @@ async function hmacSha256Hex(secret, value) {
|
|
|
86
86
|
return bytesToHex(result);
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
// src/unload-transport.ts
|
|
90
|
+
var MUSHI_BEACON_MAX_BYTES = 60 * 1024;
|
|
91
|
+
function isMushiBeaconEnvelope(value) {
|
|
92
|
+
if (!value || typeof value !== "object") return false;
|
|
93
|
+
const rec = value;
|
|
94
|
+
return rec.mushiBeacon === true && typeof rec.path === "string" && rec.headers !== null && typeof rec.headers === "object";
|
|
95
|
+
}
|
|
96
|
+
function buildBeaconEnvelope(opts) {
|
|
97
|
+
return {
|
|
98
|
+
mushiBeacon: true,
|
|
99
|
+
path: opts.path,
|
|
100
|
+
headers: opts.headers,
|
|
101
|
+
body: opts.body
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function isSameOriginOrRelative(url, origin = typeof location !== "undefined" ? location.origin : "") {
|
|
105
|
+
if (!url) return false;
|
|
106
|
+
if (url.startsWith("/")) return true;
|
|
107
|
+
if (!origin) return false;
|
|
108
|
+
try {
|
|
109
|
+
return new URL(url, origin).origin === origin;
|
|
110
|
+
} catch {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
function extractTunnelPath(url) {
|
|
115
|
+
try {
|
|
116
|
+
const parsed = url.startsWith("/") ? new URL(url, "https://tunnel.local") : new URL(url);
|
|
117
|
+
return `${parsed.pathname}${parsed.search}`;
|
|
118
|
+
} catch {
|
|
119
|
+
return url;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function sendOnUnload(opts) {
|
|
123
|
+
const fetchImpl = opts.fetchImpl ?? (typeof fetch === "function" ? fetch : void 0);
|
|
124
|
+
if (fetchImpl) {
|
|
125
|
+
try {
|
|
126
|
+
void fetchImpl(opts.url, {
|
|
127
|
+
method: "POST",
|
|
128
|
+
headers: {
|
|
129
|
+
"Content-Type": "application/json",
|
|
130
|
+
...opts.headers
|
|
131
|
+
},
|
|
132
|
+
body: opts.body,
|
|
133
|
+
keepalive: true
|
|
134
|
+
});
|
|
135
|
+
return true;
|
|
136
|
+
} catch {
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
const beacon = opts.sendBeacon ?? (typeof navigator !== "undefined" ? navigator.sendBeacon?.bind(navigator) : void 0);
|
|
140
|
+
if (!beacon || !isSameOriginOrRelative(opts.url)) return false;
|
|
141
|
+
let parsedBody = opts.body;
|
|
142
|
+
try {
|
|
143
|
+
parsedBody = JSON.parse(opts.body);
|
|
144
|
+
} catch {
|
|
145
|
+
parsedBody = opts.body;
|
|
146
|
+
}
|
|
147
|
+
const envelope = JSON.stringify(
|
|
148
|
+
buildBeaconEnvelope({
|
|
149
|
+
path: opts.path ?? extractTunnelPath(opts.url),
|
|
150
|
+
headers: opts.headers,
|
|
151
|
+
body: parsedBody
|
|
152
|
+
})
|
|
153
|
+
);
|
|
154
|
+
if (envelope.length > MUSHI_BEACON_MAX_BYTES) return false;
|
|
155
|
+
try {
|
|
156
|
+
return beacon(opts.url, new Blob([envelope], { type: "application/json" }));
|
|
157
|
+
} catch {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
var pageUnloading = false;
|
|
162
|
+
function markPageUnloading() {
|
|
163
|
+
pageUnloading = true;
|
|
164
|
+
}
|
|
165
|
+
function isPageUnloading() {
|
|
166
|
+
return pageUnloading;
|
|
167
|
+
}
|
|
168
|
+
|
|
89
169
|
// src/api-client.ts
|
|
170
|
+
var lastOutbound = null;
|
|
171
|
+
function flushLastOutboundOnUnload() {
|
|
172
|
+
if (!lastOutbound) return false;
|
|
173
|
+
return sendOnUnload(lastOutbound);
|
|
174
|
+
}
|
|
90
175
|
var _credWarningEmitted = false;
|
|
91
176
|
var MUSHI_USER_TOKEN_HEADER = "X-Mushi-User-Token";
|
|
92
177
|
var MUSHI_PROJECT_HEADER = "X-Mushi-Project";
|
|
@@ -107,6 +192,11 @@ function buildSdkIngestHeaders(opts) {
|
|
|
107
192
|
var DEFAULT_API_ENDPOINT = "https://dxptnwrhwsqckaftyymj.supabase.co/functions/v1/api";
|
|
108
193
|
var MUSHI_INTERNAL_HEADER = "X-Mushi-Internal";
|
|
109
194
|
var MUSHI_INTERNAL_INIT_MARKER = "__mushiInternal";
|
|
195
|
+
function resolveRequestBaseUrl(apiEndpoint, tunnel) {
|
|
196
|
+
const trimmedTunnel = tunnel?.trim();
|
|
197
|
+
if (trimmedTunnel) return trimmedTunnel.replace(/\/$/, "");
|
|
198
|
+
return (apiEndpoint ?? DEFAULT_API_ENDPOINT).replace(/\/$/, "");
|
|
199
|
+
}
|
|
110
200
|
var DEFAULT_TIMEOUT = 1e4;
|
|
111
201
|
var DEFAULT_MAX_RETRIES = 2;
|
|
112
202
|
var DEFAULT_CIRCUIT_THRESHOLD = 4;
|
|
@@ -117,6 +207,7 @@ function createApiClient(options) {
|
|
|
117
207
|
projectId,
|
|
118
208
|
apiKey,
|
|
119
209
|
apiEndpoint = DEFAULT_API_ENDPOINT,
|
|
210
|
+
tunnel,
|
|
120
211
|
timeout = DEFAULT_TIMEOUT,
|
|
121
212
|
maxRetries = DEFAULT_MAX_RETRIES,
|
|
122
213
|
getUserToken,
|
|
@@ -124,7 +215,7 @@ function createApiClient(options) {
|
|
|
124
215
|
sdkVersion,
|
|
125
216
|
circuitBreaker
|
|
126
217
|
} = options;
|
|
127
|
-
let baseUrl = apiEndpoint
|
|
218
|
+
let baseUrl = resolveRequestBaseUrl(apiEndpoint, tunnel);
|
|
128
219
|
const cbEnabled = circuitBreaker?.enabled !== false;
|
|
129
220
|
const cbThreshold = Math.max(1, circuitBreaker?.threshold ?? DEFAULT_CIRCUIT_THRESHOLD);
|
|
130
221
|
const cbCooldownMs = Math.max(1e3, circuitBreaker?.cooldownMs ?? DEFAULT_CIRCUIT_COOLDOWN_MS);
|
|
@@ -148,21 +239,27 @@ function createApiClient(options) {
|
|
|
148
239
|
const controller = new AbortController();
|
|
149
240
|
const timer = setTimeout(() => controller.abort(), timeout);
|
|
150
241
|
const userToken = getUserToken?.() ?? null;
|
|
242
|
+
const headers = {
|
|
243
|
+
...buildSdkIngestHeaders({
|
|
244
|
+
apiKey,
|
|
245
|
+
projectId,
|
|
246
|
+
sdkPackage,
|
|
247
|
+
sdkVersion,
|
|
248
|
+
userToken,
|
|
249
|
+
internalKind
|
|
250
|
+
}),
|
|
251
|
+
...extraHeaders
|
|
252
|
+
};
|
|
253
|
+
const serialized = body ? JSON.stringify(body) : void 0;
|
|
254
|
+
if (serialized && method !== "GET") {
|
|
255
|
+
lastOutbound = { url, headers, body: serialized, path };
|
|
256
|
+
}
|
|
151
257
|
try {
|
|
152
258
|
const response = await fetch(url, {
|
|
153
259
|
method,
|
|
154
|
-
headers
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
projectId,
|
|
158
|
-
sdkPackage,
|
|
159
|
-
sdkVersion,
|
|
160
|
-
userToken,
|
|
161
|
-
internalKind
|
|
162
|
-
}),
|
|
163
|
-
...extraHeaders
|
|
164
|
-
},
|
|
165
|
-
body: body ? JSON.stringify(body) : void 0,
|
|
260
|
+
headers,
|
|
261
|
+
body: serialized,
|
|
262
|
+
keepalive: isPageUnloading(),
|
|
166
263
|
signal: controller.signal,
|
|
167
264
|
...internalKind ? { [MUSHI_INTERNAL_INIT_MARKER]: internalKind } : {}
|
|
168
265
|
});
|
|
@@ -200,7 +297,8 @@ function createApiClient(options) {
|
|
|
200
297
|
ok: false,
|
|
201
298
|
error: {
|
|
202
299
|
code: errorBody.error?.code || `HTTP_${response.status}`,
|
|
203
|
-
message: errorBody.error?.message || errorBody.message || `HTTP ${response.status} error
|
|
300
|
+
message: errorBody.error?.message || errorBody.message || `HTTP ${response.status} error`,
|
|
301
|
+
status: response.status
|
|
204
302
|
}
|
|
205
303
|
};
|
|
206
304
|
}
|
|
@@ -243,6 +341,7 @@ function createApiClient(options) {
|
|
|
243
341
|
"X-Reporter-Hmac": hmac
|
|
244
342
|
},
|
|
245
343
|
body: body ? JSON.stringify(body) : void 0,
|
|
344
|
+
keepalive: isPageUnloading(),
|
|
246
345
|
[MUSHI_INTERNAL_INIT_MARKER]: "reporter-poll"
|
|
247
346
|
});
|
|
248
347
|
if (!response.ok) {
|
|
@@ -376,6 +475,18 @@ function createApiClient(options) {
|
|
|
376
475
|
"discovery"
|
|
377
476
|
);
|
|
378
477
|
},
|
|
478
|
+
/** POST /v1/sdk/events — batched product-analytics events (Mushi.track()).
|
|
479
|
+
* Best-effort (1 retry); the event tracker spills unsent batches to
|
|
480
|
+
* localStorage and replays them on the next page load. */
|
|
481
|
+
async postProductEvents(payload) {
|
|
482
|
+
return request(
|
|
483
|
+
"POST",
|
|
484
|
+
"/v1/sdk/events",
|
|
485
|
+
payload,
|
|
486
|
+
1,
|
|
487
|
+
"discovery"
|
|
488
|
+
);
|
|
489
|
+
},
|
|
379
490
|
async submitActivity(userId, events, opts) {
|
|
380
491
|
return request(
|
|
381
492
|
"POST",
|
|
@@ -502,7 +613,7 @@ function sleep(ms) {
|
|
|
502
613
|
function getBackoffDelay(attempt) {
|
|
503
614
|
return Math.min(1e3 * 2 ** attempt + Math.random() * 500, 1e4);
|
|
504
615
|
}
|
|
505
|
-
function parseRetryAfter(value,
|
|
616
|
+
function parseRetryAfter(value, now3 = Date.now()) {
|
|
506
617
|
if (!value) return null;
|
|
507
618
|
const raw = value.trim();
|
|
508
619
|
if (!raw) return null;
|
|
@@ -510,7 +621,7 @@ function parseRetryAfter(value, now2 = Date.now()) {
|
|
|
510
621
|
if (Number.isFinite(seconds)) return seconds < 0 ? null : seconds * 1e3;
|
|
511
622
|
const at = Date.parse(raw);
|
|
512
623
|
if (Number.isNaN(at)) return null;
|
|
513
|
-
return Math.max(at -
|
|
624
|
+
return Math.max(at - now3, 0);
|
|
514
625
|
}
|
|
515
626
|
function isRetryable(error) {
|
|
516
627
|
if (error instanceof DOMException && error.name === "AbortError") return true;
|
|
@@ -518,6 +629,32 @@ function isRetryable(error) {
|
|
|
518
629
|
return false;
|
|
519
630
|
}
|
|
520
631
|
|
|
632
|
+
// src/error-filters.ts
|
|
633
|
+
function matchesErrorFilter(value, filters) {
|
|
634
|
+
if (!value || !filters || filters.length === 0) return false;
|
|
635
|
+
for (const filter of filters) {
|
|
636
|
+
if (typeof filter === "string") {
|
|
637
|
+
if (value.includes(filter)) return true;
|
|
638
|
+
continue;
|
|
639
|
+
}
|
|
640
|
+
try {
|
|
641
|
+
if (filter.test(value)) return true;
|
|
642
|
+
} catch {
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
return false;
|
|
646
|
+
}
|
|
647
|
+
function shouldDropCapturedError(input) {
|
|
648
|
+
if (matchesErrorFilter(input.message, input.ignoreErrors)) return true;
|
|
649
|
+
const filename = input.filename?.trim() || "";
|
|
650
|
+
if (filename && matchesErrorFilter(filename, input.denyUrls)) return true;
|
|
651
|
+
if (input.allowUrls && input.allowUrls.length > 0) {
|
|
652
|
+
if (!filename) return true;
|
|
653
|
+
if (!matchesErrorFilter(filename, input.allowUrls)) return true;
|
|
654
|
+
}
|
|
655
|
+
return false;
|
|
656
|
+
}
|
|
657
|
+
|
|
521
658
|
// src/region.ts
|
|
522
659
|
var REGION_ENDPOINTS = {
|
|
523
660
|
us: DEFAULT_API_ENDPOINT,
|
|
@@ -696,11 +833,14 @@ var KNOWN_CONFIG_KEYS = [
|
|
|
696
833
|
"projectId",
|
|
697
834
|
"apiKey",
|
|
698
835
|
"apiEndpoint",
|
|
836
|
+
"tunnel",
|
|
699
837
|
"timeout",
|
|
700
838
|
"maxRetries",
|
|
701
839
|
"circuitBreaker",
|
|
702
840
|
"preset",
|
|
703
841
|
"runtimeConfig",
|
|
842
|
+
"analytics",
|
|
843
|
+
"trackSessions",
|
|
704
844
|
"sentry",
|
|
705
845
|
"widget",
|
|
706
846
|
"capture",
|
|
@@ -715,8 +855,13 @@ var KNOWN_CONFIG_KEYS = [
|
|
|
715
855
|
"enabled",
|
|
716
856
|
"appVersion",
|
|
717
857
|
"sampleRate",
|
|
858
|
+
"replaySampleRate",
|
|
859
|
+
"replaysOnErrorSampleRate",
|
|
718
860
|
"beforeSend",
|
|
719
861
|
"beforeSendFeedback",
|
|
862
|
+
"ignoreErrors",
|
|
863
|
+
"denyUrls",
|
|
864
|
+
"allowUrls",
|
|
720
865
|
"onCrashedLastRun"
|
|
721
866
|
];
|
|
722
867
|
function presetDefaults(preset) {
|
|
@@ -787,23 +932,23 @@ function presetDefaults(preset) {
|
|
|
787
932
|
}
|
|
788
933
|
}
|
|
789
934
|
function expandPreset(config) {
|
|
790
|
-
const
|
|
791
|
-
if (!
|
|
935
|
+
const defaults2 = presetDefaults(config.preset);
|
|
936
|
+
if (!defaults2) return config;
|
|
792
937
|
return {
|
|
793
938
|
...config,
|
|
794
939
|
widget: {
|
|
795
|
-
...
|
|
940
|
+
...defaults2.widget,
|
|
796
941
|
...config.widget
|
|
797
942
|
},
|
|
798
943
|
capture: {
|
|
799
|
-
...
|
|
944
|
+
...defaults2.capture,
|
|
800
945
|
...config.capture
|
|
801
946
|
},
|
|
802
947
|
proactive: {
|
|
803
|
-
...
|
|
948
|
+
...defaults2.proactive,
|
|
804
949
|
...config.proactive,
|
|
805
950
|
cooldown: {
|
|
806
|
-
...
|
|
951
|
+
...defaults2.proactive?.cooldown,
|
|
807
952
|
...config.proactive?.cooldown
|
|
808
953
|
}
|
|
809
954
|
}
|
|
@@ -1214,12 +1359,12 @@ function createOfflineQueue(config = {}) {
|
|
|
1214
1359
|
const n = row.attempts;
|
|
1215
1360
|
return typeof n === "number" && Number.isFinite(n) ? n : 0;
|
|
1216
1361
|
}
|
|
1217
|
-
function isExpired(row,
|
|
1362
|
+
function isExpired(row, now3) {
|
|
1218
1363
|
const queuedAt = row.queuedAt;
|
|
1219
1364
|
if (!queuedAt) return false;
|
|
1220
1365
|
const ts = Date.parse(queuedAt);
|
|
1221
1366
|
if (Number.isNaN(ts)) return false;
|
|
1222
|
-
return
|
|
1367
|
+
return now3 - ts > MAX_QUEUE_AGE_MS;
|
|
1223
1368
|
}
|
|
1224
1369
|
function detectBackend() {
|
|
1225
1370
|
if (backendType) return backendType;
|
|
@@ -1406,7 +1551,7 @@ function createOfflineQueue(config = {}) {
|
|
|
1406
1551
|
await lsEnqueue(report);
|
|
1407
1552
|
return "localstorage";
|
|
1408
1553
|
}
|
|
1409
|
-
async function
|
|
1554
|
+
async function enqueue2(report) {
|
|
1410
1555
|
if (!enabled) return;
|
|
1411
1556
|
const currentSize = await size();
|
|
1412
1557
|
if (currentSize >= maxQueueSize) {
|
|
@@ -1424,10 +1569,10 @@ function createOfflineQueue(config = {}) {
|
|
|
1424
1569
|
async function runFlush(client) {
|
|
1425
1570
|
const { backend, rows: storedRows } = await readRows();
|
|
1426
1571
|
let rows = storedRows;
|
|
1427
|
-
const
|
|
1572
|
+
const now3 = Date.now();
|
|
1428
1573
|
const fresh = [];
|
|
1429
1574
|
for (const row of rows) {
|
|
1430
|
-
if (isExpired(row,
|
|
1575
|
+
if (isExpired(row, now3)) {
|
|
1431
1576
|
const rowId = row.id;
|
|
1432
1577
|
await removeRow(backend, rowId);
|
|
1433
1578
|
queueLog.debug("Offline queue: evicting stale report", { id: rowId });
|
|
@@ -1575,7 +1720,7 @@ function createOfflineQueue(config = {}) {
|
|
|
1575
1720
|
syncCleanup?.();
|
|
1576
1721
|
syncCleanup = null;
|
|
1577
1722
|
}
|
|
1578
|
-
return { enqueue, flush, size, clear, startAutoSync, stopAutoSync };
|
|
1723
|
+
return { enqueue: enqueue2, flush, size, clear, startAutoSync, stopAutoSync };
|
|
1579
1724
|
}
|
|
1580
1725
|
|
|
1581
1726
|
// src/environment.ts
|
|
@@ -1917,21 +2062,94 @@ function randomSuffix() {
|
|
|
1917
2062
|
return n.toString(36).padStart(6, "0").slice(0, 6);
|
|
1918
2063
|
}
|
|
1919
2064
|
|
|
2065
|
+
// src/analytics-gate.ts
|
|
2066
|
+
var AUTOMATED_UA_RE = /HeadlessChrome|PhantomJS|Lighthouse|\bbot\b|bot[/-]|crawler|spider/i;
|
|
2067
|
+
function consentKey(projectId) {
|
|
2068
|
+
return "mushi_events_consent_" + projectId;
|
|
2069
|
+
}
|
|
2070
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
2071
|
+
function dntActive() {
|
|
2072
|
+
if (typeof navigator === "undefined") return false;
|
|
2073
|
+
const nav = navigator;
|
|
2074
|
+
if (nav.globalPrivacyControl === true) return true;
|
|
2075
|
+
const winDnt = typeof window !== "undefined" ? window.doNotTrack : void 0;
|
|
2076
|
+
const dnt = nav.doNotTrack ?? nav.msDoNotTrack ?? winDnt;
|
|
2077
|
+
return dnt === "1" || dnt === "yes";
|
|
2078
|
+
}
|
|
2079
|
+
function isAutomatedBrowser() {
|
|
2080
|
+
if (typeof navigator === "undefined") return false;
|
|
2081
|
+
if (navigator.webdriver === true) return true;
|
|
2082
|
+
return AUTOMATED_UA_RE.test(navigator.userAgent ?? "");
|
|
2083
|
+
}
|
|
2084
|
+
function analyticsBlockReason(config) {
|
|
2085
|
+
if (config?.enabled === false) return "disabled";
|
|
2086
|
+
if (config?.respectDoNotTrack !== false && dntActive()) return "dnt";
|
|
2087
|
+
if (config?.excludeBots !== false && isAutomatedBrowser()) return "automation";
|
|
2088
|
+
return null;
|
|
2089
|
+
}
|
|
2090
|
+
function readStoredConsent(projectId) {
|
|
2091
|
+
if (!projectId) return null;
|
|
2092
|
+
try {
|
|
2093
|
+
const v = localStorage.getItem(consentKey(projectId));
|
|
2094
|
+
return v === "granted" || v === "denied" ? v : null;
|
|
2095
|
+
} catch {
|
|
2096
|
+
return null;
|
|
2097
|
+
}
|
|
2098
|
+
}
|
|
2099
|
+
function writeStoredConsent(projectId, state) {
|
|
2100
|
+
if (!projectId) return;
|
|
2101
|
+
try {
|
|
2102
|
+
localStorage.setItem(consentKey(projectId), state);
|
|
2103
|
+
} catch {
|
|
2104
|
+
}
|
|
2105
|
+
}
|
|
2106
|
+
function resolveAnalyticsConsent(projectId, config) {
|
|
2107
|
+
const stored = readStoredConsent(projectId);
|
|
2108
|
+
if (config?.consent === "required") return stored ?? "pending";
|
|
2109
|
+
return stored === "denied" ? "denied" : "granted";
|
|
2110
|
+
}
|
|
2111
|
+
function setAnalyticsConsent(projectId, state) {
|
|
2112
|
+
writeStoredConsent(projectId, state);
|
|
2113
|
+
for (const listener of [...listeners]) {
|
|
2114
|
+
try {
|
|
2115
|
+
listener(state);
|
|
2116
|
+
} catch {
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
}
|
|
2120
|
+
function onAnalyticsConsentChange(listener) {
|
|
2121
|
+
listeners.add(listener);
|
|
2122
|
+
return () => {
|
|
2123
|
+
listeners.delete(listener);
|
|
2124
|
+
};
|
|
2125
|
+
}
|
|
2126
|
+
|
|
1920
2127
|
// src/session-tracker.ts
|
|
1921
2128
|
var HEARTBEAT_INTERVAL_MS = 6e4;
|
|
1922
2129
|
var _client = null;
|
|
1923
2130
|
var _sdkVersion;
|
|
1924
2131
|
var _userIdHash = null;
|
|
1925
|
-
var
|
|
2132
|
+
var _reporterToken = null;
|
|
1926
2133
|
var _pageViewCount = 0;
|
|
1927
2134
|
var _heartbeatTimer = null;
|
|
1928
2135
|
var _initialized = false;
|
|
2136
|
+
var _active = false;
|
|
2137
|
+
var _started = false;
|
|
2138
|
+
var _unsubscribeConsent = null;
|
|
1929
2139
|
function now() {
|
|
1930
2140
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
1931
2141
|
}
|
|
1932
2142
|
function currentRoute() {
|
|
1933
2143
|
if (typeof window === "undefined") return "";
|
|
1934
|
-
return window.location.pathname
|
|
2144
|
+
return window.location.pathname;
|
|
2145
|
+
}
|
|
2146
|
+
function referrerOrigin() {
|
|
2147
|
+
if (typeof document === "undefined" || !document.referrer) return null;
|
|
2148
|
+
try {
|
|
2149
|
+
return new URL(document.referrer).origin;
|
|
2150
|
+
} catch {
|
|
2151
|
+
return null;
|
|
2152
|
+
}
|
|
1935
2153
|
}
|
|
1936
2154
|
function userAgent() {
|
|
1937
2155
|
if (typeof navigator === "undefined") return null;
|
|
@@ -1943,7 +2161,8 @@ function buildPayload(kind, extra) {
|
|
|
1943
2161
|
session_id: getSessionId(),
|
|
1944
2162
|
ts: now(),
|
|
1945
2163
|
page_view_count: _pageViewCount,
|
|
1946
|
-
|
|
2164
|
+
// Wire name kept for older servers; the value is the raw token (hashed server-side).
|
|
2165
|
+
reporter_token_hash: _reporterToken,
|
|
1947
2166
|
user_id_hash: _userIdHash,
|
|
1948
2167
|
user_agent: userAgent(),
|
|
1949
2168
|
sdk_version: _sdkVersion,
|
|
@@ -1951,26 +2170,42 @@ function buildPayload(kind, extra) {
|
|
|
1951
2170
|
};
|
|
1952
2171
|
}
|
|
1953
2172
|
function send(payload) {
|
|
1954
|
-
if (!_client) return;
|
|
2173
|
+
if (!_client || !_active) return;
|
|
1955
2174
|
_client.postSessionEvent(payload).catch(() => {
|
|
1956
2175
|
});
|
|
1957
2176
|
}
|
|
2177
|
+
function recordPageView(route) {
|
|
2178
|
+
if (!_active) return;
|
|
2179
|
+
_pageViewCount += 1;
|
|
2180
|
+
send(buildPayload("page_view", { route }));
|
|
2181
|
+
}
|
|
2182
|
+
function activate() {
|
|
2183
|
+
if (_active || !_initialized) return;
|
|
2184
|
+
_active = true;
|
|
2185
|
+
if (!_started) {
|
|
2186
|
+
_started = true;
|
|
2187
|
+
_pageViewCount = 1;
|
|
2188
|
+
send(buildPayload("session_start", { route: currentRoute(), referrer: referrerOrigin() }));
|
|
2189
|
+
}
|
|
2190
|
+
_heartbeatTimer = setInterval(() => {
|
|
2191
|
+
send(buildPayload("session_heartbeat", { route: currentRoute() }));
|
|
2192
|
+
}, HEARTBEAT_INTERVAL_MS);
|
|
2193
|
+
}
|
|
2194
|
+
function deactivate() {
|
|
2195
|
+
_active = false;
|
|
2196
|
+
if (_heartbeatTimer != null) {
|
|
2197
|
+
clearInterval(_heartbeatTimer);
|
|
2198
|
+
_heartbeatTimer = null;
|
|
2199
|
+
}
|
|
2200
|
+
}
|
|
1958
2201
|
function initSessionTracker(opts) {
|
|
1959
2202
|
if (_initialized || typeof window === "undefined") return;
|
|
2203
|
+
if (analyticsBlockReason(opts.analytics) !== null) return;
|
|
1960
2204
|
_initialized = true;
|
|
1961
2205
|
_client = opts.client;
|
|
1962
2206
|
_sdkVersion = opts.sdkVersion;
|
|
1963
|
-
|
|
2207
|
+
_reporterToken = opts.reporterToken ?? opts.reporterTokenHash ?? null;
|
|
1964
2208
|
_userIdHash = opts.userIdHash ?? null;
|
|
1965
|
-
const entryRoute = currentRoute();
|
|
1966
|
-
_pageViewCount = 1;
|
|
1967
|
-
send(buildPayload("session_start", {
|
|
1968
|
-
route: entryRoute,
|
|
1969
|
-
referrer: typeof document !== "undefined" ? document.referrer ?? null : null
|
|
1970
|
-
}));
|
|
1971
|
-
_heartbeatTimer = setInterval(() => {
|
|
1972
|
-
send(buildPayload("session_heartbeat", { route: currentRoute() }));
|
|
1973
|
-
}, HEARTBEAT_INTERVAL_MS);
|
|
1974
2209
|
const onHide = () => {
|
|
1975
2210
|
if (typeof document !== "undefined" && document.visibilityState === "hidden") {
|
|
1976
2211
|
send(buildPayload("session_end", { route: currentRoute() }));
|
|
@@ -1979,46 +2214,421 @@ function initSessionTracker(opts) {
|
|
|
1979
2214
|
if (typeof document !== "undefined") {
|
|
1980
2215
|
document.addEventListener("visibilitychange", onHide, { passive: true });
|
|
1981
2216
|
}
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
2217
|
+
window.addEventListener("pagehide", () => {
|
|
2218
|
+
send(buildPayload("session_end", { route: currentRoute() }));
|
|
2219
|
+
}, { passive: true });
|
|
2220
|
+
if (opts.patchHistory !== false) {
|
|
2221
|
+
patchHistoryForPageViews();
|
|
1986
2222
|
}
|
|
1987
|
-
|
|
2223
|
+
_unsubscribeConsent = onAnalyticsConsentChange((state) => {
|
|
2224
|
+
if (state === "granted") activate();
|
|
2225
|
+
else deactivate();
|
|
2226
|
+
});
|
|
2227
|
+
if (resolveAnalyticsConsent(opts.projectId ?? "", opts.analytics) === "granted") activate();
|
|
1988
2228
|
}
|
|
1989
2229
|
function trackPageView(route) {
|
|
1990
2230
|
if (!_initialized || !_client) return;
|
|
1991
|
-
|
|
1992
|
-
send(buildPayload("page_view", { route: route ?? currentRoute() }));
|
|
2231
|
+
recordPageView(route ?? currentRoute());
|
|
1993
2232
|
}
|
|
1994
2233
|
function updateSessionIdentity(userIdHash) {
|
|
1995
2234
|
_userIdHash = userIdHash;
|
|
1996
2235
|
}
|
|
1997
2236
|
function destroySessionTracker() {
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
}
|
|
2237
|
+
deactivate();
|
|
2238
|
+
_unsubscribeConsent?.();
|
|
2239
|
+
_unsubscribeConsent = null;
|
|
2002
2240
|
_initialized = false;
|
|
2241
|
+
_started = false;
|
|
2003
2242
|
_client = null;
|
|
2004
2243
|
}
|
|
2005
2244
|
var _historyPatched = false;
|
|
2006
2245
|
function patchHistoryForPageViews() {
|
|
2007
2246
|
if (_historyPatched || typeof history === "undefined") return;
|
|
2008
2247
|
_historyPatched = true;
|
|
2009
|
-
const wrap = (original) => function(...args) {
|
|
2248
|
+
const wrap = (original, countsAsPageView) => function(...args) {
|
|
2010
2249
|
const result = original.apply(this, args);
|
|
2011
|
-
if (
|
|
2012
|
-
|
|
2013
|
-
|
|
2250
|
+
if (countsAsPageView) recordPageView(currentRoute());
|
|
2251
|
+
return result;
|
|
2252
|
+
};
|
|
2253
|
+
history.pushState = wrap(history.pushState, true);
|
|
2254
|
+
history.replaceState = wrap(history.replaceState, false);
|
|
2255
|
+
window.addEventListener("popstate", () => {
|
|
2256
|
+
recordPageView(currentRoute());
|
|
2257
|
+
}, { passive: true });
|
|
2258
|
+
}
|
|
2259
|
+
|
|
2260
|
+
// src/analytics-taxonomy.ts
|
|
2261
|
+
var EVENT_NAME_RE = /^[a-z][a-z0-9_]{1,63}$/;
|
|
2262
|
+
var EVENT_PROPERTY_LIMITS = {
|
|
2263
|
+
maxKeys: 40,
|
|
2264
|
+
maxKeyLength: 64,
|
|
2265
|
+
maxValueLength: 256,
|
|
2266
|
+
maxBytes: 8192,
|
|
2267
|
+
/** Client batch size before an immediate flush. */
|
|
2268
|
+
maxClientBatch: 20,
|
|
2269
|
+
/** Server-side per-request ceiling. */
|
|
2270
|
+
maxServerBatch: 50,
|
|
2271
|
+
/** Distinct event names per project before the server returns 422. */
|
|
2272
|
+
maxEventNamesPerProject: 200
|
|
2273
|
+
};
|
|
2274
|
+
var RESERVED_PROPERTY_PREFIX = "$";
|
|
2275
|
+
var PII_PROPERTY_KEY_RE = /(email|phone|password|passwd|token|secret|ssn|address|credit|card|iban)/i;
|
|
2276
|
+
var MUSHI_SURFACES = ["web", "console", "docs", "cli", "mcp", "server", "mobile"];
|
|
2277
|
+
var MUSHI_EVENTS = {
|
|
2278
|
+
// ── Any surface (SDK `analytics.autoPageviews`) ──────────────────────────
|
|
2279
|
+
page_view: { surface: ["web", "docs", "console"], required: [] },
|
|
2280
|
+
// ── Landing + docs (apps/docs) ───────────────────────────────────────────
|
|
2281
|
+
// Every docs route; `route` repeats the reserved `$route` as a host prop so
|
|
2282
|
+
// the requirement is checkable. company_funnel_weekly counts visits from
|
|
2283
|
+
// this and landing_view.
|
|
2284
|
+
docs_page_view: { surface: "docs", required: ["route"] },
|
|
2285
|
+
landing_view: { surface: "docs", required: [] },
|
|
2286
|
+
cta_click: { surface: "docs", required: ["cta_id", "location"] },
|
|
2287
|
+
quickstart_view: { surface: "docs", required: [] },
|
|
2288
|
+
pricing_view: { surface: "docs", required: [] },
|
|
2289
|
+
connect_demo_click: { surface: "docs", required: ["href"] },
|
|
2290
|
+
signup_click: { surface: "docs", required: ["href"] },
|
|
2291
|
+
// ── Console (apps/admin) ─────────────────────────────────────────────────
|
|
2292
|
+
signup_completed: { surface: "console", required: ["signup_source"] },
|
|
2293
|
+
report_opened: { surface: ["console", "mcp"], required: ["report_id"] },
|
|
2294
|
+
fix_context_pulled: { surface: ["console", "mcp"], required: ["report_id"] },
|
|
2295
|
+
fix_dispatched: { surface: ["console", "mcp"], required: ["report_id", "agent"] },
|
|
2296
|
+
invite_sent: { surface: "console", required: [] },
|
|
2297
|
+
upgrade_clicked: { surface: "console", required: ["plan"] },
|
|
2298
|
+
test_report_sent: { surface: "console", required: ["project_id"] },
|
|
2299
|
+
// ── Server-emitted (edge functions) ──────────────────────────────────────
|
|
2300
|
+
project_created: { surface: "server", required: ["project_id"] },
|
|
2301
|
+
key_minted: { surface: "server", required: ["project_id"] },
|
|
2302
|
+
first_report_received: { surface: "server", required: ["project_id"] },
|
|
2303
|
+
fix_merged: { surface: "server", required: ["project_id"] },
|
|
2304
|
+
upgrade_completed: { surface: "server", required: ["project_id", "plan"] },
|
|
2305
|
+
// ── Growth loop (widget "Bug reports by Mushi" mark) ─────────────────────
|
|
2306
|
+
loop_impression: { surface: "web", required: [] },
|
|
2307
|
+
loop_click: { surface: "web", required: [] },
|
|
2308
|
+
loop_signup: { surface: "console", required: ["ref"] }
|
|
2309
|
+
};
|
|
2310
|
+
var MUSHI_EVENT_NAMES = Object.keys(MUSHI_EVENTS);
|
|
2311
|
+
var ACTIVATION_EVENT = "first_report_received";
|
|
2312
|
+
var HABIT_EVENTS = ["report_opened", "fix_context_pulled", "fix_dispatched"];
|
|
2313
|
+
function isValidEventName(name) {
|
|
2314
|
+
return typeof name === "string" && EVENT_NAME_RE.test(name);
|
|
2315
|
+
}
|
|
2316
|
+
function sanitizeEventProperties(input, opts = {}) {
|
|
2317
|
+
const out = {};
|
|
2318
|
+
const dropped = [];
|
|
2319
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) return { properties: out, dropped };
|
|
2320
|
+
const allow = new Set(opts.allowlist ?? []);
|
|
2321
|
+
let keys = 0;
|
|
2322
|
+
for (const [rawKey, rawValue] of Object.entries(input)) {
|
|
2323
|
+
if (keys >= EVENT_PROPERTY_LIMITS.maxKeys) {
|
|
2324
|
+
dropped.push(rawKey);
|
|
2325
|
+
continue;
|
|
2326
|
+
}
|
|
2327
|
+
const key = rawKey.slice(0, EVENT_PROPERTY_LIMITS.maxKeyLength);
|
|
2328
|
+
if (key.startsWith(RESERVED_PROPERTY_PREFIX) && !opts.allowReserved) {
|
|
2329
|
+
dropped.push(key);
|
|
2330
|
+
continue;
|
|
2331
|
+
}
|
|
2332
|
+
if (PII_PROPERTY_KEY_RE.test(key) && !allow.has(key)) {
|
|
2333
|
+
dropped.push(key);
|
|
2334
|
+
continue;
|
|
2014
2335
|
}
|
|
2336
|
+
let value;
|
|
2337
|
+
if (rawValue === null || typeof rawValue === "boolean") value = rawValue;
|
|
2338
|
+
else if (typeof rawValue === "number") value = Number.isFinite(rawValue) ? rawValue : null;
|
|
2339
|
+
else if (typeof rawValue === "string") {
|
|
2340
|
+
const s = rawValue.slice(0, EVENT_PROPERTY_LIMITS.maxValueLength);
|
|
2341
|
+
value = opts.scrub ? opts.scrub(s) : s;
|
|
2342
|
+
} else if (rawValue instanceof Date) value = rawValue.toISOString();
|
|
2343
|
+
else {
|
|
2344
|
+
dropped.push(key);
|
|
2345
|
+
continue;
|
|
2346
|
+
}
|
|
2347
|
+
out[key] = value;
|
|
2348
|
+
keys += 1;
|
|
2349
|
+
}
|
|
2350
|
+
return { properties: out, dropped };
|
|
2351
|
+
}
|
|
2352
|
+
function propertiesWithinByteLimit(properties) {
|
|
2353
|
+
try {
|
|
2354
|
+
return JSON.stringify(properties).length <= EVENT_PROPERTY_LIMITS.maxBytes;
|
|
2355
|
+
} catch {
|
|
2356
|
+
return false;
|
|
2357
|
+
}
|
|
2358
|
+
}
|
|
2359
|
+
|
|
2360
|
+
// src/event-tracker.ts
|
|
2361
|
+
var _client2 = null;
|
|
2362
|
+
var _projectId = "";
|
|
2363
|
+
var _anonId = null;
|
|
2364
|
+
var _userId = null;
|
|
2365
|
+
var _userTraits = null;
|
|
2366
|
+
var _sdkVersion2;
|
|
2367
|
+
var _config = defaults();
|
|
2368
|
+
var _scrub;
|
|
2369
|
+
var _getSessionId;
|
|
2370
|
+
var _buffer = [];
|
|
2371
|
+
var _consentBuffer = [];
|
|
2372
|
+
var _consent = "granted";
|
|
2373
|
+
var _sampledIn = true;
|
|
2374
|
+
var _flushTimer = null;
|
|
2375
|
+
var _initialized2 = false;
|
|
2376
|
+
var _flushing = false;
|
|
2377
|
+
var _pendingIdentify = false;
|
|
2378
|
+
var _unsubscribeConsent2 = null;
|
|
2379
|
+
var CONSENT_BUFFER_MAX = 50;
|
|
2380
|
+
var SPILL_MAX = 200;
|
|
2381
|
+
var SPILL_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
2382
|
+
function defaults() {
|
|
2383
|
+
return {
|
|
2384
|
+
sampleRate: 1,
|
|
2385
|
+
autoPageviews: false,
|
|
2386
|
+
flushIntervalMs: 5e3,
|
|
2387
|
+
surface: "web",
|
|
2388
|
+
propertyAllowlist: []
|
|
2389
|
+
};
|
|
2390
|
+
}
|
|
2391
|
+
function now2() {
|
|
2392
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
2393
|
+
}
|
|
2394
|
+
function spillKey() {
|
|
2395
|
+
return "mushi_events_spill_" + _projectId;
|
|
2396
|
+
}
|
|
2397
|
+
function isRetryableStatus(status) {
|
|
2398
|
+
return status === void 0 || status === 429 || status >= 500;
|
|
2399
|
+
}
|
|
2400
|
+
function analyticsIdKey() {
|
|
2401
|
+
return "mushi_analytics_id_" + _projectId;
|
|
2402
|
+
}
|
|
2403
|
+
function loadOrMintAnalyticsId() {
|
|
2404
|
+
try {
|
|
2405
|
+
const stored = localStorage.getItem(analyticsIdKey());
|
|
2406
|
+
if (stored) return stored;
|
|
2407
|
+
} catch {
|
|
2408
|
+
}
|
|
2409
|
+
return typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID() : "a-" + Math.random().toString(36).slice(2) + Date.now().toString(36);
|
|
2410
|
+
}
|
|
2411
|
+
function persistAnalyticsId() {
|
|
2412
|
+
if (!_anonId) return;
|
|
2413
|
+
try {
|
|
2414
|
+
if (localStorage.getItem(analyticsIdKey()) !== _anonId) localStorage.setItem(analyticsIdKey(), _anonId);
|
|
2415
|
+
} catch {
|
|
2416
|
+
}
|
|
2417
|
+
}
|
|
2418
|
+
function hashToUnit(s) {
|
|
2419
|
+
let h = 2166136261;
|
|
2420
|
+
for (let i = 0; i < s.length; i += 1) {
|
|
2421
|
+
h ^= s.charCodeAt(i);
|
|
2422
|
+
h = Math.imul(h, 16777619) >>> 0;
|
|
2423
|
+
}
|
|
2424
|
+
return (h >>> 0) / 4294967296;
|
|
2425
|
+
}
|
|
2426
|
+
function readSpill() {
|
|
2427
|
+
try {
|
|
2428
|
+
const raw = localStorage.getItem(spillKey());
|
|
2429
|
+
if (!raw) return [];
|
|
2430
|
+
const parsed = JSON.parse(raw);
|
|
2431
|
+
if (!parsed || Date.now() - parsed.at > SPILL_TTL_MS) {
|
|
2432
|
+
localStorage.removeItem(spillKey());
|
|
2433
|
+
return [];
|
|
2434
|
+
}
|
|
2435
|
+
return Array.isArray(parsed.events) ? parsed.events.slice(0, SPILL_MAX) : [];
|
|
2436
|
+
} catch {
|
|
2437
|
+
return [];
|
|
2438
|
+
}
|
|
2439
|
+
}
|
|
2440
|
+
function writeSpill(events) {
|
|
2441
|
+
try {
|
|
2442
|
+
if (events.length === 0) {
|
|
2443
|
+
localStorage.removeItem(spillKey());
|
|
2444
|
+
return;
|
|
2445
|
+
}
|
|
2446
|
+
localStorage.setItem(spillKey(), JSON.stringify({ at: Date.now(), events: events.slice(-SPILL_MAX) }));
|
|
2447
|
+
} catch {
|
|
2448
|
+
}
|
|
2449
|
+
}
|
|
2450
|
+
function takeSpill() {
|
|
2451
|
+
const events = readSpill();
|
|
2452
|
+
if (events.length > 0) writeSpill([]);
|
|
2453
|
+
return events;
|
|
2454
|
+
}
|
|
2455
|
+
function buildPayload2(events) {
|
|
2456
|
+
return {
|
|
2457
|
+
anon_id: _anonId,
|
|
2458
|
+
user_id: _userId,
|
|
2459
|
+
user_traits: _userTraits,
|
|
2460
|
+
session_id: _getSessionId ? _getSessionId() : null,
|
|
2461
|
+
sdk_version: _sdkVersion2,
|
|
2462
|
+
surface: _config.surface,
|
|
2463
|
+
events
|
|
2464
|
+
};
|
|
2465
|
+
}
|
|
2466
|
+
async function flushNow() {
|
|
2467
|
+
if (!_client2 || _flushing || _consent !== "granted") return;
|
|
2468
|
+
if (_buffer.length === 0 && !_pendingIdentify) return;
|
|
2469
|
+
_flushing = true;
|
|
2470
|
+
persistAnalyticsId();
|
|
2471
|
+
const batch = _buffer.splice(0, EVENT_PROPERTY_LIMITS.maxServerBatch);
|
|
2472
|
+
if (_pendingIdentify) {
|
|
2473
|
+
batch.unshift({ name: "identify", ts: now2(), properties: {} });
|
|
2474
|
+
_pendingIdentify = false;
|
|
2475
|
+
}
|
|
2476
|
+
try {
|
|
2477
|
+
const res = await _client2.postProductEvents(buildPayload2(batch));
|
|
2478
|
+
if (res.ok) {
|
|
2479
|
+
writeSpill([]);
|
|
2480
|
+
} else if (isRetryableStatus(res.error?.status)) {
|
|
2481
|
+
writeSpill([...readSpill(), ...batch.filter((e) => e.name !== "identify")]);
|
|
2482
|
+
}
|
|
2483
|
+
} catch {
|
|
2484
|
+
writeSpill([...readSpill(), ...batch.filter((e) => e.name !== "identify")]);
|
|
2485
|
+
} finally {
|
|
2486
|
+
_flushing = false;
|
|
2487
|
+
}
|
|
2488
|
+
if (_buffer.length >= EVENT_PROPERTY_LIMITS.maxClientBatch) void flushNow();
|
|
2489
|
+
}
|
|
2490
|
+
function enqueue(ev) {
|
|
2491
|
+
if (_consent === "denied") return false;
|
|
2492
|
+
if (_consent === "pending") {
|
|
2493
|
+
if (_consentBuffer.length >= CONSENT_BUFFER_MAX) return false;
|
|
2494
|
+
_consentBuffer.push(ev);
|
|
2495
|
+
return true;
|
|
2496
|
+
}
|
|
2497
|
+
_buffer.push(ev);
|
|
2498
|
+
if (_buffer.length >= EVENT_PROPERTY_LIMITS.maxClientBatch) void flushNow();
|
|
2499
|
+
return true;
|
|
2500
|
+
}
|
|
2501
|
+
function applyConsent(state) {
|
|
2502
|
+
if (state === "denied") {
|
|
2503
|
+
_consent = "denied";
|
|
2504
|
+
_buffer = [];
|
|
2505
|
+
_consentBuffer = [];
|
|
2506
|
+
_pendingIdentify = false;
|
|
2507
|
+
writeSpill([]);
|
|
2508
|
+
return;
|
|
2509
|
+
}
|
|
2510
|
+
const wasPending = _consent === "pending";
|
|
2511
|
+
_consent = "granted";
|
|
2512
|
+
if (wasPending) {
|
|
2513
|
+
_buffer.push(...takeSpill(), ..._consentBuffer);
|
|
2514
|
+
_consentBuffer = [];
|
|
2515
|
+
}
|
|
2516
|
+
if (_buffer.length > 0 || _pendingIdentify) void flushNow();
|
|
2517
|
+
}
|
|
2518
|
+
function stripUndefined(o) {
|
|
2519
|
+
const out = {};
|
|
2520
|
+
for (const [k, v] of Object.entries(o)) {
|
|
2521
|
+
if (v !== void 0) out[k] = v;
|
|
2522
|
+
}
|
|
2523
|
+
return out;
|
|
2524
|
+
}
|
|
2525
|
+
function isEventTrackingActive() {
|
|
2526
|
+
return _initialized2 && _consent === "granted" && _sampledIn;
|
|
2527
|
+
}
|
|
2528
|
+
function getEventAnonymousId() {
|
|
2529
|
+
if (!_initialized2 || _consent === "denied") return null;
|
|
2530
|
+
return _anonId;
|
|
2531
|
+
}
|
|
2532
|
+
function initEventTracker(opts) {
|
|
2533
|
+
if (_initialized2) return;
|
|
2534
|
+
if (typeof window === "undefined") return;
|
|
2535
|
+
const cfg = opts.config ?? {};
|
|
2536
|
+
_config = { ...defaults(), ...stripUndefined(cfg), propertyAllowlist: cfg.propertyAllowlist ?? [] };
|
|
2537
|
+
_projectId = opts.projectId;
|
|
2538
|
+
if (analyticsBlockReason(cfg) !== null) return;
|
|
2539
|
+
_initialized2 = true;
|
|
2540
|
+
_client2 = opts.client;
|
|
2541
|
+
_anonId = opts.anonId ?? loadOrMintAnalyticsId();
|
|
2542
|
+
_userId = opts.userId ?? null;
|
|
2543
|
+
_sdkVersion2 = opts.sdkVersion;
|
|
2544
|
+
_scrub = opts.scrub;
|
|
2545
|
+
_getSessionId = opts.getSessionId;
|
|
2546
|
+
const rate = Math.min(1, Math.max(0, _config.sampleRate));
|
|
2547
|
+
_sampledIn = rate >= 1 ? true : rate <= 0 ? false : hashToUnit(_anonId ?? "anon") < rate;
|
|
2548
|
+
if (!_sampledIn) return;
|
|
2549
|
+
_consent = resolveAnalyticsConsent(_projectId, cfg);
|
|
2550
|
+
if (_consent === "granted") _buffer.push(...takeSpill());
|
|
2551
|
+
else if (_consent === "denied") writeSpill([]);
|
|
2552
|
+
_unsubscribeConsent2 = onAnalyticsConsentChange(applyConsent);
|
|
2553
|
+
const interval = Math.max(1e3, _config.flushIntervalMs);
|
|
2554
|
+
_flushTimer = setInterval(() => {
|
|
2555
|
+
void flushNow();
|
|
2556
|
+
}, interval);
|
|
2557
|
+
const onHide = () => {
|
|
2558
|
+
if (typeof document !== "undefined" && document.visibilityState === "hidden") void flushNow();
|
|
2559
|
+
};
|
|
2560
|
+
if (typeof document !== "undefined") document.addEventListener("visibilitychange", onHide, { passive: true });
|
|
2561
|
+
window.addEventListener("pagehide", () => {
|
|
2562
|
+
void flushNow();
|
|
2563
|
+
}, { passive: true });
|
|
2564
|
+
if (_config.autoPageviews) patchHistory();
|
|
2565
|
+
}
|
|
2566
|
+
function trackEvent(name, properties, opts) {
|
|
2567
|
+
if (!_initialized2 || !_sampledIn) return false;
|
|
2568
|
+
if (!isValidEventName(name)) return false;
|
|
2569
|
+
const { properties: props } = sanitizeEventProperties(properties, {
|
|
2570
|
+
allowlist: _config.propertyAllowlist,
|
|
2571
|
+
scrub: _scrub
|
|
2572
|
+
});
|
|
2573
|
+
let merged = props;
|
|
2574
|
+
if (opts?.reserved) {
|
|
2575
|
+
const { properties: res } = sanitizeEventProperties(opts.reserved, { allowReserved: true, scrub: _scrub });
|
|
2576
|
+
merged = { ...props, ...res };
|
|
2577
|
+
}
|
|
2578
|
+
merged.$surface = _config.surface;
|
|
2579
|
+
if (typeof location !== "undefined" && merged.$route === void 0) merged.$route = location.pathname;
|
|
2580
|
+
if (!propertiesWithinByteLimit(merged)) return false;
|
|
2581
|
+
return enqueue({
|
|
2582
|
+
name,
|
|
2583
|
+
ts: opts?.ts ?? now2(),
|
|
2584
|
+
properties: merged,
|
|
2585
|
+
...opts?.dedupKey ? { dedup_key: opts.dedupKey } : {}
|
|
2586
|
+
});
|
|
2587
|
+
}
|
|
2588
|
+
function setEventConsent(state) {
|
|
2589
|
+
if (!_initialized2) _consent = state;
|
|
2590
|
+
setAnalyticsConsent(_projectId, state);
|
|
2591
|
+
}
|
|
2592
|
+
function updateEventIdentity(userId, traits) {
|
|
2593
|
+
_userId = userId;
|
|
2594
|
+
_userTraits = traits ?? null;
|
|
2595
|
+
if (!_initialized2 || !_sampledIn || !userId || _consent === "denied") return;
|
|
2596
|
+
_pendingIdentify = true;
|
|
2597
|
+
if (_consent === "granted") void flushNow();
|
|
2598
|
+
}
|
|
2599
|
+
function flushEvents() {
|
|
2600
|
+
return flushNow();
|
|
2601
|
+
}
|
|
2602
|
+
function destroyEventTracker() {
|
|
2603
|
+
if (_flushTimer != null) {
|
|
2604
|
+
clearInterval(_flushTimer);
|
|
2605
|
+
_flushTimer = null;
|
|
2606
|
+
}
|
|
2607
|
+
_unsubscribeConsent2?.();
|
|
2608
|
+
_unsubscribeConsent2 = null;
|
|
2609
|
+
_initialized2 = false;
|
|
2610
|
+
_client2 = null;
|
|
2611
|
+
_buffer = [];
|
|
2612
|
+
_consentBuffer = [];
|
|
2613
|
+
_consent = "granted";
|
|
2614
|
+
_sampledIn = true;
|
|
2615
|
+
_pendingIdentify = false;
|
|
2616
|
+
_userId = null;
|
|
2617
|
+
_userTraits = null;
|
|
2618
|
+
_config = defaults();
|
|
2619
|
+
}
|
|
2620
|
+
var _historyPatched2 = false;
|
|
2621
|
+
function patchHistory() {
|
|
2622
|
+
if (_historyPatched2 || typeof history === "undefined") return;
|
|
2623
|
+
_historyPatched2 = true;
|
|
2624
|
+
const original = history.pushState;
|
|
2625
|
+
history.pushState = function(...args) {
|
|
2626
|
+
const result = original.apply(this, args);
|
|
2627
|
+
trackEvent("page_view");
|
|
2015
2628
|
return result;
|
|
2016
2629
|
};
|
|
2017
|
-
history.pushState = wrap(history.pushState);
|
|
2018
|
-
history.replaceState = wrap(history.replaceState);
|
|
2019
2630
|
window.addEventListener("popstate", () => {
|
|
2020
|
-
|
|
2021
|
-
send(buildPayload("page_view", { route: currentRoute() }));
|
|
2631
|
+
trackEvent("page_view");
|
|
2022
2632
|
}, { passive: true });
|
|
2023
2633
|
}
|
|
2024
2634
|
|
|
@@ -2035,12 +2645,12 @@ function createRateLimiter(config = {}) {
|
|
|
2035
2645
|
let tokens = maxBurst;
|
|
2036
2646
|
let lastRefill = Date.now();
|
|
2037
2647
|
function refill() {
|
|
2038
|
-
const
|
|
2039
|
-
const elapsed =
|
|
2648
|
+
const now3 = Date.now();
|
|
2649
|
+
const elapsed = now3 - lastRefill;
|
|
2040
2650
|
const refills = Math.floor(elapsed / refillIntervalMs);
|
|
2041
2651
|
if (refills > 0) {
|
|
2042
2652
|
tokens = Math.min(maxBurst, tokens + refills * refillRate);
|
|
2043
|
-
lastRefill =
|
|
2653
|
+
lastRefill = now3;
|
|
2044
2654
|
}
|
|
2045
2655
|
}
|
|
2046
2656
|
function tryConsume() {
|
|
@@ -2438,15 +3048,15 @@ function mushiTokens(mode) {
|
|
|
2438
3048
|
// src/identity.ts
|
|
2439
3049
|
var MUSHI_IDENTITY_TOKEN_PREFIX = "mushi.identity";
|
|
2440
3050
|
function buildIdentityClaims(input) {
|
|
2441
|
-
const
|
|
3051
|
+
const now3 = Math.floor(Date.now() / 1e3);
|
|
2442
3052
|
const ttl = input.ttlSeconds && input.ttlSeconds > 0 ? Math.min(input.ttlSeconds, 3600) : 600;
|
|
2443
3053
|
return {
|
|
2444
3054
|
projectId: input.projectId,
|
|
2445
3055
|
sub: input.userId,
|
|
2446
3056
|
...input.email ? { email: input.email } : {},
|
|
2447
3057
|
...input.name ? { name: input.name } : {},
|
|
2448
|
-
iat:
|
|
2449
|
-
exp:
|
|
3058
|
+
iat: now3,
|
|
3059
|
+
exp: now3 + ttl
|
|
2450
3060
|
};
|
|
2451
3061
|
}
|
|
2452
3062
|
function base64UrlDecode(segment) {
|
|
@@ -2486,13 +3096,22 @@ var SLUG_DOMAIN_HINTS = {
|
|
|
2486
3096
|
"glot-it": "glot.it",
|
|
2487
3097
|
glotit: "glot.it",
|
|
2488
3098
|
"solo-boss-cloud": "solo-boss.kensaur.us",
|
|
2489
|
-
"mushi-mushi": "kensaur.us"
|
|
3099
|
+
"mushi-mushi": "kensaur.us",
|
|
3100
|
+
"how-to-talk-to-girls": "talk.kensaur.us",
|
|
3101
|
+
htttg: "talk.kensaur.us",
|
|
3102
|
+
tsumagoi: "tsumagoi.kensaur.us"
|
|
2490
3103
|
};
|
|
2491
3104
|
var SLUG_ICON_URL_HINTS = {
|
|
2492
3105
|
"mushi-mushi": "https://kensaur.us/mushi-mushi/admin/favicon.svg",
|
|
2493
3106
|
"solo-boss-cloud": "https://solo-boss.kensaur.us/favicon.svg",
|
|
2494
3107
|
"glot-it": "https://kensaur.us/glot-it/apple-touch-icon.png",
|
|
2495
|
-
glotit: "https://kensaur.us/glot-it/apple-touch-icon.png"
|
|
3108
|
+
glotit: "https://kensaur.us/glot-it/apple-touch-icon.png",
|
|
3109
|
+
"the-wanting-mind": "https://kensaur.us/the-wanting-mind/pwa-512x512.png",
|
|
3110
|
+
"how-to-talk-to-girls": "https://talk.kensaur.us/pwa-192.png",
|
|
3111
|
+
htttg: "https://talk.kensaur.us/pwa-192.png",
|
|
3112
|
+
tsumagoi: "https://tsumagoi.kensaur.us/apple-touch-icon.png",
|
|
3113
|
+
"help-her-take-photo": "https://kensaur.us/help-her-take-photo/assets/apple-touch-icon.png",
|
|
3114
|
+
"yen-yen": "https://kensaur.us/yen-yen/icon.svg"
|
|
2496
3115
|
};
|
|
2497
3116
|
var UNTRUSTED_FAVICON_HOSTS = [
|
|
2498
3117
|
"www.google.com",
|
|
@@ -2664,20 +3283,27 @@ function isSafeFaviconHref(href) {
|
|
|
2664
3283
|
}
|
|
2665
3284
|
}
|
|
2666
3285
|
|
|
3286
|
+
exports.ACTIVATION_EVENT = ACTIVATION_EVENT;
|
|
2667
3287
|
exports.DEFAULT_API_ENDPOINT = DEFAULT_API_ENDPOINT;
|
|
2668
3288
|
exports.DEFAULT_MAX_RETRIES = DEFAULT_MAX_RETRIES;
|
|
2669
3289
|
exports.DEFAULT_TIMEOUT = DEFAULT_TIMEOUT;
|
|
3290
|
+
exports.EVENT_NAME_RE = EVENT_NAME_RE;
|
|
3291
|
+
exports.EVENT_PROPERTY_LIMITS = EVENT_PROPERTY_LIMITS;
|
|
3292
|
+
exports.HABIT_EVENTS = HABIT_EVENTS;
|
|
2670
3293
|
exports.MAX_REPORT_PAYLOAD_BYTES = MAX_REPORT_PAYLOAD_BYTES;
|
|
2671
3294
|
exports.MAX_SCREENSHOT_DATA_URL_BYTES = MAX_SCREENSHOT_DATA_URL_BYTES;
|
|
2672
3295
|
exports.MUSHI_ACCENT_SHADOW = MUSHI_ACCENT_SHADOW;
|
|
2673
3296
|
exports.MUSHI_BANNER_BRAND_BORDER = MUSHI_BANNER_BRAND_BORDER;
|
|
2674
3297
|
exports.MUSHI_BANNER_NEON = MUSHI_BANNER_NEON;
|
|
3298
|
+
exports.MUSHI_BEACON_MAX_BYTES = MUSHI_BEACON_MAX_BYTES;
|
|
2675
3299
|
exports.MUSHI_BORDER = MUSHI_BORDER;
|
|
2676
3300
|
exports.MUSHI_COLORS_DARK = MUSHI_COLORS_DARK;
|
|
2677
3301
|
exports.MUSHI_COLORS_LIGHT = MUSHI_COLORS_LIGHT;
|
|
2678
3302
|
exports.MUSHI_CONTROL_DISABLED = MUSHI_CONTROL_DISABLED;
|
|
2679
3303
|
exports.MUSHI_COPY = MUSHI_COPY;
|
|
2680
3304
|
exports.MUSHI_DURATION = MUSHI_DURATION;
|
|
3305
|
+
exports.MUSHI_EVENTS = MUSHI_EVENTS;
|
|
3306
|
+
exports.MUSHI_EVENT_NAMES = MUSHI_EVENT_NAMES;
|
|
2681
3307
|
exports.MUSHI_GEOMETRY = MUSHI_GEOMETRY;
|
|
2682
3308
|
exports.MUSHI_IDENTITY_TOKEN_PREFIX = MUSHI_IDENTITY_TOKEN_PREFIX;
|
|
2683
3309
|
exports.MUSHI_INTERNAL_HEADER = MUSHI_INTERNAL_HEADER;
|
|
@@ -2694,11 +3320,14 @@ exports.MUSHI_SDK_PACKAGE_HEADER = MUSHI_SDK_PACKAGE_HEADER;
|
|
|
2694
3320
|
exports.MUSHI_SDK_VERSION_HEADER = MUSHI_SDK_VERSION_HEADER;
|
|
2695
3321
|
exports.MUSHI_SHADOW_INK = MUSHI_SHADOW_INK;
|
|
2696
3322
|
exports.MUSHI_SPACING = MUSHI_SPACING;
|
|
3323
|
+
exports.MUSHI_SURFACES = MUSHI_SURFACES;
|
|
2697
3324
|
exports.MUSHI_TIER_COLORS = MUSHI_TIER_COLORS;
|
|
2698
3325
|
exports.MUSHI_TYPE = MUSHI_TYPE;
|
|
2699
3326
|
exports.MUSHI_USER_TOKEN_HEADER = MUSHI_USER_TOKEN_HEADER;
|
|
2700
3327
|
exports.MUSHI_Z = MUSHI_Z;
|
|
2701
3328
|
exports.REGION_ENDPOINTS = REGION_ENDPOINTS;
|
|
3329
|
+
exports.analyticsBlockReason = analyticsBlockReason;
|
|
3330
|
+
exports.buildBeaconEnvelope = buildBeaconEnvelope;
|
|
2702
3331
|
exports.buildIdentityClaims = buildIdentityClaims;
|
|
2703
3332
|
exports.buildSdkIngestHeaders = buildSdkIngestHeaders;
|
|
2704
3333
|
exports.captureEnvironment = captureEnvironment;
|
|
@@ -2710,41 +3339,69 @@ exports.createOfflineQueue = createOfflineQueue;
|
|
|
2710
3339
|
exports.createPiiScrubber = createPiiScrubber;
|
|
2711
3340
|
exports.createPreFilter = createPreFilter;
|
|
2712
3341
|
exports.createRateLimiter = createRateLimiter;
|
|
3342
|
+
exports.destroyEventTracker = destroyEventTracker;
|
|
2713
3343
|
exports.destroySessionTracker = destroySessionTracker;
|
|
2714
3344
|
exports.diagnoseEnvConfig = diagnoseEnvConfig;
|
|
3345
|
+
exports.dntActive = dntActive;
|
|
2715
3346
|
exports.estimateJsonBytes = estimateJsonBytes;
|
|
2716
3347
|
exports.expandPreset = expandPreset;
|
|
3348
|
+
exports.extractTunnelPath = extractTunnelPath;
|
|
2717
3349
|
exports.faviconUrlCandidates = faviconUrlCandidates;
|
|
3350
|
+
exports.flushEvents = flushEvents;
|
|
3351
|
+
exports.flushLastOutboundOnUnload = flushLastOutboundOnUnload;
|
|
2718
3352
|
exports.formatBytes = formatBytes;
|
|
3353
|
+
exports.getBackoffDelay = getBackoffDelay;
|
|
2719
3354
|
exports.getDeviceFingerprintHash = getDeviceFingerprintHash;
|
|
3355
|
+
exports.getEventAnonymousId = getEventAnonymousId;
|
|
2720
3356
|
exports.getReporterToken = getReporterToken;
|
|
2721
3357
|
exports.getSessionId = getSessionId;
|
|
2722
3358
|
exports.githubRepoDomainHint = githubRepoDomainHint;
|
|
2723
3359
|
exports.hmacSha256Hex = hmacSha256Hex;
|
|
3360
|
+
exports.initEventTracker = initEventTracker;
|
|
2724
3361
|
exports.initSessionTracker = initSessionTracker;
|
|
3362
|
+
exports.isAutomatedBrowser = isAutomatedBrowser;
|
|
3363
|
+
exports.isEventTrackingActive = isEventTrackingActive;
|
|
2725
3364
|
exports.isLikelyGenericFavicon = isLikelyGenericFavicon;
|
|
3365
|
+
exports.isMushiBeaconEnvelope = isMushiBeaconEnvelope;
|
|
3366
|
+
exports.isPageUnloading = isPageUnloading;
|
|
3367
|
+
exports.isSameOriginOrRelative = isSameOriginOrRelative;
|
|
2726
3368
|
exports.isUntrustedFaviconUrl = isUntrustedFaviconUrl;
|
|
3369
|
+
exports.isValidEventName = isValidEventName;
|
|
3370
|
+
exports.markPageUnloading = markPageUnloading;
|
|
3371
|
+
exports.matchesErrorFilter = matchesErrorFilter;
|
|
2727
3372
|
exports.mushiPalette = mushiPalette;
|
|
2728
3373
|
exports.mushiTokens = mushiTokens;
|
|
2729
3374
|
exports.newUuid = newUuid;
|
|
2730
3375
|
exports.noopLogger = noopLogger;
|
|
2731
3376
|
exports.normaliseThrown = normaliseThrown;
|
|
3377
|
+
exports.onAnalyticsConsentChange = onAnalyticsConsentChange;
|
|
2732
3378
|
exports.originToDomain = originToDomain;
|
|
2733
3379
|
exports.parseIdentityToken = parseIdentityToken;
|
|
3380
|
+
exports.parseRetryAfter = parseRetryAfter;
|
|
2734
3381
|
exports.projectFaviconUrlCandidates = projectFaviconUrlCandidates;
|
|
2735
3382
|
exports.projectInitials = projectInitials;
|
|
2736
3383
|
exports.projectInitialsThemeIndex = projectInitialsThemeIndex;
|
|
3384
|
+
exports.propertiesWithinByteLimit = propertiesWithinByteLimit;
|
|
2737
3385
|
exports.readPageFaviconHref = readPageFaviconHref;
|
|
3386
|
+
exports.resolveAnalyticsConsent = resolveAnalyticsConsent;
|
|
2738
3387
|
exports.resolveEnvConfig = resolveEnvConfig;
|
|
2739
3388
|
exports.resolveProjectDomain = resolveProjectDomain;
|
|
2740
3389
|
exports.resolveProjectFaviconDomains = resolveProjectFaviconDomains;
|
|
2741
3390
|
exports.resolveRegionEndpoint = resolveRegionEndpoint;
|
|
3391
|
+
exports.resolveRequestBaseUrl = resolveRequestBaseUrl;
|
|
2742
3392
|
exports.resolveWidgetAccent = resolveWidgetAccent;
|
|
2743
3393
|
exports.safeWidgetHex = safeWidgetHex;
|
|
3394
|
+
exports.sanitizeEventProperties = sanitizeEventProperties;
|
|
2744
3395
|
exports.scrubPii = scrubPii;
|
|
2745
3396
|
exports.scrubUrl = scrubUrl;
|
|
3397
|
+
exports.sendOnUnload = sendOnUnload;
|
|
3398
|
+
exports.setAnalyticsConsent = setAnalyticsConsent;
|
|
3399
|
+
exports.setEventConsent = setEventConsent;
|
|
2746
3400
|
exports.sha256Hex = sha256Hex;
|
|
3401
|
+
exports.shouldDropCapturedError = shouldDropCapturedError;
|
|
3402
|
+
exports.trackEvent = trackEvent;
|
|
2747
3403
|
exports.trackPageView = trackPageView;
|
|
3404
|
+
exports.updateEventIdentity = updateEventIdentity;
|
|
2748
3405
|
exports.updateSessionIdentity = updateSessionIdentity;
|
|
2749
3406
|
exports.validateConfig = validateConfig;
|
|
2750
3407
|
//# sourceMappingURL=index.cjs.map
|