@mushi-mushi/web 1.7.7 → 1.8.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 +39 -1
- package/dist/index.cjs +248 -28
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +248 -28
- package/dist/index.js.map +1 -1
- package/package.json +15 -15
package/dist/index.js
CHANGED
|
@@ -1510,6 +1510,106 @@ function getWidgetStyles(theme) {
|
|
|
1510
1510
|
text-overflow: ellipsis;
|
|
1511
1511
|
}
|
|
1512
1512
|
|
|
1513
|
+
/* Rich layout \u2014 pill + message + flat text actions (admin BetaBanner parity) */
|
|
1514
|
+
.mushi-banner--rich {
|
|
1515
|
+
justify-content: space-between;
|
|
1516
|
+
gap: 12px;
|
|
1517
|
+
min-height: 36px;
|
|
1518
|
+
height: auto;
|
|
1519
|
+
padding: 4px 12px 4px 16px;
|
|
1520
|
+
white-space: normal;
|
|
1521
|
+
}
|
|
1522
|
+
.mushi-banner-body {
|
|
1523
|
+
display: flex;
|
|
1524
|
+
align-items: center;
|
|
1525
|
+
gap: 8px;
|
|
1526
|
+
flex: 1;
|
|
1527
|
+
min-width: 0;
|
|
1528
|
+
overflow: hidden;
|
|
1529
|
+
}
|
|
1530
|
+
.mushi-banner-pill {
|
|
1531
|
+
display: inline-flex;
|
|
1532
|
+
flex-shrink: 0;
|
|
1533
|
+
align-items: center;
|
|
1534
|
+
padding: 1px 6px;
|
|
1535
|
+
border-radius: 3px;
|
|
1536
|
+
border: 1px solid currentColor;
|
|
1537
|
+
font-size: 10px;
|
|
1538
|
+
font-weight: 700;
|
|
1539
|
+
letter-spacing: 0.18em;
|
|
1540
|
+
text-transform: uppercase;
|
|
1541
|
+
opacity: 0.92;
|
|
1542
|
+
}
|
|
1543
|
+
.mushi-banner.neon .mushi-banner-pill {
|
|
1544
|
+
border-color: rgba(10,26,10,0.45);
|
|
1545
|
+
background: rgba(10,26,10,0.12);
|
|
1546
|
+
}
|
|
1547
|
+
.mushi-banner.brand .mushi-banner-pill {
|
|
1548
|
+
border-color: rgba(255,255,255,0.45);
|
|
1549
|
+
background: rgba(255,255,255,0.14);
|
|
1550
|
+
}
|
|
1551
|
+
.mushi-banner.subtle .mushi-banner-pill {
|
|
1552
|
+
border-color: ${ruleStrong};
|
|
1553
|
+
background: ${isDark ? "rgba(242,235,221,0.08)" : "rgba(14,13,11,0.06)"};
|
|
1554
|
+
}
|
|
1555
|
+
.mushi-banner-message {
|
|
1556
|
+
min-width: 0;
|
|
1557
|
+
overflow: hidden;
|
|
1558
|
+
text-overflow: ellipsis;
|
|
1559
|
+
white-space: nowrap;
|
|
1560
|
+
font-size: 11.5px;
|
|
1561
|
+
font-weight: 500;
|
|
1562
|
+
line-height: 1.3;
|
|
1563
|
+
opacity: 0.9;
|
|
1564
|
+
}
|
|
1565
|
+
.mushi-banner-actions {
|
|
1566
|
+
display: inline-flex;
|
|
1567
|
+
align-items: center;
|
|
1568
|
+
gap: 0;
|
|
1569
|
+
/* Shrinkable + swipe-scrollable so a long action row can never push
|
|
1570
|
+
past the viewport edge (dismiss sits outside this nav). */
|
|
1571
|
+
flex-shrink: 1;
|
|
1572
|
+
min-width: 0;
|
|
1573
|
+
overflow-x: auto;
|
|
1574
|
+
scrollbar-width: none;
|
|
1575
|
+
font-size: 11px;
|
|
1576
|
+
}
|
|
1577
|
+
.mushi-banner-actions::-webkit-scrollbar { display: none; }
|
|
1578
|
+
@media (max-width: 480px) {
|
|
1579
|
+
/* Phones: keep only the primary bug CTA (+ dismiss outside the nav). */
|
|
1580
|
+
.mushi-banner-actions .mushi-banner-extra { display: none; }
|
|
1581
|
+
}
|
|
1582
|
+
.mushi-banner-link {
|
|
1583
|
+
display: inline-flex;
|
|
1584
|
+
align-items: center;
|
|
1585
|
+
padding: 2px 8px;
|
|
1586
|
+
border: none;
|
|
1587
|
+
background: transparent;
|
|
1588
|
+
color: inherit;
|
|
1589
|
+
cursor: pointer;
|
|
1590
|
+
font: inherit;
|
|
1591
|
+
letter-spacing: inherit;
|
|
1592
|
+
text-decoration: none;
|
|
1593
|
+
opacity: 0.88;
|
|
1594
|
+
transition: opacity 0.15s ease;
|
|
1595
|
+
flex-shrink: 0;
|
|
1596
|
+
}
|
|
1597
|
+
.mushi-banner-link:hover { opacity: 1; }
|
|
1598
|
+
.mushi-banner-link:focus-visible {
|
|
1599
|
+
outline: 2px solid ${widgetAccent};
|
|
1600
|
+
outline-offset: 2px;
|
|
1601
|
+
border-radius: 2px;
|
|
1602
|
+
}
|
|
1603
|
+
.mushi-banner-divider {
|
|
1604
|
+
opacity: 0.28;
|
|
1605
|
+
padding: 0 1px;
|
|
1606
|
+
user-select: none;
|
|
1607
|
+
flex-shrink: 0;
|
|
1608
|
+
}
|
|
1609
|
+
.mushi-banner--rich .mushi-banner-dismiss {
|
|
1610
|
+
margin-left: 4px;
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1513
1613
|
.mushi-banner-btn {
|
|
1514
1614
|
display: inline-flex;
|
|
1515
1615
|
align-items: center;
|
|
@@ -1752,7 +1852,12 @@ var MushiWidget = class _MushiWidget {
|
|
|
1752
1852
|
...config.responseSlaLabel !== void 0 ? { responseSlaLabel: config.responseSlaLabel } : {},
|
|
1753
1853
|
...config.featureRequestCard !== void 0 ? { featureRequestCard: config.featureRequestCard } : {},
|
|
1754
1854
|
...config.featureRequestLabel !== void 0 ? { featureRequestLabel: config.featureRequestLabel } : {},
|
|
1755
|
-
...config.featureRequestDescription !== void 0 ? { featureRequestDescription: config.featureRequestDescription } : {}
|
|
1855
|
+
...config.featureRequestDescription !== void 0 ? { featureRequestDescription: config.featureRequestDescription } : {},
|
|
1856
|
+
// Runtime/dashboard config delivers bannerMessage/bannerLabel via
|
|
1857
|
+
// mergeRuntimeConfig → bannerConfig. The widget is constructed before
|
|
1858
|
+
// that fetch resolves, so this pass-through is what makes server-driven
|
|
1859
|
+
// banner copy actually render.
|
|
1860
|
+
...config.bannerConfig !== void 0 ? { bannerConfig: config.bannerConfig } : {}
|
|
1756
1861
|
};
|
|
1757
1862
|
this.locale = getLocale(this.config.locale === "auto" ? void 0 : this.config.locale);
|
|
1758
1863
|
if (this.host.isConnected) this.syncHostChromeState();
|
|
@@ -2137,18 +2242,16 @@ var MushiWidget = class _MushiWidget {
|
|
|
2137
2242
|
const bc = this.config.bannerConfig ?? {};
|
|
2138
2243
|
const variant = bc.variant ?? "brand";
|
|
2139
2244
|
const position = bc.position ?? "top";
|
|
2245
|
+
const message = bc.message?.trim() ?? "";
|
|
2246
|
+
const richLayout = message.length > 0;
|
|
2140
2247
|
const bugLabel = bc.bugCta ?? "\u{1F41B} Report a bug";
|
|
2141
2248
|
const showFeat = bc.featureCta !== false;
|
|
2142
2249
|
const featLabel = bc.featureCtaLabel ?? "\u2728 Request feature";
|
|
2143
2250
|
const zIdx = bc.zIndex ?? (this.config.zIndex ?? 99999) - 1;
|
|
2144
2251
|
const banner = document.createElement("div");
|
|
2145
|
-
banner.className = `mushi-banner ${variant} ${position}`;
|
|
2252
|
+
banner.className = `mushi-banner ${variant} ${position}${richLayout ? " mushi-banner--rich" : ""}`;
|
|
2146
2253
|
banner.style.setProperty("--mushi-banner-z", String(zIdx));
|
|
2147
2254
|
banner.setAttribute("role", "banner");
|
|
2148
|
-
const bugBtn = document.createElement("button");
|
|
2149
|
-
bugBtn.className = "mushi-banner-btn";
|
|
2150
|
-
bugBtn.textContent = bugLabel;
|
|
2151
|
-
bugBtn.addEventListener("click", () => this.open());
|
|
2152
2255
|
const dismissBtn = document.createElement("button");
|
|
2153
2256
|
dismissBtn.className = "mushi-banner-dismiss";
|
|
2154
2257
|
dismissBtn.textContent = "\u2715";
|
|
@@ -2158,15 +2261,81 @@ var MushiWidget = class _MushiWidget {
|
|
|
2158
2261
|
this.removeBodyNudge();
|
|
2159
2262
|
this.render();
|
|
2160
2263
|
});
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2264
|
+
if (richLayout) {
|
|
2265
|
+
const body = document.createElement("div");
|
|
2266
|
+
body.className = "mushi-banner-body";
|
|
2267
|
+
const labelText = bc.label === false ? null : bc.label ?? "Beta";
|
|
2268
|
+
if (labelText) {
|
|
2269
|
+
const pill = document.createElement("span");
|
|
2270
|
+
pill.className = "mushi-banner-pill";
|
|
2271
|
+
pill.textContent = labelText;
|
|
2272
|
+
body.appendChild(pill);
|
|
2273
|
+
}
|
|
2274
|
+
const msg = document.createElement("span");
|
|
2275
|
+
msg.className = "mushi-banner-message";
|
|
2276
|
+
msg.textContent = message;
|
|
2277
|
+
body.appendChild(msg);
|
|
2278
|
+
banner.appendChild(body);
|
|
2279
|
+
const nav = document.createElement("nav");
|
|
2280
|
+
nav.className = "mushi-banner-actions";
|
|
2281
|
+
nav.setAttribute("aria-label", "Feedback banner actions");
|
|
2282
|
+
const appendDivider = (extra = false) => {
|
|
2283
|
+
const sep = document.createElement("span");
|
|
2284
|
+
sep.className = `mushi-banner-divider${extra ? " mushi-banner-extra" : ""}`;
|
|
2285
|
+
sep.setAttribute("aria-hidden", "true");
|
|
2286
|
+
sep.textContent = "|";
|
|
2287
|
+
nav.appendChild(sep);
|
|
2288
|
+
};
|
|
2289
|
+
const appendAction = (label, onClick, extra = false) => {
|
|
2290
|
+
const btn = document.createElement("button");
|
|
2291
|
+
btn.type = "button";
|
|
2292
|
+
btn.className = `mushi-banner-link${extra ? " mushi-banner-extra" : ""}`;
|
|
2293
|
+
btn.textContent = label;
|
|
2294
|
+
btn.addEventListener("click", onClick);
|
|
2295
|
+
nav.appendChild(btn);
|
|
2296
|
+
};
|
|
2297
|
+
appendAction(bugLabel, () => this.open());
|
|
2298
|
+
if (showFeat) {
|
|
2299
|
+
appendDivider(true);
|
|
2300
|
+
appendAction(featLabel, () => this.open({ featureRequest: true }), true);
|
|
2301
|
+
}
|
|
2302
|
+
for (const link of bc.links ?? []) {
|
|
2303
|
+
const linkLabel = link.label?.trim();
|
|
2304
|
+
if (!linkLabel) continue;
|
|
2305
|
+
const href = link.href && (/^https?:\/\//i.test(link.href) || link.href.startsWith("/")) ? link.href : void 0;
|
|
2306
|
+
appendDivider(true);
|
|
2307
|
+
if (href) {
|
|
2308
|
+
const anchor = document.createElement("a");
|
|
2309
|
+
anchor.className = "mushi-banner-link mushi-banner-extra";
|
|
2310
|
+
anchor.href = href;
|
|
2311
|
+
anchor.textContent = linkLabel;
|
|
2312
|
+
anchor.target = "_blank";
|
|
2313
|
+
anchor.rel = "noopener noreferrer";
|
|
2314
|
+
nav.appendChild(anchor);
|
|
2315
|
+
} else {
|
|
2316
|
+
appendAction(linkLabel, () => {
|
|
2317
|
+
if (link.featureRequest) this.open({ featureRequest: true });
|
|
2318
|
+
else this.open();
|
|
2319
|
+
}, true);
|
|
2320
|
+
}
|
|
2321
|
+
}
|
|
2322
|
+
banner.appendChild(nav);
|
|
2323
|
+
banner.appendChild(dismissBtn);
|
|
2324
|
+
} else {
|
|
2325
|
+
const bugBtn = document.createElement("button");
|
|
2326
|
+
bugBtn.className = "mushi-banner-btn";
|
|
2327
|
+
bugBtn.textContent = bugLabel;
|
|
2328
|
+
bugBtn.addEventListener("click", () => this.open());
|
|
2329
|
+
banner.appendChild(bugBtn);
|
|
2330
|
+
if (showFeat) {
|
|
2331
|
+
const featBtn = document.createElement("button");
|
|
2332
|
+
featBtn.className = "mushi-banner-btn";
|
|
2333
|
+
featBtn.textContent = featLabel;
|
|
2334
|
+
featBtn.addEventListener("click", () => this.open({ featureRequest: true }));
|
|
2335
|
+
banner.appendChild(featBtn);
|
|
2336
|
+
}
|
|
2337
|
+
banner.appendChild(dismissBtn);
|
|
2338
|
+
}
|
|
2170
2339
|
this.shadow.appendChild(banner);
|
|
2171
2340
|
this.applyBodyNudge(position);
|
|
2172
2341
|
}
|
|
@@ -3658,18 +3827,18 @@ function createScreenshotCapture(options = {}) {
|
|
|
3658
3827
|
const url = URL.createObjectURL(blob);
|
|
3659
3828
|
return new Promise((resolve) => {
|
|
3660
3829
|
img.onload = () => {
|
|
3661
|
-
ctx.drawImage(img, 0, 0, width, height);
|
|
3662
|
-
URL.revokeObjectURL(url);
|
|
3663
3830
|
try {
|
|
3664
|
-
|
|
3665
|
-
|
|
3831
|
+
ctx.drawImage(img, 0, 0, width, height);
|
|
3832
|
+
URL.revokeObjectURL(url);
|
|
3833
|
+
resolve(canvas.toDataURL("image/jpeg", 0.7));
|
|
3666
3834
|
} catch {
|
|
3667
|
-
|
|
3835
|
+
URL.revokeObjectURL(url);
|
|
3836
|
+
resolve(buildDegradedScreenshot(width, height));
|
|
3668
3837
|
}
|
|
3669
3838
|
};
|
|
3670
3839
|
img.onerror = () => {
|
|
3671
3840
|
URL.revokeObjectURL(url);
|
|
3672
|
-
resolve(
|
|
3841
|
+
resolve(buildDegradedScreenshot(width, height));
|
|
3673
3842
|
};
|
|
3674
3843
|
img.src = url;
|
|
3675
3844
|
});
|
|
@@ -3691,6 +3860,7 @@ var DEFAULT_REDACT_SELECTORS = [
|
|
|
3691
3860
|
function buildPrivacySafeDocument(privacy) {
|
|
3692
3861
|
const clone = document.documentElement.cloneNode(true);
|
|
3693
3862
|
clone.querySelector("#mushi-mushi-widget")?.remove();
|
|
3863
|
+
stripTaintSources(clone);
|
|
3694
3864
|
const redactSelectors = privacy?.redactSelectors !== void 0 ? privacy.redactSelectors : DEFAULT_REDACT_SELECTORS;
|
|
3695
3865
|
for (const selector of redactSelectors) {
|
|
3696
3866
|
for (const el of safeQueryAll(clone, selector)) {
|
|
@@ -3729,6 +3899,46 @@ function redactElement(el) {
|
|
|
3729
3899
|
el.setAttribute("data-mushi-redacted", "true");
|
|
3730
3900
|
while (el.firstChild) el.removeChild(el.firstChild);
|
|
3731
3901
|
}
|
|
3902
|
+
function stripTaintSources(root) {
|
|
3903
|
+
const pageOrigin = typeof location !== "undefined" ? location.origin : "";
|
|
3904
|
+
for (const el of root.querySelectorAll("img, video, iframe, object, embed, picture")) {
|
|
3905
|
+
el.remove();
|
|
3906
|
+
}
|
|
3907
|
+
for (const link of root.querySelectorAll('link[rel="stylesheet"], link[as="style"]')) {
|
|
3908
|
+
const href = link.getAttribute("href");
|
|
3909
|
+
if (!href || isCrossOriginUrl(href, pageOrigin)) link.remove();
|
|
3910
|
+
}
|
|
3911
|
+
for (const script of root.querySelectorAll("script")) {
|
|
3912
|
+
script.remove();
|
|
3913
|
+
}
|
|
3914
|
+
}
|
|
3915
|
+
function isCrossOriginUrl(raw, pageOrigin) {
|
|
3916
|
+
if (!raw || raw.startsWith("data:") || raw.startsWith("blob:")) return false;
|
|
3917
|
+
try {
|
|
3918
|
+
const resolved = new URL(raw, typeof location !== "undefined" ? location.href : "http://localhost/");
|
|
3919
|
+
return Boolean(pageOrigin) && resolved.origin !== pageOrigin;
|
|
3920
|
+
} catch {
|
|
3921
|
+
return true;
|
|
3922
|
+
}
|
|
3923
|
+
}
|
|
3924
|
+
function buildDegradedScreenshot(width, height) {
|
|
3925
|
+
const canvas = document.createElement("canvas");
|
|
3926
|
+
const ctx = canvas.getContext("2d");
|
|
3927
|
+
if (!ctx) return null;
|
|
3928
|
+
canvas.width = width;
|
|
3929
|
+
canvas.height = height;
|
|
3930
|
+
ctx.fillStyle = "#111827";
|
|
3931
|
+
ctx.fillRect(0, 0, width, height);
|
|
3932
|
+
ctx.fillStyle = "#e5e7eb";
|
|
3933
|
+
ctx.font = "13px system-ui, sans-serif";
|
|
3934
|
+
ctx.fillText("Screenshot: layout captured (external media stripped)", 16, 28);
|
|
3935
|
+
ctx.fillText(`${width}\xD7${height}px`, 16, 48);
|
|
3936
|
+
try {
|
|
3937
|
+
return canvas.toDataURL("image/jpeg", 0.7);
|
|
3938
|
+
} catch {
|
|
3939
|
+
return null;
|
|
3940
|
+
}
|
|
3941
|
+
}
|
|
3732
3942
|
function maskElement(el) {
|
|
3733
3943
|
if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {
|
|
3734
3944
|
el.value = "";
|
|
@@ -4780,7 +4990,7 @@ function createProactiveManager(config = {}) {
|
|
|
4780
4990
|
|
|
4781
4991
|
// src/version.ts
|
|
4782
4992
|
var MUSHI_SDK_PACKAGE = "@mushi-mushi/web";
|
|
4783
|
-
var MUSHI_SDK_VERSION = "1.
|
|
4993
|
+
var MUSHI_SDK_VERSION = "1.8.0" ;
|
|
4784
4994
|
|
|
4785
4995
|
// src/mushi.ts
|
|
4786
4996
|
var instance = null;
|
|
@@ -4961,8 +5171,10 @@ function createInstance(config) {
|
|
|
4961
5171
|
});
|
|
4962
5172
|
let detachAutoBreadcrumbs = null;
|
|
4963
5173
|
detachAutoBreadcrumbs = installAutoBreadcrumbs(breadcrumbs);
|
|
5174
|
+
let screenshotCaptureInFlight = false;
|
|
4964
5175
|
async function takeScreenshotWithoutChrome() {
|
|
4965
|
-
if (!screenshotCap) return null;
|
|
5176
|
+
if (!screenshotCap || screenshotCaptureInFlight) return null;
|
|
5177
|
+
screenshotCaptureInFlight = true;
|
|
4966
5178
|
const panelWasVisible = widget.getIsOpen();
|
|
4967
5179
|
if (panelWasVisible) widget.hidePanel();
|
|
4968
5180
|
const host = document.getElementById("mushi-mushi-widget");
|
|
@@ -4974,6 +5186,7 @@ function createInstance(config) {
|
|
|
4974
5186
|
try {
|
|
4975
5187
|
return await screenshotCap.take();
|
|
4976
5188
|
} finally {
|
|
5189
|
+
screenshotCaptureInFlight = false;
|
|
4977
5190
|
if (host) host.style.visibility = prevVisibility;
|
|
4978
5191
|
if (panelWasVisible) widget.showPanel();
|
|
4979
5192
|
}
|
|
@@ -5647,14 +5860,21 @@ function mergeRuntimeConfig(config, runtime) {
|
|
|
5647
5860
|
const nativeTrigger = runtime.native?.triggerMode;
|
|
5648
5861
|
const runtimeLauncher = runtime.widget?.launcher;
|
|
5649
5862
|
const widgetTrigger = runtimeLauncher ?? runtime.widget?.trigger ?? (nativeTrigger === "none" || nativeTrigger === "shake" ? "manual" : void 0);
|
|
5650
|
-
const
|
|
5651
|
-
const
|
|
5652
|
-
const
|
|
5653
|
-
const
|
|
5654
|
-
const
|
|
5863
|
+
const runtimeWidget = runtime.widget;
|
|
5864
|
+
const runtimeBannerVariant = runtimeWidget?.bannerVariant;
|
|
5865
|
+
const runtimeBannerPosition = runtimeWidget?.bannerPosition;
|
|
5866
|
+
const runtimeBannerMessage = runtimeWidget?.bannerMessage;
|
|
5867
|
+
const runtimeBannerLabel = runtimeWidget?.bannerLabel;
|
|
5868
|
+
const runtimeBannerBugCta = runtimeWidget?.bannerBugCta;
|
|
5869
|
+
const runtimeBannerFeatureCta = runtimeWidget?.bannerFeatureCta;
|
|
5870
|
+
const derivedBannerConfig = runtimeBannerVariant || runtimeBannerPosition || runtimeBannerMessage != null || runtimeBannerLabel != null || runtimeBannerBugCta != null || runtimeBannerFeatureCta != null ? {
|
|
5655
5871
|
...config.widget?.bannerConfig ?? {},
|
|
5656
5872
|
...runtimeBannerVariant ? { variant: runtimeBannerVariant } : {},
|
|
5657
5873
|
...runtimeBannerPosition ? { position: runtimeBannerPosition } : {},
|
|
5874
|
+
...runtimeBannerMessage != null ? { message: runtimeBannerMessage } : {},
|
|
5875
|
+
// Dashboard sends an empty string to hide the pill (the runtime
|
|
5876
|
+
// payload has no way to express the local-config `label: false`).
|
|
5877
|
+
...runtimeBannerLabel != null ? { label: runtimeBannerLabel === "" ? false : runtimeBannerLabel } : {},
|
|
5658
5878
|
...runtimeBannerBugCta != null ? { bugCta: runtimeBannerBugCta ?? void 0 } : {},
|
|
5659
5879
|
...runtimeBannerFeatureCta != null ? { featureCta: runtimeBannerFeatureCta } : {}
|
|
5660
5880
|
} : void 0;
|