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