@mushi-mushi/core 1.19.0 → 1.19.2
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 +12 -0
- package/README.md +1 -0
- package/dist/index.cjs +229 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +65 -1
- package/dist/index.d.ts +65 -1
- package/dist/index.js +214 -8
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/CONTRIBUTING.md
CHANGED
|
@@ -27,12 +27,24 @@ pnpm build
|
|
|
27
27
|
|
|
28
28
|
```bash
|
|
29
29
|
pnpm dev # Start all dev servers (admin on :6464)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
| Surface | URL |
|
|
33
|
+
| --- | --- |
|
|
34
|
+
| Admin console | http://localhost:6464 |
|
|
35
|
+
| Docs site | http://localhost:3000 |
|
|
36
|
+
| CLI console override | `MUSHI_CONSOLE_URL=http://localhost:6464` |
|
|
37
|
+
|
|
38
|
+
```bash
|
|
30
39
|
pnpm test # Run Vitest across all packages
|
|
31
40
|
pnpm typecheck # TypeScript checks
|
|
32
41
|
pnpm lint # ESLint
|
|
33
42
|
pnpm format # Prettier
|
|
43
|
+
pnpm smoke:cli-setup # Playwright: onboarding ?setup=cli + success panel (skips headless if unsigned)
|
|
34
44
|
```
|
|
35
45
|
|
|
46
|
+
Set `CLI_SETUP_SMOKE_STRICT=1` to fail the smoke script when Playwright is not signed in (CI gate).
|
|
47
|
+
|
|
36
48
|
Ad-hoc screenshots captured during UI reviews can live temporarily at the repo
|
|
37
49
|
root, but root-level `*.png` files are intentionally ignored. Canonical
|
|
38
50
|
screenshots that should be versioned belong under `docs/screenshots/`.
|
package/README.md
CHANGED
|
@@ -38,6 +38,7 @@ Core types, API client, and utilities for the Mushi Mushi SDK.
|
|
|
38
38
|
| `MushiCaptureExceptionOptions` | 1.0+ — options for `Mushi.captureException(err, opts)`: `level`, `tags`, `extras`, `category`, `userIntent` overrides for the structured report. |
|
|
39
39
|
| `NormalisedException` | 1.0+ — return type of `normaliseThrown(err)` (`{ name, message, stack?, cause? }`); used internally by `captureException` and exposed for adapters that want to ship their own thin wrappers. |
|
|
40
40
|
| `MushiBannerLink` | 1.8+ — flat action on the rich banner layout: `{ label, href? }` opens an external URL in a new tab, `{ label, featureRequest: true }` opens the widget in feature-request mode. Consumed via `MushiBannerConfig.links`. |
|
|
41
|
+
| `screenshotSensitiveHint` (on `MushiWidgetConfig`) | 1.19+ — privacy caption under the screenshot preview: `true` = localized default, `string` = custom copy, `false` = hide caption (preview + Remove remain). Console-configurable via `GET /v1/sdk/config`. See [`docs/SDK_SCREENSHOT_PREVIEW.md`](../../docs/SDK_SCREENSHOT_PREVIEW.md). |
|
|
41
42
|
|
|
42
43
|
Constants: `MUSHI_INTERNAL_HEADER` (`'X-Mushi-Internal'`),
|
|
43
44
|
`MUSHI_INTERNAL_INIT_MARKER`, and the `MushiInternalRequestKind` literal union
|
package/dist/index.cjs
CHANGED
|
@@ -43,9 +43,23 @@ function formatBytes(n) {
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
// src/api-client.ts
|
|
46
|
+
var _credWarningEmitted = false;
|
|
46
47
|
var MUSHI_USER_TOKEN_HEADER = "X-Mushi-User-Token";
|
|
48
|
+
var MUSHI_PROJECT_HEADER = "X-Mushi-Project";
|
|
47
49
|
var MUSHI_SDK_PACKAGE_HEADER = "X-Mushi-SDK-Package";
|
|
48
50
|
var MUSHI_SDK_VERSION_HEADER = "X-Mushi-SDK-Version";
|
|
51
|
+
function buildSdkIngestHeaders(opts) {
|
|
52
|
+
const headers = {
|
|
53
|
+
"Content-Type": "application/json",
|
|
54
|
+
"X-Mushi-Api-Key": opts.apiKey,
|
|
55
|
+
[MUSHI_PROJECT_HEADER]: opts.projectId
|
|
56
|
+
};
|
|
57
|
+
if (opts.sdkPackage) headers[MUSHI_SDK_PACKAGE_HEADER] = opts.sdkPackage;
|
|
58
|
+
if (opts.sdkVersion) headers[MUSHI_SDK_VERSION_HEADER] = opts.sdkVersion;
|
|
59
|
+
if (opts.userToken) headers[MUSHI_USER_TOKEN_HEADER] = opts.userToken;
|
|
60
|
+
if (opts.internalKind) headers[MUSHI_INTERNAL_HEADER] = opts.internalKind;
|
|
61
|
+
return headers;
|
|
62
|
+
}
|
|
49
63
|
var DEFAULT_API_ENDPOINT = "https://dxptnwrhwsqckaftyymj.supabase.co/functions/v1/api";
|
|
50
64
|
var MUSHI_INTERNAL_HEADER = "X-Mushi-Internal";
|
|
51
65
|
var MUSHI_INTERNAL_INIT_MARKER = "__mushiInternal";
|
|
@@ -72,13 +86,14 @@ function createApiClient(options) {
|
|
|
72
86
|
const response = await fetch(url, {
|
|
73
87
|
method,
|
|
74
88
|
headers: {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
89
|
+
...buildSdkIngestHeaders({
|
|
90
|
+
apiKey,
|
|
91
|
+
projectId,
|
|
92
|
+
sdkPackage,
|
|
93
|
+
sdkVersion,
|
|
94
|
+
userToken,
|
|
95
|
+
internalKind
|
|
96
|
+
}),
|
|
82
97
|
...extraHeaders
|
|
83
98
|
},
|
|
84
99
|
body: body ? JSON.stringify(body) : void 0,
|
|
@@ -98,6 +113,13 @@ function createApiClient(options) {
|
|
|
98
113
|
}
|
|
99
114
|
if (!response.ok) {
|
|
100
115
|
const errorBody = await response.json().catch(() => ({}));
|
|
116
|
+
if ((response.status === 401 || response.status === 403) && !_credWarningEmitted) {
|
|
117
|
+
_credWarningEmitted = true;
|
|
118
|
+
const where = baseUrl === DEFAULT_API_ENDPOINT ? "Get the correct values at: https://kensaur.us/mushi-mushi/admin/projects" : "Get the correct values from your Mushi console's Projects page.";
|
|
119
|
+
console.error(
|
|
120
|
+
`[Mushi] Credentials rejected (HTTP ${response.status}). Check your Project ID and API key scope (must be "report:write"). ` + where
|
|
121
|
+
);
|
|
122
|
+
}
|
|
101
123
|
if (response.status >= 500 && retries > 0) {
|
|
102
124
|
await sleep(getBackoffDelay(maxRetries - retries));
|
|
103
125
|
return request(method, path, body, retries - 1, internalKind, extraHeaders);
|
|
@@ -1806,6 +1828,190 @@ function parseIdentityToken(token) {
|
|
|
1806
1828
|
}
|
|
1807
1829
|
}
|
|
1808
1830
|
|
|
1831
|
+
// src/favicon.ts
|
|
1832
|
+
var MULTI_TENANT_PARENT_HOSTS = /* @__PURE__ */ new Set(["kensaur.us", "www.kensaur.us"]);
|
|
1833
|
+
var SLUG_DOMAIN_HINTS = {
|
|
1834
|
+
"glot-it": "glot.it",
|
|
1835
|
+
glotit: "glot.it",
|
|
1836
|
+
"solo-boss-cloud": "sbc-front.vercel.app",
|
|
1837
|
+
"mushi-mushi": "mushimushi.dev"
|
|
1838
|
+
};
|
|
1839
|
+
var SLUG_ICON_URL_HINTS = {
|
|
1840
|
+
"mushi-mushi": "https://kensaur.us/mushi-mushi/admin/favicon.svg",
|
|
1841
|
+
"solo-boss-cloud": "https://sbc-front.vercel.app/favicon.svg",
|
|
1842
|
+
"glot-it": "https://kensaur.us/glot-it/apple-touch-icon.png",
|
|
1843
|
+
glotit: "https://kensaur.us/glot-it/apple-touch-icon.png"
|
|
1844
|
+
};
|
|
1845
|
+
var UNTRUSTED_FAVICON_HOSTS = [
|
|
1846
|
+
"www.google.com",
|
|
1847
|
+
"icons.duckduckgo.com",
|
|
1848
|
+
"icon.horse"
|
|
1849
|
+
];
|
|
1850
|
+
function isUntrustedFaviconUrl(url) {
|
|
1851
|
+
try {
|
|
1852
|
+
const host = new URL(url).hostname.toLowerCase();
|
|
1853
|
+
return UNTRUSTED_FAVICON_HOSTS.some((h) => host === h || host.endsWith(`.${h}`));
|
|
1854
|
+
} catch {
|
|
1855
|
+
return false;
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
function faviconUrlCandidates(domain, _size = 32) {
|
|
1859
|
+
return [
|
|
1860
|
+
`https://${domain}/favicon.svg`,
|
|
1861
|
+
`https://${domain}/favicon.ico`,
|
|
1862
|
+
`https://${domain}/favicon-32x32.png`,
|
|
1863
|
+
`https://${domain}/apple-touch-icon.png`
|
|
1864
|
+
];
|
|
1865
|
+
}
|
|
1866
|
+
function resolveProjectFaviconDomains(source) {
|
|
1867
|
+
const out = [];
|
|
1868
|
+
const add = (d) => {
|
|
1869
|
+
const n = d?.trim().toLowerCase();
|
|
1870
|
+
if (!n || out.includes(n)) return;
|
|
1871
|
+
out.push(n);
|
|
1872
|
+
};
|
|
1873
|
+
add(githubRepoDomainHint(source.repo_url));
|
|
1874
|
+
add(SLUG_DOMAIN_HINTS[source.project_slug.toLowerCase()]);
|
|
1875
|
+
const fromOrigin = source.sdk_origin ? originToDomain(source.sdk_origin) : null;
|
|
1876
|
+
if (fromOrigin && !MULTI_TENANT_PARENT_HOSTS.has(fromOrigin)) {
|
|
1877
|
+
add(fromOrigin);
|
|
1878
|
+
}
|
|
1879
|
+
return out;
|
|
1880
|
+
}
|
|
1881
|
+
function projectFaviconUrlCandidates(source, size = 32) {
|
|
1882
|
+
if (source.icon_url?.trim()) return [source.icon_url.trim()];
|
|
1883
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1884
|
+
const urls = [];
|
|
1885
|
+
const push = (url) => {
|
|
1886
|
+
if (!url || seen.has(url) || isUntrustedFaviconUrl(url)) return;
|
|
1887
|
+
seen.add(url);
|
|
1888
|
+
urls.push(url);
|
|
1889
|
+
};
|
|
1890
|
+
const slugHint = SLUG_ICON_URL_HINTS[source.project_slug.toLowerCase()];
|
|
1891
|
+
if (slugHint) push(slugHint);
|
|
1892
|
+
for (const domain of resolveProjectFaviconDomains(source)) {
|
|
1893
|
+
for (const url of faviconUrlCandidates(domain, size)) {
|
|
1894
|
+
push(url);
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1897
|
+
return urls;
|
|
1898
|
+
}
|
|
1899
|
+
function originToDomain(origin) {
|
|
1900
|
+
const trimmed = origin.trim();
|
|
1901
|
+
if (!trimmed) return null;
|
|
1902
|
+
if (/^(capacitor|file|content|app):/i.test(trimmed)) return null;
|
|
1903
|
+
try {
|
|
1904
|
+
const url = new URL(trimmed.includes("://") ? trimmed : `https://${trimmed}`);
|
|
1905
|
+
const host = url.hostname.toLowerCase();
|
|
1906
|
+
if (host === "localhost" || host === "127.0.0.1" || host.endsWith(".local")) {
|
|
1907
|
+
return null;
|
|
1908
|
+
}
|
|
1909
|
+
return host;
|
|
1910
|
+
} catch {
|
|
1911
|
+
return null;
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
function githubRepoDomainHint(repoUrl) {
|
|
1915
|
+
if (!repoUrl?.trim()) return null;
|
|
1916
|
+
try {
|
|
1917
|
+
const u = new URL(repoUrl.trim());
|
|
1918
|
+
const host = u.hostname.toLowerCase();
|
|
1919
|
+
const parts = u.pathname.replace(/^\/+|\/+$/g, "").split("/").filter(Boolean);
|
|
1920
|
+
if (parts.length < 2) return null;
|
|
1921
|
+
const repo = parts[1].replace(/\.git$/i, "").toLowerCase();
|
|
1922
|
+
if (/^[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/i.test(repo)) return repo;
|
|
1923
|
+
if (host === "github.com" && repo.endsWith(".github.io")) return repo;
|
|
1924
|
+
if (host !== "github.com" && host !== "www.github.com") return host;
|
|
1925
|
+
return null;
|
|
1926
|
+
} catch {
|
|
1927
|
+
return null;
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1930
|
+
function resolveProjectDomain(source) {
|
|
1931
|
+
return resolveProjectFaviconDomains(source)[0] ?? null;
|
|
1932
|
+
}
|
|
1933
|
+
function isLikelyGenericFavicon(img) {
|
|
1934
|
+
if (typeof document === "undefined") return false;
|
|
1935
|
+
if (!img.complete || img.naturalWidth === 0 || img.naturalHeight === 0) return false;
|
|
1936
|
+
try {
|
|
1937
|
+
const canvas = document.createElement("canvas");
|
|
1938
|
+
const w = img.naturalWidth;
|
|
1939
|
+
const h = img.naturalHeight;
|
|
1940
|
+
canvas.width = w;
|
|
1941
|
+
canvas.height = h;
|
|
1942
|
+
const ctx = canvas.getContext("2d");
|
|
1943
|
+
if (!ctx) return false;
|
|
1944
|
+
ctx.drawImage(img, 0, 0);
|
|
1945
|
+
const { data } = ctx.getImageData(0, 0, w, h);
|
|
1946
|
+
let opaque = 0;
|
|
1947
|
+
let satSum = 0;
|
|
1948
|
+
let maxDiff = 0;
|
|
1949
|
+
for (let i = 0; i < data.length; i += 4) {
|
|
1950
|
+
const r = data[i];
|
|
1951
|
+
const g = data[i + 1];
|
|
1952
|
+
const b = data[i + 2];
|
|
1953
|
+
const a = data[i + 3];
|
|
1954
|
+
if (a < 128) continue;
|
|
1955
|
+
opaque++;
|
|
1956
|
+
const max = Math.max(r, g, b);
|
|
1957
|
+
const min = Math.min(r, g, b);
|
|
1958
|
+
const diff = max - min;
|
|
1959
|
+
maxDiff = Math.max(maxDiff, diff);
|
|
1960
|
+
satSum += diff;
|
|
1961
|
+
}
|
|
1962
|
+
if (opaque === 0) return true;
|
|
1963
|
+
const avgSat = satSum / opaque;
|
|
1964
|
+
return avgSat < 14 && maxDiff < 24;
|
|
1965
|
+
} catch {
|
|
1966
|
+
return isUntrustedFaviconUrl(img.src);
|
|
1967
|
+
}
|
|
1968
|
+
}
|
|
1969
|
+
function projectInitials(name) {
|
|
1970
|
+
const parts = name.trim().split(/[\s._-]+/).filter(Boolean);
|
|
1971
|
+
if (parts.length >= 2) {
|
|
1972
|
+
return `${parts[0][0] ?? ""}${parts[1][0] ?? ""}`.toUpperCase();
|
|
1973
|
+
}
|
|
1974
|
+
const compact = name.replace(/[^a-zA-Z0-9]/g, "");
|
|
1975
|
+
return (compact.slice(0, 2) || name.slice(0, 2) || "?").toUpperCase();
|
|
1976
|
+
}
|
|
1977
|
+
var INITIALS_CHIP_THEMES = [
|
|
1978
|
+
"info",
|
|
1979
|
+
"brand",
|
|
1980
|
+
"warn",
|
|
1981
|
+
"ok",
|
|
1982
|
+
"accent"
|
|
1983
|
+
];
|
|
1984
|
+
function projectInitialsThemeIndex(projectId) {
|
|
1985
|
+
let hash = 0;
|
|
1986
|
+
for (let i = 0; i < projectId.length; i++) {
|
|
1987
|
+
hash = (hash + projectId.charCodeAt(i)) % INITIALS_CHIP_THEMES.length;
|
|
1988
|
+
}
|
|
1989
|
+
return hash;
|
|
1990
|
+
}
|
|
1991
|
+
function readPageFaviconHref() {
|
|
1992
|
+
if (typeof document === "undefined") return null;
|
|
1993
|
+
const selectors = [
|
|
1994
|
+
'link[rel="icon"]',
|
|
1995
|
+
'link[rel="shortcut icon"]',
|
|
1996
|
+
'link[rel="apple-touch-icon"]'
|
|
1997
|
+
];
|
|
1998
|
+
for (const sel of selectors) {
|
|
1999
|
+
const link = document.querySelector(sel);
|
|
2000
|
+
const href = link?.href;
|
|
2001
|
+
if (href && isSafeFaviconHref(href)) return href;
|
|
2002
|
+
}
|
|
2003
|
+
return null;
|
|
2004
|
+
}
|
|
2005
|
+
function isSafeFaviconHref(href) {
|
|
2006
|
+
try {
|
|
2007
|
+
const base = typeof location !== "undefined" ? location.href : void 0;
|
|
2008
|
+
const protocol = new URL(href, base).protocol;
|
|
2009
|
+
return protocol === "http:" || protocol === "https:";
|
|
2010
|
+
} catch {
|
|
2011
|
+
return false;
|
|
2012
|
+
}
|
|
2013
|
+
}
|
|
2014
|
+
|
|
1809
2015
|
exports.DEFAULT_API_ENDPOINT = DEFAULT_API_ENDPOINT;
|
|
1810
2016
|
exports.MAX_REPORT_PAYLOAD_BYTES = MAX_REPORT_PAYLOAD_BYTES;
|
|
1811
2017
|
exports.MAX_SCREENSHOT_DATA_URL_BYTES = MAX_SCREENSHOT_DATA_URL_BYTES;
|
|
@@ -1817,12 +2023,17 @@ exports.MUSHI_IDENTITY_TOKEN_PREFIX = MUSHI_IDENTITY_TOKEN_PREFIX;
|
|
|
1817
2023
|
exports.MUSHI_INTERNAL_HEADER = MUSHI_INTERNAL_HEADER;
|
|
1818
2024
|
exports.MUSHI_INTERNAL_INIT_MARKER = MUSHI_INTERNAL_INIT_MARKER;
|
|
1819
2025
|
exports.MUSHI_MOTION = MUSHI_MOTION;
|
|
2026
|
+
exports.MUSHI_PROJECT_HEADER = MUSHI_PROJECT_HEADER;
|
|
1820
2027
|
exports.MUSHI_RADIUS = MUSHI_RADIUS;
|
|
2028
|
+
exports.MUSHI_SDK_PACKAGE_HEADER = MUSHI_SDK_PACKAGE_HEADER;
|
|
2029
|
+
exports.MUSHI_SDK_VERSION_HEADER = MUSHI_SDK_VERSION_HEADER;
|
|
1821
2030
|
exports.MUSHI_SPACING = MUSHI_SPACING;
|
|
1822
2031
|
exports.MUSHI_TYPE = MUSHI_TYPE;
|
|
2032
|
+
exports.MUSHI_USER_TOKEN_HEADER = MUSHI_USER_TOKEN_HEADER;
|
|
1823
2033
|
exports.MUSHI_Z = MUSHI_Z;
|
|
1824
2034
|
exports.REGION_ENDPOINTS = REGION_ENDPOINTS;
|
|
1825
2035
|
exports.buildIdentityClaims = buildIdentityClaims;
|
|
2036
|
+
exports.buildSdkIngestHeaders = buildSdkIngestHeaders;
|
|
1826
2037
|
exports.captureEnvironment = captureEnvironment;
|
|
1827
2038
|
exports.checkReportPayloadSize = checkReportPayloadSize;
|
|
1828
2039
|
exports.createApiClient = createApiClient;
|
|
@@ -1833,17 +2044,28 @@ exports.createPiiScrubber = createPiiScrubber;
|
|
|
1833
2044
|
exports.createPreFilter = createPreFilter;
|
|
1834
2045
|
exports.createRateLimiter = createRateLimiter;
|
|
1835
2046
|
exports.estimateJsonBytes = estimateJsonBytes;
|
|
2047
|
+
exports.faviconUrlCandidates = faviconUrlCandidates;
|
|
1836
2048
|
exports.formatBytes = formatBytes;
|
|
1837
2049
|
exports.getDeviceFingerprintHash = getDeviceFingerprintHash;
|
|
1838
2050
|
exports.getReporterToken = getReporterToken;
|
|
1839
2051
|
exports.getSessionId = getSessionId;
|
|
2052
|
+
exports.githubRepoDomainHint = githubRepoDomainHint;
|
|
2053
|
+
exports.isLikelyGenericFavicon = isLikelyGenericFavicon;
|
|
2054
|
+
exports.isUntrustedFaviconUrl = isUntrustedFaviconUrl;
|
|
1840
2055
|
exports.mushiPalette = mushiPalette;
|
|
1841
2056
|
exports.mushiTokens = mushiTokens;
|
|
1842
2057
|
exports.newUuid = newUuid;
|
|
1843
2058
|
exports.noopLogger = noopLogger;
|
|
1844
2059
|
exports.normaliseThrown = normaliseThrown;
|
|
2060
|
+
exports.originToDomain = originToDomain;
|
|
1845
2061
|
exports.parseIdentityToken = parseIdentityToken;
|
|
2062
|
+
exports.projectFaviconUrlCandidates = projectFaviconUrlCandidates;
|
|
2063
|
+
exports.projectInitials = projectInitials;
|
|
2064
|
+
exports.projectInitialsThemeIndex = projectInitialsThemeIndex;
|
|
2065
|
+
exports.readPageFaviconHref = readPageFaviconHref;
|
|
1846
2066
|
exports.resolveEnvConfig = resolveEnvConfig;
|
|
2067
|
+
exports.resolveProjectDomain = resolveProjectDomain;
|
|
2068
|
+
exports.resolveProjectFaviconDomains = resolveProjectFaviconDomains;
|
|
1847
2069
|
exports.resolveRegionEndpoint = resolveRegionEndpoint;
|
|
1848
2070
|
exports.scrubPii = scrubPii;
|
|
1849
2071
|
//# sourceMappingURL=index.cjs.map
|