@mushi-mushi/core 1.22.5 → 1.25.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/CONTRIBUTING.md +10 -2
- package/README.md +56 -1
- package/SECURITY.md +24 -1
- package/dist/index.cjs +348 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +120 -2
- package/dist/index.d.ts +120 -2
- package/dist/index.js +342 -13
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/CONTRIBUTING.md
CHANGED
|
@@ -76,6 +76,7 @@ packages/
|
|
|
76
76
|
apps/
|
|
77
77
|
admin/ Admin dashboard (React + Tailwind)
|
|
78
78
|
docs/ Documentation site (Nextra — apps/docs)
|
|
79
|
+
testers/ Tester marketplace portal (Next.js)
|
|
79
80
|
tooling/
|
|
80
81
|
eslint-config/ Shared ESLint flat config
|
|
81
82
|
tsconfig/ Shared TypeScript configs
|
|
@@ -118,15 +119,22 @@ If your PR changes **user-visible SDK or CLI behavior**, update docs in the **sa
|
|
|
118
119
|
Guardrails (run locally before pushing):
|
|
119
120
|
|
|
120
121
|
```bash
|
|
121
|
-
pnpm check:docs-stats # root README stat badges
|
|
122
|
+
pnpm check:docs-stats # root README stat badges + glance <sub> line
|
|
122
123
|
pnpm check:sdk-version-matrix # apps/docs/content/sdks/index.mdx versions
|
|
123
124
|
pnpm check:internal-doc-links # dead /foo links in MDX
|
|
124
|
-
pnpm check:onboarding-drift # phantom MCP env vars / stale tool names
|
|
125
|
+
pnpm check:onboarding-drift # phantom MCP env vars / stale tool names / native install coords
|
|
126
|
+
pnpm check:catalog-sync # MCP catalog ↔ admin counts ↔ prose "N tools"
|
|
127
|
+
pnpm check:mcp-tools-doc # mcp-tools.generated.mdx stale check
|
|
125
128
|
pnpm check:bundle-docs # @mushi-mushi/web size-limit vs README
|
|
129
|
+
pnpm check:docs-drift # versions + public docs links
|
|
126
130
|
pnpm gen:llms-txt # regenerate apps/docs/public/llms.txt after nav changes
|
|
127
131
|
pnpm gen:mcp-tools-doc # regenerate sdks/mcp-tools.generated.mdx from catalog.ts
|
|
128
132
|
```
|
|
129
133
|
|
|
134
|
+
**Same-PR rule:** if behavior, env vars, CLI commands, MCP tools, or install
|
|
135
|
+
coordinates change, update the matching docs in this PR and run
|
|
136
|
+
`pnpm check:docs-drift && pnpm check:onboarding-drift && pnpm check:catalog-sync`.
|
|
137
|
+
|
|
130
138
|
README changes on npm packages only appear on [npmjs.com](https://www.npmjs.com) after the next **Changesets publish**.
|
|
131
139
|
|
|
132
140
|
## Changesets
|
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ Core types, API client, and shared utilities for every Mushi SDK package.
|
|
|
23
23
|
|
|
24
24
|
| Type | Purpose |
|
|
25
25
|
|----------------------------|-----------------------------------------------------------------------------------------------|
|
|
26
|
-
| `MushiPreset` | `'production-calm' \| 'beta-loud' \| 'internal-debug' \| 'manual-only'`
|
|
26
|
+
| `MushiPreset` | Posture bundles: `'production-calm' \| 'beta-loud' \| 'internal-debug' \| 'manual-only'` plus the tiers `'minimal' \| 'standard' \| 'full'`. See [Config presets](#config-presets--precedence). |
|
|
27
27
|
| `MushiWidgetAnchor` | Raw-CSS positioning (`top` / `right` / `bottom` / `left`) for the widget launcher. |
|
|
28
28
|
| `MushiPrivacyConfig` | `maskSelectors`, `blockSelectors`, `allowUserRemoveScreenshot` for screenshot redaction. |
|
|
29
29
|
| `MushiUrlMatcher` | `string \| RegExp` element used by `capture.ignoreUrls` and `apiCascade.ignoreUrls`. |
|
|
@@ -46,6 +46,61 @@ Constants: `MUSHI_INTERNAL_HEADER` (`'X-Mushi-Internal'`),
|
|
|
46
46
|
`MUSHI_INTERNAL_INIT_MARKER`, and the `MushiInternalRequestKind` literal union
|
|
47
47
|
are re-exported so framework adapters can build their own self-noise filters.
|
|
48
48
|
|
|
49
|
+
## Config presets & precedence
|
|
50
|
+
|
|
51
|
+
Pass `preset` to pick a posture bundle instead of hand-assembling
|
|
52
|
+
`widget` / `capture` / `proactive` flags. `expandPreset(config)` expands the
|
|
53
|
+
preset into those nested objects; **your explicit config always wins** on every
|
|
54
|
+
key (the merge is per-sub-object, so overriding one `capture` flag keeps the
|
|
55
|
+
rest of the preset's defaults).
|
|
56
|
+
|
|
57
|
+
| Field | `minimal` | `standard` | `full` |
|
|
58
|
+
|----------------------|----------------|--------------------|------------------|
|
|
59
|
+
| Widget | on (`auto`) | on (SDK default) | on (`auto`) |
|
|
60
|
+
| `capture.console` | ✅ | default | ✅ |
|
|
61
|
+
| `capture.network` | ❌ | default | ✅ |
|
|
62
|
+
| `capture.performance`| ❌ | default | ✅ |
|
|
63
|
+
| `capture.screenshot` | `on-report` | default | `auto` |
|
|
64
|
+
| `capture.replay` | `off` | default | `lite` |
|
|
65
|
+
| Proactive triggers | none | default | all on |
|
|
66
|
+
|
|
67
|
+
- **`minimal`** — widget + console capture only, screenshot on report; no
|
|
68
|
+
network/performance/replay, no proactive nudges. Leanest footprint.
|
|
69
|
+
- **`standard`** — today's SDK defaults. Expansion is a **no-op** (the config
|
|
70
|
+
is returned untouched); the preset exists so the intent is explicit and
|
|
71
|
+
documented.
|
|
72
|
+
- **`full`** — every capture channel on (including a self-contained `lite`
|
|
73
|
+
session replay) and all proactive triggers. Loudest / most data.
|
|
74
|
+
|
|
75
|
+
The four legacy postures — `production-calm`, `beta-loud`, `internal-debug`,
|
|
76
|
+
`manual-only` — are still supported and expand the same way.
|
|
77
|
+
|
|
78
|
+
### Precedence
|
|
79
|
+
|
|
80
|
+
Highest wins. `resolveEnvConfig()` only ever supplies
|
|
81
|
+
`projectId` / `apiKey` / `apiEndpoint`, so it never competes with a preset for
|
|
82
|
+
the nested option objects.
|
|
83
|
+
|
|
84
|
+
| Priority | Source | Example |
|
|
85
|
+
|----------|-------------------------------------|-------------------------------------------|
|
|
86
|
+
| 1 (wins) | Explicit config | `capture: { network: true }` |
|
|
87
|
+
| 2 | `preset` | `preset: 'minimal'` → `network: false` |
|
|
88
|
+
| 3 | Environment variables | `NEXT_PUBLIC_MUSHI_*` / `VITE_MUSHI_*` |
|
|
89
|
+
| 4 | SDK defaults | built-in fallbacks |
|
|
90
|
+
|
|
91
|
+
### Validation
|
|
92
|
+
|
|
93
|
+
`validateConfig(config)` runs at init and **fails loud but never throws**: it
|
|
94
|
+
`console.error`s on unknown top-level keys (usually a typo) and on an invalid
|
|
95
|
+
`preset` value, then continues. Set `MUSHI_SILENT=1` to suppress the warnings.
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
import { expandPreset, validateConfig } from '@mushi-mushi/core';
|
|
99
|
+
|
|
100
|
+
validateConfig(config); // warns on typos / bad preset (never throws)
|
|
101
|
+
const resolved = expandPreset(config); // preset → nested objects, explicit wins
|
|
102
|
+
```
|
|
103
|
+
|
|
49
104
|
## Usage
|
|
50
105
|
|
|
51
106
|
```typescript
|
package/SECURITY.md
CHANGED
|
@@ -111,7 +111,7 @@ What we treat as in-scope attacker capabilities, and what we don't.
|
|
|
111
111
|
| User pasting a Stripe / OpenAI / GitHub PAT into a bug report | ✅ | PII scrubber redacts ~15 vendor token formats client-side before the report leaves the device. Mirrors `packages/core/src/pii-scrubber.ts` across iOS, Android, Flutter, React Native. |
|
|
112
112
|
| Stolen end-user device with the SDK installed | ⚠️ partial | Offline queue is AsyncStorage / Keychain / SharedPreferences — no app-level encryption beyond the OS default. Reports waiting to flush are vulnerable to a forensic attacker. |
|
|
113
113
|
| Compromised Supabase service-role key | ❌ | Treated as a tier-0 incident; would require key rotation and audit-log forensics. Not defendable in software. |
|
|
114
|
-
| Compromise of `kensaurus@gmail.com` | ❌ | Treated as a project-fork event; downstream consumers should pin to the last known-good version and follow the new release channel. |
|
|
114
|
+
| Compromise of `kensaurus@gmail.com` mail account | ❌ | Treated as a project-fork event; downstream consumers should pin to the last known-good version and follow the new release channel. |
|
|
115
115
|
| Physical / OS-level attacker on an end-user device | ❌ | Out of scope. |
|
|
116
116
|
| Malicious fork using the Mushi name to ship malware | ❌ (technical) ✅ (legal) | The MIT / AGPLv3 grant lets the fork exist; the trademark policy (`TRADEMARK.md`) makes shipping it under the Mushi name an infringement we will pursue. |
|
|
117
117
|
|
|
@@ -157,6 +157,29 @@ Structured fields you set explicitly (`metadata.userEmail`,
|
|
|
157
157
|
opt-in attribution data, and silently rewriting them would break
|
|
158
158
|
support workflows.
|
|
159
159
|
|
|
160
|
+
### CLI funnel telemetry
|
|
161
|
+
|
|
162
|
+
The Mushi CLI sends two fire-and-forget signals to `/v1/cli/funnel` on your
|
|
163
|
+
project's endpoint to help us detect where the onboarding wizard drops off:
|
|
164
|
+
|
|
165
|
+
| Signal | When | What is sent |
|
|
166
|
+
|--------|------|-------------|
|
|
167
|
+
| `wizard_env_written` | After `npx mushi-mushi` writes `.env.local` | `event`, `source: "cli"` |
|
|
168
|
+
| `mcp_setup_done` | After `mushi setup --ide` verifies the MCP key | `event`, `source: "cli"` |
|
|
169
|
+
|
|
170
|
+
No file contents, stack traces, or identifiers beyond your project ID and API
|
|
171
|
+
key are sent. The key is already on the endpoint — this adds no new disclosure.
|
|
172
|
+
|
|
173
|
+
**Opt out** by setting `MUSHI_NO_TELEMETRY=1` in your shell or CI environment:
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
MUSHI_NO_TELEMETRY=1 npx mushi-mushi
|
|
177
|
+
MUSHI_NO_TELEMETRY=1 mushi setup --ide cursor
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
These signals never block or delay the wizard — they time out silently after
|
|
181
|
+
4 seconds and errors are discarded.
|
|
182
|
+
|
|
160
183
|
### Where data lives
|
|
161
184
|
|
|
162
185
|
- **Reports & telemetry** — Supabase Postgres in the `us-west-1` region.
|
package/dist/index.cjs
CHANGED
|
@@ -360,6 +360,17 @@ function createApiClient(options) {
|
|
|
360
360
|
);
|
|
361
361
|
},
|
|
362
362
|
// ─── Rewards program (P1) ──────────────────────────────────
|
|
363
|
+
/** POST /v1/sdk/session — lightweight session lifecycle events.
|
|
364
|
+
* Best-effort (1 retry, no offline queue) — mirrors postDiscoveryEvent. */
|
|
365
|
+
async postSessionEvent(payload) {
|
|
366
|
+
return request(
|
|
367
|
+
"POST",
|
|
368
|
+
"/v1/sdk/session",
|
|
369
|
+
payload,
|
|
370
|
+
1,
|
|
371
|
+
"discovery"
|
|
372
|
+
);
|
|
373
|
+
},
|
|
363
374
|
async submitActivity(userId, events, opts) {
|
|
364
375
|
return request(
|
|
365
376
|
"POST",
|
|
@@ -601,6 +612,219 @@ function resolveEnvConfig() {
|
|
|
601
612
|
if (apiEndpoint) result.apiEndpoint = apiEndpoint;
|
|
602
613
|
return result;
|
|
603
614
|
}
|
|
615
|
+
var ENV_PREFIXES = ["VITE_", "NEXT_PUBLIC_", "NUXT_PUBLIC_", "EXPO_PUBLIC_", ""];
|
|
616
|
+
var FIELD_SUFFIX = {
|
|
617
|
+
projectId: "MUSHI_PROJECT_ID",
|
|
618
|
+
apiKey: "MUSHI_API_KEY"
|
|
619
|
+
};
|
|
620
|
+
function diagnoseEnvConfig() {
|
|
621
|
+
const resolved = resolveEnvConfig();
|
|
622
|
+
const missing = [];
|
|
623
|
+
if (!resolved.projectId) missing.push("projectId");
|
|
624
|
+
if (!resolved.apiKey) missing.push("apiKey");
|
|
625
|
+
if (missing.length === 0) {
|
|
626
|
+
return { resolved, missing, nearMisses: [], message: null };
|
|
627
|
+
}
|
|
628
|
+
const importMetaEnv = (() => {
|
|
629
|
+
try {
|
|
630
|
+
const env = undefined;
|
|
631
|
+
return typeof env === "object" && env !== null ? env : null;
|
|
632
|
+
} catch {
|
|
633
|
+
return null;
|
|
634
|
+
}
|
|
635
|
+
})();
|
|
636
|
+
const processEnv = (() => {
|
|
637
|
+
try {
|
|
638
|
+
return typeof process !== "undefined" && process.env ? process.env : null;
|
|
639
|
+
} catch {
|
|
640
|
+
return null;
|
|
641
|
+
}
|
|
642
|
+
})();
|
|
643
|
+
const nearMisses = [];
|
|
644
|
+
for (const field of missing) {
|
|
645
|
+
for (const prefix of ENV_PREFIXES) {
|
|
646
|
+
const key = `${prefix}${FIELD_SUFFIX[field]}`;
|
|
647
|
+
if (importMetaEnv && importMetaEnv[key]) {
|
|
648
|
+
nearMisses.push({ field, key, via: "import.meta.env" });
|
|
649
|
+
} else if (processEnv && processEnv[key]) {
|
|
650
|
+
nearMisses.push({ field, key, via: "process.env" });
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
const expectedPrefix = importMetaEnv ? "VITE_MUSHI_*" : "NEXT_PUBLIC_MUSHI_* (Next.js), EXPO_PUBLIC_MUSHI_* (Expo), or MUSHI_* (Node / React Native)";
|
|
655
|
+
const lines = [`[mushi] Missing ${missing.join(" and ")} \u2014 the SDK will not initialize.`];
|
|
656
|
+
for (const m of nearMisses) {
|
|
657
|
+
lines.push(
|
|
658
|
+
`[mushi] Found ${m.key} in ${m.via}, but this runtime did not resolve it \u2014 it is either the wrong prefix for your bundler or not exposed to the client bundle.`
|
|
659
|
+
);
|
|
660
|
+
}
|
|
661
|
+
lines.push(`[mushi] This runtime reads: ${expectedPrefix}. Set the matching variable(s) and restart your dev server.`);
|
|
662
|
+
if (nearMisses.length === 0) {
|
|
663
|
+
lines.push(
|
|
664
|
+
`[mushi] If you already set the variable, check the prefix: a mis-prefixed var is stripped from client bundles and resolves to undefined with no error.`
|
|
665
|
+
);
|
|
666
|
+
}
|
|
667
|
+
return { resolved, missing, nearMisses, message: lines.join("\n") };
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// src/presets.ts
|
|
671
|
+
var KNOWN_PRESETS = [
|
|
672
|
+
"production-calm",
|
|
673
|
+
"beta-loud",
|
|
674
|
+
"internal-debug",
|
|
675
|
+
"manual-only",
|
|
676
|
+
"minimal",
|
|
677
|
+
"standard",
|
|
678
|
+
"full"
|
|
679
|
+
];
|
|
680
|
+
var KNOWN_CONFIG_KEYS = [
|
|
681
|
+
"projectId",
|
|
682
|
+
"apiKey",
|
|
683
|
+
"apiEndpoint",
|
|
684
|
+
"timeout",
|
|
685
|
+
"maxRetries",
|
|
686
|
+
"circuitBreaker",
|
|
687
|
+
"preset",
|
|
688
|
+
"runtimeConfig",
|
|
689
|
+
"sentry",
|
|
690
|
+
"widget",
|
|
691
|
+
"capture",
|
|
692
|
+
"privacy",
|
|
693
|
+
"proactive",
|
|
694
|
+
"preFilter",
|
|
695
|
+
"integrations",
|
|
696
|
+
"offline",
|
|
697
|
+
"rewards",
|
|
698
|
+
"assistant",
|
|
699
|
+
"debug",
|
|
700
|
+
"enabled",
|
|
701
|
+
"appVersion",
|
|
702
|
+
"beforeSendFeedback",
|
|
703
|
+
"onCrashedLastRun"
|
|
704
|
+
];
|
|
705
|
+
function presetDefaults(preset) {
|
|
706
|
+
switch (preset) {
|
|
707
|
+
case "manual-only":
|
|
708
|
+
return {
|
|
709
|
+
widget: { trigger: "manual", outdatedBanner: "console-only" },
|
|
710
|
+
capture: { console: true, network: true, performance: false, screenshot: "on-report", elementSelector: false },
|
|
711
|
+
proactive: { rageClick: false, longTask: false, apiCascade: false, errorBoundary: false }
|
|
712
|
+
};
|
|
713
|
+
case "beta-loud":
|
|
714
|
+
return {
|
|
715
|
+
widget: { trigger: "auto", outdatedBanner: "banner" },
|
|
716
|
+
capture: { console: true, network: true, performance: true, screenshot: "auto", elementSelector: true },
|
|
717
|
+
proactive: { rageClick: true, longTask: true, apiCascade: true, errorBoundary: true }
|
|
718
|
+
};
|
|
719
|
+
case "internal-debug":
|
|
720
|
+
return {
|
|
721
|
+
widget: { trigger: "auto", outdatedBanner: "banner", brandFooter: true },
|
|
722
|
+
capture: { console: true, network: true, performance: true, screenshot: "auto", elementSelector: true },
|
|
723
|
+
proactive: {
|
|
724
|
+
rageClick: true,
|
|
725
|
+
longTask: true,
|
|
726
|
+
apiCascade: true,
|
|
727
|
+
errorBoundary: true,
|
|
728
|
+
cooldown: { maxProactivePerSession: 10, dismissCooldownHours: 0, suppressAfterDismissals: 99 }
|
|
729
|
+
}
|
|
730
|
+
};
|
|
731
|
+
case "production-calm":
|
|
732
|
+
return {
|
|
733
|
+
widget: { trigger: "auto", outdatedBanner: "console-only" },
|
|
734
|
+
capture: { console: true, network: true, performance: false, screenshot: "on-report", elementSelector: false },
|
|
735
|
+
proactive: { rageClick: false, longTask: false, apiCascade: false, errorBoundary: false }
|
|
736
|
+
};
|
|
737
|
+
// Lean posture: widget on, console capture only, screenshot on-report,
|
|
738
|
+
// no network/performance/replay, no proactive nudges.
|
|
739
|
+
case "minimal":
|
|
740
|
+
return {
|
|
741
|
+
widget: { trigger: "auto" },
|
|
742
|
+
capture: {
|
|
743
|
+
console: true,
|
|
744
|
+
network: false,
|
|
745
|
+
performance: false,
|
|
746
|
+
screenshot: "on-report",
|
|
747
|
+
replay: "off",
|
|
748
|
+
elementSelector: false
|
|
749
|
+
},
|
|
750
|
+
proactive: { rageClick: false, longTask: false, apiCascade: false, errorBoundary: false }
|
|
751
|
+
};
|
|
752
|
+
// Everything-on posture: full capture (incl. self-contained `lite` replay)
|
|
753
|
+
// and every proactive trigger.
|
|
754
|
+
case "full":
|
|
755
|
+
return {
|
|
756
|
+
widget: { trigger: "auto" },
|
|
757
|
+
capture: {
|
|
758
|
+
console: true,
|
|
759
|
+
network: true,
|
|
760
|
+
performance: true,
|
|
761
|
+
screenshot: "auto",
|
|
762
|
+
replay: "lite",
|
|
763
|
+
elementSelector: true
|
|
764
|
+
},
|
|
765
|
+
proactive: { rageClick: true, longTask: true, apiCascade: true, errorBoundary: true }
|
|
766
|
+
};
|
|
767
|
+
// 'standard' and anything unrecognised → no expansion.
|
|
768
|
+
default:
|
|
769
|
+
return void 0;
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
function expandPreset(config) {
|
|
773
|
+
const defaults = presetDefaults(config.preset);
|
|
774
|
+
if (!defaults) return config;
|
|
775
|
+
return {
|
|
776
|
+
...config,
|
|
777
|
+
widget: {
|
|
778
|
+
...defaults.widget,
|
|
779
|
+
...config.widget
|
|
780
|
+
},
|
|
781
|
+
capture: {
|
|
782
|
+
...defaults.capture,
|
|
783
|
+
...config.capture
|
|
784
|
+
},
|
|
785
|
+
proactive: {
|
|
786
|
+
...defaults.proactive,
|
|
787
|
+
...config.proactive,
|
|
788
|
+
cooldown: {
|
|
789
|
+
...defaults.proactive?.cooldown,
|
|
790
|
+
...config.proactive?.cooldown
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
};
|
|
794
|
+
}
|
|
795
|
+
function isSilenced() {
|
|
796
|
+
const truthy = (v) => v === "1" || v === "true";
|
|
797
|
+
try {
|
|
798
|
+
if (typeof process !== "undefined" && process.env && truthy(process.env.MUSHI_SILENT)) return true;
|
|
799
|
+
} catch {
|
|
800
|
+
}
|
|
801
|
+
try {
|
|
802
|
+
const env = undefined;
|
|
803
|
+
if (env && typeof env === "object" && (truthy(env.MUSHI_SILENT) || truthy(env.VITE_MUSHI_SILENT))) return true;
|
|
804
|
+
} catch {
|
|
805
|
+
}
|
|
806
|
+
return false;
|
|
807
|
+
}
|
|
808
|
+
function validateConfig(config) {
|
|
809
|
+
try {
|
|
810
|
+
if (isSilenced()) return;
|
|
811
|
+
if (!config || typeof config !== "object") return;
|
|
812
|
+
const unknownKeys = Object.keys(config).filter((key) => !KNOWN_CONFIG_KEYS.includes(key));
|
|
813
|
+
if (unknownKeys.length > 0) {
|
|
814
|
+
console.error(
|
|
815
|
+
`[mushi] Unknown config key${unknownKeys.length > 1 ? "s" : ""}: ${unknownKeys.map((k) => `\`${k}\``).join(", ")} \u2014 check for typos (see MushiConfig). Ignored.`
|
|
816
|
+
);
|
|
817
|
+
}
|
|
818
|
+
if (config.preset !== void 0 && !KNOWN_PRESETS.includes(config.preset)) {
|
|
819
|
+
console.error(
|
|
820
|
+
`[mushi] Unknown preset \`${String(config.preset)}\` \u2014 expected one of ${KNOWN_PRESETS.map(
|
|
821
|
+
(p) => `'${p}'`
|
|
822
|
+
).join(", ")}. Preset ignored.`
|
|
823
|
+
);
|
|
824
|
+
}
|
|
825
|
+
} catch {
|
|
826
|
+
}
|
|
827
|
+
}
|
|
604
828
|
|
|
605
829
|
// src/pre-filter.ts
|
|
606
830
|
var DEFAULT_MIN_LENGTH = 10;
|
|
@@ -956,12 +1180,12 @@ function createOfflineQueue(config = {}) {
|
|
|
956
1180
|
const n = row.attempts;
|
|
957
1181
|
return typeof n === "number" && Number.isFinite(n) ? n : 0;
|
|
958
1182
|
}
|
|
959
|
-
function isExpired(row,
|
|
1183
|
+
function isExpired(row, now2) {
|
|
960
1184
|
const queuedAt = row.queuedAt;
|
|
961
1185
|
if (!queuedAt) return false;
|
|
962
1186
|
const ts = Date.parse(queuedAt);
|
|
963
1187
|
if (Number.isNaN(ts)) return false;
|
|
964
|
-
return
|
|
1188
|
+
return now2 - ts > MAX_QUEUE_AGE_MS;
|
|
965
1189
|
}
|
|
966
1190
|
function detectBackend() {
|
|
967
1191
|
if (backendType) return backendType;
|
|
@@ -1140,10 +1364,10 @@ function createOfflineQueue(config = {}) {
|
|
|
1140
1364
|
} else {
|
|
1141
1365
|
rows = lsRead();
|
|
1142
1366
|
}
|
|
1143
|
-
const
|
|
1367
|
+
const now2 = Date.now();
|
|
1144
1368
|
const fresh = [];
|
|
1145
1369
|
for (const row of rows) {
|
|
1146
|
-
if (isExpired(row,
|
|
1370
|
+
if (isExpired(row, now2)) {
|
|
1147
1371
|
const rowId = row.id;
|
|
1148
1372
|
await removeRow(backend, rowId);
|
|
1149
1373
|
queueLog.debug("Offline queue: evicting stale report", { id: rowId });
|
|
@@ -1608,6 +1832,111 @@ function randomSuffix() {
|
|
|
1608
1832
|
return n.toString(36).padStart(6, "0").slice(0, 6);
|
|
1609
1833
|
}
|
|
1610
1834
|
|
|
1835
|
+
// src/session-tracker.ts
|
|
1836
|
+
var HEARTBEAT_INTERVAL_MS = 6e4;
|
|
1837
|
+
var _client = null;
|
|
1838
|
+
var _sdkVersion;
|
|
1839
|
+
var _userIdHash = null;
|
|
1840
|
+
var _reporterTokenHash = null;
|
|
1841
|
+
var _pageViewCount = 0;
|
|
1842
|
+
var _heartbeatTimer = null;
|
|
1843
|
+
var _initialized = false;
|
|
1844
|
+
function now() {
|
|
1845
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
1846
|
+
}
|
|
1847
|
+
function currentRoute() {
|
|
1848
|
+
if (typeof window === "undefined") return "";
|
|
1849
|
+
return window.location.pathname + window.location.search;
|
|
1850
|
+
}
|
|
1851
|
+
function userAgent() {
|
|
1852
|
+
if (typeof navigator === "undefined") return null;
|
|
1853
|
+
return navigator.userAgent ?? null;
|
|
1854
|
+
}
|
|
1855
|
+
function buildPayload(kind, extra) {
|
|
1856
|
+
return {
|
|
1857
|
+
kind,
|
|
1858
|
+
session_id: getSessionId(),
|
|
1859
|
+
ts: now(),
|
|
1860
|
+
page_view_count: _pageViewCount,
|
|
1861
|
+
reporter_token_hash: _reporterTokenHash,
|
|
1862
|
+
user_id_hash: _userIdHash,
|
|
1863
|
+
user_agent: userAgent(),
|
|
1864
|
+
sdk_version: _sdkVersion,
|
|
1865
|
+
...extra
|
|
1866
|
+
};
|
|
1867
|
+
}
|
|
1868
|
+
function send(payload) {
|
|
1869
|
+
if (!_client) return;
|
|
1870
|
+
_client.postSessionEvent(payload).catch(() => {
|
|
1871
|
+
});
|
|
1872
|
+
}
|
|
1873
|
+
function initSessionTracker(opts) {
|
|
1874
|
+
if (_initialized || typeof window === "undefined") return;
|
|
1875
|
+
_initialized = true;
|
|
1876
|
+
_client = opts.client;
|
|
1877
|
+
_sdkVersion = opts.sdkVersion;
|
|
1878
|
+
_reporterTokenHash = opts.reporterTokenHash ?? null;
|
|
1879
|
+
_userIdHash = opts.userIdHash ?? null;
|
|
1880
|
+
const entryRoute = currentRoute();
|
|
1881
|
+
_pageViewCount = 1;
|
|
1882
|
+
send(buildPayload("session_start", {
|
|
1883
|
+
route: entryRoute,
|
|
1884
|
+
referrer: typeof document !== "undefined" ? document.referrer ?? null : null
|
|
1885
|
+
}));
|
|
1886
|
+
_heartbeatTimer = setInterval(() => {
|
|
1887
|
+
send(buildPayload("session_heartbeat", { route: currentRoute() }));
|
|
1888
|
+
}, HEARTBEAT_INTERVAL_MS);
|
|
1889
|
+
const onHide = () => {
|
|
1890
|
+
if (typeof document !== "undefined" && document.visibilityState === "hidden") {
|
|
1891
|
+
send(buildPayload("session_end", { route: currentRoute() }));
|
|
1892
|
+
}
|
|
1893
|
+
};
|
|
1894
|
+
if (typeof document !== "undefined") {
|
|
1895
|
+
document.addEventListener("visibilitychange", onHide, { passive: true });
|
|
1896
|
+
}
|
|
1897
|
+
if (typeof window !== "undefined") {
|
|
1898
|
+
window.addEventListener("pagehide", () => {
|
|
1899
|
+
send(buildPayload("session_end", { route: currentRoute() }));
|
|
1900
|
+
}, { passive: true });
|
|
1901
|
+
}
|
|
1902
|
+
patchHistoryForPageViews();
|
|
1903
|
+
}
|
|
1904
|
+
function trackPageView(route) {
|
|
1905
|
+
if (!_initialized || !_client) return;
|
|
1906
|
+
_pageViewCount += 1;
|
|
1907
|
+
send(buildPayload("page_view", { route: route ?? currentRoute() }));
|
|
1908
|
+
}
|
|
1909
|
+
function updateSessionIdentity(userIdHash) {
|
|
1910
|
+
_userIdHash = userIdHash;
|
|
1911
|
+
}
|
|
1912
|
+
function destroySessionTracker() {
|
|
1913
|
+
if (_heartbeatTimer != null) {
|
|
1914
|
+
clearInterval(_heartbeatTimer);
|
|
1915
|
+
_heartbeatTimer = null;
|
|
1916
|
+
}
|
|
1917
|
+
_initialized = false;
|
|
1918
|
+
_client = null;
|
|
1919
|
+
}
|
|
1920
|
+
var _historyPatched = false;
|
|
1921
|
+
function patchHistoryForPageViews() {
|
|
1922
|
+
if (_historyPatched || typeof history === "undefined") return;
|
|
1923
|
+
_historyPatched = true;
|
|
1924
|
+
const wrap = (original) => function(...args) {
|
|
1925
|
+
const result = original.apply(this, args);
|
|
1926
|
+
if (original === history.pushState) {
|
|
1927
|
+
_pageViewCount += 1;
|
|
1928
|
+
send(buildPayload("page_view", { route: currentRoute() }));
|
|
1929
|
+
}
|
|
1930
|
+
return result;
|
|
1931
|
+
};
|
|
1932
|
+
history.pushState = wrap(history.pushState);
|
|
1933
|
+
history.replaceState = wrap(history.replaceState);
|
|
1934
|
+
window.addEventListener("popstate", () => {
|
|
1935
|
+
_pageViewCount += 1;
|
|
1936
|
+
send(buildPayload("page_view", { route: currentRoute() }));
|
|
1937
|
+
}, { passive: true });
|
|
1938
|
+
}
|
|
1939
|
+
|
|
1611
1940
|
// src/rate-limiter.ts
|
|
1612
1941
|
var DEFAULT_MAX_BURST = 10;
|
|
1613
1942
|
var DEFAULT_REFILL_RATE = 1;
|
|
@@ -1621,12 +1950,12 @@ function createRateLimiter(config = {}) {
|
|
|
1621
1950
|
let tokens = maxBurst;
|
|
1622
1951
|
let lastRefill = Date.now();
|
|
1623
1952
|
function refill() {
|
|
1624
|
-
const
|
|
1625
|
-
const elapsed =
|
|
1953
|
+
const now2 = Date.now();
|
|
1954
|
+
const elapsed = now2 - lastRefill;
|
|
1626
1955
|
const refills = Math.floor(elapsed / refillIntervalMs);
|
|
1627
1956
|
if (refills > 0) {
|
|
1628
1957
|
tokens = Math.min(maxBurst, tokens + refills * refillRate);
|
|
1629
|
-
lastRefill =
|
|
1958
|
+
lastRefill = now2;
|
|
1630
1959
|
}
|
|
1631
1960
|
}
|
|
1632
1961
|
function tryConsume() {
|
|
@@ -1940,15 +2269,15 @@ function mushiTokens(mode) {
|
|
|
1940
2269
|
// src/identity.ts
|
|
1941
2270
|
var MUSHI_IDENTITY_TOKEN_PREFIX = "mushi.identity";
|
|
1942
2271
|
function buildIdentityClaims(input) {
|
|
1943
|
-
const
|
|
2272
|
+
const now2 = Math.floor(Date.now() / 1e3);
|
|
1944
2273
|
const ttl = input.ttlSeconds && input.ttlSeconds > 0 ? Math.min(input.ttlSeconds, 3600) : 600;
|
|
1945
2274
|
return {
|
|
1946
2275
|
projectId: input.projectId,
|
|
1947
2276
|
sub: input.userId,
|
|
1948
2277
|
...input.email ? { email: input.email } : {},
|
|
1949
2278
|
...input.name ? { name: input.name } : {},
|
|
1950
|
-
iat:
|
|
1951
|
-
exp:
|
|
2279
|
+
iat: now2,
|
|
2280
|
+
exp: now2 + ttl
|
|
1952
2281
|
};
|
|
1953
2282
|
}
|
|
1954
2283
|
function base64UrlDecode(segment) {
|
|
@@ -1987,12 +2316,12 @@ var MULTI_TENANT_PARENT_HOSTS = /* @__PURE__ */ new Set(["kensaur.us", "www.kens
|
|
|
1987
2316
|
var SLUG_DOMAIN_HINTS = {
|
|
1988
2317
|
"glot-it": "glot.it",
|
|
1989
2318
|
glotit: "glot.it",
|
|
1990
|
-
"solo-boss-cloud": "
|
|
2319
|
+
"solo-boss-cloud": "solo-boss.kensaur.us",
|
|
1991
2320
|
"mushi-mushi": "kensaur.us"
|
|
1992
2321
|
};
|
|
1993
2322
|
var SLUG_ICON_URL_HINTS = {
|
|
1994
2323
|
"mushi-mushi": "https://kensaur.us/mushi-mushi/admin/favicon.svg",
|
|
1995
|
-
"solo-boss-cloud": "https://
|
|
2324
|
+
"solo-boss-cloud": "https://solo-boss.kensaur.us/favicon.svg",
|
|
1996
2325
|
"glot-it": "https://kensaur.us/glot-it/apple-touch-icon.png",
|
|
1997
2326
|
glotit: "https://kensaur.us/glot-it/apple-touch-icon.png"
|
|
1998
2327
|
};
|
|
@@ -2206,7 +2535,10 @@ exports.createOfflineQueue = createOfflineQueue;
|
|
|
2206
2535
|
exports.createPiiScrubber = createPiiScrubber;
|
|
2207
2536
|
exports.createPreFilter = createPreFilter;
|
|
2208
2537
|
exports.createRateLimiter = createRateLimiter;
|
|
2538
|
+
exports.destroySessionTracker = destroySessionTracker;
|
|
2539
|
+
exports.diagnoseEnvConfig = diagnoseEnvConfig;
|
|
2209
2540
|
exports.estimateJsonBytes = estimateJsonBytes;
|
|
2541
|
+
exports.expandPreset = expandPreset;
|
|
2210
2542
|
exports.faviconUrlCandidates = faviconUrlCandidates;
|
|
2211
2543
|
exports.formatBytes = formatBytes;
|
|
2212
2544
|
exports.getDeviceFingerprintHash = getDeviceFingerprintHash;
|
|
@@ -2214,6 +2546,7 @@ exports.getReporterToken = getReporterToken;
|
|
|
2214
2546
|
exports.getSessionId = getSessionId;
|
|
2215
2547
|
exports.githubRepoDomainHint = githubRepoDomainHint;
|
|
2216
2548
|
exports.hmacSha256Hex = hmacSha256Hex;
|
|
2549
|
+
exports.initSessionTracker = initSessionTracker;
|
|
2217
2550
|
exports.isLikelyGenericFavicon = isLikelyGenericFavicon;
|
|
2218
2551
|
exports.isUntrustedFaviconUrl = isUntrustedFaviconUrl;
|
|
2219
2552
|
exports.mushiPalette = mushiPalette;
|
|
@@ -2235,5 +2568,8 @@ exports.resolveWidgetAccent = resolveWidgetAccent;
|
|
|
2235
2568
|
exports.safeWidgetHex = safeWidgetHex;
|
|
2236
2569
|
exports.scrubPii = scrubPii;
|
|
2237
2570
|
exports.sha256Hex = sha256Hex;
|
|
2571
|
+
exports.trackPageView = trackPageView;
|
|
2572
|
+
exports.updateSessionIdentity = updateSessionIdentity;
|
|
2573
|
+
exports.validateConfig = validateConfig;
|
|
2238
2574
|
//# sourceMappingURL=index.cjs.map
|
|
2239
2575
|
//# sourceMappingURL=index.cjs.map
|