makethisbetter 1.2.0 → 1.3.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/dist/i18n/de.d.ts +2 -0
- package/dist/i18n/de.d.ts.map +1 -1
- package/dist/i18n/en.d.ts +2 -0
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/es.d.ts +2 -0
- package/dist/i18n/es.d.ts.map +1 -1
- package/dist/i18n/fr.d.ts +2 -0
- package/dist/i18n/fr.d.ts.map +1 -1
- package/dist/i18n/ja.d.ts +2 -0
- package/dist/i18n/ja.d.ts.map +1 -1
- package/dist/i18n/ko.d.ts +2 -0
- package/dist/i18n/ko.d.ts.map +1 -1
- package/dist/i18n/zh-CN.d.ts +2 -0
- package/dist/i18n/zh-CN.d.ts.map +1 -1
- package/dist/makethisbetter.cjs.js +19 -19
- package/dist/makethisbetter.cjs.js.map +1 -1
- package/dist/makethisbetter.esm.js +261 -243
- package/dist/makethisbetter.esm.js.map +1 -1
- package/dist/makethisbetter.js +17 -17
- package/dist/makethisbetter.js.map +1 -1
- package/dist/popup/success.d.ts +4 -1
- package/dist/popup/success.d.ts.map +1 -1
- package/dist/record/session.cdn.test.d.ts +2 -0
- package/dist/record/session.cdn.test.d.ts.map +1 -0
- package/dist/record/session.d.ts.map +1 -1
- package/dist/types.d.ts +1 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/widget/controller.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/i18n/de.ts +2 -0
- package/src/i18n/en.ts +2 -0
- package/src/i18n/es.ts +2 -0
- package/src/i18n/fr.ts +2 -0
- package/src/i18n/ja.ts +2 -0
- package/src/i18n/ko.ts +2 -0
- package/src/i18n/zh-CN.ts +2 -0
- package/src/popup/success.test.ts +13 -0
- package/src/popup/success.ts +10 -3
- package/src/record/session.cdn.test.ts +89 -0
- package/src/record/session.ts +21 -7
- package/src/types.ts +1 -0
- package/src/widget/controller.test.ts +4 -1
- package/src/widget/controller.ts +8 -5
package/dist/popup/success.d.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import type { ShadowContainer } from '../widget/shadow';
|
|
2
2
|
import type { I18nMessages } from '../i18n';
|
|
3
|
+
export interface SuccessCardOptions {
|
|
4
|
+
skipFollowup?: boolean;
|
|
5
|
+
}
|
|
3
6
|
export declare class SuccessCard {
|
|
4
7
|
private el;
|
|
5
|
-
constructor(shadow: ShadowContainer, messages: I18nMessages, onClose: () => void, onViewFeedback?: () => void);
|
|
8
|
+
constructor(shadow: ShadowContainer, messages: I18nMessages, onClose: () => void, onViewFeedback?: () => void, options?: SuccessCardOptions);
|
|
6
9
|
destroy(): void;
|
|
7
10
|
}
|
|
8
11
|
//# sourceMappingURL=success.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"success.d.ts","sourceRoot":"","sources":["../../src/popup/success.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AACvD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAK3C,qBAAa,WAAW;IACtB,OAAO,CAAC,EAAE,CAAgB;gBAEd,MAAM,EAAE,eAAe,EAAE,QAAQ,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,IAAI,EAAE,cAAc,CAAC,EAAE,MAAM,IAAI;
|
|
1
|
+
{"version":3,"file":"success.d.ts","sourceRoot":"","sources":["../../src/popup/success.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AACvD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAK3C,MAAM,WAAW,kBAAkB;IACjC,YAAY,CAAC,EAAE,OAAO,CAAA;CACvB;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,EAAE,CAAgB;gBAEd,MAAM,EAAE,eAAe,EAAE,QAAQ,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,IAAI,EAAE,cAAc,CAAC,EAAE,MAAM,IAAI,EAAE,OAAO,CAAC,EAAE,kBAAkB;IAiC3I,OAAO,IAAI,IAAI;CAGhB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.cdn.test.d.ts","sourceRoot":"","sources":["../../src/record/session.cdn.test.ts"],"names":[],"mappings":""}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/record/session.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AAEvD,KAAK,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AAQzC,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,UAAU,EAAE,CAAA;IACpB,QAAQ,EAAE,MAAM,CAAA;CACjB;
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/record/session.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AAEvD,KAAK,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AAQzC,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,UAAU,EAAE,CAAA;IACpB,QAAQ,EAAE,MAAM,CAAA;CACjB;AAuDD,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAmB;IACjC,OAAO,CAAC,MAAM,CAAsB;IACpC,OAAO,CAAC,SAAS,CAAI;IACrB,OAAO,CAAC,KAAK,CAA6C;IAC1D,OAAO,CAAC,YAAY,CAAyC;IAC7D,OAAO,CAAC,MAAM,CAAiB;IAC/B,OAAO,CAAC,YAAY,CAAC,CAAY;IACjC,OAAO,CAAC,OAAO,CAAQ;gBAEX,MAAM,EAAE,eAAe,EAAE,YAAY,CAAC,EAAE,MAAM,IAAI;IAKxD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IA2B5B,IAAI,IAAI,eAAe;IAavB,WAAW,IAAI,MAAM;IAKrB,OAAO,IAAI,IAAI;IAUf,OAAO,CAAC,YAAY;IAOpB,OAAO,CAAC,mBAAmB;IAO3B,OAAO,CAAC,eAAe;CAOxB"}
|
package/dist/types.d.ts
CHANGED
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,oBAAoB;IACnC,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,QAAQ,CAAA;IACpB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;IAC3B,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,CAAA;IACjC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,oBAAoB,CAAC,EAAE,OAAO,CAAA;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAA;IACnC,IAAI,CAAC,EAAE;QACL,EAAE,CAAC,EAAE,MAAM,CAAA;QACX,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,IAAI,CAAC,EAAE,MAAM,CAAA;KACd,CAAA;CACF;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,KAAK,GAAG,MAAM,CAAA;IACpB,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;IACT,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,MAAM,CAAA;IAClB,OAAO,EAAE,MAAM,CAAA;IACf,EAAE,EAAE,MAAM,CAAA;IACV,YAAY,EAAE,MAAM,CAAA;IACpB,aAAa,EAAE,MAAM,CAAA;IACrB,cAAc,EAAE,MAAM,EAAE,CAAA;IACxB,WAAW,EAAE,UAAU,EAAE,CAAA;IACzB,cAAc,CAAC,EAAE;QACf,QAAQ,EAAE,MAAM,CAAA;QAChB,IAAI,EAAE,MAAM,CAAA;QACZ,IAAI,EAAE,MAAM,CAAA;KACb,CAAA;IACD,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAA;IAC5C,kBAAkB,CAAC,EAAE,MAAM,CAAA;CAC5B;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,EAAE,MAAM,CAAA;IAClB,oBAAoB,CAAC,EAAE,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,oBAAoB;IACnC,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,QAAQ,CAAA;IACpB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;IAC3B,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,CAAA;IACjC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,oBAAoB,CAAC,EAAE,OAAO,CAAA;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAA;IACnC,IAAI,CAAC,EAAE;QACL,EAAE,CAAC,EAAE,MAAM,CAAA;QACX,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,IAAI,CAAC,EAAE,MAAM,CAAA;KACd,CAAA;CACF;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,KAAK,GAAG,MAAM,CAAA;IACpB,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;IACT,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,MAAM,CAAA;IAClB,OAAO,EAAE,MAAM,CAAA;IACf,EAAE,EAAE,MAAM,CAAA;IACV,YAAY,EAAE,MAAM,CAAA;IACpB,aAAa,EAAE,MAAM,CAAA;IACrB,cAAc,EAAE,MAAM,EAAE,CAAA;IACxB,WAAW,EAAE,UAAU,EAAE,CAAA;IACzB,cAAc,CAAC,EAAE;QACf,QAAQ,EAAE,MAAM,CAAA;QAChB,IAAI,EAAE,MAAM,CAAA;QACZ,IAAI,EAAE,MAAM,CAAA;KACb,CAAA;IACD,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAA;IAC5C,kBAAkB,CAAC,EAAE,MAAM,CAAA;CAC5B;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,EAAE,MAAM,CAAA;IAClB,oBAAoB,CAAC,EAAE,OAAO,CAAA;IAC9B,aAAa,CAAC,EAAE,OAAO,CAAA;CACxB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,cAAc,EAAE,CAAA;IAC1B,IAAI,EAAE,OAAO,CAAA;CACd"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../src/widget/controller.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../src/widget/controller.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,oBAAoB,EAAc,MAAM,UAAU,CAAA;AAIhE,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,MAAM,CAAsB;IACpC,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,GAAG,CAA2B;IACtC,OAAO,CAAC,OAAO,CAAiC;IAChD,OAAO,CAAC,OAAO,CAAiC;IAChD,OAAO,CAAC,KAAK,CAA4B;IACzC,OAAO,CAAC,OAAO,CAA2B;IAC1C,OAAO,CAAC,OAAO,CAA2B;IAC1C,OAAO,CAAC,iBAAiB,CAAqC;IAC9D,OAAO,CAAC,SAAS,CAAW;IAC5B,OAAO,CAAC,gBAAgB,CAAuB;IAC/C,OAAO,CAAC,mBAAmB,CAAmC;IAC9D,OAAO,CAAC,QAAQ,CAAgC;IAChD,OAAO,CAAC,IAAI,CAAe;IAC3B,OAAO,CAAC,iBAAiB,CAA0B;IACnD,OAAO,CAAC,gBAAgB,CAAgC;gBAE5C,MAAM,EAAE,oBAAoB;IAcxC,OAAO,CAAC,OAAO;IAYf,OAAO,CAAC,cAAc;IAStB,OAAO,CAAC,iBAAiB;IAYzB,OAAO,CAAC,wBAAwB;IAKhC,OAAO,CAAC,mBAAmB;IAgB3B,OAAO,CAAC,uBAAuB;IA8B/B,OAAO,CAAC,SAAS;YAcH,YAAY;IAwC1B,OAAO,CAAC,WAAW;IAenB,OAAO,CAAC,WAAW;IAiBnB,OAAO,CAAC,OAAO;IAgBf,OAAO,IAAI,IAAI;CAWhB"}
|
package/package.json
CHANGED
package/src/i18n/de.ts
CHANGED
|
@@ -22,6 +22,8 @@ export default {
|
|
|
22
22
|
success: {
|
|
23
23
|
title: 'Vielen Dank!',
|
|
24
24
|
message: 'Ihr Feedback wurde empfangen.',
|
|
25
|
+
title_no_ai: 'Gesendet!',
|
|
26
|
+
message_no_ai: 'Wir kümmern uns darum. Sie werden benachrichtigt, sobald es gelöst ist.',
|
|
25
27
|
view_feedback: 'Mein Feedback ansehen',
|
|
26
28
|
close: 'Schließen',
|
|
27
29
|
},
|
package/src/i18n/en.ts
CHANGED
|
@@ -22,6 +22,8 @@ export default {
|
|
|
22
22
|
success: {
|
|
23
23
|
title: 'Thanks!',
|
|
24
24
|
message: 'Your feedback has been received.',
|
|
25
|
+
title_no_ai: 'Sent — thanks!',
|
|
26
|
+
message_no_ai: "We'll take it from here. You'll hear back when it's resolved.",
|
|
25
27
|
view_feedback: 'View my feedback',
|
|
26
28
|
close: 'Close',
|
|
27
29
|
},
|
package/src/i18n/es.ts
CHANGED
|
@@ -22,6 +22,8 @@ export default {
|
|
|
22
22
|
success: {
|
|
23
23
|
title: '¡Gracias!',
|
|
24
24
|
message: 'Tu comentario ha sido recibido.',
|
|
25
|
+
title_no_ai: '¡Enviado!',
|
|
26
|
+
message_no_ai: 'Nos encargamos. Te avisaremos cuando se resuelva.',
|
|
25
27
|
view_feedback: 'Ver mis comentarios',
|
|
26
28
|
close: 'Cerrar',
|
|
27
29
|
},
|
package/src/i18n/fr.ts
CHANGED
|
@@ -22,6 +22,8 @@ export default {
|
|
|
22
22
|
success: {
|
|
23
23
|
title: 'Merci !',
|
|
24
24
|
message: 'Votre avis a bien été reçu.',
|
|
25
|
+
title_no_ai: 'Envoyé !',
|
|
26
|
+
message_no_ai: "On s'en occupe. Vous serez informé(e) dès que ce sera résolu.",
|
|
25
27
|
view_feedback: 'Voir mes avis',
|
|
26
28
|
close: 'Fermer',
|
|
27
29
|
},
|
package/src/i18n/ja.ts
CHANGED
package/src/i18n/ko.ts
CHANGED
package/src/i18n/zh-CN.ts
CHANGED
|
@@ -72,4 +72,17 @@ describe('SuccessCard', () => {
|
|
|
72
72
|
|
|
73
73
|
shadow.destroy()
|
|
74
74
|
})
|
|
75
|
+
|
|
76
|
+
it('shows depleted copy when skipFollowup is true', () => {
|
|
77
|
+
const shadow = new ShadowContainer()
|
|
78
|
+
const onClose = vi.fn()
|
|
79
|
+
const messages = getMessages('en')
|
|
80
|
+
const card = new SuccessCard(shadow, messages, onClose, undefined, { skipFollowup: true })
|
|
81
|
+
|
|
82
|
+
expect(shadow.root.querySelector('.mtb-success-title')?.textContent).toBe(messages.success.title_no_ai)
|
|
83
|
+
expect(shadow.root.querySelector('.mtb-success-msg')?.textContent).toBe(messages.success.message_no_ai)
|
|
84
|
+
|
|
85
|
+
card.destroy()
|
|
86
|
+
shadow.destroy()
|
|
87
|
+
})
|
|
75
88
|
})
|
package/src/popup/success.ts
CHANGED
|
@@ -4,14 +4,21 @@ import { BRAND_COLOR } from '../styles'
|
|
|
4
4
|
|
|
5
5
|
const SUCCESS_AUTO_CLOSE_MS = 5000
|
|
6
6
|
|
|
7
|
+
export interface SuccessCardOptions {
|
|
8
|
+
skipFollowup?: boolean
|
|
9
|
+
}
|
|
10
|
+
|
|
7
11
|
export class SuccessCard {
|
|
8
12
|
private el: HTMLDivElement
|
|
9
13
|
|
|
10
|
-
constructor(shadow: ShadowContainer, messages: I18nMessages, onClose: () => void, onViewFeedback?: () => void) {
|
|
14
|
+
constructor(shadow: ShadowContainer, messages: I18nMessages, onClose: () => void, onViewFeedback?: () => void, options?: SuccessCardOptions) {
|
|
11
15
|
this.el = shadow.el<HTMLDivElement>('div', 'mtb-success')
|
|
12
16
|
this.el.style.right = '20px'
|
|
13
17
|
this.el.style.bottom = '20px'
|
|
14
18
|
|
|
19
|
+
const title = options?.skipFollowup ? messages.success.title_no_ai : messages.success.title
|
|
20
|
+
const msg = options?.skipFollowup ? messages.success.message_no_ai : messages.success.message
|
|
21
|
+
|
|
15
22
|
const viewLink = onViewFeedback
|
|
16
23
|
? `<button class="mtb-view-feedback-link">${messages.success.view_feedback}</button>`
|
|
17
24
|
: ''
|
|
@@ -22,8 +29,8 @@ export class SuccessCard {
|
|
|
22
29
|
<polyline points="20 6 9 17 4 12"/>
|
|
23
30
|
</svg>
|
|
24
31
|
</div>
|
|
25
|
-
<div class="mtb-success-title">${
|
|
26
|
-
<div class="mtb-success-msg">${
|
|
32
|
+
<div class="mtb-success-title">${title}</div>
|
|
33
|
+
<div class="mtb-success-msg">${msg}</div>
|
|
27
34
|
${viewLink}
|
|
28
35
|
<button class="mtb-close-link">${messages.success.close}</button>
|
|
29
36
|
`
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
|
|
2
|
+
import type { RecordSession } from './session'
|
|
3
|
+
import type { ShadowContainer } from '../widget/shadow'
|
|
4
|
+
|
|
5
|
+
const mockStopFn = vi.fn()
|
|
6
|
+
const mockRecord = Object.assign(
|
|
7
|
+
vi.fn(() => mockStopFn),
|
|
8
|
+
{ addCustomEvent: vi.fn() },
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
// Force the bundler import path to fail so start() exercises the CDN fallback,
|
|
12
|
+
// mirroring the IIFE/CDN build where the bare specifier is unresolvable.
|
|
13
|
+
vi.mock('rrweb', () => {
|
|
14
|
+
throw new Error('rrweb not installed')
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
function createMockShadow(): ShadowContainer {
|
|
18
|
+
return {
|
|
19
|
+
el: <T extends HTMLElement>(tag: string, className?: string): T => {
|
|
20
|
+
const el = document.createElement(tag) as T
|
|
21
|
+
if (className) el.className = className
|
|
22
|
+
return el
|
|
23
|
+
},
|
|
24
|
+
append: vi.fn(),
|
|
25
|
+
remove: vi.fn(),
|
|
26
|
+
root: document.createElement('div') as unknown as ShadowRoot,
|
|
27
|
+
destroy: vi.fn(),
|
|
28
|
+
} as unknown as ShadowContainer
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// session.ts caches the rrweb loader promise at module level, so each test
|
|
32
|
+
// re-imports a fresh module instance.
|
|
33
|
+
async function freshSession(): Promise<RecordSession> {
|
|
34
|
+
vi.resetModules()
|
|
35
|
+
const { RecordSession: Cls } = await import('./session')
|
|
36
|
+
return new Cls(createMockShadow())
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
describe('RecordSession CDN fallback', () => {
|
|
40
|
+
let session: RecordSession | null = null
|
|
41
|
+
|
|
42
|
+
beforeEach(() => {
|
|
43
|
+
vi.clearAllMocks()
|
|
44
|
+
delete (globalThis as Record<string, unknown>).rrwebRecord
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
afterEach(() => {
|
|
48
|
+
session?.destroy()
|
|
49
|
+
session = null
|
|
50
|
+
vi.restoreAllMocks()
|
|
51
|
+
delete (globalThis as Record<string, unknown>).rrwebRecord
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('loads @rrweb/record UMD build via classic script tag', async () => {
|
|
55
|
+
const appended: HTMLScriptElement[] = []
|
|
56
|
+
vi.spyOn(document.head, 'appendChild').mockImplementation((node) => {
|
|
57
|
+
const script = node as HTMLScriptElement
|
|
58
|
+
appended.push(script)
|
|
59
|
+
// Simulate the UMD script executing: it sets the global, then load fires
|
|
60
|
+
;(globalThis as Record<string, unknown>).rrwebRecord = { record: mockRecord }
|
|
61
|
+
script.onload?.(new Event('load'))
|
|
62
|
+
return node
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
session = await freshSession()
|
|
66
|
+
await session.start()
|
|
67
|
+
|
|
68
|
+
expect(appended).toHaveLength(1)
|
|
69
|
+
const script = appended[0]
|
|
70
|
+
// rrweb 2.x publishes no UMD build in the `rrweb` package — its dist files
|
|
71
|
+
// are ESM and throw "Unexpected token 'export'" in a classic script tag.
|
|
72
|
+
// The record-only UMD lives in @rrweb/record's static /umd/ directory.
|
|
73
|
+
expect(script.src).toContain('/npm/@rrweb/record@')
|
|
74
|
+
expect(script.src).toContain('/umd/record.min.js')
|
|
75
|
+
expect(script.integrity).toMatch(/^sha384-/)
|
|
76
|
+
expect(script.crossOrigin).toBe('anonymous')
|
|
77
|
+
expect(mockRecord).toHaveBeenCalledOnce()
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('rejects when the CDN script exposes no rrwebRecord global', async () => {
|
|
81
|
+
vi.spyOn(document.head, 'appendChild').mockImplementation((node) => {
|
|
82
|
+
;(node as HTMLScriptElement).onload?.(new Event('load'))
|
|
83
|
+
return node
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
session = await freshSession()
|
|
87
|
+
await expect(session.start()).rejects.toThrow(/not found after script load/)
|
|
88
|
+
})
|
|
89
|
+
})
|
package/src/record/session.ts
CHANGED
|
@@ -18,8 +18,12 @@ const MAX_DURATION_S = 60
|
|
|
18
18
|
// floating tag or unverified content would be a supply-chain hole. Bumping
|
|
19
19
|
// the version requires recomputing the hash from the exact CDN file:
|
|
20
20
|
// curl -sL <url> | openssl dgst -sha384 -binary | openssl base64 -A
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
// Must stay on @rrweb/record's static /umd/ build: the `rrweb` package ships
|
|
22
|
+
// no UMD in 2.x (its dist files are ESM and throw "Unexpected token 'export'"
|
|
23
|
+
// in a classic script tag), and jsDelivr's auto-minified files are unsafe for
|
|
24
|
+
// SRI because their hash changes when jsDelivr upgrades its minifier.
|
|
25
|
+
const RRWEB_CDN = 'https://cdn.jsdelivr.net/npm/@rrweb/record@2.1.0/umd/record.min.js'
|
|
26
|
+
const RRWEB_SRI = 'sha384-MrD66HBNSykaP2N95+6hQCFlF5oH2tvL3TD/zyvHNkP/sAFWZx98DX9MEDy8MdVT'
|
|
23
27
|
|
|
24
28
|
let rrwebLoaded: Promise<RRWebRecordFn> | null = null
|
|
25
29
|
|
|
@@ -34,18 +38,28 @@ async function loadViaImport(): Promise<RRWebRecordFn> {
|
|
|
34
38
|
return mod.record as unknown as RRWebRecordFn
|
|
35
39
|
}
|
|
36
40
|
|
|
41
|
+
interface RRWebGlobals {
|
|
42
|
+
rrwebRecord?: { record: RRWebRecordFn }
|
|
43
|
+
rrweb?: { record: RRWebRecordFn }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function findGlobalRecord(): RRWebRecordFn | undefined {
|
|
47
|
+
const win = globalThis as unknown as Window & RRWebGlobals
|
|
48
|
+
return win.rrwebRecord?.record ?? win.rrweb?.record
|
|
49
|
+
}
|
|
50
|
+
|
|
37
51
|
function loadViaCdn(): Promise<RRWebRecordFn> {
|
|
38
52
|
return new Promise((resolve, reject) => {
|
|
39
|
-
const
|
|
40
|
-
if (
|
|
53
|
+
const existing = findGlobalRecord()
|
|
54
|
+
if (existing) { resolve(existing); return }
|
|
41
55
|
const script = document.createElement('script')
|
|
42
56
|
script.src = RRWEB_CDN
|
|
43
57
|
script.integrity = RRWEB_SRI
|
|
44
58
|
script.crossOrigin = 'anonymous'
|
|
45
59
|
script.onload = () => {
|
|
46
|
-
const
|
|
47
|
-
if (
|
|
48
|
-
else reject(new Error('rrweb global not found after script load'))
|
|
60
|
+
const record = findGlobalRecord()
|
|
61
|
+
if (record) resolve(record)
|
|
62
|
+
else reject(new Error('rrweb record global not found after script load'))
|
|
49
63
|
}
|
|
50
64
|
script.onerror = () => reject(new Error('Failed to load rrweb from CDN'))
|
|
51
65
|
document.head.appendChild(script)
|
package/src/types.ts
CHANGED
|
@@ -92,9 +92,12 @@ describe('MakeThisBetter widget flow', () => {
|
|
|
92
92
|
shadow.querySelector<HTMLButtonElement>('.mtb-submit-btn')!.click()
|
|
93
93
|
|
|
94
94
|
await vi.waitFor(() => {
|
|
95
|
-
expect(shadow.querySelector('.mtb-success-title')?.textContent).toBe('
|
|
95
|
+
expect(shadow.querySelector('.mtb-success-title')?.textContent).toBe('Sent — thanks!')
|
|
96
96
|
}, { timeout: 5000 })
|
|
97
97
|
|
|
98
|
+
expect(shadow.querySelector('.mtb-success-msg')?.textContent).toBe(
|
|
99
|
+
"We'll take it from here. You'll hear back when it's resolved."
|
|
100
|
+
)
|
|
98
101
|
expect(shadow.querySelector('.mtb-clarify')).toBeNull()
|
|
99
102
|
expect(fetchSpy).toHaveBeenCalledTimes(1)
|
|
100
103
|
})
|
package/src/widget/controller.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { AnnotationSession } from '../annotate/session'
|
|
|
7
7
|
import { CommentPopup } from '../popup/comment'
|
|
8
8
|
import { ClarifyCard } from '../popup/clarify'
|
|
9
9
|
import { SuccessCard } from '../popup/success'
|
|
10
|
+
import type { SuccessCardOptions } from '../popup/success'
|
|
10
11
|
import { captureScreenshot } from '../screenshot/capture'
|
|
11
12
|
import { ConsoleErrorCollector } from '../context/console'
|
|
12
13
|
import { FrustrationDetector } from '../context/frustration'
|
|
@@ -115,8 +116,9 @@ export class WidgetController {
|
|
|
115
116
|
if (!this.recordingManager) return
|
|
116
117
|
this.recordingManager.stop()
|
|
117
118
|
this.showPopup(window.innerWidth / 2, window.innerHeight / 2, this.messages.record.timer_label)
|
|
118
|
-
}).catch(() => {
|
|
119
|
+
}).catch((error) => {
|
|
119
120
|
// rrweb failed to load (network / SRI mismatch) — fall back to markup
|
|
121
|
+
console.warn('[MakeThisBetter] Screen recording unavailable, falling back to markup:', error)
|
|
120
122
|
if (this.mode !== 'recording') return
|
|
121
123
|
this.toolbar?.setMode('markup')
|
|
122
124
|
this.handleToolbarModeChange('markup')
|
|
@@ -176,8 +178,9 @@ export class WidgetController {
|
|
|
176
178
|
this.session = null
|
|
177
179
|
this.recordingManager?.destroy()
|
|
178
180
|
this.recordingManager = null
|
|
179
|
-
|
|
180
|
-
|
|
181
|
+
const skipFollowup = feedback.skip_followup === true || feedback.ai_clarify_available === false
|
|
182
|
+
if (skipFollowup) {
|
|
183
|
+
this.showSuccess(feedback.project_id, { skipFollowup: true })
|
|
181
184
|
} else {
|
|
182
185
|
this.showClarify(feedback.id, feedback.project_id)
|
|
183
186
|
}
|
|
@@ -202,7 +205,7 @@ export class WidgetController {
|
|
|
202
205
|
})
|
|
203
206
|
}
|
|
204
207
|
|
|
205
|
-
private showSuccess(projectId: string): void {
|
|
208
|
+
private showSuccess(projectId: string, options?: SuccessCardOptions): void {
|
|
206
209
|
this.mode = 'idle'
|
|
207
210
|
this.tab?.setActive(false)
|
|
208
211
|
const baseUrl = this.config.apiUrl
|
|
@@ -216,7 +219,7 @@ export class WidgetController {
|
|
|
216
219
|
}, hasIdentity ? async () => {
|
|
217
220
|
const token = await this.apiClient.resolveUserToken()
|
|
218
221
|
if (token) window.open(`${boardUrl}?identity=${encodeURIComponent(token)}`, '_blank')
|
|
219
|
-
} : undefined)
|
|
222
|
+
} : undefined, options)
|
|
220
223
|
}
|
|
221
224
|
|
|
222
225
|
private exitAll(): void {
|