@mushi-mushi/core 1.18.0 → 1.19.1
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 +260 -47
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +71 -1
- package/dist/index.d.ts +71 -1
- package/dist/index.js +245 -48
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.cts
CHANGED
|
@@ -102,6 +102,20 @@ interface MushiWidgetConfig {
|
|
|
102
102
|
brandFooter?: boolean;
|
|
103
103
|
/** How the widget should surface SDK freshness warnings. Defaults to auto. */
|
|
104
104
|
outdatedBanner?: 'auto' | 'banner' | 'console-only' | 'off';
|
|
105
|
+
/**
|
|
106
|
+
* Privacy nudge shown beside an attached screenshot preview, reminding the
|
|
107
|
+
* reporter to remove anything sensitive (balances, PII, tokens) before they
|
|
108
|
+
* submit. The widget always renders the captured image as a visible preview
|
|
109
|
+
* so the user can see exactly what will be sent and remove it if needed; this
|
|
110
|
+
* flag only controls the accompanying caption.
|
|
111
|
+
* - `true` (default) — show the localized default caption.
|
|
112
|
+
* - string — show this custom caption verbatim.
|
|
113
|
+
* - `false` — hide the caption (the preview + remove control still show).
|
|
114
|
+
*
|
|
115
|
+
* Settable per-host via the SDK config and remotely via the Mushi console
|
|
116
|
+
* runtime config (it travels in the `widget` block of `GET /v1/sdk/config`).
|
|
117
|
+
*/
|
|
118
|
+
screenshotSensitiveHint?: boolean | string;
|
|
105
119
|
/**
|
|
106
120
|
* Beta mode: injects discreet "early access" messaging into the widget panel.
|
|
107
121
|
* Shows a beta strip on the category step and a contact footer on the success
|
|
@@ -1522,6 +1536,8 @@ interface MushiCrossAppReport {
|
|
|
1522
1536
|
project_id: string | null;
|
|
1523
1537
|
app_name: string | null;
|
|
1524
1538
|
app_slug: string | null;
|
|
1539
|
+
/** Hostname from latest SDK heartbeat — used for favicon in cross-app list. */
|
|
1540
|
+
app_domain?: string | null;
|
|
1525
1541
|
}
|
|
1526
1542
|
/** A single entry in the global tester leaderboard. */
|
|
1527
1543
|
interface MushiLeaderboardEntry {
|
|
@@ -1543,6 +1559,22 @@ interface MushiTesterReputation {
|
|
|
1543
1559
|
total_points: number;
|
|
1544
1560
|
}
|
|
1545
1561
|
|
|
1562
|
+
/** Header carrying the signed end-user identity token (verified server-side). */
|
|
1563
|
+
declare const MUSHI_USER_TOKEN_HEADER = "X-Mushi-User-Token";
|
|
1564
|
+
/** Project scope header — paired with the API key on every SDK request. */
|
|
1565
|
+
declare const MUSHI_PROJECT_HEADER = "X-Mushi-Project";
|
|
1566
|
+
/** Build-time SDK package identity — recorded on every authenticated heartbeat. */
|
|
1567
|
+
declare const MUSHI_SDK_PACKAGE_HEADER = "X-Mushi-SDK-Package";
|
|
1568
|
+
declare const MUSHI_SDK_VERSION_HEADER = "X-Mushi-SDK-Version";
|
|
1569
|
+
/** Build the standard authenticated SDK ingest headers (shared by api-client + offline flush). */
|
|
1570
|
+
declare function buildSdkIngestHeaders(opts: {
|
|
1571
|
+
apiKey: string;
|
|
1572
|
+
projectId: string;
|
|
1573
|
+
sdkPackage?: string;
|
|
1574
|
+
sdkVersion?: string;
|
|
1575
|
+
userToken?: string | null;
|
|
1576
|
+
internalKind?: MushiInternalRequestKind;
|
|
1577
|
+
}): Record<string, string>;
|
|
1546
1578
|
interface ApiClientOptions {
|
|
1547
1579
|
projectId: string;
|
|
1548
1580
|
apiKey: string;
|
|
@@ -2134,4 +2166,42 @@ declare function buildIdentityClaims(input: {
|
|
|
2134
2166
|
*/
|
|
2135
2167
|
declare function parseIdentityToken(token: string): MushiIdentityClaims | null;
|
|
2136
2168
|
|
|
2137
|
-
|
|
2169
|
+
/**
|
|
2170
|
+
* Shared favicon resolution helpers for admin console and SDK widget.
|
|
2171
|
+
*/
|
|
2172
|
+
interface ProjectFaviconSource {
|
|
2173
|
+
project_id: string;
|
|
2174
|
+
project_name: string;
|
|
2175
|
+
project_slug: string;
|
|
2176
|
+
/** Operator override — wins over inferred domain when set. */
|
|
2177
|
+
icon_url?: string | null;
|
|
2178
|
+
/** e.g. https://kensaur.us — from SDK heartbeat or API key last_seen_origin */
|
|
2179
|
+
sdk_origin?: string | null;
|
|
2180
|
+
/** Connected GitHub/GitLab repo — used when SDK has not heartbeated yet. */
|
|
2181
|
+
repo_url?: string | null;
|
|
2182
|
+
}
|
|
2183
|
+
declare function isUntrustedFaviconUrl(url: string): boolean;
|
|
2184
|
+
/** Per-domain favicon URLs — first-party assets only (no CDN globes). */
|
|
2185
|
+
declare function faviconUrlCandidates(domain: string, _size?: number): string[];
|
|
2186
|
+
/** Ordered unique domains to try for a project (most specific first). */
|
|
2187
|
+
declare function resolveProjectFaviconDomains(source: ProjectFaviconSource): string[];
|
|
2188
|
+
/** Flat URL list across all candidate domains for a project. */
|
|
2189
|
+
declare function projectFaviconUrlCandidates(source: ProjectFaviconSource, size?: number): string[];
|
|
2190
|
+
declare function originToDomain(origin: string): string | null;
|
|
2191
|
+
/** Derive a production hostname from a connected repo URL when possible. */
|
|
2192
|
+
declare function githubRepoDomainHint(repoUrl: string | null | undefined): string | null;
|
|
2193
|
+
/** Best single domain — first entry from {@link resolveProjectFaviconDomains}. */
|
|
2194
|
+
declare function resolveProjectDomain(source: ProjectFaviconSource): string | null;
|
|
2195
|
+
/**
|
|
2196
|
+
* Google's favicon CDN returns HTTP 200 with a gray globe when it has no icon.
|
|
2197
|
+
* Treat low-saturation loaded images as "miss" so callers try the next URL or initials.
|
|
2198
|
+
*/
|
|
2199
|
+
declare function isLikelyGenericFavicon(img: HTMLImageElement): boolean;
|
|
2200
|
+
/** Two-letter initials for favicon fallback chips. */
|
|
2201
|
+
declare function projectInitials(name: string): string;
|
|
2202
|
+
/** Stable theme index (0–4) for initials chip coloring. */
|
|
2203
|
+
declare function projectInitialsThemeIndex(projectId: string): number;
|
|
2204
|
+
/** Read the host page favicon href (web SDK only). */
|
|
2205
|
+
declare function readPageFaviconHref(): string | null;
|
|
2206
|
+
|
|
2207
|
+
export { type ApiClientOptions, type BreadcrumbBuffer, type BreadcrumbBufferOptions, DEFAULT_API_ENDPOINT, type LogEntry, type LogFormat, type LogLevel, type Logger, type LoggerOptions, MAX_REPORT_PAYLOAD_BYTES, MAX_SCREENSHOT_DATA_URL_BYTES, MUSHI_COLORS_DARK, MUSHI_COLORS_LIGHT, MUSHI_COPY, MUSHI_GEOMETRY, MUSHI_IDENTITY_TOKEN_PREFIX, MUSHI_INTERNAL_HEADER, MUSHI_INTERNAL_INIT_MARKER, MUSHI_MOTION, MUSHI_PROJECT_HEADER, MUSHI_RADIUS, MUSHI_SDK_PACKAGE_HEADER, MUSHI_SDK_VERSION_HEADER, MUSHI_SPACING, MUSHI_TYPE, MUSHI_USER_TOKEN_HEADER, MUSHI_Z, type MushiActivityEvent, type MushiApiCascadeConfig, type MushiApiClient, type MushiApiResponse, type MushiAssistantConfig, type MushiAssistantReply, type MushiAssistantStep, type MushiBannerConfig, type MushiBannerLink, type MushiBetaChangelogEntry, type MushiBetaModeConfig, type MushiBreadcrumb, type MushiCaptureConfig, type MushiCaptureEventInput, type MushiCaptureExceptionOptions, type MushiColorPalette, type MushiConfig, type MushiConsoleEntry, type MushiCooldownConfig, type MushiCrossAppReport, type MushiCustomCategory, type MushiDiagnosticsResult, type MushiDiscoverInventoryConfig, type MushiDiscoveryEventPayload, type MushiEnvironment, type MushiEventHandler, type MushiEventType, type MushiHallOfFameEntry, type MushiIdentityClaims, type MushiIntegrationsConfig, type MushiInternalRequestKind, type MushiLeaderboardEntry, type MushiNetworkEntry, type MushiOfflineConfig, type MushiOnDeviceClassifier, type MushiOnDeviceClassifierInput, type MushiOnDeviceClassifierResult, type MushiPageContext, type MushiPerformanceMetrics, type MushiPreFilterConfig, type MushiPreset, type MushiPrivacyConfig, type MushiProactiveConfig, type MushiRegion, type MushiReport, type MushiReportBuilder, type MushiReportCategory, type MushiReportStatus, type MushiReporterComment, type MushiReporterReport, type MushiReputationResult, type MushiRewardsConfig, type MushiRuntimeSdkConfig, type MushiSDKInstance, type MushiSdkVersionInfo, type MushiSelectedElement, type MushiSentryConfig, type MushiSentryContext, type MushiTesterReputation, type MushiThemeMode, type MushiTierResult, type MushiTimelineEntry, type MushiTimelineKind, type MushiTokenSnapshot, type MushiTracePropagationConfig, type MushiUrlMatcher, type MushiWidgetAnchor, type MushiWidgetConfig, type NormalisedException, type OfflineQueue, type PayloadGuardResult, type PiiScrubberConfig, type PreFilterResult, type ProjectFaviconSource, REGION_ENDPOINTS, type RateLimiter, type RateLimiterConfig, type ResolvedEnvConfig, buildIdentityClaims, buildSdkIngestHeaders, captureEnvironment, checkReportPayloadSize, createApiClient, createBreadcrumbBuffer, createLogger, createOfflineQueue, createPiiScrubber, createPreFilter, createRateLimiter, estimateJsonBytes, faviconUrlCandidates, formatBytes, getDeviceFingerprintHash, getReporterToken, getSessionId, githubRepoDomainHint, isLikelyGenericFavicon, isUntrustedFaviconUrl, mushiPalette, mushiTokens, newUuid, noopLogger, normaliseThrown, originToDomain, parseIdentityToken, projectFaviconUrlCandidates, projectInitials, projectInitialsThemeIndex, readPageFaviconHref, resolveEnvConfig, resolveProjectDomain, resolveProjectFaviconDomains, resolveRegionEndpoint, scrubPii };
|
package/dist/index.d.ts
CHANGED
|
@@ -102,6 +102,20 @@ interface MushiWidgetConfig {
|
|
|
102
102
|
brandFooter?: boolean;
|
|
103
103
|
/** How the widget should surface SDK freshness warnings. Defaults to auto. */
|
|
104
104
|
outdatedBanner?: 'auto' | 'banner' | 'console-only' | 'off';
|
|
105
|
+
/**
|
|
106
|
+
* Privacy nudge shown beside an attached screenshot preview, reminding the
|
|
107
|
+
* reporter to remove anything sensitive (balances, PII, tokens) before they
|
|
108
|
+
* submit. The widget always renders the captured image as a visible preview
|
|
109
|
+
* so the user can see exactly what will be sent and remove it if needed; this
|
|
110
|
+
* flag only controls the accompanying caption.
|
|
111
|
+
* - `true` (default) — show the localized default caption.
|
|
112
|
+
* - string — show this custom caption verbatim.
|
|
113
|
+
* - `false` — hide the caption (the preview + remove control still show).
|
|
114
|
+
*
|
|
115
|
+
* Settable per-host via the SDK config and remotely via the Mushi console
|
|
116
|
+
* runtime config (it travels in the `widget` block of `GET /v1/sdk/config`).
|
|
117
|
+
*/
|
|
118
|
+
screenshotSensitiveHint?: boolean | string;
|
|
105
119
|
/**
|
|
106
120
|
* Beta mode: injects discreet "early access" messaging into the widget panel.
|
|
107
121
|
* Shows a beta strip on the category step and a contact footer on the success
|
|
@@ -1522,6 +1536,8 @@ interface MushiCrossAppReport {
|
|
|
1522
1536
|
project_id: string | null;
|
|
1523
1537
|
app_name: string | null;
|
|
1524
1538
|
app_slug: string | null;
|
|
1539
|
+
/** Hostname from latest SDK heartbeat — used for favicon in cross-app list. */
|
|
1540
|
+
app_domain?: string | null;
|
|
1525
1541
|
}
|
|
1526
1542
|
/** A single entry in the global tester leaderboard. */
|
|
1527
1543
|
interface MushiLeaderboardEntry {
|
|
@@ -1543,6 +1559,22 @@ interface MushiTesterReputation {
|
|
|
1543
1559
|
total_points: number;
|
|
1544
1560
|
}
|
|
1545
1561
|
|
|
1562
|
+
/** Header carrying the signed end-user identity token (verified server-side). */
|
|
1563
|
+
declare const MUSHI_USER_TOKEN_HEADER = "X-Mushi-User-Token";
|
|
1564
|
+
/** Project scope header — paired with the API key on every SDK request. */
|
|
1565
|
+
declare const MUSHI_PROJECT_HEADER = "X-Mushi-Project";
|
|
1566
|
+
/** Build-time SDK package identity — recorded on every authenticated heartbeat. */
|
|
1567
|
+
declare const MUSHI_SDK_PACKAGE_HEADER = "X-Mushi-SDK-Package";
|
|
1568
|
+
declare const MUSHI_SDK_VERSION_HEADER = "X-Mushi-SDK-Version";
|
|
1569
|
+
/** Build the standard authenticated SDK ingest headers (shared by api-client + offline flush). */
|
|
1570
|
+
declare function buildSdkIngestHeaders(opts: {
|
|
1571
|
+
apiKey: string;
|
|
1572
|
+
projectId: string;
|
|
1573
|
+
sdkPackage?: string;
|
|
1574
|
+
sdkVersion?: string;
|
|
1575
|
+
userToken?: string | null;
|
|
1576
|
+
internalKind?: MushiInternalRequestKind;
|
|
1577
|
+
}): Record<string, string>;
|
|
1546
1578
|
interface ApiClientOptions {
|
|
1547
1579
|
projectId: string;
|
|
1548
1580
|
apiKey: string;
|
|
@@ -2134,4 +2166,42 @@ declare function buildIdentityClaims(input: {
|
|
|
2134
2166
|
*/
|
|
2135
2167
|
declare function parseIdentityToken(token: string): MushiIdentityClaims | null;
|
|
2136
2168
|
|
|
2137
|
-
|
|
2169
|
+
/**
|
|
2170
|
+
* Shared favicon resolution helpers for admin console and SDK widget.
|
|
2171
|
+
*/
|
|
2172
|
+
interface ProjectFaviconSource {
|
|
2173
|
+
project_id: string;
|
|
2174
|
+
project_name: string;
|
|
2175
|
+
project_slug: string;
|
|
2176
|
+
/** Operator override — wins over inferred domain when set. */
|
|
2177
|
+
icon_url?: string | null;
|
|
2178
|
+
/** e.g. https://kensaur.us — from SDK heartbeat or API key last_seen_origin */
|
|
2179
|
+
sdk_origin?: string | null;
|
|
2180
|
+
/** Connected GitHub/GitLab repo — used when SDK has not heartbeated yet. */
|
|
2181
|
+
repo_url?: string | null;
|
|
2182
|
+
}
|
|
2183
|
+
declare function isUntrustedFaviconUrl(url: string): boolean;
|
|
2184
|
+
/** Per-domain favicon URLs — first-party assets only (no CDN globes). */
|
|
2185
|
+
declare function faviconUrlCandidates(domain: string, _size?: number): string[];
|
|
2186
|
+
/** Ordered unique domains to try for a project (most specific first). */
|
|
2187
|
+
declare function resolveProjectFaviconDomains(source: ProjectFaviconSource): string[];
|
|
2188
|
+
/** Flat URL list across all candidate domains for a project. */
|
|
2189
|
+
declare function projectFaviconUrlCandidates(source: ProjectFaviconSource, size?: number): string[];
|
|
2190
|
+
declare function originToDomain(origin: string): string | null;
|
|
2191
|
+
/** Derive a production hostname from a connected repo URL when possible. */
|
|
2192
|
+
declare function githubRepoDomainHint(repoUrl: string | null | undefined): string | null;
|
|
2193
|
+
/** Best single domain — first entry from {@link resolveProjectFaviconDomains}. */
|
|
2194
|
+
declare function resolveProjectDomain(source: ProjectFaviconSource): string | null;
|
|
2195
|
+
/**
|
|
2196
|
+
* Google's favicon CDN returns HTTP 200 with a gray globe when it has no icon.
|
|
2197
|
+
* Treat low-saturation loaded images as "miss" so callers try the next URL or initials.
|
|
2198
|
+
*/
|
|
2199
|
+
declare function isLikelyGenericFavicon(img: HTMLImageElement): boolean;
|
|
2200
|
+
/** Two-letter initials for favicon fallback chips. */
|
|
2201
|
+
declare function projectInitials(name: string): string;
|
|
2202
|
+
/** Stable theme index (0–4) for initials chip coloring. */
|
|
2203
|
+
declare function projectInitialsThemeIndex(projectId: string): number;
|
|
2204
|
+
/** Read the host page favicon href (web SDK only). */
|
|
2205
|
+
declare function readPageFaviconHref(): string | null;
|
|
2206
|
+
|
|
2207
|
+
export { type ApiClientOptions, type BreadcrumbBuffer, type BreadcrumbBufferOptions, DEFAULT_API_ENDPOINT, type LogEntry, type LogFormat, type LogLevel, type Logger, type LoggerOptions, MAX_REPORT_PAYLOAD_BYTES, MAX_SCREENSHOT_DATA_URL_BYTES, MUSHI_COLORS_DARK, MUSHI_COLORS_LIGHT, MUSHI_COPY, MUSHI_GEOMETRY, MUSHI_IDENTITY_TOKEN_PREFIX, MUSHI_INTERNAL_HEADER, MUSHI_INTERNAL_INIT_MARKER, MUSHI_MOTION, MUSHI_PROJECT_HEADER, MUSHI_RADIUS, MUSHI_SDK_PACKAGE_HEADER, MUSHI_SDK_VERSION_HEADER, MUSHI_SPACING, MUSHI_TYPE, MUSHI_USER_TOKEN_HEADER, MUSHI_Z, type MushiActivityEvent, type MushiApiCascadeConfig, type MushiApiClient, type MushiApiResponse, type MushiAssistantConfig, type MushiAssistantReply, type MushiAssistantStep, type MushiBannerConfig, type MushiBannerLink, type MushiBetaChangelogEntry, type MushiBetaModeConfig, type MushiBreadcrumb, type MushiCaptureConfig, type MushiCaptureEventInput, type MushiCaptureExceptionOptions, type MushiColorPalette, type MushiConfig, type MushiConsoleEntry, type MushiCooldownConfig, type MushiCrossAppReport, type MushiCustomCategory, type MushiDiagnosticsResult, type MushiDiscoverInventoryConfig, type MushiDiscoveryEventPayload, type MushiEnvironment, type MushiEventHandler, type MushiEventType, type MushiHallOfFameEntry, type MushiIdentityClaims, type MushiIntegrationsConfig, type MushiInternalRequestKind, type MushiLeaderboardEntry, type MushiNetworkEntry, type MushiOfflineConfig, type MushiOnDeviceClassifier, type MushiOnDeviceClassifierInput, type MushiOnDeviceClassifierResult, type MushiPageContext, type MushiPerformanceMetrics, type MushiPreFilterConfig, type MushiPreset, type MushiPrivacyConfig, type MushiProactiveConfig, type MushiRegion, type MushiReport, type MushiReportBuilder, type MushiReportCategory, type MushiReportStatus, type MushiReporterComment, type MushiReporterReport, type MushiReputationResult, type MushiRewardsConfig, type MushiRuntimeSdkConfig, type MushiSDKInstance, type MushiSdkVersionInfo, type MushiSelectedElement, type MushiSentryConfig, type MushiSentryContext, type MushiTesterReputation, type MushiThemeMode, type MushiTierResult, type MushiTimelineEntry, type MushiTimelineKind, type MushiTokenSnapshot, type MushiTracePropagationConfig, type MushiUrlMatcher, type MushiWidgetAnchor, type MushiWidgetConfig, type NormalisedException, type OfflineQueue, type PayloadGuardResult, type PiiScrubberConfig, type PreFilterResult, type ProjectFaviconSource, REGION_ENDPOINTS, type RateLimiter, type RateLimiterConfig, type ResolvedEnvConfig, buildIdentityClaims, buildSdkIngestHeaders, captureEnvironment, checkReportPayloadSize, createApiClient, createBreadcrumbBuffer, createLogger, createOfflineQueue, createPiiScrubber, createPreFilter, createRateLimiter, estimateJsonBytes, faviconUrlCandidates, formatBytes, getDeviceFingerprintHash, getReporterToken, getSessionId, githubRepoDomainHint, isLikelyGenericFavicon, isUntrustedFaviconUrl, mushiPalette, mushiTokens, newUuid, noopLogger, normaliseThrown, originToDomain, parseIdentityToken, projectFaviconUrlCandidates, projectInitials, projectInitialsThemeIndex, readPageFaviconHref, resolveEnvConfig, resolveProjectDomain, resolveProjectFaviconDomains, resolveRegionEndpoint, scrubPii };
|
package/dist/index.js
CHANGED
|
@@ -41,9 +41,23 @@ function formatBytes(n) {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
// src/api-client.ts
|
|
44
|
+
var _credWarningEmitted = false;
|
|
44
45
|
var MUSHI_USER_TOKEN_HEADER = "X-Mushi-User-Token";
|
|
46
|
+
var MUSHI_PROJECT_HEADER = "X-Mushi-Project";
|
|
45
47
|
var MUSHI_SDK_PACKAGE_HEADER = "X-Mushi-SDK-Package";
|
|
46
48
|
var MUSHI_SDK_VERSION_HEADER = "X-Mushi-SDK-Version";
|
|
49
|
+
function buildSdkIngestHeaders(opts) {
|
|
50
|
+
const headers = {
|
|
51
|
+
"Content-Type": "application/json",
|
|
52
|
+
"X-Mushi-Api-Key": opts.apiKey,
|
|
53
|
+
[MUSHI_PROJECT_HEADER]: opts.projectId
|
|
54
|
+
};
|
|
55
|
+
if (opts.sdkPackage) headers[MUSHI_SDK_PACKAGE_HEADER] = opts.sdkPackage;
|
|
56
|
+
if (opts.sdkVersion) headers[MUSHI_SDK_VERSION_HEADER] = opts.sdkVersion;
|
|
57
|
+
if (opts.userToken) headers[MUSHI_USER_TOKEN_HEADER] = opts.userToken;
|
|
58
|
+
if (opts.internalKind) headers[MUSHI_INTERNAL_HEADER] = opts.internalKind;
|
|
59
|
+
return headers;
|
|
60
|
+
}
|
|
47
61
|
var DEFAULT_API_ENDPOINT = "https://dxptnwrhwsqckaftyymj.supabase.co/functions/v1/api";
|
|
48
62
|
var MUSHI_INTERNAL_HEADER = "X-Mushi-Internal";
|
|
49
63
|
var MUSHI_INTERNAL_INIT_MARKER = "__mushiInternal";
|
|
@@ -70,13 +84,14 @@ function createApiClient(options) {
|
|
|
70
84
|
const response = await fetch(url, {
|
|
71
85
|
method,
|
|
72
86
|
headers: {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
87
|
+
...buildSdkIngestHeaders({
|
|
88
|
+
apiKey,
|
|
89
|
+
projectId,
|
|
90
|
+
sdkPackage,
|
|
91
|
+
sdkVersion,
|
|
92
|
+
userToken,
|
|
93
|
+
internalKind
|
|
94
|
+
}),
|
|
80
95
|
...extraHeaders
|
|
81
96
|
},
|
|
82
97
|
body: body ? JSON.stringify(body) : void 0,
|
|
@@ -96,6 +111,12 @@ function createApiClient(options) {
|
|
|
96
111
|
}
|
|
97
112
|
if (!response.ok) {
|
|
98
113
|
const errorBody = await response.json().catch(() => ({}));
|
|
114
|
+
if ((response.status === 401 || response.status === 403) && !_credWarningEmitted) {
|
|
115
|
+
_credWarningEmitted = true;
|
|
116
|
+
console.error(
|
|
117
|
+
`[Mushi] Credentials rejected (HTTP ${response.status}). Check your Project ID and API key scope (must be "report:write"). Get the correct values at: https://kensaur.us/mushi-mushi/admin/projects`
|
|
118
|
+
);
|
|
119
|
+
}
|
|
99
120
|
if (response.status >= 500 && retries > 0) {
|
|
100
121
|
await sleep(getBackoffDelay(maxRetries - retries));
|
|
101
122
|
return request(method, path, body, retries - 1, internalKind, extraHeaders);
|
|
@@ -998,6 +1019,30 @@ function createOfflineQueue(config = {}) {
|
|
|
998
1019
|
lsWrite(rows);
|
|
999
1020
|
}
|
|
1000
1021
|
}
|
|
1022
|
+
async function removeRow(backend, rowId) {
|
|
1023
|
+
try {
|
|
1024
|
+
if (backend === "indexeddb") await idbDelete(rowId);
|
|
1025
|
+
else lsDelete(rowId);
|
|
1026
|
+
} catch (err) {
|
|
1027
|
+
queueLog.debug("Offline queue: row removal failed (will age out)", {
|
|
1028
|
+
id: rowId,
|
|
1029
|
+
err: String(err)
|
|
1030
|
+
});
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
async function persistRow(backend, row) {
|
|
1034
|
+
try {
|
|
1035
|
+
if (backend === "indexeddb") await idbPutRow(row);
|
|
1036
|
+
else lsUpdateRow(row);
|
|
1037
|
+
return true;
|
|
1038
|
+
} catch (err) {
|
|
1039
|
+
queueLog.debug("Offline queue: row persist failed", {
|
|
1040
|
+
id: row.id,
|
|
1041
|
+
err: String(err)
|
|
1042
|
+
});
|
|
1043
|
+
return false;
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1001
1046
|
async function enqueue(report) {
|
|
1002
1047
|
if (!enabled) return;
|
|
1003
1048
|
const currentSize = await size();
|
|
@@ -1043,12 +1088,7 @@ function createOfflineQueue(config = {}) {
|
|
|
1043
1088
|
for (const row of rows) {
|
|
1044
1089
|
if (isExpired(row, now)) {
|
|
1045
1090
|
const rowId = row.id;
|
|
1046
|
-
|
|
1047
|
-
if (backend === "indexeddb") await idbDelete(rowId);
|
|
1048
|
-
else lsDelete(rowId);
|
|
1049
|
-
} catch {
|
|
1050
|
-
lsDelete(rowId);
|
|
1051
|
-
}
|
|
1091
|
+
await removeRow(backend, rowId);
|
|
1052
1092
|
queueLog.debug("Offline queue: evicting stale report", { id: rowId });
|
|
1053
1093
|
} else {
|
|
1054
1094
|
fresh.push(row);
|
|
@@ -1063,23 +1103,13 @@ function createOfflineQueue(config = {}) {
|
|
|
1063
1103
|
const rowId = row.id;
|
|
1064
1104
|
const report = await unwrapForSend(row);
|
|
1065
1105
|
if (!report) {
|
|
1066
|
-
|
|
1067
|
-
if (backend === "indexeddb") await idbDelete(rowId);
|
|
1068
|
-
else lsDelete(rowId);
|
|
1069
|
-
} catch {
|
|
1070
|
-
lsDelete(rowId);
|
|
1071
|
-
}
|
|
1106
|
+
await removeRow(backend, rowId);
|
|
1072
1107
|
failed++;
|
|
1073
1108
|
continue;
|
|
1074
1109
|
}
|
|
1075
1110
|
const result = await client.submitReport(report);
|
|
1076
1111
|
if (result.ok) {
|
|
1077
|
-
|
|
1078
|
-
if (backend === "indexeddb") await idbDelete(rowId);
|
|
1079
|
-
else lsDelete(rowId);
|
|
1080
|
-
} catch {
|
|
1081
|
-
lsDelete(rowId);
|
|
1082
|
-
}
|
|
1112
|
+
await removeRow(backend, rowId);
|
|
1083
1113
|
sent++;
|
|
1084
1114
|
} else {
|
|
1085
1115
|
const permanent = result.error?.code === "HTTP_400" || result.error?.code === "HTTP_413" || result.error?.code === "HTTP_422" || result.error?.code === "INGEST_ERROR" || result.error?.code === "VALIDATION_ERROR" || // A payload that exceeds the size guard will never shrink on its own;
|
|
@@ -1091,21 +1121,11 @@ function createOfflineQueue(config = {}) {
|
|
|
1091
1121
|
);
|
|
1092
1122
|
const transient = !permanent && (result.error?.code === "NETWORK_ERROR" || result.error?.code === "HTTP_403" || result.error?.code === "HTTP_429" || result.error?.code === "HTTP_502" || result.error?.code === "HTTP_503" || result.error?.code === "HTTP_504" || typeof result.error?.code === "string" && result.error.code.startsWith("HTTP_5"));
|
|
1093
1123
|
if (permanent) {
|
|
1094
|
-
|
|
1095
|
-
if (backend === "indexeddb") await idbDelete(rowId);
|
|
1096
|
-
else lsDelete(rowId);
|
|
1097
|
-
} catch {
|
|
1098
|
-
lsDelete(rowId);
|
|
1099
|
-
}
|
|
1124
|
+
await removeRow(backend, rowId);
|
|
1100
1125
|
} else if (transient) {
|
|
1101
1126
|
const nextAttempts = rowAttempts(row) + 1;
|
|
1102
1127
|
if (nextAttempts >= MAX_DELIVERY_ATTEMPTS) {
|
|
1103
|
-
|
|
1104
|
-
if (backend === "indexeddb") await idbDelete(rowId);
|
|
1105
|
-
else lsDelete(rowId);
|
|
1106
|
-
} catch {
|
|
1107
|
-
lsDelete(rowId);
|
|
1108
|
-
}
|
|
1128
|
+
await removeRow(backend, rowId);
|
|
1109
1129
|
queueLog.warn("Offline queue: giving up on report after repeated failures", {
|
|
1110
1130
|
id: rowId,
|
|
1111
1131
|
attempts: nextAttempts,
|
|
@@ -1113,17 +1133,20 @@ function createOfflineQueue(config = {}) {
|
|
|
1113
1133
|
});
|
|
1114
1134
|
} else {
|
|
1115
1135
|
row.attempts = nextAttempts;
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1136
|
+
const persisted = await persistRow(backend, row);
|
|
1137
|
+
if (persisted) {
|
|
1138
|
+
queueLog.debug("Offline queue: transient failure, will retry", {
|
|
1139
|
+
id: rowId,
|
|
1140
|
+
attempts: nextAttempts,
|
|
1141
|
+
code: result.error?.code
|
|
1142
|
+
});
|
|
1143
|
+
} else {
|
|
1144
|
+
await removeRow(backend, rowId);
|
|
1145
|
+
queueLog.warn("Offline queue: dropping report (attempt counter unpersistable)", {
|
|
1146
|
+
id: rowId,
|
|
1147
|
+
code: result.error?.code
|
|
1148
|
+
});
|
|
1121
1149
|
}
|
|
1122
|
-
queueLog.debug("Offline queue: transient failure, will retry", {
|
|
1123
|
-
id: rowId,
|
|
1124
|
-
attempts: nextAttempts,
|
|
1125
|
-
code: result.error?.code
|
|
1126
|
-
});
|
|
1127
1150
|
}
|
|
1128
1151
|
}
|
|
1129
1152
|
failed++;
|
|
@@ -1802,6 +1825,180 @@ function parseIdentityToken(token) {
|
|
|
1802
1825
|
}
|
|
1803
1826
|
}
|
|
1804
1827
|
|
|
1805
|
-
|
|
1828
|
+
// src/favicon.ts
|
|
1829
|
+
var MULTI_TENANT_PARENT_HOSTS = /* @__PURE__ */ new Set(["kensaur.us", "www.kensaur.us"]);
|
|
1830
|
+
var SLUG_DOMAIN_HINTS = {
|
|
1831
|
+
"glot-it": "glot.it",
|
|
1832
|
+
glotit: "glot.it",
|
|
1833
|
+
"solo-boss-cloud": "sbc-front.vercel.app",
|
|
1834
|
+
"mushi-mushi": "mushimushi.dev"
|
|
1835
|
+
};
|
|
1836
|
+
var SLUG_ICON_URL_HINTS = {
|
|
1837
|
+
"mushi-mushi": "https://kensaur.us/mushi-mushi/admin/favicon.svg",
|
|
1838
|
+
"solo-boss-cloud": "https://sbc-front.vercel.app/favicon.svg",
|
|
1839
|
+
"glot-it": "https://kensaur.us/glot-it/apple-touch-icon.png",
|
|
1840
|
+
glotit: "https://kensaur.us/glot-it/apple-touch-icon.png"
|
|
1841
|
+
};
|
|
1842
|
+
var UNTRUSTED_FAVICON_HOSTS = [
|
|
1843
|
+
"www.google.com",
|
|
1844
|
+
"icons.duckduckgo.com",
|
|
1845
|
+
"icon.horse"
|
|
1846
|
+
];
|
|
1847
|
+
function isUntrustedFaviconUrl(url) {
|
|
1848
|
+
try {
|
|
1849
|
+
const host = new URL(url).hostname.toLowerCase();
|
|
1850
|
+
return UNTRUSTED_FAVICON_HOSTS.some((h) => host === h || host.endsWith(`.${h}`));
|
|
1851
|
+
} catch {
|
|
1852
|
+
return false;
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
function faviconUrlCandidates(domain, _size = 32) {
|
|
1856
|
+
return [
|
|
1857
|
+
`https://${domain}/favicon.svg`,
|
|
1858
|
+
`https://${domain}/favicon.ico`,
|
|
1859
|
+
`https://${domain}/favicon-32x32.png`,
|
|
1860
|
+
`https://${domain}/apple-touch-icon.png`
|
|
1861
|
+
];
|
|
1862
|
+
}
|
|
1863
|
+
function resolveProjectFaviconDomains(source) {
|
|
1864
|
+
const out = [];
|
|
1865
|
+
const add = (d) => {
|
|
1866
|
+
const n = d?.trim().toLowerCase();
|
|
1867
|
+
if (!n || out.includes(n)) return;
|
|
1868
|
+
out.push(n);
|
|
1869
|
+
};
|
|
1870
|
+
add(githubRepoDomainHint(source.repo_url));
|
|
1871
|
+
add(SLUG_DOMAIN_HINTS[source.project_slug.toLowerCase()]);
|
|
1872
|
+
const fromOrigin = source.sdk_origin ? originToDomain(source.sdk_origin) : null;
|
|
1873
|
+
if (fromOrigin && !MULTI_TENANT_PARENT_HOSTS.has(fromOrigin)) {
|
|
1874
|
+
add(fromOrigin);
|
|
1875
|
+
}
|
|
1876
|
+
return out;
|
|
1877
|
+
}
|
|
1878
|
+
function projectFaviconUrlCandidates(source, size = 32) {
|
|
1879
|
+
if (source.icon_url?.trim()) return [source.icon_url.trim()];
|
|
1880
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1881
|
+
const urls = [];
|
|
1882
|
+
const push = (url) => {
|
|
1883
|
+
if (!url || seen.has(url) || isUntrustedFaviconUrl(url)) return;
|
|
1884
|
+
seen.add(url);
|
|
1885
|
+
urls.push(url);
|
|
1886
|
+
};
|
|
1887
|
+
const slugHint = SLUG_ICON_URL_HINTS[source.project_slug.toLowerCase()];
|
|
1888
|
+
if (slugHint) push(slugHint);
|
|
1889
|
+
for (const domain of resolveProjectFaviconDomains(source)) {
|
|
1890
|
+
for (const url of faviconUrlCandidates(domain, size)) {
|
|
1891
|
+
push(url);
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
return urls;
|
|
1895
|
+
}
|
|
1896
|
+
function originToDomain(origin) {
|
|
1897
|
+
const trimmed = origin.trim();
|
|
1898
|
+
if (!trimmed) return null;
|
|
1899
|
+
if (/^(capacitor|file|content|app):/i.test(trimmed)) return null;
|
|
1900
|
+
try {
|
|
1901
|
+
const url = new URL(trimmed.includes("://") ? trimmed : `https://${trimmed}`);
|
|
1902
|
+
const host = url.hostname.toLowerCase();
|
|
1903
|
+
if (host === "localhost" || host === "127.0.0.1" || host.endsWith(".local")) {
|
|
1904
|
+
return null;
|
|
1905
|
+
}
|
|
1906
|
+
return host;
|
|
1907
|
+
} catch {
|
|
1908
|
+
return null;
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
1911
|
+
function githubRepoDomainHint(repoUrl) {
|
|
1912
|
+
if (!repoUrl?.trim()) return null;
|
|
1913
|
+
try {
|
|
1914
|
+
const u = new URL(repoUrl.trim());
|
|
1915
|
+
const host = u.hostname.toLowerCase();
|
|
1916
|
+
const parts = u.pathname.replace(/^\/+|\/+$/g, "").split("/").filter(Boolean);
|
|
1917
|
+
if (parts.length < 2) return null;
|
|
1918
|
+
const repo = parts[1].replace(/\.git$/i, "").toLowerCase();
|
|
1919
|
+
if (/^[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/i.test(repo)) return repo;
|
|
1920
|
+
if (host === "github.com" && repo.endsWith(".github.io")) return repo;
|
|
1921
|
+
if (host !== "github.com" && host !== "www.github.com") return host;
|
|
1922
|
+
return null;
|
|
1923
|
+
} catch {
|
|
1924
|
+
return null;
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1927
|
+
function resolveProjectDomain(source) {
|
|
1928
|
+
return resolveProjectFaviconDomains(source)[0] ?? null;
|
|
1929
|
+
}
|
|
1930
|
+
function isLikelyGenericFavicon(img) {
|
|
1931
|
+
if (typeof document === "undefined") return false;
|
|
1932
|
+
if (!img.complete || img.naturalWidth === 0 || img.naturalHeight === 0) return false;
|
|
1933
|
+
try {
|
|
1934
|
+
const canvas = document.createElement("canvas");
|
|
1935
|
+
const w = img.naturalWidth;
|
|
1936
|
+
const h = img.naturalHeight;
|
|
1937
|
+
canvas.width = w;
|
|
1938
|
+
canvas.height = h;
|
|
1939
|
+
const ctx = canvas.getContext("2d");
|
|
1940
|
+
if (!ctx) return false;
|
|
1941
|
+
ctx.drawImage(img, 0, 0);
|
|
1942
|
+
const { data } = ctx.getImageData(0, 0, w, h);
|
|
1943
|
+
let opaque = 0;
|
|
1944
|
+
let satSum = 0;
|
|
1945
|
+
let maxDiff = 0;
|
|
1946
|
+
for (let i = 0; i < data.length; i += 4) {
|
|
1947
|
+
const r = data[i];
|
|
1948
|
+
const g = data[i + 1];
|
|
1949
|
+
const b = data[i + 2];
|
|
1950
|
+
const a = data[i + 3];
|
|
1951
|
+
if (a < 128) continue;
|
|
1952
|
+
opaque++;
|
|
1953
|
+
const max = Math.max(r, g, b);
|
|
1954
|
+
const min = Math.min(r, g, b);
|
|
1955
|
+
const diff = max - min;
|
|
1956
|
+
maxDiff = Math.max(maxDiff, diff);
|
|
1957
|
+
satSum += diff;
|
|
1958
|
+
}
|
|
1959
|
+
if (opaque === 0) return true;
|
|
1960
|
+
const avgSat = satSum / opaque;
|
|
1961
|
+
return avgSat < 14 && maxDiff < 24;
|
|
1962
|
+
} catch {
|
|
1963
|
+
return isUntrustedFaviconUrl(img.src);
|
|
1964
|
+
}
|
|
1965
|
+
}
|
|
1966
|
+
function projectInitials(name) {
|
|
1967
|
+
const parts = name.trim().split(/[\s._-]+/).filter(Boolean);
|
|
1968
|
+
if (parts.length >= 2) {
|
|
1969
|
+
return `${parts[0][0] ?? ""}${parts[1][0] ?? ""}`.toUpperCase();
|
|
1970
|
+
}
|
|
1971
|
+
const compact = name.replace(/[^a-zA-Z0-9]/g, "");
|
|
1972
|
+
return (compact.slice(0, 2) || name.slice(0, 2) || "?").toUpperCase();
|
|
1973
|
+
}
|
|
1974
|
+
var INITIALS_CHIP_THEMES = [
|
|
1975
|
+
"info",
|
|
1976
|
+
"brand",
|
|
1977
|
+
"warn",
|
|
1978
|
+
"ok",
|
|
1979
|
+
"accent"
|
|
1980
|
+
];
|
|
1981
|
+
function projectInitialsThemeIndex(projectId) {
|
|
1982
|
+
let hash = 0;
|
|
1983
|
+
for (let i = 0; i < projectId.length; i++) {
|
|
1984
|
+
hash = (hash + projectId.charCodeAt(i)) % INITIALS_CHIP_THEMES.length;
|
|
1985
|
+
}
|
|
1986
|
+
return hash;
|
|
1987
|
+
}
|
|
1988
|
+
function readPageFaviconHref() {
|
|
1989
|
+
if (typeof document === "undefined") return null;
|
|
1990
|
+
const selectors = [
|
|
1991
|
+
'link[rel="icon"]',
|
|
1992
|
+
'link[rel="shortcut icon"]',
|
|
1993
|
+
'link[rel="apple-touch-icon"]'
|
|
1994
|
+
];
|
|
1995
|
+
for (const sel of selectors) {
|
|
1996
|
+
const link = document.querySelector(sel);
|
|
1997
|
+
if (link?.href) return link.href;
|
|
1998
|
+
}
|
|
1999
|
+
return null;
|
|
2000
|
+
}
|
|
2001
|
+
|
|
2002
|
+
export { DEFAULT_API_ENDPOINT, MAX_REPORT_PAYLOAD_BYTES, MAX_SCREENSHOT_DATA_URL_BYTES, MUSHI_COLORS_DARK, MUSHI_COLORS_LIGHT, MUSHI_COPY, MUSHI_GEOMETRY, MUSHI_IDENTITY_TOKEN_PREFIX, MUSHI_INTERNAL_HEADER, MUSHI_INTERNAL_INIT_MARKER, MUSHI_MOTION, MUSHI_PROJECT_HEADER, MUSHI_RADIUS, MUSHI_SDK_PACKAGE_HEADER, MUSHI_SDK_VERSION_HEADER, MUSHI_SPACING, MUSHI_TYPE, MUSHI_USER_TOKEN_HEADER, MUSHI_Z, REGION_ENDPOINTS, buildIdentityClaims, buildSdkIngestHeaders, captureEnvironment, checkReportPayloadSize, createApiClient, createBreadcrumbBuffer, createLogger, createOfflineQueue, createPiiScrubber, createPreFilter, createRateLimiter, estimateJsonBytes, faviconUrlCandidates, formatBytes, getDeviceFingerprintHash, getReporterToken, getSessionId, githubRepoDomainHint, isLikelyGenericFavicon, isUntrustedFaviconUrl, mushiPalette, mushiTokens, newUuid, noopLogger, normaliseThrown, originToDomain, parseIdentityToken, projectFaviconUrlCandidates, projectInitials, projectInitialsThemeIndex, readPageFaviconHref, resolveEnvConfig, resolveProjectDomain, resolveProjectFaviconDomains, resolveRegionEndpoint, scrubPii };
|
|
1806
2003
|
//# sourceMappingURL=index.js.map
|
|
1807
2004
|
//# sourceMappingURL=index.js.map
|