@firstlook-uat/sdk 0.4.2 → 0.4.4
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 +430 -400
- 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 T() {
|
|
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 O {
|
|
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 P = 100, z = 2 * 1024 * 1024;
|
|
380
|
+
class B {
|
|
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 > z) return;
|
|
448
|
+
this.snapshots.length >= P && 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 Q {
|
|
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 q = ["#e17055", "#6c5ce7", "#00b894", "#fdcb6e", "#ffffff"];
|
|
720
|
+
class W {
|
|
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 = q[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 q) {
|
|
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 _ {
|
|
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 W(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,99 @@ 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.typeBuffer = "", this.sequenceTimer = null, this.typeTimer = 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.length === 1) {
|
|
949
|
+
if (this.typeBuffer += e.key, this.typeTimer && clearTimeout(this.typeTimer), this.typeTimer = setTimeout(() => {
|
|
950
|
+
this.typeBuffer = "";
|
|
951
|
+
}, 3e3), this.typeBuffer.endsWith(x.MAGIC_WORD)) {
|
|
952
|
+
this.typeBuffer = "", this.onActivate();
|
|
953
|
+
return;
|
|
954
|
+
}
|
|
955
|
+
this.typeBuffer.length > 20 && (this.typeBuffer = this.typeBuffer.slice(-12));
|
|
956
|
+
}
|
|
957
|
+
if (e.key.startsWith("Arrow")) {
|
|
958
|
+
this.sequence.push(e.key), this.sequenceTimer && clearTimeout(this.sequenceTimer), this.sequenceTimer = setTimeout(() => {
|
|
959
|
+
this.sequence = [];
|
|
960
|
+
}, 3e3);
|
|
961
|
+
const t = x.KONAMI;
|
|
962
|
+
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());
|
|
963
|
+
}
|
|
945
964
|
}, window.addEventListener("keydown", this.handler);
|
|
946
965
|
}
|
|
947
966
|
stop() {
|
|
948
|
-
this.handler && (window.removeEventListener("keydown", this.handler), this.handler = null);
|
|
967
|
+
this.handler && (window.removeEventListener("keydown", this.handler), this.handler = null), this.sequenceTimer && (clearTimeout(this.sequenceTimer), this.sequenceTimer = null), this.typeTimer && (clearTimeout(this.typeTimer), this.typeTimer = null);
|
|
949
968
|
}
|
|
950
|
-
}
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
969
|
+
};
|
|
970
|
+
x.KONAMI = [
|
|
971
|
+
"ArrowUp",
|
|
972
|
+
"ArrowUp",
|
|
973
|
+
"ArrowDown",
|
|
974
|
+
"ArrowDown",
|
|
975
|
+
"ArrowLeft",
|
|
976
|
+
"ArrowRight",
|
|
977
|
+
"ArrowLeft",
|
|
978
|
+
"ArrowRight"
|
|
979
|
+
], x.MAGIC_WORD = "1st/look.";
|
|
980
|
+
let S = x;
|
|
981
|
+
class X {
|
|
982
|
+
constructor(e, t) {
|
|
983
|
+
this.shadowRoot = e, this.callbacks = t, this.root = null, this.outsideClickHandler = null;
|
|
954
984
|
}
|
|
955
985
|
show() {
|
|
956
986
|
if (this.root) return;
|
|
957
987
|
this.root = r("div", { className: "fl-debug-menu" }), this.root.style.pointerEvents = "auto";
|
|
958
|
-
for (const
|
|
959
|
-
this.root.addEventListener(
|
|
960
|
-
const
|
|
988
|
+
for (const t of ["click", "mousedown", "mouseup", "pointerdown", "pointerup", "touchstart", "touchend"])
|
|
989
|
+
this.root.addEventListener(t, (s) => s.stopPropagation());
|
|
990
|
+
const e = [
|
|
961
991
|
{ icon: "▶", label: "Start Session", action: this.callbacks.onStartSession },
|
|
962
992
|
{ icon: "📸", label: "Report Issue", action: this.callbacks.onReportIssue },
|
|
963
993
|
{ icon: "✖", label: "Close SDK", action: this.callbacks.onClose }
|
|
964
994
|
];
|
|
965
|
-
for (const
|
|
995
|
+
for (const t of e) {
|
|
966
996
|
const s = r("button", { className: "fl-debug-menu-item" }, [
|
|
967
|
-
r("span", { className: "fl-debug-menu-item-icon" }, [
|
|
968
|
-
|
|
997
|
+
r("span", { className: "fl-debug-menu-item-icon" }, [t.icon]),
|
|
998
|
+
t.label
|
|
969
999
|
]);
|
|
970
1000
|
s.onclick = (i) => {
|
|
971
|
-
i.stopPropagation(), this.hide(),
|
|
1001
|
+
i.stopPropagation(), this.hide(), t.action();
|
|
972
1002
|
}, this.root.appendChild(s);
|
|
973
1003
|
}
|
|
974
1004
|
this.shadowRoot.appendChild(this.root), setTimeout(() => {
|
|
975
|
-
this.outsideClickHandler = (
|
|
976
|
-
this.root && !this.root.contains(
|
|
1005
|
+
this.outsideClickHandler = (t) => {
|
|
1006
|
+
this.root && !this.root.contains(t.target) && this.hide();
|
|
977
1007
|
}, document.addEventListener("click", this.outsideClickHandler, { capture: !0 });
|
|
978
1008
|
}, 100);
|
|
979
1009
|
}
|
|
980
1010
|
hide() {
|
|
981
|
-
var
|
|
982
|
-
(
|
|
1011
|
+
var e;
|
|
1012
|
+
(e = this.root) == null || e.remove(), this.root = null, this.outsideClickHandler && (document.removeEventListener("click", this.outsideClickHandler, { capture: !0 }), this.outsideClickHandler = null);
|
|
983
1013
|
}
|
|
984
1014
|
get visible() {
|
|
985
1015
|
return this.root !== null;
|
|
986
1016
|
}
|
|
987
1017
|
}
|
|
988
|
-
const
|
|
1018
|
+
const G = (
|
|
989
1019
|
/* css */
|
|
990
1020
|
`
|
|
991
1021
|
:host {
|
|
@@ -1487,9 +1517,9 @@ const X = (
|
|
|
1487
1517
|
.fl-feedback-modal-actions { padding: 0 16px 16px; }
|
|
1488
1518
|
`
|
|
1489
1519
|
), Y = 5;
|
|
1490
|
-
class
|
|
1520
|
+
class J {
|
|
1491
1521
|
constructor() {
|
|
1492
|
-
this.config = null, this.events = new
|
|
1522
|
+
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
1523
|
}
|
|
1494
1524
|
// ----------------------------------------------------------------
|
|
1495
1525
|
// Public API
|
|
@@ -1498,12 +1528,12 @@ class G {
|
|
|
1498
1528
|
* Initialize the SDK with configuration.
|
|
1499
1529
|
* This does NOT activate the UI — it only sets up triggers.
|
|
1500
1530
|
*/
|
|
1501
|
-
async init(
|
|
1531
|
+
async init(e) {
|
|
1502
1532
|
if (this.state !== "idle") {
|
|
1503
1533
|
console.warn("[FirstLook] SDK already initialized.");
|
|
1504
1534
|
return;
|
|
1505
1535
|
}
|
|
1506
|
-
this.config =
|
|
1536
|
+
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
1537
|
}
|
|
1508
1538
|
/**
|
|
1509
1539
|
* Manually activate the SDK UI (bypassing triggers).
|
|
@@ -1518,15 +1548,15 @@ class G {
|
|
|
1518
1548
|
/**
|
|
1519
1549
|
* Start a UAT session with a list of quests.
|
|
1520
1550
|
*/
|
|
1521
|
-
async startSession(
|
|
1551
|
+
async startSession(e) {
|
|
1522
1552
|
var s;
|
|
1523
1553
|
if (this.state !== "active")
|
|
1524
1554
|
throw new Error("[FirstLook] Cannot start session: SDK not active. Call activate() first.");
|
|
1525
|
-
(s = this.debugMenu) == null || s.hide(), this.sessionId =
|
|
1555
|
+
(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 B(
|
|
1526
1556
|
this.config,
|
|
1527
1557
|
this.events,
|
|
1528
1558
|
this.fieldMasker.getCombinedSelector()
|
|
1529
|
-
), this.voiceRecorder = new
|
|
1559
|
+
), this.voiceRecorder = new Q(this.events), this.issueReporter = new _(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 O(this.shadowRoot, {
|
|
1530
1560
|
onOkWithFeedback: (i) => this.handleQuestOk(i),
|
|
1531
1561
|
onConcern: (i) => this.handleConcern(i),
|
|
1532
1562
|
onNg: () => this.handleQuestNg(),
|
|
@@ -1536,18 +1566,18 @@ class G {
|
|
|
1536
1566
|
onMinimize: () => {
|
|
1537
1567
|
}
|
|
1538
1568
|
}), this.questManager.startSession(this.sessionStartTime), this.renderCurrentQuest();
|
|
1539
|
-
const
|
|
1540
|
-
return
|
|
1569
|
+
const t = this.config.recording.maxDuration;
|
|
1570
|
+
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
1571
|
}
|
|
1542
1572
|
/**
|
|
1543
1573
|
* Fetch quest definitions from the API and start a session.
|
|
1544
1574
|
*/
|
|
1545
|
-
async startSessionFromRemote(
|
|
1575
|
+
async startSessionFromRemote(e) {
|
|
1546
1576
|
if (this.state !== "active")
|
|
1547
1577
|
throw new Error("[FirstLook] Cannot start session: SDK not active. Call activate() first.");
|
|
1548
1578
|
if (!this.config)
|
|
1549
1579
|
throw new Error("[FirstLook] SDK not initialized.");
|
|
1550
|
-
const
|
|
1580
|
+
const t = this.config.endpoint.replace(/\/ingest-session$/, ""), s = await fetch(`${t}/export-quests?id=${encodeURIComponent(e)}`, {
|
|
1551
1581
|
headers: { "X-API-Key": this.config.apiKey }
|
|
1552
1582
|
});
|
|
1553
1583
|
if (!s.ok)
|
|
@@ -1559,12 +1589,12 @@ class G {
|
|
|
1559
1589
|
* End the current session, persist data, and trigger upload.
|
|
1560
1590
|
*/
|
|
1561
1591
|
async endSession() {
|
|
1562
|
-
var i, n, o, l,
|
|
1592
|
+
var i, n, o, l, a, h, d, m, f, p, g;
|
|
1563
1593
|
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
|
|
1594
|
+
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();
|
|
1595
|
+
const e = this.questManager.getResults();
|
|
1566
1596
|
await Promise.allSettled(
|
|
1567
|
-
|
|
1597
|
+
e.map(async (b) => {
|
|
1568
1598
|
if (b.voiceMemoBlob) {
|
|
1569
1599
|
try {
|
|
1570
1600
|
b.voiceMemoBase64 = await this.blobToBase64(b.voiceMemoBlob);
|
|
@@ -1585,29 +1615,29 @@ class G {
|
|
|
1585
1615
|
);
|
|
1586
1616
|
})
|
|
1587
1617
|
);
|
|
1588
|
-
const
|
|
1618
|
+
const t = {
|
|
1589
1619
|
sessionId: this.sessionId,
|
|
1590
1620
|
projectId: this.config.projectId,
|
|
1591
1621
|
userId: this.config.userId,
|
|
1592
1622
|
role: this.config.role,
|
|
1593
|
-
deviceInfo:
|
|
1623
|
+
deviceInfo: T(),
|
|
1594
1624
|
startedAt: new Date(this.sessionStartTime).toISOString(),
|
|
1595
1625
|
endedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1596
1626
|
duration: Math.floor((Date.now() - this.sessionStartTime) / 1e3),
|
|
1597
|
-
quests:
|
|
1627
|
+
quests: e,
|
|
1598
1628
|
recordings: ((h = this.sessionRecorder) == null ? void 0 : h.getSnapshots()) ?? []
|
|
1599
1629
|
};
|
|
1600
|
-
await ((d = this.storage) == null ? void 0 : d.saveSession(
|
|
1630
|
+
await ((d = this.storage) == null ? void 0 : d.saveSession(t));
|
|
1601
1631
|
const s = ((m = this.issueReporter) == null ? void 0 : m.getAnnotations()) ?? [];
|
|
1602
1632
|
for (const b of s)
|
|
1603
1633
|
await ((f = this.storage) == null ? void 0 : f.saveAnnotation(this.sessionId, b));
|
|
1604
|
-
return await ((p = this.storage) == null ? void 0 : p.enqueueUpload({ session:
|
|
1634
|
+
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
1635
|
}
|
|
1606
1636
|
/**
|
|
1607
1637
|
* Subscribe to SDK events.
|
|
1608
1638
|
*/
|
|
1609
|
-
on(
|
|
1610
|
-
return this.events.on(
|
|
1639
|
+
on(e, t) {
|
|
1640
|
+
return this.events.on(e, t);
|
|
1611
1641
|
}
|
|
1612
1642
|
/**
|
|
1613
1643
|
* Get current SDK state.
|
|
@@ -1619,26 +1649,26 @@ class G {
|
|
|
1619
1649
|
* Completely destroy the SDK instance and clean up all resources.
|
|
1620
1650
|
*/
|
|
1621
1651
|
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(), (
|
|
1652
|
+
var e, t, s, i, n, o, l, a, h, d, m, f;
|
|
1653
|
+
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(() => {
|
|
1654
|
+
}), (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
1655
|
}
|
|
1626
1656
|
// ----------------------------------------------------------------
|
|
1627
1657
|
// Private methods
|
|
1628
1658
|
// ----------------------------------------------------------------
|
|
1629
1659
|
createShadowHost() {
|
|
1630
|
-
var
|
|
1631
|
-
(
|
|
1632
|
-
const
|
|
1633
|
-
|
|
1660
|
+
var t;
|
|
1661
|
+
(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" });
|
|
1662
|
+
const e = document.createElement("style");
|
|
1663
|
+
e.textContent = G, this.shadowRoot.appendChild(e);
|
|
1634
1664
|
}
|
|
1635
1665
|
setupTriggers() {
|
|
1636
|
-
const
|
|
1637
|
-
this.tapTrigger = new
|
|
1666
|
+
const e = this.config.triggers;
|
|
1667
|
+
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
1668
|
}
|
|
1639
1669
|
onActivate() {
|
|
1640
|
-
var
|
|
1641
|
-
this.state === "initialized" && (this.state = "active", (
|
|
1670
|
+
var e, t, s;
|
|
1671
|
+
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
1672
|
onStartSession: () => {
|
|
1643
1673
|
this.events.emit({ type: "sdk:activated" });
|
|
1644
1674
|
},
|
|
@@ -1651,76 +1681,76 @@ class G {
|
|
|
1651
1681
|
}
|
|
1652
1682
|
}), this.debugMenu.show());
|
|
1653
1683
|
}
|
|
1654
|
-
handleQuestOk(
|
|
1684
|
+
handleQuestOk(e) {
|
|
1655
1685
|
var i;
|
|
1656
1686
|
if (!this.questManager || !this.sessionRecorder) return;
|
|
1657
|
-
const
|
|
1658
|
-
this.questManager.completeCurrentQuest(
|
|
1659
|
-
comment:
|
|
1687
|
+
const t = this.sessionRecorder.getActionLogs(), s = (i = this.questOverlay) == null ? void 0 : i.getChecklist();
|
|
1688
|
+
this.questManager.completeCurrentQuest(t, {
|
|
1689
|
+
comment: e || void 0,
|
|
1660
1690
|
checklist: s
|
|
1661
1691
|
}), this.renderCurrentQuest();
|
|
1662
1692
|
}
|
|
1663
|
-
handleConcern(
|
|
1693
|
+
handleConcern(e) {
|
|
1664
1694
|
var i;
|
|
1665
1695
|
if (!this.questManager || !this.sessionRecorder) return;
|
|
1666
|
-
const
|
|
1667
|
-
this.questManager.completeCurrentQuest(
|
|
1668
|
-
comment:
|
|
1696
|
+
const t = this.sessionRecorder.getActionLogs(), s = (i = this.questOverlay) == null ? void 0 : i.getChecklist();
|
|
1697
|
+
this.questManager.completeCurrentQuest(t, {
|
|
1698
|
+
comment: e,
|
|
1669
1699
|
concern: !0,
|
|
1670
1700
|
checklist: s
|
|
1671
1701
|
}), this.renderCurrentQuest();
|
|
1672
1702
|
}
|
|
1673
1703
|
handleQuestNg() {
|
|
1674
1704
|
if (!this.questManager || !this.questOverlay) return;
|
|
1675
|
-
const
|
|
1676
|
-
|
|
1705
|
+
const e = this.questManager.getCurrentQuest();
|
|
1706
|
+
e && this.questOverlay.renderFeedbackModal(e.title, "ng");
|
|
1677
1707
|
}
|
|
1678
|
-
handleNgFeedbackSubmit(
|
|
1708
|
+
handleNgFeedbackSubmit(e) {
|
|
1679
1709
|
var i;
|
|
1680
1710
|
if (!this.questManager || !this.sessionRecorder) return;
|
|
1681
|
-
const
|
|
1682
|
-
this.questManager.failCurrentQuest(
|
|
1711
|
+
const t = this.sessionRecorder.getActionLogs(), s = (i = this.questOverlay) == null ? void 0 : i.getChecklist();
|
|
1712
|
+
this.questManager.failCurrentQuest(t, e || void 0, void 0, s), this.renderCurrentQuest();
|
|
1683
1713
|
}
|
|
1684
|
-
handleMemo(
|
|
1685
|
-
if (!this.questManager || !
|
|
1686
|
-
const
|
|
1687
|
-
comment:
|
|
1714
|
+
handleMemo(e) {
|
|
1715
|
+
if (!this.questManager || !e) return;
|
|
1716
|
+
const t = {
|
|
1717
|
+
comment: e,
|
|
1688
1718
|
timestamp: Date.now(),
|
|
1689
1719
|
relativeTime: Date.now() - this.sessionStartTime
|
|
1690
1720
|
};
|
|
1691
|
-
this.questManager.addFeedback(
|
|
1721
|
+
this.questManager.addFeedback(t);
|
|
1692
1722
|
}
|
|
1693
1723
|
renderCurrentQuest() {
|
|
1694
1724
|
var s;
|
|
1695
1725
|
if (!this.questManager || !this.questOverlay) return;
|
|
1696
|
-
const
|
|
1697
|
-
if (
|
|
1726
|
+
const e = this.questManager.getStatus();
|
|
1727
|
+
if (e.isBlocked) {
|
|
1698
1728
|
const n = this.questManager.getResults().find((l) => l.status === "FAILED"), o = n ? n.questId : "Unknown";
|
|
1699
1729
|
this.questOverlay.renderBlocked(o);
|
|
1700
1730
|
return;
|
|
1701
1731
|
}
|
|
1702
|
-
if (
|
|
1703
|
-
this.questOverlay.renderSummary(
|
|
1732
|
+
if (e.isFinished) {
|
|
1733
|
+
this.questOverlay.renderSummary(e.completed, e.failed, e.total);
|
|
1704
1734
|
return;
|
|
1705
1735
|
}
|
|
1706
|
-
const
|
|
1707
|
-
|
|
1708
|
-
|
|
1736
|
+
const t = this.questManager.getCurrentQuest();
|
|
1737
|
+
t && this.questOverlay.renderQuest(
|
|
1738
|
+
t,
|
|
1709
1739
|
this.questManager.getQuestStatuses(),
|
|
1710
1740
|
((s = this.voiceRecorder) == null ? void 0 : s.recording) ?? !1
|
|
1711
1741
|
);
|
|
1712
1742
|
}
|
|
1713
|
-
async flushUploadQueue(
|
|
1743
|
+
async flushUploadQueue(e) {
|
|
1714
1744
|
if (!(!this.storage || !this.config || this.isFlushing)) {
|
|
1715
1745
|
this.isFlushing = !0;
|
|
1716
1746
|
try {
|
|
1717
|
-
if (
|
|
1747
|
+
if (e) {
|
|
1718
1748
|
const s = navigator.connection;
|
|
1719
1749
|
if (s && s.type && s.type !== "wifi" && s.effectiveType !== "4g")
|
|
1720
1750
|
return;
|
|
1721
1751
|
}
|
|
1722
|
-
const
|
|
1723
|
-
for (const s of
|
|
1752
|
+
const t = await this.storage.getPendingUploads();
|
|
1753
|
+
for (const s of t) {
|
|
1724
1754
|
if (s.attempts >= Y) {
|
|
1725
1755
|
await this.storage.removeFromQueue(s.key);
|
|
1726
1756
|
continue;
|
|
@@ -1749,32 +1779,32 @@ class G {
|
|
|
1749
1779
|
}
|
|
1750
1780
|
}
|
|
1751
1781
|
async backupSession() {
|
|
1752
|
-
var
|
|
1782
|
+
var e;
|
|
1753
1783
|
if (!(!this.storage || !this.sessionId || !this.config || !this.sessionRecorder))
|
|
1754
1784
|
try {
|
|
1755
|
-
const
|
|
1785
|
+
const t = {
|
|
1756
1786
|
sessionId: this.sessionId,
|
|
1757
1787
|
projectId: this.config.projectId,
|
|
1758
1788
|
userId: this.config.userId,
|
|
1759
1789
|
role: this.config.role,
|
|
1760
|
-
deviceInfo:
|
|
1790
|
+
deviceInfo: T(),
|
|
1761
1791
|
startedAt: new Date(this.sessionStartTime).toISOString(),
|
|
1762
1792
|
duration: Math.floor((Date.now() - this.sessionStartTime) / 1e3),
|
|
1763
|
-
quests: ((
|
|
1793
|
+
quests: ((e = this.questManager) == null ? void 0 : e.getResults()) ?? [],
|
|
1764
1794
|
recordings: this.sessionRecorder.getSnapshots()
|
|
1765
1795
|
};
|
|
1766
|
-
await this.storage.saveSession(
|
|
1796
|
+
await this.storage.saveSession(t);
|
|
1767
1797
|
} catch {
|
|
1768
1798
|
}
|
|
1769
1799
|
}
|
|
1770
|
-
blobToBase64(
|
|
1771
|
-
return new Promise((
|
|
1800
|
+
blobToBase64(e) {
|
|
1801
|
+
return new Promise((t, s) => {
|
|
1772
1802
|
const i = new FileReader();
|
|
1773
|
-
i.onloadend = () =>
|
|
1803
|
+
i.onloadend = () => t(i.result), i.onerror = () => s(i.error), i.readAsDataURL(e);
|
|
1774
1804
|
});
|
|
1775
1805
|
}
|
|
1776
1806
|
}
|
|
1777
1807
|
export {
|
|
1778
|
-
|
|
1808
|
+
J as FirstLookSDK
|
|
1779
1809
|
};
|
|
1780
1810
|
//# sourceMappingURL=firstlook.es.js.map
|