@firstlook-uat/sdk 0.4.2 → 0.4.3
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/firstlook.es.js +423 -402
- package/dist/firstlook.es.js.map +1 -1
- package/dist/firstlook.umd.js +5 -5
- package/dist/firstlook.umd.js.map +1 -1
- package/package.json +1 -1
package/dist/firstlook.es.js
CHANGED
|
@@ -1,39 +1,39 @@
|
|
|
1
|
-
const
|
|
1
|
+
const A = [
|
|
2
2
|
'input[type="password"]',
|
|
3
3
|
"[data-sensitive]",
|
|
4
4
|
"[data-mask]",
|
|
5
5
|
".uat-mask"
|
|
6
6
|
];
|
|
7
|
-
function
|
|
8
|
-
var
|
|
9
|
-
if (!
|
|
7
|
+
function R(c) {
|
|
8
|
+
var e, t, s, i, n, o, l, a, h, d;
|
|
9
|
+
if (!c.endpoint)
|
|
10
10
|
throw new Error("[FirstLook] 'endpoint' is required. Set your Supabase ingest-session URL.");
|
|
11
11
|
return {
|
|
12
|
-
projectId:
|
|
13
|
-
apiKey:
|
|
14
|
-
userId:
|
|
15
|
-
role:
|
|
16
|
-
context:
|
|
17
|
-
endpoint:
|
|
12
|
+
projectId: c.projectId,
|
|
13
|
+
apiKey: c.apiKey,
|
|
14
|
+
userId: c.userId,
|
|
15
|
+
role: c.role,
|
|
16
|
+
context: c.context ?? {},
|
|
17
|
+
endpoint: c.endpoint,
|
|
18
18
|
triggers: {
|
|
19
|
-
tapCount: ((
|
|
20
|
-
deepLink: ((
|
|
21
|
-
keyboard: ((s =
|
|
22
|
-
customCheck: (i =
|
|
19
|
+
tapCount: ((e = c.triggers) == null ? void 0 : e.tapCount) ?? 5,
|
|
20
|
+
deepLink: ((t = c.triggers) == null ? void 0 : t.deepLink) ?? !0,
|
|
21
|
+
keyboard: ((s = c.triggers) == null ? void 0 : s.keyboard) ?? !0,
|
|
22
|
+
customCheck: (i = c.triggers) == null ? void 0 : i.customCheck
|
|
23
23
|
},
|
|
24
24
|
security: {
|
|
25
|
-
watermark: ((n =
|
|
26
|
-
maskSelectors: ((o =
|
|
25
|
+
watermark: ((n = c.security) == null ? void 0 : n.watermark) ?? !0,
|
|
26
|
+
maskSelectors: ((o = c.security) == null ? void 0 : o.maskSelectors) ?? A
|
|
27
27
|
},
|
|
28
28
|
recording: {
|
|
29
|
-
domSnapshot: ((l =
|
|
30
|
-
voice: ((
|
|
31
|
-
maxDuration: ((h =
|
|
32
|
-
snapshotInterval: ((d =
|
|
29
|
+
domSnapshot: ((l = c.recording) == null ? void 0 : l.domSnapshot) ?? !0,
|
|
30
|
+
voice: ((a = c.recording) == null ? void 0 : a.voice) ?? !0,
|
|
31
|
+
maxDuration: ((h = c.recording) == null ? void 0 : h.maxDuration) ?? 600,
|
|
32
|
+
snapshotInterval: ((d = c.recording) == null ? void 0 : d.snapshotInterval) ?? 1e3
|
|
33
33
|
}
|
|
34
34
|
};
|
|
35
35
|
}
|
|
36
|
-
function
|
|
36
|
+
function q() {
|
|
37
37
|
return {
|
|
38
38
|
userAgent: navigator.userAgent,
|
|
39
39
|
platform: navigator.platform,
|
|
@@ -44,28 +44,28 @@ function y() {
|
|
|
44
44
|
touchSupport: "ontouchstart" in window || navigator.maxTouchPoints > 0
|
|
45
45
|
};
|
|
46
46
|
}
|
|
47
|
-
class
|
|
47
|
+
class L {
|
|
48
48
|
constructor() {
|
|
49
49
|
this.listeners = /* @__PURE__ */ new Map();
|
|
50
50
|
}
|
|
51
|
-
on(
|
|
52
|
-
return this.listeners.has(
|
|
51
|
+
on(e, t) {
|
|
52
|
+
return this.listeners.has(e) || this.listeners.set(e, /* @__PURE__ */ new Set()), this.listeners.get(e).add(t), () => this.off(e, t);
|
|
53
53
|
}
|
|
54
|
-
off(
|
|
54
|
+
off(e, t) {
|
|
55
55
|
var s;
|
|
56
|
-
(s = this.listeners.get(
|
|
56
|
+
(s = this.listeners.get(e)) == null || s.delete(t);
|
|
57
57
|
}
|
|
58
|
-
emit(
|
|
59
|
-
var
|
|
60
|
-
(
|
|
58
|
+
emit(e) {
|
|
59
|
+
var t, s;
|
|
60
|
+
(t = this.listeners.get(e.type)) == null || t.forEach((i) => {
|
|
61
61
|
try {
|
|
62
|
-
i(
|
|
62
|
+
i(e);
|
|
63
63
|
} catch (n) {
|
|
64
64
|
console.error("[FirstLook] Event listener error:", n);
|
|
65
65
|
}
|
|
66
66
|
}), (s = this.listeners.get("*")) == null || s.forEach((i) => {
|
|
67
67
|
try {
|
|
68
|
-
i(
|
|
68
|
+
i(e);
|
|
69
69
|
} catch (n) {
|
|
70
70
|
console.error("[FirstLook] Event listener error:", n);
|
|
71
71
|
}
|
|
@@ -75,15 +75,15 @@ class E {
|
|
|
75
75
|
this.listeners.clear();
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
|
-
class
|
|
79
|
-
constructor(
|
|
80
|
-
this.events =
|
|
78
|
+
class E {
|
|
79
|
+
constructor(e) {
|
|
80
|
+
this.events = e, this.quests = [], this.results = [], this.currentIndex = -1, this.sessionStartTime = 0, this.blocked = !1, this.pendingFeedbacks = [];
|
|
81
81
|
}
|
|
82
|
-
loadQuests(
|
|
83
|
-
this.quests = [...
|
|
82
|
+
loadQuests(e) {
|
|
83
|
+
this.quests = [...e].sort((t, s) => t.order - s.order), this.results = [], this.currentIndex = -1, this.blocked = !1;
|
|
84
84
|
}
|
|
85
|
-
startSession(
|
|
86
|
-
this.sessionStartTime =
|
|
85
|
+
startSession(e) {
|
|
86
|
+
this.sessionStartTime = e, this.advance();
|
|
87
87
|
}
|
|
88
88
|
getCurrentQuest() {
|
|
89
89
|
return this.blocked || this.currentIndex < 0 || this.currentIndex >= this.quests.length ? null : this.quests[this.currentIndex];
|
|
@@ -91,20 +91,20 @@ class R {
|
|
|
91
91
|
getStatus() {
|
|
92
92
|
return {
|
|
93
93
|
total: this.quests.length,
|
|
94
|
-
completed: this.results.filter((
|
|
95
|
-
failed: this.results.filter((
|
|
94
|
+
completed: this.results.filter((e) => e.status === "COMPLETED").length,
|
|
95
|
+
failed: this.results.filter((e) => e.status === "FAILED").length,
|
|
96
96
|
current: this.currentIndex,
|
|
97
97
|
isBlocked: this.blocked,
|
|
98
98
|
isFinished: this.currentIndex >= this.quests.length
|
|
99
99
|
};
|
|
100
100
|
}
|
|
101
101
|
getQuestStatuses() {
|
|
102
|
-
return this.quests.map((
|
|
103
|
-
const s = this.results.find((i) => i.questId ===
|
|
104
|
-
return s ? s.status :
|
|
102
|
+
return this.quests.map((e, t) => {
|
|
103
|
+
const s = this.results.find((i) => i.questId === e.id);
|
|
104
|
+
return s ? s.status : t === this.currentIndex && !this.blocked ? "ACTIVE" : this.blocked && t >= this.currentIndex ? "BLOCKED" : "PENDING";
|
|
105
105
|
});
|
|
106
106
|
}
|
|
107
|
-
completeCurrentQuest(
|
|
107
|
+
completeCurrentQuest(e, t) {
|
|
108
108
|
const s = this.getCurrentQuest();
|
|
109
109
|
if (!s) return null;
|
|
110
110
|
const i = {
|
|
@@ -112,16 +112,16 @@ class R {
|
|
|
112
112
|
status: "COMPLETED",
|
|
113
113
|
timestamp: Date.now(),
|
|
114
114
|
relativeTime: Date.now() - this.sessionStartTime,
|
|
115
|
-
voiceMemoBlob:
|
|
116
|
-
logs:
|
|
117
|
-
comment:
|
|
118
|
-
concern: (
|
|
115
|
+
voiceMemoBlob: t == null ? void 0 : t.voiceMemo,
|
|
116
|
+
logs: e,
|
|
117
|
+
comment: t == null ? void 0 : t.comment,
|
|
118
|
+
concern: (t == null ? void 0 : t.concern) ?? !1,
|
|
119
119
|
feedbacks: this.drainFeedbacks(),
|
|
120
|
-
checklist:
|
|
120
|
+
checklist: t == null ? void 0 : t.checklist
|
|
121
121
|
};
|
|
122
122
|
return this.results.push(i), this.events.emit({ type: "quest:completed", questId: s.id }), this.advance(), i;
|
|
123
123
|
}
|
|
124
|
-
failCurrentQuest(
|
|
124
|
+
failCurrentQuest(e, t, s, i) {
|
|
125
125
|
const n = this.getCurrentQuest();
|
|
126
126
|
if (!n) return null;
|
|
127
127
|
const o = {
|
|
@@ -130,113 +130,113 @@ class R {
|
|
|
130
130
|
timestamp: Date.now(),
|
|
131
131
|
relativeTime: Date.now() - this.sessionStartTime,
|
|
132
132
|
voiceMemoBlob: s,
|
|
133
|
-
logs:
|
|
134
|
-
comment:
|
|
133
|
+
logs: e,
|
|
134
|
+
comment: t,
|
|
135
135
|
concern: !1,
|
|
136
136
|
feedbacks: this.drainFeedbacks(),
|
|
137
137
|
checklist: i
|
|
138
138
|
};
|
|
139
139
|
return this.results.push(o), this.events.emit({ type: "quest:failed", questId: n.id }), n.blocking ? (this.blocked = !0, this.events.emit({ type: "quest:blocked", questId: n.id })) : this.advance(), o;
|
|
140
140
|
}
|
|
141
|
-
addFeedback(
|
|
142
|
-
this.pendingFeedbacks.push(
|
|
141
|
+
addFeedback(e) {
|
|
142
|
+
this.pendingFeedbacks.push(e);
|
|
143
143
|
}
|
|
144
144
|
getResults() {
|
|
145
145
|
return [...this.results];
|
|
146
146
|
}
|
|
147
147
|
drainFeedbacks() {
|
|
148
|
-
const
|
|
149
|
-
return this.pendingFeedbacks = [],
|
|
148
|
+
const e = this.pendingFeedbacks;
|
|
149
|
+
return this.pendingFeedbacks = [], e;
|
|
150
150
|
}
|
|
151
151
|
advance() {
|
|
152
152
|
this.currentIndex++, this.currentIndex < this.quests.length && this.events.emit({ type: "quest:started", questId: this.quests[this.currentIndex].id });
|
|
153
153
|
}
|
|
154
154
|
}
|
|
155
|
-
function r(
|
|
156
|
-
const s = document.createElement(
|
|
157
|
-
if (t)
|
|
158
|
-
for (const [i, n] of Object.entries(t))
|
|
159
|
-
i === "className" ? s.className = n : s.setAttribute(i, n);
|
|
155
|
+
function r(c, e, t) {
|
|
156
|
+
const s = document.createElement(c);
|
|
160
157
|
if (e)
|
|
161
|
-
for (const i of e)
|
|
158
|
+
for (const [i, n] of Object.entries(e))
|
|
159
|
+
i === "className" ? s.className = n : s.setAttribute(i, n);
|
|
160
|
+
if (t)
|
|
161
|
+
for (const i of t)
|
|
162
162
|
typeof i == "string" ? s.appendChild(document.createTextNode(i)) : s.appendChild(i);
|
|
163
163
|
return s;
|
|
164
164
|
}
|
|
165
|
-
function
|
|
165
|
+
function D() {
|
|
166
166
|
return `fl_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
167
167
|
}
|
|
168
|
-
function
|
|
169
|
-
const
|
|
170
|
-
return `${
|
|
168
|
+
function N(c) {
|
|
169
|
+
const e = Math.floor(c / 60), t = c % 60;
|
|
170
|
+
return `${e.toString().padStart(2, "0")}:${t.toString().padStart(2, "0")}`;
|
|
171
171
|
}
|
|
172
|
-
function
|
|
173
|
-
const
|
|
174
|
-
`),
|
|
172
|
+
function F(c) {
|
|
173
|
+
const e = c.split(`
|
|
174
|
+
`), t = [], s = [];
|
|
175
175
|
let i = !1;
|
|
176
|
-
for (const n of
|
|
176
|
+
for (const n of e) {
|
|
177
177
|
const o = n.trim(), l = o.match(/^(?:(\d+)[\.\)]\s+|[-*]\s+)(.+)$/);
|
|
178
178
|
l ? (i = !0, s.push({
|
|
179
179
|
index: s.length,
|
|
180
180
|
text: l[2],
|
|
181
181
|
checked: !1
|
|
182
|
-
})) : !i && o &&
|
|
182
|
+
})) : !i && o && t.push(o);
|
|
183
183
|
}
|
|
184
184
|
return {
|
|
185
|
-
preamble:
|
|
185
|
+
preamble: t.join(`
|
|
186
186
|
`),
|
|
187
187
|
items: s
|
|
188
188
|
};
|
|
189
189
|
}
|
|
190
|
-
class
|
|
191
|
-
constructor(
|
|
192
|
-
this.shadowRoot =
|
|
190
|
+
class P {
|
|
191
|
+
constructor(e, t) {
|
|
192
|
+
this.shadowRoot = e, this.callbacks = t, this.minimized = !1, this.timerInterval = null, this.startTime = 0, this.checklist = [], this.root = document.createElement("div"), this.root.className = "fl-quest-overlay", this.root.style.pointerEvents = "auto";
|
|
193
193
|
for (const s of ["click", "mousedown", "mouseup", "pointerdown", "pointerup", "touchstart", "touchend"])
|
|
194
194
|
this.root.addEventListener(s, (i) => i.stopPropagation());
|
|
195
195
|
this.shadowRoot.appendChild(this.root);
|
|
196
196
|
}
|
|
197
|
-
renderQuest(
|
|
197
|
+
renderQuest(e, t, s) {
|
|
198
198
|
this.minimized = !1, this.root.className = "fl-quest-overlay", this.root.innerHTML = "";
|
|
199
|
-
const i =
|
|
199
|
+
const i = t.indexOf("ACTIVE"), n = r("div", { className: "fl-quest-header" }, [
|
|
200
200
|
r("div", { className: "fl-quest-header-left" }, [
|
|
201
|
-
r("span", { className: "fl-quest-badge" }, [`Q${i + 1}/${
|
|
202
|
-
r("span", { className: "fl-quest-title" }, [
|
|
201
|
+
r("span", { className: "fl-quest-badge" }, [`Q${i + 1}/${t.length}`]),
|
|
202
|
+
r("span", { className: "fl-quest-title" }, [e.title])
|
|
203
203
|
]),
|
|
204
204
|
this.createMinimizeBtn()
|
|
205
205
|
]);
|
|
206
206
|
this.root.appendChild(n);
|
|
207
207
|
const o = r("div", { className: "fl-quest-content" }), l = r("div", { className: "fl-quest-progress" });
|
|
208
|
-
for (const f of
|
|
208
|
+
for (const f of t) {
|
|
209
209
|
const p = f === "COMPLETED" ? "fl-completed" : f === "FAILED" ? "fl-failed" : f === "ACTIVE" ? "fl-active" : "";
|
|
210
210
|
l.appendChild(r("div", { className: `fl-quest-progress-dot ${p}` }));
|
|
211
211
|
}
|
|
212
|
-
const
|
|
213
|
-
this.checklist =
|
|
212
|
+
const a = F(e.description);
|
|
213
|
+
this.checklist = a.items;
|
|
214
214
|
const h = r("div", { className: "fl-quest-body" });
|
|
215
|
-
if (h.appendChild(l),
|
|
215
|
+
if (h.appendChild(l), a.items.length > 0) {
|
|
216
216
|
const f = r("div", { className: "fl-checklist-counter" }), p = () => {
|
|
217
217
|
const b = this.checklist.filter((k) => k.checked).length;
|
|
218
218
|
f.textContent = `✓ ${b}/${this.checklist.length} 確認済み`;
|
|
219
219
|
};
|
|
220
|
-
p(), h.appendChild(f),
|
|
220
|
+
p(), h.appendChild(f), a.preamble && h.appendChild(r("p", { className: "fl-quest-description" }, [a.preamble]));
|
|
221
221
|
const g = r("ul", { className: "fl-checklist" });
|
|
222
222
|
for (const b of this.checklist) {
|
|
223
223
|
const k = r("li", { className: "fl-checklist-item" }), v = document.createElement("input");
|
|
224
224
|
v.type = "checkbox", v.className = "fl-checklist-checkbox", v.checked = b.checked;
|
|
225
|
-
const
|
|
225
|
+
const I = r("span", { className: "fl-checklist-text" }, [b.text]);
|
|
226
226
|
v.onchange = () => {
|
|
227
227
|
b.checked = v.checked, k.classList.toggle("fl-checked", b.checked), p();
|
|
228
|
-
}, k.onclick = (
|
|
229
|
-
|
|
230
|
-
}, k.appendChild(v), k.appendChild(
|
|
228
|
+
}, k.onclick = (M) => {
|
|
229
|
+
M.target !== v && (v.checked = !v.checked, b.checked = v.checked, k.classList.toggle("fl-checked", b.checked), p());
|
|
230
|
+
}, k.appendChild(v), k.appendChild(I), g.appendChild(k);
|
|
231
231
|
}
|
|
232
232
|
h.appendChild(g);
|
|
233
233
|
} else
|
|
234
|
-
h.appendChild(r("p", { className: "fl-quest-description" }, [
|
|
234
|
+
h.appendChild(r("p", { className: "fl-quest-description" }, [e.description]));
|
|
235
235
|
h.appendChild(r("div", { className: "fl-quest-memo-row" }, [
|
|
236
|
-
this.createButton("fl-btn fl-btn-memo", "📝 メモを残す", () => this.renderFeedbackModal(
|
|
236
|
+
this.createButton("fl-btn fl-btn-memo", "📝 メモを残す", () => this.renderFeedbackModal(e.title))
|
|
237
237
|
])), h.appendChild(r("div", { className: "fl-quest-actions" }, [
|
|
238
|
-
this.createButton("fl-btn fl-btn-ok", "✓ OK", () => this.renderFeedbackModal(
|
|
239
|
-
this.createButton("fl-btn fl-btn-concern", "⚠ 気になる", () => this.renderFeedbackModal(
|
|
238
|
+
this.createButton("fl-btn fl-btn-ok", "✓ OK", () => this.renderFeedbackModal(e.title, "ok")),
|
|
239
|
+
this.createButton("fl-btn fl-btn-concern", "⚠ 気になる", () => this.renderFeedbackModal(e.title, "concern")),
|
|
240
240
|
this.createButton("fl-btn fl-btn-ng", "✗ NG", this.callbacks.onNg)
|
|
241
241
|
])), o.appendChild(h), s && o.appendChild(
|
|
242
242
|
r("div", { className: "fl-voice-indicator" }, [
|
|
@@ -256,7 +256,7 @@ class N {
|
|
|
256
256
|
this.minimized && (this.minimized = !1, this.root.className = "fl-quest-overlay", this.root.innerHTML = "", this.root.appendChild(n), this.root.appendChild(o), this.startTimer(m));
|
|
257
257
|
};
|
|
258
258
|
}
|
|
259
|
-
renderFeedbackModal(
|
|
259
|
+
renderFeedbackModal(e, t = "memo") {
|
|
260
260
|
const s = r("div", { className: "fl-feedback-modal" }), i = {
|
|
261
261
|
ng: "fl-quest-header fl-quest-header-ng",
|
|
262
262
|
ok: "fl-quest-header fl-quest-header-ok",
|
|
@@ -272,31 +272,31 @@ class N {
|
|
|
272
272
|
ok: "fl-quest-badge fl-badge-ok",
|
|
273
273
|
concern: "fl-quest-badge fl-badge-concern",
|
|
274
274
|
memo: "fl-quest-badge"
|
|
275
|
-
}, l = r("div", { className: i[
|
|
275
|
+
}, l = r("div", { className: i[t] }, [
|
|
276
276
|
r("div", { className: "fl-quest-header-left" }, [
|
|
277
|
-
r("span", { className: o[
|
|
278
|
-
r("span", { className: "fl-quest-title" }, [
|
|
277
|
+
r("span", { className: o[t] }, [n[t]]),
|
|
278
|
+
r("span", { className: "fl-quest-title" }, [e])
|
|
279
279
|
])
|
|
280
280
|
]);
|
|
281
281
|
s.appendChild(l);
|
|
282
|
-
const
|
|
282
|
+
const a = r("div", { className: "fl-quest-content" }), h = {
|
|
283
283
|
ng: "何がうまくいかなかったか教えてください...",
|
|
284
284
|
ok: "コメント(任意)",
|
|
285
285
|
concern: "気になった点を教えてください...",
|
|
286
286
|
memo: "気づいたことをメモ...📝"
|
|
287
287
|
}, d = document.createElement("textarea");
|
|
288
|
-
d.className = "fl-feedback-textarea fl-feedback-textarea-modal", d.placeholder = h[
|
|
289
|
-
const m = "送信", f =
|
|
288
|
+
d.className = "fl-feedback-textarea fl-feedback-textarea-modal", d.placeholder = h[t], d.rows = 3, a.appendChild(d);
|
|
289
|
+
const m = "送信", f = t === "ok" ? "スキップ" : "キャンセル", p = r("div", { className: "fl-quest-actions fl-feedback-modal-actions" }, [
|
|
290
290
|
this.createButton("fl-btn fl-btn-skip", f, () => {
|
|
291
|
-
|
|
291
|
+
t === "ng" ? this.callbacks.onNgWithFeedback("") : t === "ok" ? this.callbacks.onOkWithFeedback("") : t === "concern" && this.callbacks.onConcern(""), s.remove();
|
|
292
292
|
}),
|
|
293
293
|
this.createButton("fl-btn fl-btn-finish", m, () => {
|
|
294
294
|
const g = d.value.trim();
|
|
295
|
-
if (
|
|
295
|
+
if (t === "ng")
|
|
296
296
|
this.callbacks.onNgWithFeedback(g);
|
|
297
|
-
else if (
|
|
297
|
+
else if (t === "ok")
|
|
298
298
|
this.callbacks.onOkWithFeedback(g);
|
|
299
|
-
else if (
|
|
299
|
+
else if (t === "concern")
|
|
300
300
|
this.callbacks.onConcern(g);
|
|
301
301
|
else {
|
|
302
302
|
if (!g) {
|
|
@@ -308,37 +308,37 @@ class N {
|
|
|
308
308
|
s.remove();
|
|
309
309
|
})
|
|
310
310
|
]);
|
|
311
|
-
|
|
311
|
+
a.appendChild(p), s.appendChild(a), this.root.appendChild(s), d.focus();
|
|
312
312
|
}
|
|
313
|
-
renderSummary(
|
|
313
|
+
renderSummary(e, t, s) {
|
|
314
314
|
this.stopTimer(), this.root.className = "fl-quest-overlay", this.root.innerHTML = "";
|
|
315
315
|
const i = r("div", { className: "fl-summary" }, [
|
|
316
|
-
r("div", { className: "fl-summary-icon" }, [
|
|
316
|
+
r("div", { className: "fl-summary-icon" }, [e === s ? "🎉" : "📋"]),
|
|
317
317
|
r("div", { className: "fl-summary-title" }, [
|
|
318
|
-
|
|
318
|
+
e === s ? "All Quests Complete!" : "Session Complete"
|
|
319
319
|
]),
|
|
320
320
|
r("div", { className: "fl-summary-subtitle" }, [
|
|
321
|
-
`${
|
|
321
|
+
`${e + t} of ${s} quests attempted`
|
|
322
322
|
]),
|
|
323
323
|
r("div", { className: "fl-summary-stats" }, [
|
|
324
|
-
this.createStat(
|
|
325
|
-
this.createStat(
|
|
324
|
+
this.createStat(e.toString(), "Passed", "fl-ok"),
|
|
325
|
+
this.createStat(t.toString(), "Failed", "fl-ng")
|
|
326
326
|
]),
|
|
327
327
|
this.createButton("fl-btn fl-btn-finish", "Finish & Upload", this.callbacks.onFinish)
|
|
328
328
|
]);
|
|
329
329
|
this.root.appendChild(i);
|
|
330
330
|
}
|
|
331
|
-
renderBlocked(
|
|
331
|
+
renderBlocked(e) {
|
|
332
332
|
this.stopTimer(), this.root.className = "fl-quest-overlay", this.root.innerHTML = "";
|
|
333
|
-
const
|
|
333
|
+
const t = r("div", { className: "fl-summary" }, [
|
|
334
334
|
r("div", { className: "fl-summary-icon" }, ["🛑"]),
|
|
335
335
|
r("div", { className: "fl-summary-title" }, ["Blocked"]),
|
|
336
336
|
r("div", { className: "fl-summary-subtitle" }, [
|
|
337
|
-
`Quest "${
|
|
337
|
+
`Quest "${e}" is blocking. Session halted.`
|
|
338
338
|
]),
|
|
339
339
|
this.createButton("fl-btn fl-btn-finish", "Finish & Upload", this.callbacks.onFinish)
|
|
340
340
|
]);
|
|
341
|
-
this.root.appendChild(
|
|
341
|
+
this.root.appendChild(t);
|
|
342
342
|
}
|
|
343
343
|
getChecklist() {
|
|
344
344
|
return this.checklist.length > 0 ? [...this.checklist] : void 0;
|
|
@@ -347,39 +347,39 @@ class N {
|
|
|
347
347
|
this.stopTimer(), this.root.remove();
|
|
348
348
|
}
|
|
349
349
|
createMinimizeBtn() {
|
|
350
|
-
const
|
|
351
|
-
return
|
|
352
|
-
|
|
350
|
+
const e = r("button", { className: "fl-quest-minimize-btn" }, ["−"]);
|
|
351
|
+
return e.onclick = (t) => {
|
|
352
|
+
t.stopPropagation(), this.minimized = !0, this.root.className = "fl-quest-overlay fl-minimized", this.root.innerHTML = "";
|
|
353
353
|
const s = r("span", { className: "fl-minimize-icon" }, ["🔍"]);
|
|
354
354
|
this.root.appendChild(s), this.callbacks.onMinimize();
|
|
355
|
-
},
|
|
355
|
+
}, e;
|
|
356
356
|
}
|
|
357
|
-
createButton(
|
|
358
|
-
const i = r("button", { className:
|
|
359
|
-
return i.textContent =
|
|
357
|
+
createButton(e, t, s) {
|
|
358
|
+
const i = r("button", { className: e });
|
|
359
|
+
return i.textContent = t, i.onclick = (n) => {
|
|
360
360
|
n.stopPropagation(), s();
|
|
361
361
|
}, i;
|
|
362
362
|
}
|
|
363
|
-
createStat(
|
|
363
|
+
createStat(e, t, s) {
|
|
364
364
|
return r("div", { className: "fl-stat" }, [
|
|
365
|
-
r("div", { className: `fl-stat-value ${s}` }, [
|
|
366
|
-
r("div", { className: "fl-stat-label" }, [
|
|
365
|
+
r("div", { className: `fl-stat-value ${s}` }, [e]),
|
|
366
|
+
r("div", { className: "fl-stat-label" }, [t])
|
|
367
367
|
]);
|
|
368
368
|
}
|
|
369
|
-
startTimer(
|
|
369
|
+
startTimer(e) {
|
|
370
370
|
this.stopTimer(), this.startTime || (this.startTime = Date.now()), this.timerInterval = setInterval(() => {
|
|
371
|
-
const
|
|
372
|
-
|
|
371
|
+
const t = Math.floor((Date.now() - this.startTime) / 1e3);
|
|
372
|
+
e.textContent = N(t);
|
|
373
373
|
}, 1e3);
|
|
374
374
|
}
|
|
375
375
|
stopTimer() {
|
|
376
376
|
this.timerInterval && (clearInterval(this.timerInterval), this.timerInterval = null);
|
|
377
377
|
}
|
|
378
378
|
}
|
|
379
|
-
const
|
|
380
|
-
class
|
|
381
|
-
constructor(
|
|
382
|
-
this.config =
|
|
379
|
+
const z = 100, O = 2 * 1024 * 1024;
|
|
380
|
+
class Q {
|
|
381
|
+
constructor(e, t, s) {
|
|
382
|
+
this.config = e, this.events = t, this.maskSelector = s, this.actionLogs = [], this.snapshots = [], this.snapshotTimer = null, this.startTime = 0, this.running = !1, this.handleClick = this.onClick.bind(this), this.handleScroll = this.throttle(this.onScroll.bind(this), 500), this.handleInput = this.onInput.bind(this);
|
|
383
383
|
}
|
|
384
384
|
start() {
|
|
385
385
|
this.running || (this.running = !0, this.startTime = Date.now(), this.actionLogs = [], this.snapshots = [], document.addEventListener("click", this.handleClick, { capture: !0, passive: !0 }), document.addEventListener("scroll", this.handleScroll, { capture: !0, passive: !0 }), document.addEventListener("input", this.handleInput, { capture: !0, passive: !0 }), this.config.recording.domSnapshot && (this.captureSnapshot(), this.snapshotTimer = setInterval(
|
|
@@ -399,17 +399,17 @@ class z {
|
|
|
399
399
|
getElapsedMs() {
|
|
400
400
|
return this.running ? Date.now() - this.startTime : 0;
|
|
401
401
|
}
|
|
402
|
-
addLog(
|
|
402
|
+
addLog(e) {
|
|
403
403
|
this.actionLogs.push({
|
|
404
|
-
...
|
|
404
|
+
...e,
|
|
405
405
|
timestamp: Date.now() - this.startTime
|
|
406
406
|
});
|
|
407
407
|
}
|
|
408
|
-
onClick(
|
|
409
|
-
const
|
|
408
|
+
onClick(e) {
|
|
409
|
+
const t = e.target;
|
|
410
410
|
this.addLog({
|
|
411
411
|
type: "click",
|
|
412
|
-
target:
|
|
412
|
+
target: w(t)
|
|
413
413
|
});
|
|
414
414
|
}
|
|
415
415
|
onScroll() {
|
|
@@ -418,34 +418,34 @@ class z {
|
|
|
418
418
|
value: `${window.scrollX},${window.scrollY}`
|
|
419
419
|
});
|
|
420
420
|
}
|
|
421
|
-
onInput(
|
|
422
|
-
const
|
|
423
|
-
if (
|
|
424
|
-
this.addLog({ type: "input", target:
|
|
421
|
+
onInput(e) {
|
|
422
|
+
const t = e.target;
|
|
423
|
+
if (t.hasAttribute("data-fl-masked"))
|
|
424
|
+
this.addLog({ type: "input", target: w(t), value: "[MASKED]" });
|
|
425
425
|
else {
|
|
426
|
-
const s =
|
|
426
|
+
const s = t.value;
|
|
427
427
|
this.addLog({
|
|
428
428
|
type: "input",
|
|
429
|
-
target:
|
|
429
|
+
target: w(t),
|
|
430
430
|
value: s == null ? void 0 : s.slice(0, 100)
|
|
431
431
|
});
|
|
432
432
|
}
|
|
433
433
|
}
|
|
434
434
|
captureSnapshot() {
|
|
435
435
|
try {
|
|
436
|
-
const
|
|
436
|
+
const e = document.documentElement.cloneNode(!0);
|
|
437
437
|
if (this.maskSelector) {
|
|
438
|
-
const n =
|
|
438
|
+
const n = e.querySelectorAll(this.maskSelector);
|
|
439
439
|
for (const o of n)
|
|
440
440
|
(o instanceof HTMLInputElement || o instanceof HTMLTextAreaElement) && (o.value = "***"), o.textContent = "***";
|
|
441
441
|
}
|
|
442
|
-
const
|
|
443
|
-
for (const n of
|
|
444
|
-
const s =
|
|
442
|
+
const t = e.querySelectorAll("script");
|
|
443
|
+
for (const n of t) n.remove();
|
|
444
|
+
const s = e.querySelector("#firstlook-sdk-root");
|
|
445
445
|
s == null || s.remove();
|
|
446
|
-
const i =
|
|
447
|
-
if (i.length >
|
|
448
|
-
this.snapshots.length >=
|
|
446
|
+
const i = e.outerHTML;
|
|
447
|
+
if (i.length > O) return;
|
|
448
|
+
this.snapshots.length >= z && this.snapshots.shift(), this.snapshots.push({
|
|
449
449
|
type: "dom-snapshot",
|
|
450
450
|
timestamp: Date.now() - this.startTime,
|
|
451
451
|
data: i
|
|
@@ -453,22 +453,22 @@ class z {
|
|
|
453
453
|
} catch {
|
|
454
454
|
}
|
|
455
455
|
}
|
|
456
|
-
throttle(
|
|
456
|
+
throttle(e, t) {
|
|
457
457
|
let s = 0;
|
|
458
458
|
return (...i) => {
|
|
459
459
|
const n = Date.now();
|
|
460
|
-
n - s >=
|
|
460
|
+
n - s >= t && (s = n, e(...i));
|
|
461
461
|
};
|
|
462
462
|
}
|
|
463
463
|
}
|
|
464
|
-
function
|
|
464
|
+
function w(c) {
|
|
465
465
|
var n;
|
|
466
|
-
const
|
|
467
|
-
return `${
|
|
466
|
+
const e = c.tagName.toLowerCase(), t = c.id ? `#${c.id}` : "", s = c.className && typeof c.className == "string" ? `.${c.className.trim().split(/\s+/).slice(0, 2).join(".")}` : "", i = ((n = c.textContent) == null ? void 0 : n.trim().slice(0, 30)) || "";
|
|
467
|
+
return `${e}${t}${s}${i ? ` "${i}"` : ""}`;
|
|
468
468
|
}
|
|
469
|
-
class
|
|
470
|
-
constructor(
|
|
471
|
-
this.events =
|
|
469
|
+
class B {
|
|
470
|
+
constructor(e) {
|
|
471
|
+
this.events = e, this.mediaRecorder = null, this.stream = null, this.chunks = [], this._recording = !1;
|
|
472
472
|
}
|
|
473
473
|
get recording() {
|
|
474
474
|
return this._recording;
|
|
@@ -477,57 +477,57 @@ class O {
|
|
|
477
477
|
if (!this._recording)
|
|
478
478
|
try {
|
|
479
479
|
this.stream = await navigator.mediaDevices.getUserMedia({ audio: !0 });
|
|
480
|
-
const
|
|
481
|
-
this.mediaRecorder = new MediaRecorder(this.stream,
|
|
480
|
+
const e = this.getSupportedMimeType(), t = e ? { mimeType: e } : {};
|
|
481
|
+
this.mediaRecorder = new MediaRecorder(this.stream, t), this.chunks = [], this.mediaRecorder.ondataavailable = (s) => {
|
|
482
482
|
s.data.size > 0 && this.chunks.push(s.data);
|
|
483
483
|
}, this.mediaRecorder.start(1e3), this._recording = !0;
|
|
484
|
-
} catch (
|
|
485
|
-
console.warn("[FirstLook] Voice recording unavailable:",
|
|
484
|
+
} catch (e) {
|
|
485
|
+
console.warn("[FirstLook] Voice recording unavailable:", e), this.events.emit({
|
|
486
486
|
type: "error",
|
|
487
|
-
error: new Error(`Voice recording failed: ${
|
|
487
|
+
error: new Error(`Voice recording failed: ${e.message}`)
|
|
488
488
|
});
|
|
489
489
|
}
|
|
490
490
|
}
|
|
491
491
|
async stopAsync() {
|
|
492
|
-
return !this._recording || !this.mediaRecorder ? null : new Promise((
|
|
492
|
+
return !this._recording || !this.mediaRecorder ? null : new Promise((e) => {
|
|
493
493
|
this.mediaRecorder.onstop = () => {
|
|
494
494
|
var s;
|
|
495
|
-
const
|
|
496
|
-
this.cleanup(), t
|
|
495
|
+
const t = this.chunks.length > 0 ? new Blob(this.chunks, { type: ((s = this.mediaRecorder) == null ? void 0 : s.mimeType) || "audio/webm" }) : null;
|
|
496
|
+
this.cleanup(), e(t);
|
|
497
497
|
}, this.mediaRecorder.stop();
|
|
498
498
|
});
|
|
499
499
|
}
|
|
500
|
-
async captureSnippet(
|
|
501
|
-
return await this.start(), new Promise((
|
|
500
|
+
async captureSnippet(e = 1e4) {
|
|
501
|
+
return await this.start(), new Promise((t) => {
|
|
502
502
|
setTimeout(async () => {
|
|
503
503
|
const s = await this.stopAsync();
|
|
504
|
-
|
|
505
|
-
},
|
|
504
|
+
t(s);
|
|
505
|
+
}, e);
|
|
506
506
|
});
|
|
507
507
|
}
|
|
508
508
|
cleanup() {
|
|
509
509
|
if (this._recording = !1, this.stream) {
|
|
510
|
-
for (const
|
|
511
|
-
|
|
510
|
+
for (const e of this.stream.getTracks())
|
|
511
|
+
e.stop();
|
|
512
512
|
this.stream = null;
|
|
513
513
|
}
|
|
514
514
|
this.mediaRecorder = null, this.chunks = [];
|
|
515
515
|
}
|
|
516
516
|
getSupportedMimeType() {
|
|
517
|
-
const
|
|
517
|
+
const e = [
|
|
518
518
|
"audio/webm;codecs=opus",
|
|
519
519
|
"audio/webm",
|
|
520
520
|
"audio/ogg;codecs=opus",
|
|
521
521
|
"audio/mp4"
|
|
522
522
|
];
|
|
523
|
-
for (const
|
|
524
|
-
if (typeof MediaRecorder < "u" && MediaRecorder.isTypeSupported(
|
|
523
|
+
for (const t of e)
|
|
524
|
+
if (typeof MediaRecorder < "u" && MediaRecorder.isTypeSupported(t)) return t;
|
|
525
525
|
return "";
|
|
526
526
|
}
|
|
527
527
|
}
|
|
528
|
-
class
|
|
529
|
-
constructor(
|
|
530
|
-
this.shadowRoot =
|
|
528
|
+
class U {
|
|
529
|
+
constructor(e, t) {
|
|
530
|
+
this.shadowRoot = e, this.config = t, this.container = null, this.refreshInterval = null, this.cachedIp = null;
|
|
531
531
|
}
|
|
532
532
|
show() {
|
|
533
533
|
this.container || (this.container = document.createElement("div"), this.container.className = "fl-watermark", this.renderTiles(), this.shadowRoot.appendChild(this.container), this.fetchIp().then(() => this.renderTiles()), this.refreshInterval = setInterval(() => this.renderTiles(), 6e4));
|
|
@@ -538,8 +538,8 @@ class Q {
|
|
|
538
538
|
async fetchIp() {
|
|
539
539
|
if (!this.cachedIp)
|
|
540
540
|
try {
|
|
541
|
-
const
|
|
542
|
-
|
|
541
|
+
const e = await fetch("https://api.ipify.org?format=text");
|
|
542
|
+
e.ok && (this.cachedIp = await e.text());
|
|
543
543
|
} catch {
|
|
544
544
|
this.cachedIp = "N/A";
|
|
545
545
|
}
|
|
@@ -547,17 +547,17 @@ class Q {
|
|
|
547
547
|
renderTiles() {
|
|
548
548
|
if (!this.container) return;
|
|
549
549
|
this.container.innerHTML = "";
|
|
550
|
-
const
|
|
551
|
-
for (let
|
|
550
|
+
const e = this.cachedIp ?? "", t = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19), s = e ? `${this.config.userId} | ${t} | ${e}` : `${this.config.userId} | ${t}`, i = 320, n = 80, o = Math.ceil(window.innerWidth / i) + 1, l = Math.ceil(window.innerHeight / n) + 1;
|
|
551
|
+
for (let a = 0; a < l; a++)
|
|
552
552
|
for (let h = 0; h < o; h++) {
|
|
553
553
|
const d = document.createElement("span");
|
|
554
|
-
d.className = "fl-watermark-tile", d.textContent = s, d.style.left = `${h * i}px`, d.style.top = `${
|
|
554
|
+
d.className = "fl-watermark-tile", d.textContent = s, d.style.left = `${h * i}px`, d.style.top = `${a * n}px`, this.container.appendChild(d);
|
|
555
555
|
}
|
|
556
556
|
}
|
|
557
557
|
}
|
|
558
|
-
class
|
|
559
|
-
constructor(
|
|
560
|
-
this.selectors =
|
|
558
|
+
class $ {
|
|
559
|
+
constructor(e) {
|
|
560
|
+
this.selectors = e, this.observer = null, this.maskedElements = /* @__PURE__ */ new Set();
|
|
561
561
|
}
|
|
562
562
|
start() {
|
|
563
563
|
this.scanAndMark(), this.observer = new MutationObserver(() => this.scanAndMark()), this.observer.observe(document.body, {
|
|
@@ -568,24 +568,24 @@ class B {
|
|
|
568
568
|
});
|
|
569
569
|
}
|
|
570
570
|
stop() {
|
|
571
|
-
var
|
|
572
|
-
(
|
|
573
|
-
for (const
|
|
574
|
-
|
|
571
|
+
var e;
|
|
572
|
+
(e = this.observer) == null || e.disconnect(), this.observer = null;
|
|
573
|
+
for (const t of this.maskedElements)
|
|
574
|
+
t.removeAttribute("data-fl-masked");
|
|
575
575
|
this.maskedElements.clear();
|
|
576
576
|
}
|
|
577
|
-
isMasked(
|
|
578
|
-
return
|
|
577
|
+
isMasked(e) {
|
|
578
|
+
return e.hasAttribute("data-fl-masked");
|
|
579
579
|
}
|
|
580
580
|
getCombinedSelector() {
|
|
581
581
|
return this.selectors.join(", ");
|
|
582
582
|
}
|
|
583
583
|
scanAndMark() {
|
|
584
|
-
const
|
|
585
|
-
if (
|
|
584
|
+
const e = this.getCombinedSelector();
|
|
585
|
+
if (e)
|
|
586
586
|
try {
|
|
587
|
-
const
|
|
588
|
-
for (const s of
|
|
587
|
+
const t = document.querySelectorAll(e);
|
|
588
|
+
for (const s of t)
|
|
589
589
|
this.maskedElements.has(s) || (s.setAttribute("data-fl-masked", "true"), this.maskedElements.add(s));
|
|
590
590
|
for (const s of this.maskedElements)
|
|
591
591
|
document.contains(s) || this.maskedElements.delete(s);
|
|
@@ -593,93 +593,93 @@ class B {
|
|
|
593
593
|
}
|
|
594
594
|
}
|
|
595
595
|
}
|
|
596
|
-
const
|
|
596
|
+
const j = "firstlook_uat", K = 1, u = {
|
|
597
597
|
sessions: "sessions",
|
|
598
598
|
recordings: "recordings",
|
|
599
599
|
annotations: "annotations",
|
|
600
600
|
uploadQueue: "upload_queue"
|
|
601
601
|
};
|
|
602
|
-
class
|
|
602
|
+
class H {
|
|
603
603
|
constructor() {
|
|
604
604
|
this.db = null;
|
|
605
605
|
}
|
|
606
606
|
async open() {
|
|
607
607
|
if (!this.db)
|
|
608
|
-
return new Promise((
|
|
609
|
-
const s = indexedDB.open(
|
|
608
|
+
return new Promise((e, t) => {
|
|
609
|
+
const s = indexedDB.open(j, K);
|
|
610
610
|
s.onupgradeneeded = () => {
|
|
611
611
|
const i = s.result;
|
|
612
612
|
i.objectStoreNames.contains(u.sessions) || i.createObjectStore(u.sessions, { keyPath: "sessionId" }), i.objectStoreNames.contains(u.recordings) || i.createObjectStore(u.recordings, { autoIncrement: !0 }).createIndex("sessionId", "sessionId", { unique: !1 }), i.objectStoreNames.contains(u.annotations) || i.createObjectStore(u.annotations, { autoIncrement: !0 }).createIndex("sessionId", "sessionId", { unique: !1 }), i.objectStoreNames.contains(u.uploadQueue) || i.createObjectStore(u.uploadQueue, { autoIncrement: !0 });
|
|
613
613
|
}, s.onsuccess = () => {
|
|
614
|
-
this.db = s.result,
|
|
615
|
-
}, s.onerror = () =>
|
|
614
|
+
this.db = s.result, e();
|
|
615
|
+
}, s.onerror = () => t(s.error);
|
|
616
616
|
});
|
|
617
617
|
}
|
|
618
|
-
async saveSession(
|
|
619
|
-
await this.put(u.sessions,
|
|
618
|
+
async saveSession(e) {
|
|
619
|
+
await this.put(u.sessions, e);
|
|
620
620
|
}
|
|
621
|
-
async getSession(
|
|
622
|
-
return this.get(u.sessions,
|
|
621
|
+
async getSession(e) {
|
|
622
|
+
return this.get(u.sessions, e);
|
|
623
623
|
}
|
|
624
|
-
async saveRecording(
|
|
625
|
-
await this.put(u.recordings, { sessionId:
|
|
624
|
+
async saveRecording(e, t) {
|
|
625
|
+
await this.put(u.recordings, { sessionId: e, ...t });
|
|
626
626
|
}
|
|
627
|
-
async saveAnnotation(
|
|
628
|
-
await this.put(u.annotations, { sessionId:
|
|
627
|
+
async saveAnnotation(e, t) {
|
|
628
|
+
await this.put(u.annotations, { sessionId: e, ...t });
|
|
629
629
|
}
|
|
630
|
-
async getAnnotations(
|
|
631
|
-
return this.getAllByIndex(u.annotations, "sessionId",
|
|
630
|
+
async getAnnotations(e) {
|
|
631
|
+
return this.getAllByIndex(u.annotations, "sessionId", e);
|
|
632
632
|
}
|
|
633
|
-
async enqueueUpload(
|
|
633
|
+
async enqueueUpload(e) {
|
|
634
634
|
await this.put(u.uploadQueue, {
|
|
635
|
-
payload:
|
|
635
|
+
payload: e,
|
|
636
636
|
createdAt: Date.now(),
|
|
637
637
|
attempts: 0
|
|
638
638
|
});
|
|
639
639
|
}
|
|
640
640
|
async getPendingUploads() {
|
|
641
|
-
const
|
|
642
|
-
return new Promise((
|
|
643
|
-
const o =
|
|
641
|
+
const e = this.ensureDb();
|
|
642
|
+
return new Promise((t, s) => {
|
|
643
|
+
const o = e.transaction(u.uploadQueue, "readonly").objectStore(u.uploadQueue).openCursor(), l = [];
|
|
644
644
|
o.onsuccess = () => {
|
|
645
|
-
const
|
|
646
|
-
|
|
647
|
-
key:
|
|
648
|
-
payload:
|
|
649
|
-
attempts:
|
|
650
|
-
lastAttemptAt:
|
|
651
|
-
}),
|
|
645
|
+
const a = o.result;
|
|
646
|
+
a ? (l.push({
|
|
647
|
+
key: a.key,
|
|
648
|
+
payload: a.value.payload,
|
|
649
|
+
attempts: a.value.attempts,
|
|
650
|
+
lastAttemptAt: a.value.lastAttemptAt
|
|
651
|
+
}), a.continue()) : t(l);
|
|
652
652
|
}, o.onerror = () => s(o.error);
|
|
653
653
|
});
|
|
654
654
|
}
|
|
655
|
-
async removeFromQueue(
|
|
656
|
-
const
|
|
655
|
+
async removeFromQueue(e) {
|
|
656
|
+
const t = this.ensureDb();
|
|
657
657
|
return new Promise((s, i) => {
|
|
658
|
-
const o =
|
|
658
|
+
const o = t.transaction(u.uploadQueue, "readwrite").objectStore(u.uploadQueue).delete(e);
|
|
659
659
|
o.onsuccess = () => s(), o.onerror = () => i(o.error);
|
|
660
660
|
});
|
|
661
661
|
}
|
|
662
|
-
async incrementAttempts(
|
|
663
|
-
const
|
|
662
|
+
async incrementAttempts(e) {
|
|
663
|
+
const t = this.ensureDb();
|
|
664
664
|
return new Promise((s, i) => {
|
|
665
|
-
const n =
|
|
665
|
+
const n = t.transaction(u.uploadQueue, "readwrite"), o = n.objectStore(u.uploadQueue), l = o.get(e);
|
|
666
666
|
l.onsuccess = () => {
|
|
667
|
-
const
|
|
668
|
-
|
|
667
|
+
const a = l.result;
|
|
668
|
+
a && (a.attempts = (a.attempts ?? 0) + 1, a.lastAttemptAt = Date.now(), o.put(a, e));
|
|
669
669
|
}, n.oncomplete = () => s(), n.onerror = () => i(n.error);
|
|
670
670
|
});
|
|
671
671
|
}
|
|
672
|
-
async clearSession(
|
|
672
|
+
async clearSession(e) {
|
|
673
673
|
const s = this.ensureDb().transaction(
|
|
674
674
|
[u.sessions, u.recordings, u.annotations],
|
|
675
675
|
"readwrite"
|
|
676
676
|
);
|
|
677
|
-
s.objectStore(u.sessions).delete(
|
|
677
|
+
s.objectStore(u.sessions).delete(e);
|
|
678
678
|
for (const i of [u.recordings, u.annotations]) {
|
|
679
|
-
const l = s.objectStore(i).index("sessionId").openCursor(IDBKeyRange.only(
|
|
679
|
+
const l = s.objectStore(i).index("sessionId").openCursor(IDBKeyRange.only(e));
|
|
680
680
|
l.onsuccess = () => {
|
|
681
|
-
const
|
|
682
|
-
|
|
681
|
+
const a = l.result;
|
|
682
|
+
a && (a.delete(), a.continue());
|
|
683
683
|
};
|
|
684
684
|
}
|
|
685
685
|
return new Promise((i, n) => {
|
|
@@ -687,69 +687,69 @@ class j {
|
|
|
687
687
|
});
|
|
688
688
|
}
|
|
689
689
|
close() {
|
|
690
|
-
var
|
|
691
|
-
(
|
|
690
|
+
var e;
|
|
691
|
+
(e = this.db) == null || e.close(), this.db = null;
|
|
692
692
|
}
|
|
693
693
|
ensureDb() {
|
|
694
694
|
if (!this.db) throw new Error("[FirstLook] Storage not opened. Call open() first.");
|
|
695
695
|
return this.db;
|
|
696
696
|
}
|
|
697
|
-
async put(
|
|
697
|
+
async put(e, t) {
|
|
698
698
|
const s = this.ensureDb();
|
|
699
699
|
return new Promise((i, n) => {
|
|
700
|
-
const o = s.transaction(
|
|
701
|
-
o.objectStore(
|
|
700
|
+
const o = s.transaction(e, "readwrite");
|
|
701
|
+
o.objectStore(e).put(t), o.oncomplete = () => i(), o.onerror = () => n(o.error);
|
|
702
702
|
});
|
|
703
703
|
}
|
|
704
|
-
async get(
|
|
704
|
+
async get(e, t) {
|
|
705
705
|
const s = this.ensureDb();
|
|
706
706
|
return new Promise((i, n) => {
|
|
707
|
-
const l = s.transaction(
|
|
707
|
+
const l = s.transaction(e, "readonly").objectStore(e).get(t);
|
|
708
708
|
l.onsuccess = () => i(l.result), l.onerror = () => n(l.error);
|
|
709
709
|
});
|
|
710
710
|
}
|
|
711
|
-
async getAllByIndex(
|
|
711
|
+
async getAllByIndex(e, t, s) {
|
|
712
712
|
const i = this.ensureDb();
|
|
713
713
|
return new Promise((n, o) => {
|
|
714
|
-
const h = i.transaction(
|
|
714
|
+
const h = i.transaction(e, "readonly").objectStore(e).index(t).getAll(s);
|
|
715
715
|
h.onsuccess = () => n(h.result), h.onerror = () => o(h.error);
|
|
716
716
|
});
|
|
717
717
|
}
|
|
718
718
|
}
|
|
719
|
-
const
|
|
720
|
-
class
|
|
721
|
-
constructor(
|
|
722
|
-
this.shadowRoot =
|
|
719
|
+
const T = ["#e17055", "#6c5ce7", "#00b894", "#fdcb6e", "#ffffff"];
|
|
720
|
+
class _ {
|
|
721
|
+
constructor(e) {
|
|
722
|
+
this.shadowRoot = e, this.overlay = null, this.canvas = null, this.ctx = null, this.drawing = !1, this.currentPath = [], this.paths = [], this.selectedColor = T[0], this.screenshotDataUrl = "";
|
|
723
723
|
}
|
|
724
724
|
async open() {
|
|
725
|
-
return this.screenshotDataUrl = await this.captureScreenshot(), new Promise((
|
|
725
|
+
return this.screenshotDataUrl = await this.captureScreenshot(), new Promise((e) => {
|
|
726
726
|
this.overlay = r("div", { className: "fl-annotation-overlay" });
|
|
727
|
-
const
|
|
728
|
-
this.canvas = document.createElement("canvas"), this.canvas.className = "fl-annotation-canvas",
|
|
727
|
+
const t = r("div", { className: "fl-annotation-canvas-wrap" });
|
|
728
|
+
this.canvas = document.createElement("canvas"), this.canvas.className = "fl-annotation-canvas", t.appendChild(this.canvas), this.overlay.appendChild(t);
|
|
729
729
|
const s = r("input", {
|
|
730
730
|
className: "fl-annotation-comment",
|
|
731
731
|
type: "text",
|
|
732
732
|
placeholder: "Add a comment..."
|
|
733
733
|
}), i = r("div", { className: "fl-color-picker" });
|
|
734
|
-
for (const
|
|
735
|
-
const h = r("div", { className: `fl-color-swatch ${
|
|
736
|
-
h.style.background =
|
|
737
|
-
this.selectedColor =
|
|
734
|
+
for (const a of T) {
|
|
735
|
+
const h = r("div", { className: `fl-color-swatch ${a === this.selectedColor ? "fl-selected" : ""}` });
|
|
736
|
+
h.style.background = a, h.onclick = () => {
|
|
737
|
+
this.selectedColor = a, i.querySelectorAll(".fl-color-swatch").forEach((d) => d.classList.remove("fl-selected")), h.classList.add("fl-selected");
|
|
738
738
|
}, i.appendChild(h);
|
|
739
739
|
}
|
|
740
740
|
const n = r("button", { className: "fl-btn fl-btn-ok" }, ["Submit"]);
|
|
741
741
|
n.onclick = () => {
|
|
742
|
-
const
|
|
742
|
+
const a = {
|
|
743
743
|
screenshotDataUrl: this.getAnnotatedImage(),
|
|
744
744
|
drawings: [...this.paths],
|
|
745
745
|
comment: s.value,
|
|
746
746
|
timestamp: Date.now()
|
|
747
747
|
};
|
|
748
|
-
this.close(),
|
|
748
|
+
this.close(), e(a);
|
|
749
749
|
};
|
|
750
750
|
const o = r("button", { className: "fl-btn fl-btn-ng" }, ["Cancel"]);
|
|
751
751
|
o.onclick = () => {
|
|
752
|
-
this.close(),
|
|
752
|
+
this.close(), e(null);
|
|
753
753
|
};
|
|
754
754
|
const l = r("div", { className: "fl-annotation-toolbar" }, [
|
|
755
755
|
i,
|
|
@@ -761,12 +761,12 @@ class K {
|
|
|
761
761
|
});
|
|
762
762
|
}
|
|
763
763
|
close() {
|
|
764
|
-
var
|
|
765
|
-
(
|
|
764
|
+
var e;
|
|
765
|
+
(e = this.overlay) == null || e.remove(), this.overlay = null, this.canvas = null, this.ctx = null, this.paths = [], this.currentPath = [];
|
|
766
766
|
}
|
|
767
767
|
async captureScreenshot() {
|
|
768
768
|
try {
|
|
769
|
-
const
|
|
769
|
+
const e = window.innerWidth, t = window.innerHeight, s = document.documentElement.cloneNode(!0), i = s.querySelector("#firstlook-sdk-root");
|
|
770
770
|
i == null || i.remove();
|
|
771
771
|
const n = s.querySelectorAll('[data-fl-masked], input[type="password"]');
|
|
772
772
|
for (const p of n)
|
|
@@ -777,47 +777,47 @@ class K {
|
|
|
777
777
|
const p = window.getComputedStyle(document.body);
|
|
778
778
|
o.style.backgroundColor = p.backgroundColor, o.style.color = p.color, o.style.fontFamily = p.fontFamily, o.style.margin = "0", o.style.overflow = "hidden";
|
|
779
779
|
}
|
|
780
|
-
const l = new XMLSerializer().serializeToString(s),
|
|
780
|
+
const l = new XMLSerializer().serializeToString(s), a = `<svg xmlns="http://www.w3.org/2000/svg" width="${e}" height="${t}">
|
|
781
781
|
<foreignObject width="100%" height="100%">
|
|
782
782
|
${l}
|
|
783
783
|
</foreignObject>
|
|
784
|
-
</svg>`, h = new Blob([
|
|
785
|
-
m.width =
|
|
784
|
+
</svg>`, h = new Blob([a], { type: "image/svg+xml;charset=utf-8" }), d = URL.createObjectURL(h), m = document.createElement("canvas");
|
|
785
|
+
m.width = e * window.devicePixelRatio, m.height = t * window.devicePixelRatio;
|
|
786
786
|
const f = m.getContext("2d");
|
|
787
787
|
return f.scale(window.devicePixelRatio, window.devicePixelRatio), new Promise((p) => {
|
|
788
788
|
const g = new Image();
|
|
789
789
|
g.onload = () => {
|
|
790
|
-
f.drawImage(g, 0, 0,
|
|
790
|
+
f.drawImage(g, 0, 0, e, t), URL.revokeObjectURL(d), p(m.toDataURL("image/png"));
|
|
791
791
|
}, g.onerror = () => {
|
|
792
|
-
URL.revokeObjectURL(d), p(this.captureScreenshotFallback(
|
|
792
|
+
URL.revokeObjectURL(d), p(this.captureScreenshotFallback(e, t));
|
|
793
793
|
}, g.src = d;
|
|
794
794
|
});
|
|
795
795
|
} catch {
|
|
796
796
|
return this.captureScreenshotFallback(window.innerWidth, window.innerHeight);
|
|
797
797
|
}
|
|
798
798
|
}
|
|
799
|
-
captureScreenshotFallback(
|
|
799
|
+
captureScreenshotFallback(e, t) {
|
|
800
800
|
const s = document.createElement("canvas");
|
|
801
|
-
s.width =
|
|
801
|
+
s.width = e, s.height = t;
|
|
802
802
|
const i = s.getContext("2d"), n = window.getComputedStyle(document.body);
|
|
803
|
-
return i.fillStyle = n.backgroundColor || "#ffffff", i.fillRect(0, 0,
|
|
803
|
+
return i.fillStyle = n.backgroundColor || "#ffffff", i.fillRect(0, 0, e, t), i.fillStyle = "#333", i.font = "14px sans-serif", i.fillText(`URL: ${window.location.href}`, 20, 30), i.fillText(`Time: ${(/* @__PURE__ */ new Date()).toISOString()}`, 20, 50), i.fillText("(SVG capture unavailable — metadata view)", 20, 70), s.toDataURL("image/png");
|
|
804
804
|
}
|
|
805
805
|
initCanvas() {
|
|
806
806
|
if (!this.canvas) return;
|
|
807
|
-
const
|
|
808
|
-
|
|
809
|
-
const
|
|
810
|
-
this.canvas.width =
|
|
811
|
-
},
|
|
807
|
+
const e = new Image();
|
|
808
|
+
e.onload = () => {
|
|
809
|
+
const t = window.innerWidth * 0.9, s = window.innerHeight * 0.7, i = Math.min(t / e.width, s / e.height, 1);
|
|
810
|
+
this.canvas.width = e.width * i, this.canvas.height = e.height * i, this.ctx = this.canvas.getContext("2d"), this.ctx.drawImage(e, 0, 0, this.canvas.width, this.canvas.height), this.canvas.addEventListener("pointerdown", this.onDrawStart.bind(this)), this.canvas.addEventListener("pointermove", this.onDrawMove.bind(this)), this.canvas.addEventListener("pointerup", this.onDrawEnd.bind(this)), this.canvas.addEventListener("pointerleave", this.onDrawEnd.bind(this));
|
|
811
|
+
}, e.src = this.screenshotDataUrl;
|
|
812
812
|
}
|
|
813
|
-
onDrawStart(
|
|
813
|
+
onDrawStart(e) {
|
|
814
814
|
this.drawing = !0;
|
|
815
|
-
const
|
|
816
|
-
this.currentPath = [{ x:
|
|
815
|
+
const t = this.canvas.getBoundingClientRect();
|
|
816
|
+
this.currentPath = [{ x: e.clientX - t.left, y: e.clientY - t.top }];
|
|
817
817
|
}
|
|
818
|
-
onDrawMove(
|
|
818
|
+
onDrawMove(e) {
|
|
819
819
|
if (!this.drawing || !this.ctx) return;
|
|
820
|
-
const
|
|
820
|
+
const t = this.canvas.getBoundingClientRect(), s = { x: e.clientX - t.left, y: e.clientY - t.top };
|
|
821
821
|
if (this.currentPath.push(s), this.ctx.strokeStyle = this.selectedColor, this.ctx.lineWidth = 3, this.ctx.lineCap = "round", this.ctx.lineJoin = "round", this.currentPath.length >= 2) {
|
|
822
822
|
const i = this.currentPath[this.currentPath.length - 2];
|
|
823
823
|
this.ctx.beginPath(), this.ctx.moveTo(i.x, i.y), this.ctx.lineTo(s.x, s.y), this.ctx.stroke();
|
|
@@ -831,15 +831,15 @@ class K {
|
|
|
831
831
|
}), this.drawing = !1, this.currentPath = [];
|
|
832
832
|
}
|
|
833
833
|
getAnnotatedImage() {
|
|
834
|
-
var
|
|
835
|
-
return ((
|
|
834
|
+
var e;
|
|
835
|
+
return ((e = this.canvas) == null ? void 0 : e.toDataURL("image/png")) || this.screenshotDataUrl;
|
|
836
836
|
}
|
|
837
837
|
}
|
|
838
|
-
class
|
|
839
|
-
constructor(
|
|
840
|
-
this.shadowRoot =
|
|
838
|
+
class W {
|
|
839
|
+
constructor(e, t) {
|
|
840
|
+
this.shadowRoot = e, this.events = t, this.annotations = [], this.active = !1, this.onKeydown = (s) => {
|
|
841
841
|
s.ctrlKey && s.shiftKey && s.key === "R" && (s.preventDefault(), this.trigger());
|
|
842
|
-
}, this.annotationCanvas = new
|
|
842
|
+
}, this.annotationCanvas = new _(e);
|
|
843
843
|
}
|
|
844
844
|
start() {
|
|
845
845
|
document.addEventListener("keydown", this.onKeydown);
|
|
@@ -854,17 +854,17 @@ class H {
|
|
|
854
854
|
if (!this.active) {
|
|
855
855
|
this.active = !0;
|
|
856
856
|
try {
|
|
857
|
-
const
|
|
858
|
-
|
|
857
|
+
const e = await this.annotationCanvas.open();
|
|
858
|
+
e && (this.annotations.push(e), this.events.emit({ type: "report:submitted" }));
|
|
859
859
|
} finally {
|
|
860
860
|
this.active = !1;
|
|
861
861
|
}
|
|
862
862
|
}
|
|
863
863
|
}
|
|
864
864
|
}
|
|
865
|
-
class
|
|
866
|
-
constructor(
|
|
867
|
-
this.requiredTaps =
|
|
865
|
+
class V {
|
|
866
|
+
constructor(e, t) {
|
|
867
|
+
this.requiredTaps = e, this.onActivate = t, this.tapCount = 0, this.tapTimer = null, this.handler = null;
|
|
868
868
|
}
|
|
869
869
|
start() {
|
|
870
870
|
this.handler = this.onTap.bind(this), document.addEventListener("pointerdown", this.handler, { passive: !0 });
|
|
@@ -872,17 +872,17 @@ class _ {
|
|
|
872
872
|
stop() {
|
|
873
873
|
this.handler && (document.removeEventListener("pointerdown", this.handler), this.handler = null), this.reset();
|
|
874
874
|
}
|
|
875
|
-
onTap(
|
|
875
|
+
onTap(e) {
|
|
876
876
|
this.tapCount++, this.tapTimer && clearTimeout(this.tapTimer), this.tapCount >= this.requiredTaps ? (this.reset(), this.onActivate()) : this.tapTimer = setTimeout(() => this.reset(), 800);
|
|
877
877
|
}
|
|
878
878
|
reset() {
|
|
879
879
|
this.tapCount = 0, this.tapTimer && (clearTimeout(this.tapTimer), this.tapTimer = null);
|
|
880
880
|
}
|
|
881
881
|
}
|
|
882
|
-
const
|
|
882
|
+
const y = "firstlook:uat-active";
|
|
883
883
|
class C {
|
|
884
|
-
constructor(
|
|
885
|
-
this.onActivate =
|
|
884
|
+
constructor(e) {
|
|
885
|
+
this.onActivate = e, this.handlers = [];
|
|
886
886
|
}
|
|
887
887
|
start() {
|
|
888
888
|
if (this.checkUrl()) {
|
|
@@ -893,29 +893,29 @@ class C {
|
|
|
893
893
|
this.onActivate();
|
|
894
894
|
return;
|
|
895
895
|
}
|
|
896
|
-
const
|
|
896
|
+
const e = () => {
|
|
897
897
|
this.checkUrl() && (this.persist(), this.onActivate());
|
|
898
898
|
};
|
|
899
|
-
window.addEventListener("hashchange",
|
|
899
|
+
window.addEventListener("hashchange", e), this.handlers.push(["hashchange", e]), window.addEventListener("popstate", e), this.handlers.push(["popstate", e]);
|
|
900
900
|
}
|
|
901
901
|
stop() {
|
|
902
|
-
for (const [
|
|
903
|
-
window.removeEventListener(
|
|
902
|
+
for (const [e, t] of this.handlers)
|
|
903
|
+
window.removeEventListener(e, t);
|
|
904
904
|
this.handlers = [];
|
|
905
905
|
}
|
|
906
906
|
static clearPersisted() {
|
|
907
907
|
try {
|
|
908
|
-
sessionStorage.removeItem(
|
|
908
|
+
sessionStorage.removeItem(y);
|
|
909
909
|
} catch {
|
|
910
910
|
}
|
|
911
911
|
}
|
|
912
912
|
checkUrl() {
|
|
913
|
-
const
|
|
914
|
-
if (
|
|
913
|
+
const e = new URLSearchParams(window.location.search);
|
|
914
|
+
if (e.has("firstlook") || e.has("uat-mode") || e.get("uat") === "1")
|
|
915
915
|
return !0;
|
|
916
|
-
const
|
|
916
|
+
const t = window.location.hash, s = t.indexOf("?");
|
|
917
917
|
if (s !== -1) {
|
|
918
|
-
const i = new URLSearchParams(
|
|
918
|
+
const i = new URLSearchParams(t.substring(s));
|
|
919
919
|
if (i.has("firstlook") || i.has("uat-mode") || i.get("uat") === "1")
|
|
920
920
|
return !0;
|
|
921
921
|
}
|
|
@@ -923,69 +923,90 @@ class C {
|
|
|
923
923
|
}
|
|
924
924
|
persist() {
|
|
925
925
|
try {
|
|
926
|
-
sessionStorage.setItem(
|
|
926
|
+
sessionStorage.setItem(y, "1");
|
|
927
927
|
} catch {
|
|
928
928
|
}
|
|
929
929
|
}
|
|
930
930
|
hasPersisted() {
|
|
931
931
|
try {
|
|
932
|
-
return sessionStorage.getItem(
|
|
932
|
+
return sessionStorage.getItem(y) === "1";
|
|
933
933
|
} catch {
|
|
934
934
|
return !1;
|
|
935
935
|
}
|
|
936
936
|
}
|
|
937
937
|
}
|
|
938
|
-
class
|
|
939
|
-
constructor(
|
|
940
|
-
this.onActivate =
|
|
938
|
+
const x = class x {
|
|
939
|
+
constructor(e) {
|
|
940
|
+
this.onActivate = e, this.handler = null, this.sequence = [], this.sequenceTimer = null;
|
|
941
941
|
}
|
|
942
942
|
start() {
|
|
943
|
-
this.handler = (
|
|
944
|
-
|
|
943
|
+
this.handler = (e) => {
|
|
944
|
+
if ((e.key === "u" || e.key === "U") && e.shiftKey && (e.ctrlKey || e.metaKey)) {
|
|
945
|
+
e.preventDefault(), this.onActivate();
|
|
946
|
+
return;
|
|
947
|
+
}
|
|
948
|
+
if (e.key.startsWith("Arrow")) {
|
|
949
|
+
this.sequence.push(e.key), this.sequenceTimer && clearTimeout(this.sequenceTimer), this.sequenceTimer = setTimeout(() => {
|
|
950
|
+
this.sequence = [];
|
|
951
|
+
}, 3e3);
|
|
952
|
+
const t = x.KONAMI;
|
|
953
|
+
this.sequence.length > t.length && (this.sequence = this.sequence.slice(-t.length)), this.sequence.length === t.length && this.sequence.every((s, i) => s === t[i]) && (this.sequence = [], this.onActivate());
|
|
954
|
+
}
|
|
945
955
|
}, window.addEventListener("keydown", this.handler);
|
|
946
956
|
}
|
|
947
957
|
stop() {
|
|
948
|
-
this.handler && (window.removeEventListener("keydown", this.handler), this.handler = null);
|
|
958
|
+
this.handler && (window.removeEventListener("keydown", this.handler), this.handler = null), this.sequenceTimer && (clearTimeout(this.sequenceTimer), this.sequenceTimer = null);
|
|
949
959
|
}
|
|
950
|
-
}
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
960
|
+
};
|
|
961
|
+
x.KONAMI = [
|
|
962
|
+
"ArrowUp",
|
|
963
|
+
"ArrowUp",
|
|
964
|
+
"ArrowDown",
|
|
965
|
+
"ArrowDown",
|
|
966
|
+
"ArrowLeft",
|
|
967
|
+
"ArrowRight",
|
|
968
|
+
"ArrowLeft",
|
|
969
|
+
"ArrowRight"
|
|
970
|
+
];
|
|
971
|
+
let S = x;
|
|
972
|
+
class X {
|
|
973
|
+
constructor(e, t) {
|
|
974
|
+
this.shadowRoot = e, this.callbacks = t, this.root = null, this.outsideClickHandler = null;
|
|
954
975
|
}
|
|
955
976
|
show() {
|
|
956
977
|
if (this.root) return;
|
|
957
978
|
this.root = r("div", { className: "fl-debug-menu" }), this.root.style.pointerEvents = "auto";
|
|
958
|
-
for (const
|
|
959
|
-
this.root.addEventListener(
|
|
960
|
-
const
|
|
979
|
+
for (const t of ["click", "mousedown", "mouseup", "pointerdown", "pointerup", "touchstart", "touchend"])
|
|
980
|
+
this.root.addEventListener(t, (s) => s.stopPropagation());
|
|
981
|
+
const e = [
|
|
961
982
|
{ icon: "▶", label: "Start Session", action: this.callbacks.onStartSession },
|
|
962
983
|
{ icon: "📸", label: "Report Issue", action: this.callbacks.onReportIssue },
|
|
963
984
|
{ icon: "✖", label: "Close SDK", action: this.callbacks.onClose }
|
|
964
985
|
];
|
|
965
|
-
for (const
|
|
986
|
+
for (const t of e) {
|
|
966
987
|
const s = r("button", { className: "fl-debug-menu-item" }, [
|
|
967
|
-
r("span", { className: "fl-debug-menu-item-icon" }, [
|
|
968
|
-
|
|
988
|
+
r("span", { className: "fl-debug-menu-item-icon" }, [t.icon]),
|
|
989
|
+
t.label
|
|
969
990
|
]);
|
|
970
991
|
s.onclick = (i) => {
|
|
971
|
-
i.stopPropagation(), this.hide(),
|
|
992
|
+
i.stopPropagation(), this.hide(), t.action();
|
|
972
993
|
}, this.root.appendChild(s);
|
|
973
994
|
}
|
|
974
995
|
this.shadowRoot.appendChild(this.root), setTimeout(() => {
|
|
975
|
-
this.outsideClickHandler = (
|
|
976
|
-
this.root && !this.root.contains(
|
|
996
|
+
this.outsideClickHandler = (t) => {
|
|
997
|
+
this.root && !this.root.contains(t.target) && this.hide();
|
|
977
998
|
}, document.addEventListener("click", this.outsideClickHandler, { capture: !0 });
|
|
978
999
|
}, 100);
|
|
979
1000
|
}
|
|
980
1001
|
hide() {
|
|
981
|
-
var
|
|
982
|
-
(
|
|
1002
|
+
var e;
|
|
1003
|
+
(e = this.root) == null || e.remove(), this.root = null, this.outsideClickHandler && (document.removeEventListener("click", this.outsideClickHandler, { capture: !0 }), this.outsideClickHandler = null);
|
|
983
1004
|
}
|
|
984
1005
|
get visible() {
|
|
985
1006
|
return this.root !== null;
|
|
986
1007
|
}
|
|
987
1008
|
}
|
|
988
|
-
const
|
|
1009
|
+
const Y = (
|
|
989
1010
|
/* css */
|
|
990
1011
|
`
|
|
991
1012
|
:host {
|
|
@@ -1486,10 +1507,10 @@ const X = (
|
|
|
1486
1507
|
}
|
|
1487
1508
|
.fl-feedback-modal-actions { padding: 0 16px 16px; }
|
|
1488
1509
|
`
|
|
1489
|
-
),
|
|
1490
|
-
class
|
|
1510
|
+
), G = 5;
|
|
1511
|
+
class J {
|
|
1491
1512
|
constructor() {
|
|
1492
|
-
this.config = null, this.events = new
|
|
1513
|
+
this.config = null, this.events = new L(), this.state = "idle", this.hostElement = null, this.shadowRoot = null, this.questManager = null, this.questOverlay = null, this.sessionRecorder = null, this.voiceRecorder = null, this.watermark = null, this.fieldMasker = null, this.storage = null, this.issueReporter = null, this.debugMenu = null, this.tapTrigger = null, this.deepLinkTrigger = null, this.keyboardTrigger = null, this.sessionId = null, this.sessionStartTime = 0, this.maxDurationTimer = null, this.backupTimer = null, this.isFlushing = !1;
|
|
1493
1514
|
}
|
|
1494
1515
|
// ----------------------------------------------------------------
|
|
1495
1516
|
// Public API
|
|
@@ -1498,12 +1519,12 @@ class G {
|
|
|
1498
1519
|
* Initialize the SDK with configuration.
|
|
1499
1520
|
* This does NOT activate the UI — it only sets up triggers.
|
|
1500
1521
|
*/
|
|
1501
|
-
async init(
|
|
1522
|
+
async init(e) {
|
|
1502
1523
|
if (this.state !== "idle") {
|
|
1503
1524
|
console.warn("[FirstLook] SDK already initialized.");
|
|
1504
1525
|
return;
|
|
1505
1526
|
}
|
|
1506
|
-
this.config =
|
|
1527
|
+
this.config = R(e), this.storage = new H(), await this.storage.open(), this.createShadowHost(), this.setupTriggers(), this.fieldMasker = new $(this.config.security.maskSelectors), this.state = "initialized", this.events.emit({ type: "sdk:initialized" }), this.flushUploadQueue(!0);
|
|
1507
1528
|
}
|
|
1508
1529
|
/**
|
|
1509
1530
|
* Manually activate the SDK UI (bypassing triggers).
|
|
@@ -1518,15 +1539,15 @@ class G {
|
|
|
1518
1539
|
/**
|
|
1519
1540
|
* Start a UAT session with a list of quests.
|
|
1520
1541
|
*/
|
|
1521
|
-
async startSession(
|
|
1542
|
+
async startSession(e) {
|
|
1522
1543
|
var s;
|
|
1523
1544
|
if (this.state !== "active")
|
|
1524
1545
|
throw new Error("[FirstLook] Cannot start session: SDK not active. Call activate() first.");
|
|
1525
|
-
(s = this.debugMenu) == null || s.hide(), this.sessionId =
|
|
1546
|
+
(s = this.debugMenu) == null || s.hide(), this.sessionId = D(), this.sessionStartTime = Date.now(), this.questManager = new E(this.events), this.questManager.loadQuests(e), this.sessionRecorder = new Q(
|
|
1526
1547
|
this.config,
|
|
1527
1548
|
this.events,
|
|
1528
1549
|
this.fieldMasker.getCombinedSelector()
|
|
1529
|
-
), this.voiceRecorder = new
|
|
1550
|
+
), this.voiceRecorder = new B(this.events), this.issueReporter = new W(this.shadowRoot, this.events), this.issueReporter.start(), this.sessionRecorder.start(), this.fieldMasker.start(), this.config.recording.voice && await this.voiceRecorder.start(), this.config.security.watermark && (this.watermark = new U(this.shadowRoot, this.config), this.watermark.show()), this.questOverlay = new P(this.shadowRoot, {
|
|
1530
1551
|
onOkWithFeedback: (i) => this.handleQuestOk(i),
|
|
1531
1552
|
onConcern: (i) => this.handleConcern(i),
|
|
1532
1553
|
onNg: () => this.handleQuestNg(),
|
|
@@ -1536,18 +1557,18 @@ class G {
|
|
|
1536
1557
|
onMinimize: () => {
|
|
1537
1558
|
}
|
|
1538
1559
|
}), this.questManager.startSession(this.sessionStartTime), this.renderCurrentQuest();
|
|
1539
|
-
const
|
|
1540
|
-
return
|
|
1560
|
+
const t = this.config.recording.maxDuration;
|
|
1561
|
+
return t > 0 && (this.maxDurationTimer = setTimeout(() => this.endSession(), t * 1e3)), this.backupTimer = setInterval(() => this.backupSession(), 3e4), this.state = "recording", this.events.emit({ type: "session:started", sessionId: this.sessionId }), this.sessionId;
|
|
1541
1562
|
}
|
|
1542
1563
|
/**
|
|
1543
1564
|
* Fetch quest definitions from the API and start a session.
|
|
1544
1565
|
*/
|
|
1545
|
-
async startSessionFromRemote(
|
|
1566
|
+
async startSessionFromRemote(e) {
|
|
1546
1567
|
if (this.state !== "active")
|
|
1547
1568
|
throw new Error("[FirstLook] Cannot start session: SDK not active. Call activate() first.");
|
|
1548
1569
|
if (!this.config)
|
|
1549
1570
|
throw new Error("[FirstLook] SDK not initialized.");
|
|
1550
|
-
const
|
|
1571
|
+
const t = this.config.endpoint.replace(/\/ingest-session$/, ""), s = await fetch(`${t}/export-quests?id=${encodeURIComponent(e)}`, {
|
|
1551
1572
|
headers: { "X-API-Key": this.config.apiKey }
|
|
1552
1573
|
});
|
|
1553
1574
|
if (!s.ok)
|
|
@@ -1559,12 +1580,12 @@ class G {
|
|
|
1559
1580
|
* End the current session, persist data, and trigger upload.
|
|
1560
1581
|
*/
|
|
1561
1582
|
async endSession() {
|
|
1562
|
-
var i, n, o, l,
|
|
1583
|
+
var i, n, o, l, a, h, d, m, f, p, g;
|
|
1563
1584
|
if (this.state !== "recording" || !this.sessionId) return null;
|
|
1564
|
-
this.maxDurationTimer && (clearTimeout(this.maxDurationTimer), this.maxDurationTimer = null), this.backupTimer && (clearInterval(this.backupTimer), this.backupTimer = null), (i = this.sessionRecorder) == null || i.stop(), await ((n = this.voiceRecorder) == null ? void 0 : n.stopAsync()), (o = this.issueReporter) == null || o.stop(), (l = this.fieldMasker) == null || l.stop(), (
|
|
1565
|
-
const
|
|
1585
|
+
this.maxDurationTimer && (clearTimeout(this.maxDurationTimer), this.maxDurationTimer = null), this.backupTimer && (clearInterval(this.backupTimer), this.backupTimer = null), (i = this.sessionRecorder) == null || i.stop(), await ((n = this.voiceRecorder) == null ? void 0 : n.stopAsync()), (o = this.issueReporter) == null || o.stop(), (l = this.fieldMasker) == null || l.stop(), (a = this.watermark) == null || a.hide();
|
|
1586
|
+
const e = this.questManager.getResults();
|
|
1566
1587
|
await Promise.allSettled(
|
|
1567
|
-
|
|
1588
|
+
e.map(async (b) => {
|
|
1568
1589
|
if (b.voiceMemoBlob) {
|
|
1569
1590
|
try {
|
|
1570
1591
|
b.voiceMemoBase64 = await this.blobToBase64(b.voiceMemoBlob);
|
|
@@ -1585,29 +1606,29 @@ class G {
|
|
|
1585
1606
|
);
|
|
1586
1607
|
})
|
|
1587
1608
|
);
|
|
1588
|
-
const
|
|
1609
|
+
const t = {
|
|
1589
1610
|
sessionId: this.sessionId,
|
|
1590
1611
|
projectId: this.config.projectId,
|
|
1591
1612
|
userId: this.config.userId,
|
|
1592
1613
|
role: this.config.role,
|
|
1593
|
-
deviceInfo:
|
|
1614
|
+
deviceInfo: q(),
|
|
1594
1615
|
startedAt: new Date(this.sessionStartTime).toISOString(),
|
|
1595
1616
|
endedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1596
1617
|
duration: Math.floor((Date.now() - this.sessionStartTime) / 1e3),
|
|
1597
|
-
quests:
|
|
1618
|
+
quests: e,
|
|
1598
1619
|
recordings: ((h = this.sessionRecorder) == null ? void 0 : h.getSnapshots()) ?? []
|
|
1599
1620
|
};
|
|
1600
|
-
await ((d = this.storage) == null ? void 0 : d.saveSession(
|
|
1621
|
+
await ((d = this.storage) == null ? void 0 : d.saveSession(t));
|
|
1601
1622
|
const s = ((m = this.issueReporter) == null ? void 0 : m.getAnnotations()) ?? [];
|
|
1602
1623
|
for (const b of s)
|
|
1603
1624
|
await ((f = this.storage) == null ? void 0 : f.saveAnnotation(this.sessionId, b));
|
|
1604
|
-
return await ((p = this.storage) == null ? void 0 : p.enqueueUpload({ session:
|
|
1625
|
+
return await ((p = this.storage) == null ? void 0 : p.enqueueUpload({ session: t, annotations: s })), (g = this.questOverlay) == null || g.destroy(), this.questOverlay = null, this.state = "finished", this.events.emit({ type: "session:ended", sessionId: this.sessionId }), this.flushUploadQueue(!1), t;
|
|
1605
1626
|
}
|
|
1606
1627
|
/**
|
|
1607
1628
|
* Subscribe to SDK events.
|
|
1608
1629
|
*/
|
|
1609
|
-
on(
|
|
1610
|
-
return this.events.on(
|
|
1630
|
+
on(e, t) {
|
|
1631
|
+
return this.events.on(e, t);
|
|
1611
1632
|
}
|
|
1612
1633
|
/**
|
|
1613
1634
|
* Get current SDK state.
|
|
@@ -1619,26 +1640,26 @@ class G {
|
|
|
1619
1640
|
* Completely destroy the SDK instance and clean up all resources.
|
|
1620
1641
|
*/
|
|
1621
1642
|
destroy() {
|
|
1622
|
-
var
|
|
1623
|
-
this.maxDurationTimer && (clearTimeout(this.maxDurationTimer), this.maxDurationTimer = null), this.backupTimer && (clearInterval(this.backupTimer), this.backupTimer = null), (
|
|
1624
|
-
}), (o = this.issueReporter) == null || o.stop(), (l = this.fieldMasker) == null || l.stop(), (
|
|
1643
|
+
var e, t, s, i, n, o, l, a, h, d, m, f;
|
|
1644
|
+
this.maxDurationTimer && (clearTimeout(this.maxDurationTimer), this.maxDurationTimer = null), this.backupTimer && (clearInterval(this.backupTimer), this.backupTimer = null), (e = this.tapTrigger) == null || e.stop(), (t = this.deepLinkTrigger) == null || t.stop(), (s = this.keyboardTrigger) == null || s.stop(), C.clearPersisted(), (i = this.sessionRecorder) == null || i.stop(), (n = this.voiceRecorder) == null || n.stopAsync().catch(() => {
|
|
1645
|
+
}), (o = this.issueReporter) == null || o.stop(), (l = this.fieldMasker) == null || l.stop(), (a = this.watermark) == null || a.hide(), (h = this.questOverlay) == null || h.destroy(), (d = this.debugMenu) == null || d.hide(), (m = this.hostElement) == null || m.remove(), (f = this.storage) == null || f.close(), this.events.removeAll(), this.config = null, this.questManager = null, this.questOverlay = null, this.sessionRecorder = null, this.voiceRecorder = null, this.issueReporter = null, this.fieldMasker = null, this.debugMenu = null, this.tapTrigger = null, this.deepLinkTrigger = null, this.keyboardTrigger = null, this.storage = null, this.hostElement = null, this.shadowRoot = null, this.sessionId = null, this.state = "idle";
|
|
1625
1646
|
}
|
|
1626
1647
|
// ----------------------------------------------------------------
|
|
1627
1648
|
// Private methods
|
|
1628
1649
|
// ----------------------------------------------------------------
|
|
1629
1650
|
createShadowHost() {
|
|
1630
|
-
var
|
|
1631
|
-
(
|
|
1632
|
-
const
|
|
1633
|
-
|
|
1651
|
+
var t;
|
|
1652
|
+
(t = document.getElementById("firstlook-sdk-root")) == null || t.remove(), this.hostElement = document.createElement("div"), this.hostElement.id = "firstlook-sdk-root", this.hostElement.style.cssText = "position:fixed;inset:0;z-index:2147483647;pointer-events:none;", document.body.appendChild(this.hostElement), this.shadowRoot = this.hostElement.attachShadow({ mode: "closed" });
|
|
1653
|
+
const e = document.createElement("style");
|
|
1654
|
+
e.textContent = Y, this.shadowRoot.appendChild(e);
|
|
1634
1655
|
}
|
|
1635
1656
|
setupTriggers() {
|
|
1636
|
-
const
|
|
1637
|
-
this.tapTrigger = new
|
|
1657
|
+
const e = this.config.triggers;
|
|
1658
|
+
this.tapTrigger = new V(e.tapCount, () => this.onActivate()), this.tapTrigger.start(), e.deepLink && (this.deepLinkTrigger = new C(() => this.onActivate()), this.deepLinkTrigger.start()), e.keyboard && (this.keyboardTrigger = new S(() => this.onActivate()), this.keyboardTrigger.start()), e.customCheck && e.customCheck() && this.onActivate();
|
|
1638
1659
|
}
|
|
1639
1660
|
onActivate() {
|
|
1640
|
-
var
|
|
1641
|
-
this.state === "initialized" && (this.state = "active", (
|
|
1661
|
+
var e, t, s;
|
|
1662
|
+
this.state === "initialized" && (this.state = "active", (e = this.tapTrigger) == null || e.stop(), (t = this.deepLinkTrigger) == null || t.stop(), (s = this.keyboardTrigger) == null || s.stop(), this.events.emit({ type: "sdk:activated" }), this.debugMenu = new X(this.shadowRoot, {
|
|
1642
1663
|
onStartSession: () => {
|
|
1643
1664
|
this.events.emit({ type: "sdk:activated" });
|
|
1644
1665
|
},
|
|
@@ -1651,77 +1672,77 @@ class G {
|
|
|
1651
1672
|
}
|
|
1652
1673
|
}), this.debugMenu.show());
|
|
1653
1674
|
}
|
|
1654
|
-
handleQuestOk(
|
|
1675
|
+
handleQuestOk(e) {
|
|
1655
1676
|
var i;
|
|
1656
1677
|
if (!this.questManager || !this.sessionRecorder) return;
|
|
1657
|
-
const
|
|
1658
|
-
this.questManager.completeCurrentQuest(
|
|
1659
|
-
comment:
|
|
1678
|
+
const t = this.sessionRecorder.getActionLogs(), s = (i = this.questOverlay) == null ? void 0 : i.getChecklist();
|
|
1679
|
+
this.questManager.completeCurrentQuest(t, {
|
|
1680
|
+
comment: e || void 0,
|
|
1660
1681
|
checklist: s
|
|
1661
1682
|
}), this.renderCurrentQuest();
|
|
1662
1683
|
}
|
|
1663
|
-
handleConcern(
|
|
1684
|
+
handleConcern(e) {
|
|
1664
1685
|
var i;
|
|
1665
1686
|
if (!this.questManager || !this.sessionRecorder) return;
|
|
1666
|
-
const
|
|
1667
|
-
this.questManager.completeCurrentQuest(
|
|
1668
|
-
comment:
|
|
1687
|
+
const t = this.sessionRecorder.getActionLogs(), s = (i = this.questOverlay) == null ? void 0 : i.getChecklist();
|
|
1688
|
+
this.questManager.completeCurrentQuest(t, {
|
|
1689
|
+
comment: e,
|
|
1669
1690
|
concern: !0,
|
|
1670
1691
|
checklist: s
|
|
1671
1692
|
}), this.renderCurrentQuest();
|
|
1672
1693
|
}
|
|
1673
1694
|
handleQuestNg() {
|
|
1674
1695
|
if (!this.questManager || !this.questOverlay) return;
|
|
1675
|
-
const
|
|
1676
|
-
|
|
1696
|
+
const e = this.questManager.getCurrentQuest();
|
|
1697
|
+
e && this.questOverlay.renderFeedbackModal(e.title, "ng");
|
|
1677
1698
|
}
|
|
1678
|
-
handleNgFeedbackSubmit(
|
|
1699
|
+
handleNgFeedbackSubmit(e) {
|
|
1679
1700
|
var i;
|
|
1680
1701
|
if (!this.questManager || !this.sessionRecorder) return;
|
|
1681
|
-
const
|
|
1682
|
-
this.questManager.failCurrentQuest(
|
|
1702
|
+
const t = this.sessionRecorder.getActionLogs(), s = (i = this.questOverlay) == null ? void 0 : i.getChecklist();
|
|
1703
|
+
this.questManager.failCurrentQuest(t, e || void 0, void 0, s), this.renderCurrentQuest();
|
|
1683
1704
|
}
|
|
1684
|
-
handleMemo(
|
|
1685
|
-
if (!this.questManager || !
|
|
1686
|
-
const
|
|
1687
|
-
comment:
|
|
1705
|
+
handleMemo(e) {
|
|
1706
|
+
if (!this.questManager || !e) return;
|
|
1707
|
+
const t = {
|
|
1708
|
+
comment: e,
|
|
1688
1709
|
timestamp: Date.now(),
|
|
1689
1710
|
relativeTime: Date.now() - this.sessionStartTime
|
|
1690
1711
|
};
|
|
1691
|
-
this.questManager.addFeedback(
|
|
1712
|
+
this.questManager.addFeedback(t);
|
|
1692
1713
|
}
|
|
1693
1714
|
renderCurrentQuest() {
|
|
1694
1715
|
var s;
|
|
1695
1716
|
if (!this.questManager || !this.questOverlay) return;
|
|
1696
|
-
const
|
|
1697
|
-
if (
|
|
1717
|
+
const e = this.questManager.getStatus();
|
|
1718
|
+
if (e.isBlocked) {
|
|
1698
1719
|
const n = this.questManager.getResults().find((l) => l.status === "FAILED"), o = n ? n.questId : "Unknown";
|
|
1699
1720
|
this.questOverlay.renderBlocked(o);
|
|
1700
1721
|
return;
|
|
1701
1722
|
}
|
|
1702
|
-
if (
|
|
1703
|
-
this.questOverlay.renderSummary(
|
|
1723
|
+
if (e.isFinished) {
|
|
1724
|
+
this.questOverlay.renderSummary(e.completed, e.failed, e.total);
|
|
1704
1725
|
return;
|
|
1705
1726
|
}
|
|
1706
|
-
const
|
|
1707
|
-
|
|
1708
|
-
|
|
1727
|
+
const t = this.questManager.getCurrentQuest();
|
|
1728
|
+
t && this.questOverlay.renderQuest(
|
|
1729
|
+
t,
|
|
1709
1730
|
this.questManager.getQuestStatuses(),
|
|
1710
1731
|
((s = this.voiceRecorder) == null ? void 0 : s.recording) ?? !1
|
|
1711
1732
|
);
|
|
1712
1733
|
}
|
|
1713
|
-
async flushUploadQueue(
|
|
1734
|
+
async flushUploadQueue(e) {
|
|
1714
1735
|
if (!(!this.storage || !this.config || this.isFlushing)) {
|
|
1715
1736
|
this.isFlushing = !0;
|
|
1716
1737
|
try {
|
|
1717
|
-
if (
|
|
1738
|
+
if (e) {
|
|
1718
1739
|
const s = navigator.connection;
|
|
1719
1740
|
if (s && s.type && s.type !== "wifi" && s.effectiveType !== "4g")
|
|
1720
1741
|
return;
|
|
1721
1742
|
}
|
|
1722
|
-
const
|
|
1723
|
-
for (const s of
|
|
1724
|
-
if (s.attempts >=
|
|
1743
|
+
const t = await this.storage.getPendingUploads();
|
|
1744
|
+
for (const s of t) {
|
|
1745
|
+
if (s.attempts >= G) {
|
|
1725
1746
|
await this.storage.removeFromQueue(s.key);
|
|
1726
1747
|
continue;
|
|
1727
1748
|
}
|
|
@@ -1749,32 +1770,32 @@ class G {
|
|
|
1749
1770
|
}
|
|
1750
1771
|
}
|
|
1751
1772
|
async backupSession() {
|
|
1752
|
-
var
|
|
1773
|
+
var e;
|
|
1753
1774
|
if (!(!this.storage || !this.sessionId || !this.config || !this.sessionRecorder))
|
|
1754
1775
|
try {
|
|
1755
|
-
const
|
|
1776
|
+
const t = {
|
|
1756
1777
|
sessionId: this.sessionId,
|
|
1757
1778
|
projectId: this.config.projectId,
|
|
1758
1779
|
userId: this.config.userId,
|
|
1759
1780
|
role: this.config.role,
|
|
1760
|
-
deviceInfo:
|
|
1781
|
+
deviceInfo: q(),
|
|
1761
1782
|
startedAt: new Date(this.sessionStartTime).toISOString(),
|
|
1762
1783
|
duration: Math.floor((Date.now() - this.sessionStartTime) / 1e3),
|
|
1763
|
-
quests: ((
|
|
1784
|
+
quests: ((e = this.questManager) == null ? void 0 : e.getResults()) ?? [],
|
|
1764
1785
|
recordings: this.sessionRecorder.getSnapshots()
|
|
1765
1786
|
};
|
|
1766
|
-
await this.storage.saveSession(
|
|
1787
|
+
await this.storage.saveSession(t);
|
|
1767
1788
|
} catch {
|
|
1768
1789
|
}
|
|
1769
1790
|
}
|
|
1770
|
-
blobToBase64(
|
|
1771
|
-
return new Promise((
|
|
1791
|
+
blobToBase64(e) {
|
|
1792
|
+
return new Promise((t, s) => {
|
|
1772
1793
|
const i = new FileReader();
|
|
1773
|
-
i.onloadend = () =>
|
|
1794
|
+
i.onloadend = () => t(i.result), i.onerror = () => s(i.error), i.readAsDataURL(e);
|
|
1774
1795
|
});
|
|
1775
1796
|
}
|
|
1776
1797
|
}
|
|
1777
1798
|
export {
|
|
1778
|
-
|
|
1799
|
+
J as FirstLookSDK
|
|
1779
1800
|
};
|
|
1780
1801
|
//# sourceMappingURL=firstlook.es.js.map
|