@mushi-mushi/web 0.1.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/LICENSE +21 -0
- package/README.md +71 -0
- package/dist/index.cjs +1576 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +142 -0
- package/dist/index.d.ts +142 -0
- package/dist/index.js +1564 -0
- package/dist/index.js.map +1 -0
- package/package.json +78 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1564 @@
|
|
|
1
|
+
import { createLogger, noopLogger, createApiClient, createPreFilter, createOfflineQueue, createRateLimiter, createPiiScrubber, getReporterToken, getSessionId, captureEnvironment } from '@mushi-mushi/core';
|
|
2
|
+
|
|
3
|
+
// src/mushi.ts
|
|
4
|
+
|
|
5
|
+
// src/i18n/en.ts
|
|
6
|
+
var en = {
|
|
7
|
+
widget: {
|
|
8
|
+
trigger: "Report Issue",
|
|
9
|
+
title: "Report an Issue",
|
|
10
|
+
close: "Close",
|
|
11
|
+
back: "Back",
|
|
12
|
+
submit: "Submit",
|
|
13
|
+
submitting: "Submitting\u2026",
|
|
14
|
+
submitted: "Thank you! Your report has been submitted.",
|
|
15
|
+
error: "Something went wrong. Please try again."
|
|
16
|
+
},
|
|
17
|
+
step1: {
|
|
18
|
+
heading: "What kind of issue?",
|
|
19
|
+
categories: {
|
|
20
|
+
bug: "Bug",
|
|
21
|
+
slow: "Slow / Laggy",
|
|
22
|
+
visual: "Visual Glitch",
|
|
23
|
+
confusing: "Confusing",
|
|
24
|
+
other: "Other"
|
|
25
|
+
},
|
|
26
|
+
categoryDescriptions: {
|
|
27
|
+
bug: "Something is broken or not working",
|
|
28
|
+
slow: "Performance issue or slow loading",
|
|
29
|
+
visual: "Layout, styling, or display problem",
|
|
30
|
+
confusing: "Hard to understand or navigate",
|
|
31
|
+
other: "Something else"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
step2: {
|
|
35
|
+
heading: "What happened?",
|
|
36
|
+
intents: {
|
|
37
|
+
bug: ["Crash", "Unresponsive", "Data loss", "Wrong result", "Other"],
|
|
38
|
+
slow: ["Page load", "Interaction", "API call", "Animation", "Other"],
|
|
39
|
+
visual: ["Layout broken", "Overlapping", "Missing element", "Wrong color/font", "Other"],
|
|
40
|
+
confusing: ["Unclear label", "Missing help", "Unexpected flow", "Lost navigation", "Other"],
|
|
41
|
+
other: ["Feature request", "Accessibility", "Typo", "Other"]
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
step3: {
|
|
45
|
+
heading: "Tell us more",
|
|
46
|
+
descriptionPlaceholder: "Describe what happened\u2026",
|
|
47
|
+
screenshotButton: "Attach Screenshot",
|
|
48
|
+
screenshotAttached: "Screenshot attached",
|
|
49
|
+
elementButton: "Select Element",
|
|
50
|
+
elementSelected: "Element selected",
|
|
51
|
+
optional: "(optional)"
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// src/i18n/ja.ts
|
|
56
|
+
var ja = {
|
|
57
|
+
widget: {
|
|
58
|
+
trigger: "\u554F\u984C\u3092\u5831\u544A",
|
|
59
|
+
title: "\u554F\u984C\u3092\u5831\u544A\u3059\u308B",
|
|
60
|
+
close: "\u9589\u3058\u308B",
|
|
61
|
+
back: "\u623B\u308B",
|
|
62
|
+
submit: "\u9001\u4FE1",
|
|
63
|
+
submitting: "\u9001\u4FE1\u4E2D\u2026",
|
|
64
|
+
submitted: "\u3042\u308A\u304C\u3068\u3046\u3054\u3056\u3044\u307E\u3059\uFF01\u30EC\u30DD\u30FC\u30C8\u304C\u9001\u4FE1\u3055\u308C\u307E\u3057\u305F\u3002",
|
|
65
|
+
error: "\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F\u3002\u3082\u3046\u4E00\u5EA6\u304A\u8A66\u3057\u304F\u3060\u3055\u3044\u3002"
|
|
66
|
+
},
|
|
67
|
+
step1: {
|
|
68
|
+
heading: "\u3069\u306E\u3088\u3046\u306A\u554F\u984C\u3067\u3059\u304B\uFF1F",
|
|
69
|
+
categories: {
|
|
70
|
+
bug: "\u30D0\u30B0",
|
|
71
|
+
slow: "\u9045\u3044\u30FB\u91CD\u3044",
|
|
72
|
+
visual: "\u8868\u793A\u306E\u554F\u984C",
|
|
73
|
+
confusing: "\u308F\u304B\u308A\u306B\u304F\u3044",
|
|
74
|
+
other: "\u305D\u306E\u4ED6"
|
|
75
|
+
},
|
|
76
|
+
categoryDescriptions: {
|
|
77
|
+
bug: "\u52D5\u4F5C\u3057\u306A\u3044\u3001\u58CA\u308C\u3066\u3044\u308B",
|
|
78
|
+
slow: "\u30D1\u30D5\u30A9\u30FC\u30DE\u30F3\u30B9\u304C\u60AA\u3044\u3001\u8AAD\u307F\u8FBC\u307F\u304C\u9045\u3044",
|
|
79
|
+
visual: "\u30EC\u30A4\u30A2\u30A6\u30C8\u3001\u30C7\u30B6\u30A4\u30F3\u3001\u8868\u793A\u306E\u554F\u984C",
|
|
80
|
+
confusing: "\u7406\u89E3\u3057\u306B\u304F\u3044\u3001\u64CD\u4F5C\u304C\u308F\u304B\u308A\u306B\u304F\u3044",
|
|
81
|
+
other: "\u305D\u306E\u4ED6\u306E\u554F\u984C"
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
step2: {
|
|
85
|
+
heading: "\u4F55\u304C\u8D77\u304D\u307E\u3057\u305F\u304B\uFF1F",
|
|
86
|
+
intents: {
|
|
87
|
+
bug: ["\u30AF\u30E9\u30C3\u30B7\u30E5", "\u7121\u53CD\u5FDC", "\u30C7\u30FC\u30BF\u6D88\u5931", "\u8AA4\u3063\u305F\u7D50\u679C", "\u305D\u306E\u4ED6"],
|
|
88
|
+
slow: ["\u30DA\u30FC\u30B8\u8AAD\u8FBC", "\u64CD\u4F5C\u53CD\u5FDC", "API\u901A\u4FE1", "\u30A2\u30CB\u30E1\u30FC\u30B7\u30E7\u30F3", "\u305D\u306E\u4ED6"],
|
|
89
|
+
visual: ["\u30EC\u30A4\u30A2\u30A6\u30C8\u5D29\u308C", "\u8981\u7D20\u306E\u91CD\u306A\u308A", "\u8981\u7D20\u304C\u8868\u793A\u3055\u308C\u306A\u3044", "\u8272/\u30D5\u30A9\u30F3\u30C8\u304C\u9055\u3046", "\u305D\u306E\u4ED6"],
|
|
90
|
+
confusing: ["\u30E9\u30D9\u30EB\u304C\u4E0D\u660E\u77AD", "\u30D8\u30EB\u30D7\u304C\u306A\u3044", "\u4E88\u60F3\u5916\u306E\u30D5\u30ED\u30FC", "\u30CA\u30D3\u30B2\u30FC\u30B7\u30E7\u30F3\u8FF7\u5B50", "\u305D\u306E\u4ED6"],
|
|
91
|
+
other: ["\u6A5F\u80FD\u8981\u671B", "\u30A2\u30AF\u30BB\u30B7\u30D3\u30EA\u30C6\u30A3", "\u8AA4\u5B57\u8131\u5B57", "\u305D\u306E\u4ED6"]
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
step3: {
|
|
95
|
+
heading: "\u8A73\u7D30\u3092\u6559\u3048\u3066\u304F\u3060\u3055\u3044",
|
|
96
|
+
descriptionPlaceholder: "\u4F55\u304C\u8D77\u304D\u305F\u304B\u8AAC\u660E\u3057\u3066\u304F\u3060\u3055\u3044\u2026",
|
|
97
|
+
screenshotButton: "\u30B9\u30AF\u30EA\u30FC\u30F3\u30B7\u30E7\u30C3\u30C8\u6DFB\u4ED8",
|
|
98
|
+
screenshotAttached: "\u30B9\u30AF\u30EA\u30FC\u30F3\u30B7\u30E7\u30C3\u30C8\u6DFB\u4ED8\u6E08\u307F",
|
|
99
|
+
elementButton: "\u8981\u7D20\u3092\u9078\u629E",
|
|
100
|
+
elementSelected: "\u8981\u7D20\u9078\u629E\u6E08\u307F",
|
|
101
|
+
optional: "\uFF08\u4EFB\u610F\uFF09"
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// src/i18n/th.ts
|
|
106
|
+
var th = {
|
|
107
|
+
widget: {
|
|
108
|
+
trigger: "\u0E23\u0E32\u0E22\u0E07\u0E32\u0E19\u0E1B\u0E31\u0E0D\u0E2B\u0E32",
|
|
109
|
+
title: "\u0E23\u0E32\u0E22\u0E07\u0E32\u0E19\u0E1B\u0E31\u0E0D\u0E2B\u0E32",
|
|
110
|
+
close: "\u0E1B\u0E34\u0E14",
|
|
111
|
+
back: "\u0E01\u0E25\u0E31\u0E1A",
|
|
112
|
+
submit: "\u0E2A\u0E48\u0E07",
|
|
113
|
+
submitting: "\u0E01\u0E33\u0E25\u0E31\u0E07\u0E2A\u0E48\u0E07\u2026",
|
|
114
|
+
submitted: "\u0E02\u0E2D\u0E1A\u0E04\u0E38\u0E13\u0E04\u0E23\u0E31\u0E1A/\u0E04\u0E48\u0E30! \u0E23\u0E32\u0E22\u0E07\u0E32\u0E19\u0E02\u0E2D\u0E07\u0E04\u0E38\u0E13\u0E16\u0E39\u0E01\u0E2A\u0E48\u0E07\u0E41\u0E25\u0E49\u0E27",
|
|
115
|
+
error: "\u0E40\u0E01\u0E34\u0E14\u0E02\u0E49\u0E2D\u0E1C\u0E34\u0E14\u0E1E\u0E25\u0E32\u0E14 \u0E01\u0E23\u0E38\u0E13\u0E32\u0E25\u0E2D\u0E07\u0E43\u0E2B\u0E21\u0E48\u0E2D\u0E35\u0E01\u0E04\u0E23\u0E31\u0E49\u0E07"
|
|
116
|
+
},
|
|
117
|
+
step1: {
|
|
118
|
+
heading: "\u0E1B\u0E31\u0E0D\u0E2B\u0E32\u0E1B\u0E23\u0E30\u0E40\u0E20\u0E17\u0E44\u0E2B\u0E19?",
|
|
119
|
+
categories: {
|
|
120
|
+
bug: "\u0E1A\u0E31\u0E01",
|
|
121
|
+
slow: "\u0E0A\u0E49\u0E32 / \u0E41\u0E25\u0E04",
|
|
122
|
+
visual: "\u0E41\u0E2A\u0E14\u0E07\u0E1C\u0E25\u0E1C\u0E34\u0E14\u0E1B\u0E01\u0E15\u0E34",
|
|
123
|
+
confusing: "\u0E2A\u0E31\u0E1A\u0E2A\u0E19",
|
|
124
|
+
other: "\u0E2D\u0E37\u0E48\u0E19\u0E46"
|
|
125
|
+
},
|
|
126
|
+
categoryDescriptions: {
|
|
127
|
+
bug: "\u0E21\u0E35\u0E1A\u0E32\u0E07\u0E2D\u0E22\u0E48\u0E32\u0E07\u0E40\u0E2A\u0E35\u0E22\u0E2B\u0E23\u0E37\u0E2D\u0E17\u0E33\u0E07\u0E32\u0E19\u0E44\u0E21\u0E48\u0E16\u0E39\u0E01\u0E15\u0E49\u0E2D\u0E07",
|
|
128
|
+
slow: "\u0E1B\u0E31\u0E0D\u0E2B\u0E32\u0E1B\u0E23\u0E30\u0E2A\u0E34\u0E17\u0E18\u0E34\u0E20\u0E32\u0E1E\u0E2B\u0E23\u0E37\u0E2D\u0E42\u0E2B\u0E25\u0E14\u0E0A\u0E49\u0E32",
|
|
129
|
+
visual: "\u0E1B\u0E31\u0E0D\u0E2B\u0E32\u0E40\u0E25\u0E22\u0E4C\u0E40\u0E2D\u0E32\u0E15\u0E4C \u0E2A\u0E44\u0E15\u0E25\u0E4C \u0E2B\u0E23\u0E37\u0E2D\u0E01\u0E32\u0E23\u0E41\u0E2A\u0E14\u0E07\u0E1C\u0E25",
|
|
130
|
+
confusing: "\u0E40\u0E02\u0E49\u0E32\u0E43\u0E08\u0E22\u0E32\u0E01\u0E2B\u0E23\u0E37\u0E2D\u0E19\u0E33\u0E17\u0E32\u0E07\u0E22\u0E32\u0E01",
|
|
131
|
+
other: "\u0E2D\u0E22\u0E48\u0E32\u0E07\u0E2D\u0E37\u0E48\u0E19"
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
step2: {
|
|
135
|
+
heading: "\u0E40\u0E01\u0E34\u0E14\u0E2D\u0E30\u0E44\u0E23\u0E02\u0E36\u0E49\u0E19?",
|
|
136
|
+
intents: {
|
|
137
|
+
bug: ["\u0E41\u0E04\u0E23\u0E0A", "\u0E44\u0E21\u0E48\u0E15\u0E2D\u0E1A\u0E2A\u0E19\u0E2D\u0E07", "\u0E02\u0E49\u0E2D\u0E21\u0E39\u0E25\u0E2B\u0E32\u0E22", "\u0E1C\u0E25\u0E25\u0E31\u0E1E\u0E18\u0E4C\u0E1C\u0E34\u0E14", "\u0E2D\u0E37\u0E48\u0E19\u0E46"],
|
|
138
|
+
slow: ["\u0E42\u0E2B\u0E25\u0E14\u0E2B\u0E19\u0E49\u0E32", "\u0E01\u0E32\u0E23\u0E42\u0E15\u0E49\u0E15\u0E2D\u0E1A", "API", "\u0E41\u0E2D\u0E19\u0E34\u0E40\u0E21\u0E0A\u0E31\u0E19", "\u0E2D\u0E37\u0E48\u0E19\u0E46"],
|
|
139
|
+
visual: ["\u0E40\u0E25\u0E22\u0E4C\u0E40\u0E2D\u0E32\u0E15\u0E4C\u0E40\u0E2A\u0E35\u0E22", "\u0E0B\u0E49\u0E2D\u0E19\u0E17\u0E31\u0E1A", "\u0E2D\u0E07\u0E04\u0E4C\u0E1B\u0E23\u0E30\u0E01\u0E2D\u0E1A\u0E2B\u0E32\u0E22", "\u0E2A\u0E35/\u0E1F\u0E2D\u0E19\u0E15\u0E4C\u0E1C\u0E34\u0E14", "\u0E2D\u0E37\u0E48\u0E19\u0E46"],
|
|
140
|
+
confusing: ["\u0E1B\u0E49\u0E32\u0E22\u0E44\u0E21\u0E48\u0E0A\u0E31\u0E14", "\u0E44\u0E21\u0E48\u0E21\u0E35\u0E04\u0E33\u0E41\u0E19\u0E30\u0E19\u0E33", "\u0E02\u0E31\u0E49\u0E19\u0E15\u0E2D\u0E19\u0E44\u0E21\u0E48\u0E04\u0E32\u0E14\u0E04\u0E34\u0E14", "\u0E2B\u0E25\u0E07\u0E17\u0E32\u0E07", "\u0E2D\u0E37\u0E48\u0E19\u0E46"],
|
|
141
|
+
other: ["\u0E02\u0E2D\u0E1F\u0E35\u0E40\u0E08\u0E2D\u0E23\u0E4C", "\u0E01\u0E32\u0E23\u0E40\u0E02\u0E49\u0E32\u0E16\u0E36\u0E07", "\u0E1E\u0E34\u0E21\u0E1E\u0E4C\u0E1C\u0E34\u0E14", "\u0E2D\u0E37\u0E48\u0E19\u0E46"]
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
step3: {
|
|
145
|
+
heading: "\u0E1A\u0E2D\u0E01\u0E40\u0E23\u0E32\u0E40\u0E1E\u0E34\u0E48\u0E21\u0E40\u0E15\u0E34\u0E21",
|
|
146
|
+
descriptionPlaceholder: "\u0E2D\u0E18\u0E34\u0E1A\u0E32\u0E22\u0E2A\u0E34\u0E48\u0E07\u0E17\u0E35\u0E48\u0E40\u0E01\u0E34\u0E14\u0E02\u0E36\u0E49\u0E19\u2026",
|
|
147
|
+
screenshotButton: "\u0E41\u0E19\u0E1A\u0E2A\u0E01\u0E23\u0E35\u0E19\u0E0A\u0E47\u0E2D\u0E15",
|
|
148
|
+
screenshotAttached: "\u0E41\u0E19\u0E1A\u0E2A\u0E01\u0E23\u0E35\u0E19\u0E0A\u0E47\u0E2D\u0E15\u0E41\u0E25\u0E49\u0E27",
|
|
149
|
+
elementButton: "\u0E40\u0E25\u0E37\u0E2D\u0E01\u0E2D\u0E07\u0E04\u0E4C\u0E1B\u0E23\u0E30\u0E01\u0E2D\u0E1A",
|
|
150
|
+
elementSelected: "\u0E40\u0E25\u0E37\u0E2D\u0E01\u0E2D\u0E07\u0E04\u0E4C\u0E1B\u0E23\u0E30\u0E01\u0E2D\u0E1A\u0E41\u0E25\u0E49\u0E27",
|
|
151
|
+
optional: "(\u0E44\u0E21\u0E48\u0E1A\u0E31\u0E07\u0E04\u0E31\u0E1A)"
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
// src/i18n/es.ts
|
|
156
|
+
var es = {
|
|
157
|
+
widget: {
|
|
158
|
+
trigger: "Reportar problema",
|
|
159
|
+
title: "Reportar un problema",
|
|
160
|
+
close: "Cerrar",
|
|
161
|
+
back: "Volver",
|
|
162
|
+
submit: "Enviar",
|
|
163
|
+
submitting: "Enviando\u2026",
|
|
164
|
+
submitted: "\xA1Gracias! Tu reporte ha sido enviado.",
|
|
165
|
+
error: "Algo sali\xF3 mal. Por favor, int\xE9ntalo de nuevo."
|
|
166
|
+
},
|
|
167
|
+
step1: {
|
|
168
|
+
heading: "\xBFQu\xE9 tipo de problema?",
|
|
169
|
+
categories: {
|
|
170
|
+
bug: "Error",
|
|
171
|
+
slow: "Lento",
|
|
172
|
+
visual: "Problema visual",
|
|
173
|
+
confusing: "Confuso",
|
|
174
|
+
other: "Otro"
|
|
175
|
+
},
|
|
176
|
+
categoryDescriptions: {
|
|
177
|
+
bug: "Algo est\xE1 roto o no funciona",
|
|
178
|
+
slow: "Problema de rendimiento o carga lenta",
|
|
179
|
+
visual: "Problema de dise\xF1o, estilo o visualizaci\xF3n",
|
|
180
|
+
confusing: "Dif\xEDcil de entender o navegar",
|
|
181
|
+
other: "Otro problema"
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
step2: {
|
|
185
|
+
heading: "\xBFQu\xE9 pas\xF3?",
|
|
186
|
+
intents: {
|
|
187
|
+
bug: ["Crash", "Sin respuesta", "P\xE9rdida de datos", "Resultado incorrecto", "Otro"],
|
|
188
|
+
slow: ["Carga de p\xE1gina", "Interacci\xF3n", "Llamada API", "Animaci\xF3n", "Otro"],
|
|
189
|
+
visual: ["Layout roto", "Superposici\xF3n", "Elemento faltante", "Color/fuente incorrecta", "Otro"],
|
|
190
|
+
confusing: ["Etiqueta confusa", "Sin ayuda", "Flujo inesperado", "Navegaci\xF3n perdida", "Otro"],
|
|
191
|
+
other: ["Solicitud de funci\xF3n", "Accesibilidad", "Error tipogr\xE1fico", "Otro"]
|
|
192
|
+
}
|
|
193
|
+
},
|
|
194
|
+
step3: {
|
|
195
|
+
heading: "Cu\xE9ntanos m\xE1s",
|
|
196
|
+
descriptionPlaceholder: "Describe lo que pas\xF3\u2026",
|
|
197
|
+
screenshotButton: "Adjuntar captura",
|
|
198
|
+
screenshotAttached: "Captura adjunta",
|
|
199
|
+
elementButton: "Seleccionar elemento",
|
|
200
|
+
elementSelected: "Elemento seleccionado",
|
|
201
|
+
optional: "(opcional)"
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
// src/i18n/index.ts
|
|
206
|
+
var locales = { en, ja, th, es };
|
|
207
|
+
function getLocale(code) {
|
|
208
|
+
if (!code) return en;
|
|
209
|
+
const base = code.split("-")[0].toLowerCase();
|
|
210
|
+
return locales[base] ?? en;
|
|
211
|
+
}
|
|
212
|
+
function getAvailableLocales() {
|
|
213
|
+
return Object.keys(locales);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// src/styles.ts
|
|
217
|
+
function getWidgetStyles(theme) {
|
|
218
|
+
const isDark = theme === "dark";
|
|
219
|
+
const accent = "#7c3aed";
|
|
220
|
+
const accentHover = "#6d28d9";
|
|
221
|
+
const accentBgLight = isDark ? "#2e2440" : "#f5f3ff";
|
|
222
|
+
const accentBgSelected = isDark ? "#2e2440" : "#ede9fe";
|
|
223
|
+
const border = isDark ? "#3f3f46" : "#e4e4e7";
|
|
224
|
+
const bgSurface = isDark ? "#18181b" : "#ffffff";
|
|
225
|
+
const bgMuted = isDark ? "#27272a" : "#fafafa";
|
|
226
|
+
const textMuted = isDark ? "#a1a1aa" : "#71717a";
|
|
227
|
+
return `
|
|
228
|
+
:host {
|
|
229
|
+
all: initial;
|
|
230
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
231
|
+
font-size: 14px;
|
|
232
|
+
line-height: 1.5;
|
|
233
|
+
color: ${isDark ? "#e4e4e7" : "#18181b"};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
* {
|
|
237
|
+
box-sizing: border-box;
|
|
238
|
+
margin: 0;
|
|
239
|
+
padding: 0;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/* Trigger button */
|
|
243
|
+
.mushi-trigger {
|
|
244
|
+
position: fixed;
|
|
245
|
+
width: 48px;
|
|
246
|
+
height: 48px;
|
|
247
|
+
border-radius: 50%;
|
|
248
|
+
border: none;
|
|
249
|
+
cursor: pointer;
|
|
250
|
+
display: flex;
|
|
251
|
+
align-items: center;
|
|
252
|
+
justify-content: center;
|
|
253
|
+
font-size: 22px;
|
|
254
|
+
background: ${bgMuted};
|
|
255
|
+
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.15);
|
|
256
|
+
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
|
257
|
+
}
|
|
258
|
+
.mushi-trigger:hover {
|
|
259
|
+
transform: scale(1.08);
|
|
260
|
+
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
|
|
261
|
+
}
|
|
262
|
+
.mushi-trigger.bottom-right { bottom: 20px; right: 20px; }
|
|
263
|
+
.mushi-trigger.bottom-left { bottom: 20px; left: 20px; }
|
|
264
|
+
.mushi-trigger.top-right { top: 20px; right: 20px; }
|
|
265
|
+
.mushi-trigger.top-left { top: 20px; left: 20px; }
|
|
266
|
+
|
|
267
|
+
/* Panel */
|
|
268
|
+
.mushi-panel {
|
|
269
|
+
position: fixed;
|
|
270
|
+
width: 380px;
|
|
271
|
+
max-height: 560px;
|
|
272
|
+
border-radius: 12px;
|
|
273
|
+
background: ${bgSurface};
|
|
274
|
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.18);
|
|
275
|
+
border: 1px solid ${border};
|
|
276
|
+
overflow: hidden;
|
|
277
|
+
display: flex;
|
|
278
|
+
flex-direction: column;
|
|
279
|
+
}
|
|
280
|
+
.mushi-panel.open {
|
|
281
|
+
animation: mushi-slide-in 0.25s ease forwards;
|
|
282
|
+
}
|
|
283
|
+
.mushi-panel.closed { display: none; }
|
|
284
|
+
.mushi-panel.bottom-right { bottom: 76px; right: 20px; }
|
|
285
|
+
.mushi-panel.bottom-left { bottom: 76px; left: 20px; }
|
|
286
|
+
.mushi-panel.top-right { top: 76px; right: 20px; }
|
|
287
|
+
.mushi-panel.top-left { top: 76px; left: 20px; }
|
|
288
|
+
|
|
289
|
+
@keyframes mushi-slide-in {
|
|
290
|
+
from { opacity: 0; transform: translateY(8px) scale(0.98); }
|
|
291
|
+
to { opacity: 1; transform: translateY(0) scale(1); }
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/* Header */
|
|
295
|
+
.mushi-header {
|
|
296
|
+
padding: 14px 16px;
|
|
297
|
+
border-bottom: 1px solid ${border};
|
|
298
|
+
display: flex;
|
|
299
|
+
align-items: center;
|
|
300
|
+
gap: 8px;
|
|
301
|
+
}
|
|
302
|
+
.mushi-header h3 {
|
|
303
|
+
font-size: 15px;
|
|
304
|
+
font-weight: 600;
|
|
305
|
+
flex: 1;
|
|
306
|
+
}
|
|
307
|
+
.mushi-close, .mushi-back {
|
|
308
|
+
background: none;
|
|
309
|
+
border: none;
|
|
310
|
+
cursor: pointer;
|
|
311
|
+
font-size: 18px;
|
|
312
|
+
color: ${textMuted};
|
|
313
|
+
padding: 4px;
|
|
314
|
+
border-radius: 4px;
|
|
315
|
+
line-height: 1;
|
|
316
|
+
}
|
|
317
|
+
.mushi-close:hover, .mushi-back:hover {
|
|
318
|
+
background: ${bgMuted};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/* Body */
|
|
322
|
+
.mushi-body {
|
|
323
|
+
padding: 12px 16px;
|
|
324
|
+
overflow-y: auto;
|
|
325
|
+
flex: 1;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/* Step 1: Category options */
|
|
329
|
+
.mushi-option-btn {
|
|
330
|
+
display: flex;
|
|
331
|
+
align-items: center;
|
|
332
|
+
gap: 12px;
|
|
333
|
+
width: 100%;
|
|
334
|
+
padding: 12px 14px;
|
|
335
|
+
margin-bottom: 8px;
|
|
336
|
+
border-radius: 10px;
|
|
337
|
+
border: 1px solid ${border};
|
|
338
|
+
background: ${bgMuted};
|
|
339
|
+
cursor: pointer;
|
|
340
|
+
font-size: 14px;
|
|
341
|
+
color: inherit;
|
|
342
|
+
text-align: left;
|
|
343
|
+
transition: border-color 0.15s, background 0.15s, transform 0.1s;
|
|
344
|
+
}
|
|
345
|
+
.mushi-option-btn:hover {
|
|
346
|
+
border-color: ${isDark ? "#a78bfa" : accent};
|
|
347
|
+
background: ${accentBgLight};
|
|
348
|
+
transform: translateX(2px);
|
|
349
|
+
}
|
|
350
|
+
.mushi-option-btn:focus-visible {
|
|
351
|
+
outline: 2px solid ${accent};
|
|
352
|
+
outline-offset: 2px;
|
|
353
|
+
}
|
|
354
|
+
.mushi-option-icon { font-size: 20px; flex-shrink: 0; }
|
|
355
|
+
.mushi-option-text { display: flex; flex-direction: column; gap: 2px; }
|
|
356
|
+
.mushi-option-label { font-weight: 500; }
|
|
357
|
+
.mushi-option-desc { font-size: 12px; color: ${textMuted}; }
|
|
358
|
+
|
|
359
|
+
/* Step 2: Intent chips */
|
|
360
|
+
.mushi-selected-category {
|
|
361
|
+
display: flex;
|
|
362
|
+
align-items: center;
|
|
363
|
+
gap: 8px;
|
|
364
|
+
padding: 8px 12px;
|
|
365
|
+
border-radius: 8px;
|
|
366
|
+
background: ${accentBgSelected};
|
|
367
|
+
font-size: 13px;
|
|
368
|
+
font-weight: 500;
|
|
369
|
+
margin-bottom: 12px;
|
|
370
|
+
}
|
|
371
|
+
.mushi-intents {
|
|
372
|
+
display: flex;
|
|
373
|
+
flex-wrap: wrap;
|
|
374
|
+
gap: 8px;
|
|
375
|
+
}
|
|
376
|
+
.mushi-intent-btn {
|
|
377
|
+
padding: 8px 16px;
|
|
378
|
+
border-radius: 20px;
|
|
379
|
+
border: 1px solid ${border};
|
|
380
|
+
background: ${bgMuted};
|
|
381
|
+
cursor: pointer;
|
|
382
|
+
font-size: 13px;
|
|
383
|
+
color: inherit;
|
|
384
|
+
transition: border-color 0.15s, background 0.15s;
|
|
385
|
+
}
|
|
386
|
+
.mushi-intent-btn:hover {
|
|
387
|
+
border-color: ${isDark ? "#a78bfa" : accent};
|
|
388
|
+
background: ${accentBgLight};
|
|
389
|
+
}
|
|
390
|
+
.mushi-intent-btn:focus-visible {
|
|
391
|
+
outline: 2px solid ${accent};
|
|
392
|
+
outline-offset: 2px;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/* Step 3: Details */
|
|
396
|
+
.mushi-textarea {
|
|
397
|
+
width: 100%;
|
|
398
|
+
min-height: 90px;
|
|
399
|
+
padding: 10px 12px;
|
|
400
|
+
border-radius: 8px;
|
|
401
|
+
border: 1px solid ${border};
|
|
402
|
+
background: ${bgMuted};
|
|
403
|
+
color: inherit;
|
|
404
|
+
font-family: inherit;
|
|
405
|
+
font-size: 14px;
|
|
406
|
+
resize: vertical;
|
|
407
|
+
outline: none;
|
|
408
|
+
transition: border-color 0.15s, box-shadow 0.15s;
|
|
409
|
+
}
|
|
410
|
+
.mushi-textarea:focus {
|
|
411
|
+
border-color: ${isDark ? "#a78bfa" : accent};
|
|
412
|
+
box-shadow: 0 0 0 2px ${isDark ? "rgba(167,139,250,0.2)" : "rgba(124,58,237,0.1)"};
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
.mushi-attachments {
|
|
416
|
+
display: flex;
|
|
417
|
+
gap: 8px;
|
|
418
|
+
margin-top: 10px;
|
|
419
|
+
}
|
|
420
|
+
.mushi-attach-btn {
|
|
421
|
+
padding: 6px 12px;
|
|
422
|
+
border-radius: 6px;
|
|
423
|
+
border: 1px solid ${border};
|
|
424
|
+
background: none;
|
|
425
|
+
cursor: pointer;
|
|
426
|
+
font-size: 12px;
|
|
427
|
+
color: ${textMuted};
|
|
428
|
+
transition: border-color 0.15s, color 0.15s;
|
|
429
|
+
}
|
|
430
|
+
.mushi-attach-btn:hover {
|
|
431
|
+
border-color: ${isDark ? "#a78bfa" : accent};
|
|
432
|
+
color: inherit;
|
|
433
|
+
}
|
|
434
|
+
.mushi-attach-btn.active {
|
|
435
|
+
border-color: ${isDark ? "#a78bfa" : accent};
|
|
436
|
+
color: ${isDark ? "#a78bfa" : accent};
|
|
437
|
+
background: ${accentBgLight};
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/* Footer */
|
|
441
|
+
.mushi-footer {
|
|
442
|
+
padding: 12px 16px;
|
|
443
|
+
border-top: 1px solid ${border};
|
|
444
|
+
display: flex;
|
|
445
|
+
align-items: center;
|
|
446
|
+
justify-content: flex-end;
|
|
447
|
+
}
|
|
448
|
+
.mushi-submit {
|
|
449
|
+
padding: 8px 24px;
|
|
450
|
+
border-radius: 8px;
|
|
451
|
+
border: none;
|
|
452
|
+
background: ${accent};
|
|
453
|
+
color: #ffffff;
|
|
454
|
+
font-size: 14px;
|
|
455
|
+
font-weight: 500;
|
|
456
|
+
cursor: pointer;
|
|
457
|
+
transition: background 0.15s;
|
|
458
|
+
}
|
|
459
|
+
.mushi-submit:hover { background: ${accentHover}; }
|
|
460
|
+
.mushi-submit:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
461
|
+
.mushi-submit:focus-visible {
|
|
462
|
+
outline: 2px solid ${accent};
|
|
463
|
+
outline-offset: 2px;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/* Step indicator dots */
|
|
467
|
+
.mushi-step-indicator {
|
|
468
|
+
display: flex;
|
|
469
|
+
justify-content: center;
|
|
470
|
+
gap: 6px;
|
|
471
|
+
padding: 10px;
|
|
472
|
+
}
|
|
473
|
+
.mushi-dot {
|
|
474
|
+
width: 6px;
|
|
475
|
+
height: 6px;
|
|
476
|
+
border-radius: 50%;
|
|
477
|
+
background: ${border};
|
|
478
|
+
transition: background 0.2s, transform 0.2s;
|
|
479
|
+
}
|
|
480
|
+
.mushi-dot.active {
|
|
481
|
+
background: ${accent};
|
|
482
|
+
transform: scale(1.3);
|
|
483
|
+
}
|
|
484
|
+
.mushi-dot.done {
|
|
485
|
+
background: ${isDark ? "#a78bfa" : "#8b5cf6"};
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/* Success */
|
|
489
|
+
.mushi-success {
|
|
490
|
+
text-align: center;
|
|
491
|
+
padding: 32px 16px;
|
|
492
|
+
}
|
|
493
|
+
.mushi-success-icon {
|
|
494
|
+
font-size: 40px;
|
|
495
|
+
margin-bottom: 12px;
|
|
496
|
+
}
|
|
497
|
+
.mushi-success p {
|
|
498
|
+
color: ${textMuted};
|
|
499
|
+
font-size: 14px;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/* Error */
|
|
503
|
+
.mushi-error {
|
|
504
|
+
color: #ef4444;
|
|
505
|
+
font-size: 12px;
|
|
506
|
+
margin-top: 8px;
|
|
507
|
+
}
|
|
508
|
+
`;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// src/widget.ts
|
|
512
|
+
var CATEGORY_ICONS = {
|
|
513
|
+
bug: "\u26A0\uFE0F",
|
|
514
|
+
slow: "\u{1F40C}",
|
|
515
|
+
visual: "\u{1F3A8}",
|
|
516
|
+
confusing: "\u{1F615}",
|
|
517
|
+
other: "\u{1F4DD}"
|
|
518
|
+
};
|
|
519
|
+
var MushiWidget = class {
|
|
520
|
+
host;
|
|
521
|
+
shadow;
|
|
522
|
+
config;
|
|
523
|
+
callbacks;
|
|
524
|
+
locale;
|
|
525
|
+
isOpen = false;
|
|
526
|
+
step = "category";
|
|
527
|
+
selectedCategory = null;
|
|
528
|
+
selectedIntent = null;
|
|
529
|
+
screenshotAttached = false;
|
|
530
|
+
elementSelected = false;
|
|
531
|
+
submitting = false;
|
|
532
|
+
constructor(config = {}, callbacks) {
|
|
533
|
+
this.config = {
|
|
534
|
+
position: config.position ?? "bottom-right",
|
|
535
|
+
theme: config.theme ?? "auto",
|
|
536
|
+
triggerText: config.triggerText ?? "\u{1F41B}",
|
|
537
|
+
expandedTitle: config.expandedTitle ?? "",
|
|
538
|
+
mode: config.mode ?? "conversational",
|
|
539
|
+
locale: config.locale ?? "auto",
|
|
540
|
+
zIndex: config.zIndex ?? 99999
|
|
541
|
+
};
|
|
542
|
+
this.callbacks = callbacks;
|
|
543
|
+
this.locale = getLocale(this.config.locale === "auto" ? void 0 : this.config.locale);
|
|
544
|
+
this.host = document.createElement("div");
|
|
545
|
+
this.host.id = "mushi-mushi-widget";
|
|
546
|
+
this.shadow = this.host.attachShadow({ mode: "closed" });
|
|
547
|
+
}
|
|
548
|
+
mount() {
|
|
549
|
+
document.body.appendChild(this.host);
|
|
550
|
+
this.render();
|
|
551
|
+
}
|
|
552
|
+
open() {
|
|
553
|
+
if (this.isOpen) return;
|
|
554
|
+
this.isOpen = true;
|
|
555
|
+
this.step = "category";
|
|
556
|
+
this.selectedCategory = null;
|
|
557
|
+
this.selectedIntent = null;
|
|
558
|
+
this.screenshotAttached = false;
|
|
559
|
+
this.elementSelected = false;
|
|
560
|
+
this.submitting = false;
|
|
561
|
+
this.render();
|
|
562
|
+
this.callbacks.onOpen();
|
|
563
|
+
}
|
|
564
|
+
close() {
|
|
565
|
+
if (!this.isOpen) return;
|
|
566
|
+
this.isOpen = false;
|
|
567
|
+
this.render();
|
|
568
|
+
this.callbacks.onClose();
|
|
569
|
+
}
|
|
570
|
+
getIsOpen() {
|
|
571
|
+
return this.isOpen;
|
|
572
|
+
}
|
|
573
|
+
setScreenshotAttached(attached) {
|
|
574
|
+
this.screenshotAttached = attached;
|
|
575
|
+
if (this.isOpen) this.render();
|
|
576
|
+
}
|
|
577
|
+
setElementSelected(selected) {
|
|
578
|
+
this.elementSelected = selected;
|
|
579
|
+
if (this.isOpen) this.render();
|
|
580
|
+
}
|
|
581
|
+
destroy() {
|
|
582
|
+
this.host.remove();
|
|
583
|
+
}
|
|
584
|
+
getTheme() {
|
|
585
|
+
if (this.config.theme !== "auto") return this.config.theme;
|
|
586
|
+
if (typeof window !== "undefined" && window.matchMedia("(prefers-color-scheme: dark)").matches) {
|
|
587
|
+
return "dark";
|
|
588
|
+
}
|
|
589
|
+
return "light";
|
|
590
|
+
}
|
|
591
|
+
render() {
|
|
592
|
+
const theme = this.getTheme();
|
|
593
|
+
const pos = this.config.position;
|
|
594
|
+
const t = this.locale;
|
|
595
|
+
this.shadow.innerHTML = "";
|
|
596
|
+
const style = document.createElement("style");
|
|
597
|
+
style.textContent = getWidgetStyles(theme);
|
|
598
|
+
this.shadow.appendChild(style);
|
|
599
|
+
const trigger = document.createElement("button");
|
|
600
|
+
trigger.className = `mushi-trigger ${pos}`;
|
|
601
|
+
trigger.textContent = this.config.triggerText;
|
|
602
|
+
trigger.setAttribute("aria-label", t.widget.trigger);
|
|
603
|
+
trigger.style.zIndex = String(this.config.zIndex);
|
|
604
|
+
trigger.addEventListener("click", () => {
|
|
605
|
+
if (this.isOpen) this.close();
|
|
606
|
+
else this.open();
|
|
607
|
+
});
|
|
608
|
+
this.shadow.appendChild(trigger);
|
|
609
|
+
const panel = document.createElement("div");
|
|
610
|
+
panel.className = `mushi-panel ${pos}${this.isOpen ? " open" : " closed"}`;
|
|
611
|
+
panel.setAttribute("role", "dialog");
|
|
612
|
+
panel.setAttribute("aria-label", t.widget.title);
|
|
613
|
+
panel.style.zIndex = String(this.config.zIndex + 1);
|
|
614
|
+
if (this.isOpen) {
|
|
615
|
+
panel.innerHTML = this.renderStep();
|
|
616
|
+
this.shadow.appendChild(panel);
|
|
617
|
+
this.attachHandlers(panel);
|
|
618
|
+
this.trapFocus(panel);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
renderStep() {
|
|
622
|
+
switch (this.step) {
|
|
623
|
+
case "category":
|
|
624
|
+
return this.renderCategoryStep();
|
|
625
|
+
case "intent":
|
|
626
|
+
return this.renderIntentStep();
|
|
627
|
+
case "details":
|
|
628
|
+
return this.renderDetailsStep();
|
|
629
|
+
case "success":
|
|
630
|
+
return this.renderSuccessStep();
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
renderHeader(title, showBack = false) {
|
|
634
|
+
const t = this.locale;
|
|
635
|
+
return `
|
|
636
|
+
<div class="mushi-header">
|
|
637
|
+
${showBack ? `<button class="mushi-back" data-action="back" aria-label="${t.widget.back}">\u2190</button>` : ""}
|
|
638
|
+
<h3>${title}</h3>
|
|
639
|
+
<button class="mushi-close" data-action="close" aria-label="${t.widget.close}">\u2715</button>
|
|
640
|
+
</div>
|
|
641
|
+
`;
|
|
642
|
+
}
|
|
643
|
+
renderCategoryStep() {
|
|
644
|
+
const t = this.locale;
|
|
645
|
+
const categories = ["bug", "slow", "visual", "confusing", "other"].map((id) => `
|
|
646
|
+
<button class="mushi-option-btn" data-category="${id}" role="radio" aria-checked="false">
|
|
647
|
+
<span class="mushi-option-icon">${CATEGORY_ICONS[id]}</span>
|
|
648
|
+
<div class="mushi-option-text">
|
|
649
|
+
<span class="mushi-option-label">${t.step1.categories[id]}</span>
|
|
650
|
+
<span class="mushi-option-desc">${t.step1.categoryDescriptions[id]}</span>
|
|
651
|
+
</div>
|
|
652
|
+
</button>
|
|
653
|
+
`).join("");
|
|
654
|
+
return `
|
|
655
|
+
${this.renderHeader(t.step1.heading)}
|
|
656
|
+
<div class="mushi-body" role="radiogroup" aria-label="${t.step1.heading}">
|
|
657
|
+
${categories}
|
|
658
|
+
</div>
|
|
659
|
+
<div class="mushi-step-indicator">
|
|
660
|
+
<span class="mushi-dot active"></span>
|
|
661
|
+
<span class="mushi-dot"></span>
|
|
662
|
+
<span class="mushi-dot"></span>
|
|
663
|
+
</div>
|
|
664
|
+
`;
|
|
665
|
+
}
|
|
666
|
+
renderIntentStep() {
|
|
667
|
+
const t = this.locale;
|
|
668
|
+
const cat = this.selectedCategory;
|
|
669
|
+
const intents = t.step2.intents[cat] || [];
|
|
670
|
+
const options = intents.map((intent) => `
|
|
671
|
+
<button class="mushi-intent-btn" data-intent="${intent}">
|
|
672
|
+
${intent}
|
|
673
|
+
</button>
|
|
674
|
+
`).join("");
|
|
675
|
+
return `
|
|
676
|
+
${this.renderHeader(t.step2.heading, true)}
|
|
677
|
+
<div class="mushi-body">
|
|
678
|
+
<div class="mushi-selected-category">
|
|
679
|
+
<span>${CATEGORY_ICONS[cat]}</span>
|
|
680
|
+
<span>${t.step1.categories[cat]}</span>
|
|
681
|
+
</div>
|
|
682
|
+
<div class="mushi-intents">
|
|
683
|
+
${options}
|
|
684
|
+
</div>
|
|
685
|
+
</div>
|
|
686
|
+
<div class="mushi-step-indicator">
|
|
687
|
+
<span class="mushi-dot done"></span>
|
|
688
|
+
<span class="mushi-dot active"></span>
|
|
689
|
+
<span class="mushi-dot"></span>
|
|
690
|
+
</div>
|
|
691
|
+
`;
|
|
692
|
+
}
|
|
693
|
+
renderDetailsStep() {
|
|
694
|
+
const t = this.locale;
|
|
695
|
+
return `
|
|
696
|
+
${this.renderHeader(t.step3.heading, true)}
|
|
697
|
+
<div class="mushi-body">
|
|
698
|
+
<textarea
|
|
699
|
+
class="mushi-textarea"
|
|
700
|
+
placeholder="${t.step3.descriptionPlaceholder}"
|
|
701
|
+
rows="4"
|
|
702
|
+
aria-label="${t.step3.heading}"
|
|
703
|
+
autofocus
|
|
704
|
+
></textarea>
|
|
705
|
+
<div class="mushi-attachments">
|
|
706
|
+
<button class="mushi-attach-btn${this.screenshotAttached ? " active" : ""}" data-action="screenshot">
|
|
707
|
+
\u{1F4F8} ${this.screenshotAttached ? t.step3.screenshotAttached : t.step3.screenshotButton}
|
|
708
|
+
</button>
|
|
709
|
+
<button class="mushi-attach-btn${this.elementSelected ? " active" : ""}" data-action="element">
|
|
710
|
+
\u{1F3AF} ${this.elementSelected ? t.step3.elementSelected : t.step3.elementButton}
|
|
711
|
+
</button>
|
|
712
|
+
</div>
|
|
713
|
+
<div class="mushi-error" style="display:none" role="alert"></div>
|
|
714
|
+
</div>
|
|
715
|
+
<div class="mushi-footer">
|
|
716
|
+
<button class="mushi-submit" data-action="submit"${this.submitting ? " disabled" : ""}>
|
|
717
|
+
${this.submitting ? t.widget.submitting : t.widget.submit}
|
|
718
|
+
</button>
|
|
719
|
+
</div>
|
|
720
|
+
<div class="mushi-step-indicator">
|
|
721
|
+
<span class="mushi-dot done"></span>
|
|
722
|
+
<span class="mushi-dot done"></span>
|
|
723
|
+
<span class="mushi-dot active"></span>
|
|
724
|
+
</div>
|
|
725
|
+
`;
|
|
726
|
+
}
|
|
727
|
+
renderSuccessStep() {
|
|
728
|
+
const t = this.locale;
|
|
729
|
+
return `
|
|
730
|
+
${this.renderHeader(t.widget.title)}
|
|
731
|
+
<div class="mushi-body">
|
|
732
|
+
<div class="mushi-success">
|
|
733
|
+
<div class="mushi-success-icon">\u2705</div>
|
|
734
|
+
<p>${t.widget.submitted}</p>
|
|
735
|
+
</div>
|
|
736
|
+
</div>
|
|
737
|
+
`;
|
|
738
|
+
}
|
|
739
|
+
attachHandlers(panel) {
|
|
740
|
+
const t = this.locale;
|
|
741
|
+
panel.querySelector('[data-action="close"]')?.addEventListener("click", () => this.close());
|
|
742
|
+
panel.querySelector('[data-action="back"]')?.addEventListener("click", () => {
|
|
743
|
+
if (this.step === "intent") {
|
|
744
|
+
this.step = "category";
|
|
745
|
+
this.selectedCategory = null;
|
|
746
|
+
} else if (this.step === "details") {
|
|
747
|
+
this.step = "intent";
|
|
748
|
+
this.selectedIntent = null;
|
|
749
|
+
}
|
|
750
|
+
this.render();
|
|
751
|
+
});
|
|
752
|
+
panel.querySelectorAll("[data-category]").forEach((btn) => {
|
|
753
|
+
btn.addEventListener("click", () => {
|
|
754
|
+
this.selectedCategory = btn.dataset.category;
|
|
755
|
+
this.step = "intent";
|
|
756
|
+
this.render();
|
|
757
|
+
});
|
|
758
|
+
});
|
|
759
|
+
panel.querySelectorAll("[data-intent]").forEach((btn) => {
|
|
760
|
+
btn.addEventListener("click", () => {
|
|
761
|
+
this.selectedIntent = btn.dataset.intent ?? null;
|
|
762
|
+
this.step = "details";
|
|
763
|
+
this.render();
|
|
764
|
+
});
|
|
765
|
+
});
|
|
766
|
+
panel.querySelector('[data-action="screenshot"]')?.addEventListener("click", () => {
|
|
767
|
+
this.callbacks.onScreenshotRequest();
|
|
768
|
+
});
|
|
769
|
+
panel.querySelector('[data-action="element"]')?.addEventListener("click", () => {
|
|
770
|
+
this.callbacks.onElementSelectorRequest?.();
|
|
771
|
+
});
|
|
772
|
+
panel.querySelector('[data-action="submit"]')?.addEventListener("click", () => {
|
|
773
|
+
const textarea = panel.querySelector(".mushi-textarea");
|
|
774
|
+
const description = textarea?.value?.trim() ?? "";
|
|
775
|
+
const errorEl = panel.querySelector(".mushi-error");
|
|
776
|
+
if (description.length < 5) {
|
|
777
|
+
if (errorEl) {
|
|
778
|
+
errorEl.textContent = t.widget.error;
|
|
779
|
+
errorEl.style.display = "block";
|
|
780
|
+
}
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
783
|
+
this.submitting = true;
|
|
784
|
+
this.render();
|
|
785
|
+
this.callbacks.onSubmit({
|
|
786
|
+
category: this.selectedCategory,
|
|
787
|
+
description,
|
|
788
|
+
intent: this.selectedIntent ?? void 0
|
|
789
|
+
});
|
|
790
|
+
setTimeout(() => {
|
|
791
|
+
this.submitting = false;
|
|
792
|
+
this.step = "success";
|
|
793
|
+
this.render();
|
|
794
|
+
setTimeout(() => {
|
|
795
|
+
if (this.step === "success") this.close();
|
|
796
|
+
}, 2500);
|
|
797
|
+
}, 500);
|
|
798
|
+
});
|
|
799
|
+
panel.addEventListener("keydown", (e) => {
|
|
800
|
+
if (e.key === "Escape") this.close();
|
|
801
|
+
});
|
|
802
|
+
}
|
|
803
|
+
trapFocus(panel) {
|
|
804
|
+
requestAnimationFrame(() => {
|
|
805
|
+
const focusable = panel.querySelectorAll("button, textarea, [tabindex]");
|
|
806
|
+
if (focusable.length > 0) focusable[0].focus();
|
|
807
|
+
});
|
|
808
|
+
}
|
|
809
|
+
};
|
|
810
|
+
|
|
811
|
+
// src/capture/console.ts
|
|
812
|
+
var MAX_ENTRIES = 50;
|
|
813
|
+
var MAX_MESSAGE_LENGTH = 500;
|
|
814
|
+
function createConsoleCapture() {
|
|
815
|
+
const entries = [];
|
|
816
|
+
const originals = {};
|
|
817
|
+
const levels = ["log", "warn", "error", "info", "debug"];
|
|
818
|
+
for (const level of levels) {
|
|
819
|
+
const original = console[level];
|
|
820
|
+
originals[level] = original;
|
|
821
|
+
console[level] = (...args) => {
|
|
822
|
+
const message = args.map((arg) => {
|
|
823
|
+
try {
|
|
824
|
+
return typeof arg === "string" ? arg : JSON.stringify(arg);
|
|
825
|
+
} catch {
|
|
826
|
+
return String(arg);
|
|
827
|
+
}
|
|
828
|
+
}).join(" ").slice(0, MAX_MESSAGE_LENGTH);
|
|
829
|
+
const entry = {
|
|
830
|
+
level,
|
|
831
|
+
message,
|
|
832
|
+
timestamp: Date.now()
|
|
833
|
+
};
|
|
834
|
+
if (level === "error" && args[0] instanceof Error) {
|
|
835
|
+
entry.stack = args[0].stack?.slice(0, 1e3);
|
|
836
|
+
}
|
|
837
|
+
entries.push(entry);
|
|
838
|
+
if (entries.length > MAX_ENTRIES) {
|
|
839
|
+
entries.shift();
|
|
840
|
+
}
|
|
841
|
+
original.call(console, ...args);
|
|
842
|
+
};
|
|
843
|
+
}
|
|
844
|
+
return {
|
|
845
|
+
getEntries() {
|
|
846
|
+
return [...entries];
|
|
847
|
+
},
|
|
848
|
+
clear() {
|
|
849
|
+
entries.length = 0;
|
|
850
|
+
},
|
|
851
|
+
destroy() {
|
|
852
|
+
for (const level of levels) {
|
|
853
|
+
if (originals[level]) {
|
|
854
|
+
console[level] = originals[level];
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
};
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
// src/capture/network.ts
|
|
862
|
+
var MAX_ENTRIES2 = 30;
|
|
863
|
+
function createNetworkCapture() {
|
|
864
|
+
const entries = [];
|
|
865
|
+
const originalFetch = globalThis.fetch;
|
|
866
|
+
globalThis.fetch = async function mushiFetchInterceptor(input, init) {
|
|
867
|
+
const startTime = Date.now();
|
|
868
|
+
const method = init?.method?.toUpperCase() ?? "GET";
|
|
869
|
+
const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
870
|
+
try {
|
|
871
|
+
const response = await originalFetch.call(globalThis, input, init);
|
|
872
|
+
addEntry({
|
|
873
|
+
method,
|
|
874
|
+
url: truncateUrl(url),
|
|
875
|
+
status: response.status,
|
|
876
|
+
duration: Date.now() - startTime,
|
|
877
|
+
timestamp: startTime
|
|
878
|
+
});
|
|
879
|
+
return response;
|
|
880
|
+
} catch (error) {
|
|
881
|
+
addEntry({
|
|
882
|
+
method,
|
|
883
|
+
url: truncateUrl(url),
|
|
884
|
+
status: 0,
|
|
885
|
+
duration: Date.now() - startTime,
|
|
886
|
+
timestamp: startTime,
|
|
887
|
+
error: error instanceof Error ? error.message : "Network error"
|
|
888
|
+
});
|
|
889
|
+
throw error;
|
|
890
|
+
}
|
|
891
|
+
};
|
|
892
|
+
function addEntry(entry) {
|
|
893
|
+
entries.push(entry);
|
|
894
|
+
if (entries.length > MAX_ENTRIES2) {
|
|
895
|
+
entries.shift();
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
return {
|
|
899
|
+
getEntries() {
|
|
900
|
+
return [...entries];
|
|
901
|
+
},
|
|
902
|
+
clear() {
|
|
903
|
+
entries.length = 0;
|
|
904
|
+
},
|
|
905
|
+
destroy() {
|
|
906
|
+
globalThis.fetch = originalFetch;
|
|
907
|
+
}
|
|
908
|
+
};
|
|
909
|
+
}
|
|
910
|
+
function truncateUrl(url) {
|
|
911
|
+
try {
|
|
912
|
+
const parsed = new URL(url);
|
|
913
|
+
const path = parsed.pathname + parsed.search;
|
|
914
|
+
if (path.length > 200) {
|
|
915
|
+
return parsed.origin + path.slice(0, 200) + "...";
|
|
916
|
+
}
|
|
917
|
+
return parsed.origin + path;
|
|
918
|
+
} catch {
|
|
919
|
+
return url.slice(0, 200);
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
// src/capture/screenshot.ts
|
|
924
|
+
function createScreenshotCapture() {
|
|
925
|
+
async function take() {
|
|
926
|
+
try {
|
|
927
|
+
if (typeof document === "undefined") return null;
|
|
928
|
+
const canvas = document.createElement("canvas");
|
|
929
|
+
const ctx = canvas.getContext("2d");
|
|
930
|
+
if (!ctx) return null;
|
|
931
|
+
const width = window.innerWidth;
|
|
932
|
+
const height = window.innerHeight;
|
|
933
|
+
const dpr = Math.min(window.devicePixelRatio || 1, 2);
|
|
934
|
+
canvas.width = width * dpr;
|
|
935
|
+
canvas.height = height * dpr;
|
|
936
|
+
ctx.scale(dpr, dpr);
|
|
937
|
+
const svgData = `
|
|
938
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}">
|
|
939
|
+
<foreignObject width="100%" height="100%">
|
|
940
|
+
<div xmlns="http://www.w3.org/1999/xhtml">
|
|
941
|
+
${new XMLSerializer().serializeToString(document.documentElement)}
|
|
942
|
+
</div>
|
|
943
|
+
</foreignObject>
|
|
944
|
+
</svg>
|
|
945
|
+
`;
|
|
946
|
+
const img = new Image();
|
|
947
|
+
const blob = new Blob([svgData], { type: "image/svg+xml;charset=utf-8" });
|
|
948
|
+
const url = URL.createObjectURL(blob);
|
|
949
|
+
return new Promise((resolve) => {
|
|
950
|
+
img.onload = () => {
|
|
951
|
+
ctx.drawImage(img, 0, 0, width, height);
|
|
952
|
+
URL.revokeObjectURL(url);
|
|
953
|
+
try {
|
|
954
|
+
const dataUrl = canvas.toDataURL("image/jpeg", 0.7);
|
|
955
|
+
resolve(dataUrl);
|
|
956
|
+
} catch {
|
|
957
|
+
resolve(null);
|
|
958
|
+
}
|
|
959
|
+
};
|
|
960
|
+
img.onerror = () => {
|
|
961
|
+
URL.revokeObjectURL(url);
|
|
962
|
+
resolve(null);
|
|
963
|
+
};
|
|
964
|
+
img.src = url;
|
|
965
|
+
});
|
|
966
|
+
} catch {
|
|
967
|
+
return null;
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
return { take };
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
// src/capture/performance.ts
|
|
974
|
+
function createPerformanceCapture() {
|
|
975
|
+
const metrics = {};
|
|
976
|
+
const observers = [];
|
|
977
|
+
if (typeof PerformanceObserver !== "undefined") {
|
|
978
|
+
try {
|
|
979
|
+
const paintObserver = new PerformanceObserver((list) => {
|
|
980
|
+
for (const entry of list.getEntries()) {
|
|
981
|
+
if (entry.name === "first-contentful-paint") {
|
|
982
|
+
metrics.fcp = entry.startTime;
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
});
|
|
986
|
+
paintObserver.observe({ type: "paint", buffered: true });
|
|
987
|
+
observers.push(paintObserver);
|
|
988
|
+
} catch {
|
|
989
|
+
}
|
|
990
|
+
try {
|
|
991
|
+
const lcpObserver = new PerformanceObserver((list) => {
|
|
992
|
+
const entries = list.getEntries();
|
|
993
|
+
if (entries.length > 0) {
|
|
994
|
+
metrics.lcp = entries[entries.length - 1].startTime;
|
|
995
|
+
}
|
|
996
|
+
});
|
|
997
|
+
lcpObserver.observe({ type: "largest-contentful-paint", buffered: true });
|
|
998
|
+
observers.push(lcpObserver);
|
|
999
|
+
} catch {
|
|
1000
|
+
}
|
|
1001
|
+
try {
|
|
1002
|
+
let clsValue = 0;
|
|
1003
|
+
const clsObserver = new PerformanceObserver((list) => {
|
|
1004
|
+
for (const entry of list.getEntries()) {
|
|
1005
|
+
if (!entry.hadRecentInput) {
|
|
1006
|
+
clsValue += entry.value ?? 0;
|
|
1007
|
+
metrics.cls = clsValue;
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
});
|
|
1011
|
+
clsObserver.observe({ type: "layout-shift", buffered: true });
|
|
1012
|
+
observers.push(clsObserver);
|
|
1013
|
+
} catch {
|
|
1014
|
+
}
|
|
1015
|
+
try {
|
|
1016
|
+
let longTaskCount = 0;
|
|
1017
|
+
const longTaskObserver = new PerformanceObserver((list) => {
|
|
1018
|
+
longTaskCount += list.getEntries().length;
|
|
1019
|
+
metrics.longTasks = longTaskCount;
|
|
1020
|
+
});
|
|
1021
|
+
longTaskObserver.observe({ type: "longtask", buffered: true });
|
|
1022
|
+
observers.push(longTaskObserver);
|
|
1023
|
+
} catch {
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
if (typeof performance !== "undefined" && performance.getEntriesByType) {
|
|
1027
|
+
try {
|
|
1028
|
+
const navEntries = performance.getEntriesByType("navigation");
|
|
1029
|
+
if (navEntries.length > 0 && navEntries[0]) {
|
|
1030
|
+
metrics.ttfb = navEntries[0].responseStart - navEntries[0].requestStart;
|
|
1031
|
+
}
|
|
1032
|
+
} catch {
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
return {
|
|
1036
|
+
getMetrics() {
|
|
1037
|
+
return { ...metrics };
|
|
1038
|
+
},
|
|
1039
|
+
destroy() {
|
|
1040
|
+
for (const obs of observers) {
|
|
1041
|
+
obs.disconnect();
|
|
1042
|
+
}
|
|
1043
|
+
observers.length = 0;
|
|
1044
|
+
}
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
// src/capture/element-selector.ts
|
|
1049
|
+
function createElementSelector() {
|
|
1050
|
+
let active = false;
|
|
1051
|
+
let overlay = null;
|
|
1052
|
+
let resolvePromise = null;
|
|
1053
|
+
function getXPath(el) {
|
|
1054
|
+
const parts = [];
|
|
1055
|
+
let current = el;
|
|
1056
|
+
while (current && current !== document.body) {
|
|
1057
|
+
let index = 1;
|
|
1058
|
+
let sibling = current.previousElementSibling;
|
|
1059
|
+
while (sibling) {
|
|
1060
|
+
if (sibling.tagName === current.tagName) index++;
|
|
1061
|
+
sibling = sibling.previousElementSibling;
|
|
1062
|
+
}
|
|
1063
|
+
const tag = current.tagName.toLowerCase();
|
|
1064
|
+
parts.unshift(index > 1 ? `${tag}[${index}]` : tag);
|
|
1065
|
+
current = current.parentElement;
|
|
1066
|
+
}
|
|
1067
|
+
return "//" + parts.join("/");
|
|
1068
|
+
}
|
|
1069
|
+
function captureElement(el) {
|
|
1070
|
+
const rect = el.getBoundingClientRect();
|
|
1071
|
+
return {
|
|
1072
|
+
tagName: el.tagName.toLowerCase(),
|
|
1073
|
+
id: el.id || void 0,
|
|
1074
|
+
className: el.className || void 0,
|
|
1075
|
+
textContent: el.textContent?.trim().slice(0, 200) || void 0,
|
|
1076
|
+
xpath: getXPath(el),
|
|
1077
|
+
rect: {
|
|
1078
|
+
x: Math.round(rect.x),
|
|
1079
|
+
y: Math.round(rect.y),
|
|
1080
|
+
width: Math.round(rect.width),
|
|
1081
|
+
height: Math.round(rect.height)
|
|
1082
|
+
}
|
|
1083
|
+
};
|
|
1084
|
+
}
|
|
1085
|
+
function createOverlay() {
|
|
1086
|
+
const el = document.createElement("div");
|
|
1087
|
+
el.style.cssText = `
|
|
1088
|
+
position: fixed;
|
|
1089
|
+
pointer-events: none;
|
|
1090
|
+
z-index: 2147483647;
|
|
1091
|
+
border: 2px solid #6366f1;
|
|
1092
|
+
background: rgba(99, 102, 241, 0.1);
|
|
1093
|
+
border-radius: 4px;
|
|
1094
|
+
transition: all 0.1s ease;
|
|
1095
|
+
display: none;
|
|
1096
|
+
`;
|
|
1097
|
+
document.body.appendChild(el);
|
|
1098
|
+
return el;
|
|
1099
|
+
}
|
|
1100
|
+
function positionOverlay(target) {
|
|
1101
|
+
if (!overlay) return;
|
|
1102
|
+
const rect = target.getBoundingClientRect();
|
|
1103
|
+
overlay.style.left = `${rect.left}px`;
|
|
1104
|
+
overlay.style.top = `${rect.top}px`;
|
|
1105
|
+
overlay.style.width = `${rect.width}px`;
|
|
1106
|
+
overlay.style.height = `${rect.height}px`;
|
|
1107
|
+
overlay.style.display = "block";
|
|
1108
|
+
}
|
|
1109
|
+
function handleMouseMove(e) {
|
|
1110
|
+
const target = e.target;
|
|
1111
|
+
if (target === overlay) return;
|
|
1112
|
+
positionOverlay(target);
|
|
1113
|
+
}
|
|
1114
|
+
function handleClick(e) {
|
|
1115
|
+
e.preventDefault();
|
|
1116
|
+
e.stopPropagation();
|
|
1117
|
+
const target = e.target;
|
|
1118
|
+
if (target === overlay) return;
|
|
1119
|
+
const captured = captureElement(target);
|
|
1120
|
+
finish(captured);
|
|
1121
|
+
}
|
|
1122
|
+
function handleKeyDown(e) {
|
|
1123
|
+
if (e.key === "Escape") {
|
|
1124
|
+
finish(null);
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
function finish(result) {
|
|
1128
|
+
deactivate();
|
|
1129
|
+
resolvePromise?.(result);
|
|
1130
|
+
resolvePromise = null;
|
|
1131
|
+
}
|
|
1132
|
+
function activate() {
|
|
1133
|
+
if (active) deactivate();
|
|
1134
|
+
return new Promise((resolve) => {
|
|
1135
|
+
resolvePromise = resolve;
|
|
1136
|
+
active = true;
|
|
1137
|
+
overlay = createOverlay();
|
|
1138
|
+
document.body.style.cursor = "crosshair";
|
|
1139
|
+
document.addEventListener("mousemove", handleMouseMove, true);
|
|
1140
|
+
document.addEventListener("click", handleClick, true);
|
|
1141
|
+
document.addEventListener("keydown", handleKeyDown, true);
|
|
1142
|
+
});
|
|
1143
|
+
}
|
|
1144
|
+
function deactivate() {
|
|
1145
|
+
if (!active) return;
|
|
1146
|
+
active = false;
|
|
1147
|
+
document.body.style.cursor = "";
|
|
1148
|
+
document.removeEventListener("mousemove", handleMouseMove, true);
|
|
1149
|
+
document.removeEventListener("click", handleClick, true);
|
|
1150
|
+
document.removeEventListener("keydown", handleKeyDown, true);
|
|
1151
|
+
if (overlay) {
|
|
1152
|
+
overlay.remove();
|
|
1153
|
+
overlay = null;
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
return { activate, deactivate, isActive: () => active };
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
// src/sentry.ts
|
|
1160
|
+
function getSentryGlobal() {
|
|
1161
|
+
try {
|
|
1162
|
+
const win = globalThis;
|
|
1163
|
+
if (win.__SENTRY__) {
|
|
1164
|
+
const sentry = win.__SENTRY__;
|
|
1165
|
+
const hub = sentry.hub;
|
|
1166
|
+
return hub;
|
|
1167
|
+
}
|
|
1168
|
+
if (win.Sentry) {
|
|
1169
|
+
return win.Sentry;
|
|
1170
|
+
}
|
|
1171
|
+
} catch {
|
|
1172
|
+
}
|
|
1173
|
+
return void 0;
|
|
1174
|
+
}
|
|
1175
|
+
function captureSentryContext(_config) {
|
|
1176
|
+
const context = {};
|
|
1177
|
+
try {
|
|
1178
|
+
const hub = getSentryGlobal();
|
|
1179
|
+
if (!hub) return context;
|
|
1180
|
+
const scope = hub.getScope?.();
|
|
1181
|
+
if (scope) {
|
|
1182
|
+
context.eventId = scope.getLastEventId?.() ?? void 0;
|
|
1183
|
+
}
|
|
1184
|
+
const client = hub.getClient?.();
|
|
1185
|
+
if (client) {
|
|
1186
|
+
const options = client.getOptions?.();
|
|
1187
|
+
context.release = options?.release;
|
|
1188
|
+
context.environment = options?.environment;
|
|
1189
|
+
}
|
|
1190
|
+
const win = globalThis;
|
|
1191
|
+
if (win.__SENTRY_REPLAY__) {
|
|
1192
|
+
const replay = win.__SENTRY_REPLAY__;
|
|
1193
|
+
context.replayId = replay.getReplayId?.() ?? void 0;
|
|
1194
|
+
}
|
|
1195
|
+
} catch {
|
|
1196
|
+
}
|
|
1197
|
+
return context;
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
// src/mushi.ts
|
|
1201
|
+
var instance = null;
|
|
1202
|
+
var Mushi = class {
|
|
1203
|
+
constructor() {
|
|
1204
|
+
}
|
|
1205
|
+
static init(config) {
|
|
1206
|
+
if (instance) {
|
|
1207
|
+
createLogger({ scope: "mushi", level: "warn", format: "pretty" }).warn("Already initialized \u2014 call destroy() first to reinitialize");
|
|
1208
|
+
return instance;
|
|
1209
|
+
}
|
|
1210
|
+
if (!config.projectId) {
|
|
1211
|
+
throw new Error("[mushi] projectId is required");
|
|
1212
|
+
}
|
|
1213
|
+
if (!config.apiKey) {
|
|
1214
|
+
throw new Error("[mushi] apiKey is required");
|
|
1215
|
+
}
|
|
1216
|
+
if (config.enabled === false) {
|
|
1217
|
+
return createNoopInstance();
|
|
1218
|
+
}
|
|
1219
|
+
instance = createInstance(config);
|
|
1220
|
+
return instance;
|
|
1221
|
+
}
|
|
1222
|
+
static getInstance() {
|
|
1223
|
+
return instance;
|
|
1224
|
+
}
|
|
1225
|
+
static destroy() {
|
|
1226
|
+
instance?.destroy();
|
|
1227
|
+
instance = null;
|
|
1228
|
+
}
|
|
1229
|
+
};
|
|
1230
|
+
function createInstance(config) {
|
|
1231
|
+
const log = config.debug ?? false ? createLogger({ scope: "mushi", level: "debug", format: "pretty" }) : noopLogger;
|
|
1232
|
+
const apiClient = createApiClient({
|
|
1233
|
+
projectId: config.projectId,
|
|
1234
|
+
apiKey: config.apiKey,
|
|
1235
|
+
apiEndpoint: config.apiEndpoint ?? "https://api.mushimushi.dev"
|
|
1236
|
+
});
|
|
1237
|
+
const preFilter = createPreFilter(config.preFilter);
|
|
1238
|
+
const offlineQueue = createOfflineQueue(config.offline);
|
|
1239
|
+
const rateLimiter = createRateLimiter({ maxBurst: 10, refillRate: 1, refillIntervalMs: 5e3 });
|
|
1240
|
+
const piiScrubber = createPiiScrubber();
|
|
1241
|
+
const consoleCap = config.capture?.console !== false ? createConsoleCapture() : null;
|
|
1242
|
+
const networkCap = config.capture?.network !== false ? createNetworkCapture() : null;
|
|
1243
|
+
const perfCap = config.capture?.performance !== false ? createPerformanceCapture() : null;
|
|
1244
|
+
const screenshotCap = config.capture?.screenshot !== "off" ? createScreenshotCapture() : null;
|
|
1245
|
+
const elementSelector = config.capture?.elementSelector !== false ? createElementSelector() : null;
|
|
1246
|
+
const listeners = /* @__PURE__ */ new Map();
|
|
1247
|
+
function emit(type, data) {
|
|
1248
|
+
listeners.get(type)?.forEach((handler) => handler({ type, data }));
|
|
1249
|
+
}
|
|
1250
|
+
let userInfo = null;
|
|
1251
|
+
const customMetadata = {};
|
|
1252
|
+
let pendingScreenshot = null;
|
|
1253
|
+
let pendingElement = null;
|
|
1254
|
+
const widget = new MushiWidget(config.widget, {
|
|
1255
|
+
onSubmit: async ({ category, description, intent }) => {
|
|
1256
|
+
log.info("Report submitted", { category, intent });
|
|
1257
|
+
await submitReport(category, description, intent);
|
|
1258
|
+
},
|
|
1259
|
+
onOpen: () => {
|
|
1260
|
+
log.debug("Widget opened");
|
|
1261
|
+
emit("widget:opened");
|
|
1262
|
+
},
|
|
1263
|
+
onClose: () => {
|
|
1264
|
+
log.debug("Widget closed");
|
|
1265
|
+
pendingScreenshot = null;
|
|
1266
|
+
pendingElement = null;
|
|
1267
|
+
emit("widget:closed");
|
|
1268
|
+
},
|
|
1269
|
+
onScreenshotRequest: async () => {
|
|
1270
|
+
if (!screenshotCap) return;
|
|
1271
|
+
log.debug("Taking screenshot");
|
|
1272
|
+
pendingScreenshot = await screenshotCap.take();
|
|
1273
|
+
widget.setScreenshotAttached(pendingScreenshot !== null);
|
|
1274
|
+
},
|
|
1275
|
+
onElementSelectorRequest: async () => {
|
|
1276
|
+
if (!elementSelector) return;
|
|
1277
|
+
log.debug("Element selector activated");
|
|
1278
|
+
const el = await elementSelector.activate();
|
|
1279
|
+
if (el) {
|
|
1280
|
+
pendingElement = el;
|
|
1281
|
+
widget.setElementSelected(true);
|
|
1282
|
+
log.debug("Element selected", { tagName: el.tagName, xpath: el.xpath });
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
});
|
|
1286
|
+
if (typeof document !== "undefined") {
|
|
1287
|
+
if (document.readyState === "loading") {
|
|
1288
|
+
document.addEventListener("DOMContentLoaded", () => widget.mount());
|
|
1289
|
+
} else {
|
|
1290
|
+
widget.mount();
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
offlineQueue.startAutoSync(apiClient);
|
|
1294
|
+
offlineQueue.flush(apiClient).then((result) => {
|
|
1295
|
+
if (result.sent > 0) log.info("Synced offline reports", { sent: result.sent });
|
|
1296
|
+
});
|
|
1297
|
+
log.info("Initialized", { projectId: config.projectId });
|
|
1298
|
+
async function submitReport(category, description, intent) {
|
|
1299
|
+
const filterResult = preFilter.check(description);
|
|
1300
|
+
if (!filterResult.passed) {
|
|
1301
|
+
log.info("Report blocked by pre-filter", { reason: filterResult.reason });
|
|
1302
|
+
return;
|
|
1303
|
+
}
|
|
1304
|
+
if (!rateLimiter.tryConsume()) {
|
|
1305
|
+
log.warn("Report throttled \u2014 rate limit exceeded");
|
|
1306
|
+
return;
|
|
1307
|
+
}
|
|
1308
|
+
const scrubbedDescription = piiScrubber.scrub(preFilter.truncate(description));
|
|
1309
|
+
const sentryCtx = config.sentry ? captureSentryContext(config.sentry) : void 0;
|
|
1310
|
+
const report = {
|
|
1311
|
+
id: crypto.randomUUID?.() ?? `mushi_${Date.now()}_${Math.random().toString(36).slice(2)}`,
|
|
1312
|
+
projectId: config.projectId,
|
|
1313
|
+
category,
|
|
1314
|
+
description: scrubbedDescription,
|
|
1315
|
+
userIntent: intent,
|
|
1316
|
+
environment: captureEnvironment(),
|
|
1317
|
+
consoleLogs: consoleCap?.getEntries(),
|
|
1318
|
+
networkLogs: networkCap?.getEntries(),
|
|
1319
|
+
performanceMetrics: perfCap?.getMetrics(),
|
|
1320
|
+
screenshotDataUrl: pendingScreenshot ?? void 0,
|
|
1321
|
+
selectedElement: pendingElement ?? void 0,
|
|
1322
|
+
metadata: {
|
|
1323
|
+
...customMetadata,
|
|
1324
|
+
...userInfo ? { user: userInfo } : {},
|
|
1325
|
+
...sentryCtx?.release ? { sentryRelease: sentryCtx.release } : {}
|
|
1326
|
+
},
|
|
1327
|
+
sessionId: getSessionId(),
|
|
1328
|
+
reporterToken: getReporterToken(),
|
|
1329
|
+
appVersion: config.integrations?.vercel?.analyticsId,
|
|
1330
|
+
sentryEventId: sentryCtx?.eventId,
|
|
1331
|
+
sentryReplayId: sentryCtx?.replayId,
|
|
1332
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1333
|
+
};
|
|
1334
|
+
if (config.integrations?.custom) {
|
|
1335
|
+
const builder = {
|
|
1336
|
+
addMetadata(key, value) {
|
|
1337
|
+
report.metadata[key] = value;
|
|
1338
|
+
},
|
|
1339
|
+
setCategory(cat) {
|
|
1340
|
+
report.category = cat;
|
|
1341
|
+
},
|
|
1342
|
+
setDescription(desc) {
|
|
1343
|
+
report.description = desc;
|
|
1344
|
+
}
|
|
1345
|
+
};
|
|
1346
|
+
config.integrations.custom(builder);
|
|
1347
|
+
}
|
|
1348
|
+
emit("report:submitted", { reportId: report.id });
|
|
1349
|
+
if (typeof navigator !== "undefined" && !navigator.onLine) {
|
|
1350
|
+
await offlineQueue.enqueue(report);
|
|
1351
|
+
log.info("Offline \u2014 report queued", { reportId: report.id });
|
|
1352
|
+
emit("report:queued", { reportId: report.id });
|
|
1353
|
+
return;
|
|
1354
|
+
}
|
|
1355
|
+
const result = await apiClient.submitReport(report);
|
|
1356
|
+
if (result.ok) {
|
|
1357
|
+
log.info("Report sent", { reportId: result.data?.reportId });
|
|
1358
|
+
emit("report:sent", { reportId: result.data?.reportId });
|
|
1359
|
+
} else {
|
|
1360
|
+
log.warn("Report failed, queuing for retry", { reportId: report.id, error: result.error });
|
|
1361
|
+
await offlineQueue.enqueue(report);
|
|
1362
|
+
emit("report:failed", { reportId: report.id, error: result.error });
|
|
1363
|
+
}
|
|
1364
|
+
pendingScreenshot = null;
|
|
1365
|
+
pendingElement = null;
|
|
1366
|
+
}
|
|
1367
|
+
const sdk = {
|
|
1368
|
+
report() {
|
|
1369
|
+
widget.open();
|
|
1370
|
+
},
|
|
1371
|
+
on(event, handler) {
|
|
1372
|
+
if (!listeners.has(event)) listeners.set(event, /* @__PURE__ */ new Set());
|
|
1373
|
+
listeners.get(event).add(handler);
|
|
1374
|
+
return () => listeners.get(event)?.delete(handler);
|
|
1375
|
+
},
|
|
1376
|
+
setUser(user) {
|
|
1377
|
+
userInfo = user;
|
|
1378
|
+
},
|
|
1379
|
+
setMetadata(key, value) {
|
|
1380
|
+
customMetadata[key] = value;
|
|
1381
|
+
},
|
|
1382
|
+
isOpen() {
|
|
1383
|
+
return widget.getIsOpen();
|
|
1384
|
+
},
|
|
1385
|
+
open() {
|
|
1386
|
+
widget.open();
|
|
1387
|
+
},
|
|
1388
|
+
close() {
|
|
1389
|
+
widget.close();
|
|
1390
|
+
},
|
|
1391
|
+
destroy() {
|
|
1392
|
+
widget.destroy();
|
|
1393
|
+
consoleCap?.destroy();
|
|
1394
|
+
networkCap?.destroy();
|
|
1395
|
+
perfCap?.destroy();
|
|
1396
|
+
elementSelector?.deactivate();
|
|
1397
|
+
offlineQueue.stopAutoSync();
|
|
1398
|
+
listeners.clear();
|
|
1399
|
+
instance = null;
|
|
1400
|
+
log.debug("Destroyed");
|
|
1401
|
+
}
|
|
1402
|
+
};
|
|
1403
|
+
return sdk;
|
|
1404
|
+
}
|
|
1405
|
+
function createNoopInstance() {
|
|
1406
|
+
return {
|
|
1407
|
+
report: () => {
|
|
1408
|
+
},
|
|
1409
|
+
on: () => () => {
|
|
1410
|
+
},
|
|
1411
|
+
setUser: () => {
|
|
1412
|
+
},
|
|
1413
|
+
setMetadata: () => {
|
|
1414
|
+
},
|
|
1415
|
+
isOpen: () => false,
|
|
1416
|
+
open: () => {
|
|
1417
|
+
},
|
|
1418
|
+
close: () => {
|
|
1419
|
+
},
|
|
1420
|
+
destroy: () => {
|
|
1421
|
+
instance = null;
|
|
1422
|
+
}
|
|
1423
|
+
};
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
// src/proactive-manager.ts
|
|
1427
|
+
var STORAGE_KEY_LAST_DISMISS = "mushi:lastDismiss";
|
|
1428
|
+
var STORAGE_KEY_CONSEC_DISMISS = "mushi:consecDismiss";
|
|
1429
|
+
function readStorage(key) {
|
|
1430
|
+
try {
|
|
1431
|
+
return localStorage.getItem(key);
|
|
1432
|
+
} catch {
|
|
1433
|
+
return null;
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
function writeStorage(key, value) {
|
|
1437
|
+
try {
|
|
1438
|
+
localStorage.setItem(key, value);
|
|
1439
|
+
} catch {
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
function createProactiveManager(config = {}) {
|
|
1443
|
+
const maxPerSession = config.maxProactivePerSession ?? 2;
|
|
1444
|
+
const cooldownHours = config.dismissCooldownHours ?? 24;
|
|
1445
|
+
const suppressThreshold = config.suppressAfterDismissals ?? 3;
|
|
1446
|
+
let sessionPromptCount = 0;
|
|
1447
|
+
const sessionTriggerTypes = /* @__PURE__ */ new Set();
|
|
1448
|
+
function shouldShow(triggerType) {
|
|
1449
|
+
const consecDismissals = parseInt(readStorage(STORAGE_KEY_CONSEC_DISMISS) ?? "0", 10);
|
|
1450
|
+
if (consecDismissals >= suppressThreshold) return false;
|
|
1451
|
+
const lastDismiss = readStorage(STORAGE_KEY_LAST_DISMISS);
|
|
1452
|
+
if (lastDismiss) {
|
|
1453
|
+
const elapsed = Date.now() - parseInt(lastDismiss, 10);
|
|
1454
|
+
if (elapsed < cooldownHours * 60 * 60 * 1e3) return false;
|
|
1455
|
+
}
|
|
1456
|
+
if (sessionPromptCount >= maxPerSession) return false;
|
|
1457
|
+
if (sessionTriggerTypes.has(triggerType)) return false;
|
|
1458
|
+
sessionTriggerTypes.add(triggerType);
|
|
1459
|
+
sessionPromptCount++;
|
|
1460
|
+
return true;
|
|
1461
|
+
}
|
|
1462
|
+
function recordDismissal() {
|
|
1463
|
+
writeStorage(STORAGE_KEY_LAST_DISMISS, String(Date.now()));
|
|
1464
|
+
const current = parseInt(readStorage(STORAGE_KEY_CONSEC_DISMISS) ?? "0", 10);
|
|
1465
|
+
writeStorage(STORAGE_KEY_CONSEC_DISMISS, String(current + 1));
|
|
1466
|
+
}
|
|
1467
|
+
function recordSubmission() {
|
|
1468
|
+
writeStorage(STORAGE_KEY_CONSEC_DISMISS, "0");
|
|
1469
|
+
}
|
|
1470
|
+
function reset() {
|
|
1471
|
+
sessionPromptCount = 0;
|
|
1472
|
+
sessionTriggerTypes.clear();
|
|
1473
|
+
}
|
|
1474
|
+
return { shouldShow, recordDismissal, recordSubmission, reset };
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
// src/proactive-triggers.ts
|
|
1478
|
+
function setupProactiveTriggers(callbacks) {
|
|
1479
|
+
const cleanups = [];
|
|
1480
|
+
let clickTimes = [];
|
|
1481
|
+
let lastClickTarget = null;
|
|
1482
|
+
function handleClick(e) {
|
|
1483
|
+
const now = Date.now();
|
|
1484
|
+
if (e.target === lastClickTarget) {
|
|
1485
|
+
clickTimes.push(now);
|
|
1486
|
+
clickTimes = clickTimes.filter((t) => now - t < 500);
|
|
1487
|
+
if (clickTimes.length >= 3) {
|
|
1488
|
+
const el = e.target;
|
|
1489
|
+
callbacks.onTrigger("rage_click", {
|
|
1490
|
+
element: el.tagName,
|
|
1491
|
+
id: el.id,
|
|
1492
|
+
text: el.textContent?.slice(0, 50)
|
|
1493
|
+
});
|
|
1494
|
+
clickTimes = [];
|
|
1495
|
+
}
|
|
1496
|
+
} else {
|
|
1497
|
+
lastClickTarget = e.target;
|
|
1498
|
+
clickTimes = [now];
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
document.addEventListener("click", handleClick, true);
|
|
1502
|
+
cleanups.push(() => document.removeEventListener("click", handleClick, true));
|
|
1503
|
+
if (typeof PerformanceObserver !== "undefined") {
|
|
1504
|
+
try {
|
|
1505
|
+
const observer = new PerformanceObserver((list) => {
|
|
1506
|
+
for (const entry of list.getEntries()) {
|
|
1507
|
+
if (entry.duration > 5e3) {
|
|
1508
|
+
callbacks.onTrigger("long_task", {
|
|
1509
|
+
duration: Math.round(entry.duration),
|
|
1510
|
+
startTime: Math.round(entry.startTime)
|
|
1511
|
+
});
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
});
|
|
1515
|
+
observer.observe({ entryTypes: ["longtask"] });
|
|
1516
|
+
cleanups.push(() => observer.disconnect());
|
|
1517
|
+
} catch {
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
const failedRequests = [];
|
|
1521
|
+
const origFetch = globalThis.fetch;
|
|
1522
|
+
globalThis.fetch = async function(...args) {
|
|
1523
|
+
try {
|
|
1524
|
+
const res = await origFetch.apply(this, args);
|
|
1525
|
+
if (!res.ok && res.status >= 400) {
|
|
1526
|
+
const now = Date.now();
|
|
1527
|
+
failedRequests.push(now);
|
|
1528
|
+
const recentFailures = failedRequests.filter((t) => now - t < 1e4);
|
|
1529
|
+
if (recentFailures.length >= 3) {
|
|
1530
|
+
callbacks.onTrigger("api_cascade", {
|
|
1531
|
+
failureCount: recentFailures.length,
|
|
1532
|
+
windowMs: 1e4
|
|
1533
|
+
});
|
|
1534
|
+
failedRequests.length = 0;
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
return res;
|
|
1538
|
+
} catch (err) {
|
|
1539
|
+
const now = Date.now();
|
|
1540
|
+
failedRequests.push(now);
|
|
1541
|
+
const recentFailures = failedRequests.filter((t) => now - t < 1e4);
|
|
1542
|
+
if (recentFailures.length >= 3) {
|
|
1543
|
+
callbacks.onTrigger("api_cascade", {
|
|
1544
|
+
failureCount: recentFailures.length,
|
|
1545
|
+
windowMs: 1e4
|
|
1546
|
+
});
|
|
1547
|
+
failedRequests.length = 0;
|
|
1548
|
+
}
|
|
1549
|
+
throw err;
|
|
1550
|
+
}
|
|
1551
|
+
};
|
|
1552
|
+
cleanups.push(() => {
|
|
1553
|
+
globalThis.fetch = origFetch;
|
|
1554
|
+
});
|
|
1555
|
+
return {
|
|
1556
|
+
destroy() {
|
|
1557
|
+
cleanups.forEach((fn) => fn());
|
|
1558
|
+
}
|
|
1559
|
+
};
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
export { Mushi, MushiWidget, createConsoleCapture, createElementSelector, createNetworkCapture, createPerformanceCapture, createProactiveManager, createScreenshotCapture, getAvailableLocales, getLocale, setupProactiveTriggers };
|
|
1563
|
+
//# sourceMappingURL=index.js.map
|
|
1564
|
+
//# sourceMappingURL=index.js.map
|