@firstlook-uat/sdk 0.4.1 → 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 +444 -424
- 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),
|
|
221
|
-
const
|
|
220
|
+
p(), h.appendChild(f), a.preamble && h.appendChild(r("p", { className: "fl-quest-description" }, [a.preamble]));
|
|
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
|
-
h.appendChild(
|
|
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" }, [
|
|
@@ -251,12 +251,12 @@ class N {
|
|
|
251
251
|
"REC"
|
|
252
252
|
])
|
|
253
253
|
);
|
|
254
|
-
const
|
|
255
|
-
d.appendChild(
|
|
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(
|
|
254
|
+
const m = r("span", { className: "fl-status-timer" }, ["00:00"]);
|
|
255
|
+
d.appendChild(m), o.appendChild(d), this.root.appendChild(o), this.startTimer(m), this.root.onclick = () => {
|
|
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,74 +272,73 @@ 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
|
|
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
|
-
this.createButton("fl-btn fl-btn-finish",
|
|
294
|
-
const
|
|
295
|
-
if (
|
|
296
|
-
this.callbacks.onNgWithFeedback(
|
|
297
|
-
else if (
|
|
298
|
-
this.callbacks.onOkWithFeedback(
|
|
299
|
-
else if (
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
if (!m) {
|
|
293
|
+
this.createButton("fl-btn fl-btn-finish", m, () => {
|
|
294
|
+
const g = d.value.trim();
|
|
295
|
+
if (t === "ng")
|
|
296
|
+
this.callbacks.onNgWithFeedback(g);
|
|
297
|
+
else if (t === "ok")
|
|
298
|
+
this.callbacks.onOkWithFeedback(g);
|
|
299
|
+
else if (t === "concern")
|
|
300
|
+
this.callbacks.onConcern(g);
|
|
301
|
+
else {
|
|
302
|
+
if (!g) {
|
|
304
303
|
s.remove();
|
|
305
304
|
return;
|
|
306
305
|
}
|
|
307
|
-
this.callbacks.onMemo(
|
|
306
|
+
this.callbacks.onMemo(g);
|
|
308
307
|
}
|
|
309
308
|
s.remove();
|
|
310
309
|
})
|
|
311
310
|
]);
|
|
312
|
-
|
|
311
|
+
a.appendChild(p), s.appendChild(a), this.root.appendChild(s), d.focus();
|
|
313
312
|
}
|
|
314
|
-
renderSummary(
|
|
313
|
+
renderSummary(e, t, s) {
|
|
315
314
|
this.stopTimer(), this.root.className = "fl-quest-overlay", this.root.innerHTML = "";
|
|
316
315
|
const i = r("div", { className: "fl-summary" }, [
|
|
317
|
-
r("div", { className: "fl-summary-icon" }, [
|
|
316
|
+
r("div", { className: "fl-summary-icon" }, [e === s ? "🎉" : "📋"]),
|
|
318
317
|
r("div", { className: "fl-summary-title" }, [
|
|
319
|
-
|
|
318
|
+
e === s ? "All Quests Complete!" : "Session Complete"
|
|
320
319
|
]),
|
|
321
320
|
r("div", { className: "fl-summary-subtitle" }, [
|
|
322
|
-
`${
|
|
321
|
+
`${e + t} of ${s} quests attempted`
|
|
323
322
|
]),
|
|
324
323
|
r("div", { className: "fl-summary-stats" }, [
|
|
325
|
-
this.createStat(
|
|
326
|
-
this.createStat(
|
|
324
|
+
this.createStat(e.toString(), "Passed", "fl-ok"),
|
|
325
|
+
this.createStat(t.toString(), "Failed", "fl-ng")
|
|
327
326
|
]),
|
|
328
327
|
this.createButton("fl-btn fl-btn-finish", "Finish & Upload", this.callbacks.onFinish)
|
|
329
328
|
]);
|
|
330
329
|
this.root.appendChild(i);
|
|
331
330
|
}
|
|
332
|
-
renderBlocked(
|
|
331
|
+
renderBlocked(e) {
|
|
333
332
|
this.stopTimer(), this.root.className = "fl-quest-overlay", this.root.innerHTML = "";
|
|
334
|
-
const
|
|
333
|
+
const t = r("div", { className: "fl-summary" }, [
|
|
335
334
|
r("div", { className: "fl-summary-icon" }, ["🛑"]),
|
|
336
335
|
r("div", { className: "fl-summary-title" }, ["Blocked"]),
|
|
337
336
|
r("div", { className: "fl-summary-subtitle" }, [
|
|
338
|
-
`Quest "${
|
|
337
|
+
`Quest "${e}" is blocking. Session halted.`
|
|
339
338
|
]),
|
|
340
339
|
this.createButton("fl-btn fl-btn-finish", "Finish & Upload", this.callbacks.onFinish)
|
|
341
340
|
]);
|
|
342
|
-
this.root.appendChild(
|
|
341
|
+
this.root.appendChild(t);
|
|
343
342
|
}
|
|
344
343
|
getChecklist() {
|
|
345
344
|
return this.checklist.length > 0 ? [...this.checklist] : void 0;
|
|
@@ -348,39 +347,39 @@ class N {
|
|
|
348
347
|
this.stopTimer(), this.root.remove();
|
|
349
348
|
}
|
|
350
349
|
createMinimizeBtn() {
|
|
351
|
-
const
|
|
352
|
-
return
|
|
353
|
-
|
|
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 = "";
|
|
354
353
|
const s = r("span", { className: "fl-minimize-icon" }, ["🔍"]);
|
|
355
354
|
this.root.appendChild(s), this.callbacks.onMinimize();
|
|
356
|
-
},
|
|
355
|
+
}, e;
|
|
357
356
|
}
|
|
358
|
-
createButton(
|
|
359
|
-
const i = r("button", { className:
|
|
360
|
-
return i.textContent =
|
|
357
|
+
createButton(e, t, s) {
|
|
358
|
+
const i = r("button", { className: e });
|
|
359
|
+
return i.textContent = t, i.onclick = (n) => {
|
|
361
360
|
n.stopPropagation(), s();
|
|
362
361
|
}, i;
|
|
363
362
|
}
|
|
364
|
-
createStat(
|
|
363
|
+
createStat(e, t, s) {
|
|
365
364
|
return r("div", { className: "fl-stat" }, [
|
|
366
|
-
r("div", { className: `fl-stat-value ${s}` }, [
|
|
367
|
-
r("div", { className: "fl-stat-label" }, [
|
|
365
|
+
r("div", { className: `fl-stat-value ${s}` }, [e]),
|
|
366
|
+
r("div", { className: "fl-stat-label" }, [t])
|
|
368
367
|
]);
|
|
369
368
|
}
|
|
370
|
-
startTimer(
|
|
369
|
+
startTimer(e) {
|
|
371
370
|
this.stopTimer(), this.startTime || (this.startTime = Date.now()), this.timerInterval = setInterval(() => {
|
|
372
|
-
const
|
|
373
|
-
|
|
371
|
+
const t = Math.floor((Date.now() - this.startTime) / 1e3);
|
|
372
|
+
e.textContent = N(t);
|
|
374
373
|
}, 1e3);
|
|
375
374
|
}
|
|
376
375
|
stopTimer() {
|
|
377
376
|
this.timerInterval && (clearInterval(this.timerInterval), this.timerInterval = null);
|
|
378
377
|
}
|
|
379
378
|
}
|
|
380
|
-
const
|
|
381
|
-
class
|
|
382
|
-
constructor(
|
|
383
|
-
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);
|
|
384
383
|
}
|
|
385
384
|
start() {
|
|
386
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(
|
|
@@ -400,17 +399,17 @@ class z {
|
|
|
400
399
|
getElapsedMs() {
|
|
401
400
|
return this.running ? Date.now() - this.startTime : 0;
|
|
402
401
|
}
|
|
403
|
-
addLog(
|
|
402
|
+
addLog(e) {
|
|
404
403
|
this.actionLogs.push({
|
|
405
|
-
...
|
|
404
|
+
...e,
|
|
406
405
|
timestamp: Date.now() - this.startTime
|
|
407
406
|
});
|
|
408
407
|
}
|
|
409
|
-
onClick(
|
|
410
|
-
const
|
|
408
|
+
onClick(e) {
|
|
409
|
+
const t = e.target;
|
|
411
410
|
this.addLog({
|
|
412
411
|
type: "click",
|
|
413
|
-
target:
|
|
412
|
+
target: w(t)
|
|
414
413
|
});
|
|
415
414
|
}
|
|
416
415
|
onScroll() {
|
|
@@ -419,34 +418,34 @@ class z {
|
|
|
419
418
|
value: `${window.scrollX},${window.scrollY}`
|
|
420
419
|
});
|
|
421
420
|
}
|
|
422
|
-
onInput(
|
|
423
|
-
const
|
|
424
|
-
if (
|
|
425
|
-
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]" });
|
|
426
425
|
else {
|
|
427
|
-
const s =
|
|
426
|
+
const s = t.value;
|
|
428
427
|
this.addLog({
|
|
429
428
|
type: "input",
|
|
430
|
-
target:
|
|
429
|
+
target: w(t),
|
|
431
430
|
value: s == null ? void 0 : s.slice(0, 100)
|
|
432
431
|
});
|
|
433
432
|
}
|
|
434
433
|
}
|
|
435
434
|
captureSnapshot() {
|
|
436
435
|
try {
|
|
437
|
-
const
|
|
436
|
+
const e = document.documentElement.cloneNode(!0);
|
|
438
437
|
if (this.maskSelector) {
|
|
439
|
-
const n =
|
|
438
|
+
const n = e.querySelectorAll(this.maskSelector);
|
|
440
439
|
for (const o of n)
|
|
441
440
|
(o instanceof HTMLInputElement || o instanceof HTMLTextAreaElement) && (o.value = "***"), o.textContent = "***";
|
|
442
441
|
}
|
|
443
|
-
const
|
|
444
|
-
for (const n of
|
|
445
|
-
const s =
|
|
442
|
+
const t = e.querySelectorAll("script");
|
|
443
|
+
for (const n of t) n.remove();
|
|
444
|
+
const s = e.querySelector("#firstlook-sdk-root");
|
|
446
445
|
s == null || s.remove();
|
|
447
|
-
const i =
|
|
448
|
-
if (i.length >
|
|
449
|
-
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({
|
|
450
449
|
type: "dom-snapshot",
|
|
451
450
|
timestamp: Date.now() - this.startTime,
|
|
452
451
|
data: i
|
|
@@ -454,22 +453,22 @@ class z {
|
|
|
454
453
|
} catch {
|
|
455
454
|
}
|
|
456
455
|
}
|
|
457
|
-
throttle(
|
|
456
|
+
throttle(e, t) {
|
|
458
457
|
let s = 0;
|
|
459
458
|
return (...i) => {
|
|
460
459
|
const n = Date.now();
|
|
461
|
-
n - s >=
|
|
460
|
+
n - s >= t && (s = n, e(...i));
|
|
462
461
|
};
|
|
463
462
|
}
|
|
464
463
|
}
|
|
465
|
-
function
|
|
464
|
+
function w(c) {
|
|
466
465
|
var n;
|
|
467
|
-
const
|
|
468
|
-
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}"` : ""}`;
|
|
469
468
|
}
|
|
470
|
-
class
|
|
471
|
-
constructor(
|
|
472
|
-
this.events =
|
|
469
|
+
class B {
|
|
470
|
+
constructor(e) {
|
|
471
|
+
this.events = e, this.mediaRecorder = null, this.stream = null, this.chunks = [], this._recording = !1;
|
|
473
472
|
}
|
|
474
473
|
get recording() {
|
|
475
474
|
return this._recording;
|
|
@@ -478,57 +477,57 @@ class O {
|
|
|
478
477
|
if (!this._recording)
|
|
479
478
|
try {
|
|
480
479
|
this.stream = await navigator.mediaDevices.getUserMedia({ audio: !0 });
|
|
481
|
-
const
|
|
482
|
-
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) => {
|
|
483
482
|
s.data.size > 0 && this.chunks.push(s.data);
|
|
484
483
|
}, this.mediaRecorder.start(1e3), this._recording = !0;
|
|
485
|
-
} catch (
|
|
486
|
-
console.warn("[FirstLook] Voice recording unavailable:",
|
|
484
|
+
} catch (e) {
|
|
485
|
+
console.warn("[FirstLook] Voice recording unavailable:", e), this.events.emit({
|
|
487
486
|
type: "error",
|
|
488
|
-
error: new Error(`Voice recording failed: ${
|
|
487
|
+
error: new Error(`Voice recording failed: ${e.message}`)
|
|
489
488
|
});
|
|
490
489
|
}
|
|
491
490
|
}
|
|
492
491
|
async stopAsync() {
|
|
493
|
-
return !this._recording || !this.mediaRecorder ? null : new Promise((
|
|
492
|
+
return !this._recording || !this.mediaRecorder ? null : new Promise((e) => {
|
|
494
493
|
this.mediaRecorder.onstop = () => {
|
|
495
494
|
var s;
|
|
496
|
-
const
|
|
497
|
-
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);
|
|
498
497
|
}, this.mediaRecorder.stop();
|
|
499
498
|
});
|
|
500
499
|
}
|
|
501
|
-
async captureSnippet(
|
|
502
|
-
return await this.start(), new Promise((
|
|
500
|
+
async captureSnippet(e = 1e4) {
|
|
501
|
+
return await this.start(), new Promise((t) => {
|
|
503
502
|
setTimeout(async () => {
|
|
504
503
|
const s = await this.stopAsync();
|
|
505
|
-
|
|
506
|
-
},
|
|
504
|
+
t(s);
|
|
505
|
+
}, e);
|
|
507
506
|
});
|
|
508
507
|
}
|
|
509
508
|
cleanup() {
|
|
510
509
|
if (this._recording = !1, this.stream) {
|
|
511
|
-
for (const
|
|
512
|
-
|
|
510
|
+
for (const e of this.stream.getTracks())
|
|
511
|
+
e.stop();
|
|
513
512
|
this.stream = null;
|
|
514
513
|
}
|
|
515
514
|
this.mediaRecorder = null, this.chunks = [];
|
|
516
515
|
}
|
|
517
516
|
getSupportedMimeType() {
|
|
518
|
-
const
|
|
517
|
+
const e = [
|
|
519
518
|
"audio/webm;codecs=opus",
|
|
520
519
|
"audio/webm",
|
|
521
520
|
"audio/ogg;codecs=opus",
|
|
522
521
|
"audio/mp4"
|
|
523
522
|
];
|
|
524
|
-
for (const
|
|
525
|
-
if (typeof MediaRecorder < "u" && MediaRecorder.isTypeSupported(
|
|
523
|
+
for (const t of e)
|
|
524
|
+
if (typeof MediaRecorder < "u" && MediaRecorder.isTypeSupported(t)) return t;
|
|
526
525
|
return "";
|
|
527
526
|
}
|
|
528
527
|
}
|
|
529
|
-
class
|
|
530
|
-
constructor(
|
|
531
|
-
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;
|
|
532
531
|
}
|
|
533
532
|
show() {
|
|
534
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));
|
|
@@ -539,8 +538,8 @@ class Q {
|
|
|
539
538
|
async fetchIp() {
|
|
540
539
|
if (!this.cachedIp)
|
|
541
540
|
try {
|
|
542
|
-
const
|
|
543
|
-
|
|
541
|
+
const e = await fetch("https://api.ipify.org?format=text");
|
|
542
|
+
e.ok && (this.cachedIp = await e.text());
|
|
544
543
|
} catch {
|
|
545
544
|
this.cachedIp = "N/A";
|
|
546
545
|
}
|
|
@@ -548,17 +547,17 @@ class Q {
|
|
|
548
547
|
renderTiles() {
|
|
549
548
|
if (!this.container) return;
|
|
550
549
|
this.container.innerHTML = "";
|
|
551
|
-
const
|
|
552
|
-
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++)
|
|
553
552
|
for (let h = 0; h < o; h++) {
|
|
554
553
|
const d = document.createElement("span");
|
|
555
|
-
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);
|
|
556
555
|
}
|
|
557
556
|
}
|
|
558
557
|
}
|
|
559
|
-
class
|
|
560
|
-
constructor(
|
|
561
|
-
this.selectors =
|
|
558
|
+
class $ {
|
|
559
|
+
constructor(e) {
|
|
560
|
+
this.selectors = e, this.observer = null, this.maskedElements = /* @__PURE__ */ new Set();
|
|
562
561
|
}
|
|
563
562
|
start() {
|
|
564
563
|
this.scanAndMark(), this.observer = new MutationObserver(() => this.scanAndMark()), this.observer.observe(document.body, {
|
|
@@ -569,24 +568,24 @@ class B {
|
|
|
569
568
|
});
|
|
570
569
|
}
|
|
571
570
|
stop() {
|
|
572
|
-
var
|
|
573
|
-
(
|
|
574
|
-
for (const
|
|
575
|
-
|
|
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");
|
|
576
575
|
this.maskedElements.clear();
|
|
577
576
|
}
|
|
578
|
-
isMasked(
|
|
579
|
-
return
|
|
577
|
+
isMasked(e) {
|
|
578
|
+
return e.hasAttribute("data-fl-masked");
|
|
580
579
|
}
|
|
581
580
|
getCombinedSelector() {
|
|
582
581
|
return this.selectors.join(", ");
|
|
583
582
|
}
|
|
584
583
|
scanAndMark() {
|
|
585
|
-
const
|
|
586
|
-
if (
|
|
584
|
+
const e = this.getCombinedSelector();
|
|
585
|
+
if (e)
|
|
587
586
|
try {
|
|
588
|
-
const
|
|
589
|
-
for (const s of
|
|
587
|
+
const t = document.querySelectorAll(e);
|
|
588
|
+
for (const s of t)
|
|
590
589
|
this.maskedElements.has(s) || (s.setAttribute("data-fl-masked", "true"), this.maskedElements.add(s));
|
|
591
590
|
for (const s of this.maskedElements)
|
|
592
591
|
document.contains(s) || this.maskedElements.delete(s);
|
|
@@ -594,93 +593,93 @@ class B {
|
|
|
594
593
|
}
|
|
595
594
|
}
|
|
596
595
|
}
|
|
597
|
-
const
|
|
596
|
+
const j = "firstlook_uat", K = 1, u = {
|
|
598
597
|
sessions: "sessions",
|
|
599
598
|
recordings: "recordings",
|
|
600
599
|
annotations: "annotations",
|
|
601
600
|
uploadQueue: "upload_queue"
|
|
602
601
|
};
|
|
603
|
-
class
|
|
602
|
+
class H {
|
|
604
603
|
constructor() {
|
|
605
604
|
this.db = null;
|
|
606
605
|
}
|
|
607
606
|
async open() {
|
|
608
607
|
if (!this.db)
|
|
609
|
-
return new Promise((
|
|
610
|
-
const s = indexedDB.open(
|
|
608
|
+
return new Promise((e, t) => {
|
|
609
|
+
const s = indexedDB.open(j, K);
|
|
611
610
|
s.onupgradeneeded = () => {
|
|
612
611
|
const i = s.result;
|
|
613
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 });
|
|
614
613
|
}, s.onsuccess = () => {
|
|
615
|
-
this.db = s.result,
|
|
616
|
-
}, s.onerror = () =>
|
|
614
|
+
this.db = s.result, e();
|
|
615
|
+
}, s.onerror = () => t(s.error);
|
|
617
616
|
});
|
|
618
617
|
}
|
|
619
|
-
async saveSession(
|
|
620
|
-
await this.put(u.sessions,
|
|
618
|
+
async saveSession(e) {
|
|
619
|
+
await this.put(u.sessions, e);
|
|
621
620
|
}
|
|
622
|
-
async getSession(
|
|
623
|
-
return this.get(u.sessions,
|
|
621
|
+
async getSession(e) {
|
|
622
|
+
return this.get(u.sessions, e);
|
|
624
623
|
}
|
|
625
|
-
async saveRecording(
|
|
626
|
-
await this.put(u.recordings, { sessionId:
|
|
624
|
+
async saveRecording(e, t) {
|
|
625
|
+
await this.put(u.recordings, { sessionId: e, ...t });
|
|
627
626
|
}
|
|
628
|
-
async saveAnnotation(
|
|
629
|
-
await this.put(u.annotations, { sessionId:
|
|
627
|
+
async saveAnnotation(e, t) {
|
|
628
|
+
await this.put(u.annotations, { sessionId: e, ...t });
|
|
630
629
|
}
|
|
631
|
-
async getAnnotations(
|
|
632
|
-
return this.getAllByIndex(u.annotations, "sessionId",
|
|
630
|
+
async getAnnotations(e) {
|
|
631
|
+
return this.getAllByIndex(u.annotations, "sessionId", e);
|
|
633
632
|
}
|
|
634
|
-
async enqueueUpload(
|
|
633
|
+
async enqueueUpload(e) {
|
|
635
634
|
await this.put(u.uploadQueue, {
|
|
636
|
-
payload:
|
|
635
|
+
payload: e,
|
|
637
636
|
createdAt: Date.now(),
|
|
638
637
|
attempts: 0
|
|
639
638
|
});
|
|
640
639
|
}
|
|
641
640
|
async getPendingUploads() {
|
|
642
|
-
const
|
|
643
|
-
return new Promise((
|
|
644
|
-
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 = [];
|
|
645
644
|
o.onsuccess = () => {
|
|
646
|
-
const
|
|
647
|
-
|
|
648
|
-
key:
|
|
649
|
-
payload:
|
|
650
|
-
attempts:
|
|
651
|
-
lastAttemptAt:
|
|
652
|
-
}),
|
|
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);
|
|
653
652
|
}, o.onerror = () => s(o.error);
|
|
654
653
|
});
|
|
655
654
|
}
|
|
656
|
-
async removeFromQueue(
|
|
657
|
-
const
|
|
655
|
+
async removeFromQueue(e) {
|
|
656
|
+
const t = this.ensureDb();
|
|
658
657
|
return new Promise((s, i) => {
|
|
659
|
-
const o =
|
|
658
|
+
const o = t.transaction(u.uploadQueue, "readwrite").objectStore(u.uploadQueue).delete(e);
|
|
660
659
|
o.onsuccess = () => s(), o.onerror = () => i(o.error);
|
|
661
660
|
});
|
|
662
661
|
}
|
|
663
|
-
async incrementAttempts(
|
|
664
|
-
const
|
|
662
|
+
async incrementAttempts(e) {
|
|
663
|
+
const t = this.ensureDb();
|
|
665
664
|
return new Promise((s, i) => {
|
|
666
|
-
const n =
|
|
665
|
+
const n = t.transaction(u.uploadQueue, "readwrite"), o = n.objectStore(u.uploadQueue), l = o.get(e);
|
|
667
666
|
l.onsuccess = () => {
|
|
668
|
-
const
|
|
669
|
-
|
|
667
|
+
const a = l.result;
|
|
668
|
+
a && (a.attempts = (a.attempts ?? 0) + 1, a.lastAttemptAt = Date.now(), o.put(a, e));
|
|
670
669
|
}, n.oncomplete = () => s(), n.onerror = () => i(n.error);
|
|
671
670
|
});
|
|
672
671
|
}
|
|
673
|
-
async clearSession(
|
|
672
|
+
async clearSession(e) {
|
|
674
673
|
const s = this.ensureDb().transaction(
|
|
675
674
|
[u.sessions, u.recordings, u.annotations],
|
|
676
675
|
"readwrite"
|
|
677
676
|
);
|
|
678
|
-
s.objectStore(u.sessions).delete(
|
|
677
|
+
s.objectStore(u.sessions).delete(e);
|
|
679
678
|
for (const i of [u.recordings, u.annotations]) {
|
|
680
|
-
const l = s.objectStore(i).index("sessionId").openCursor(IDBKeyRange.only(
|
|
679
|
+
const l = s.objectStore(i).index("sessionId").openCursor(IDBKeyRange.only(e));
|
|
681
680
|
l.onsuccess = () => {
|
|
682
|
-
const
|
|
683
|
-
|
|
681
|
+
const a = l.result;
|
|
682
|
+
a && (a.delete(), a.continue());
|
|
684
683
|
};
|
|
685
684
|
}
|
|
686
685
|
return new Promise((i, n) => {
|
|
@@ -688,69 +687,69 @@ class j {
|
|
|
688
687
|
});
|
|
689
688
|
}
|
|
690
689
|
close() {
|
|
691
|
-
var
|
|
692
|
-
(
|
|
690
|
+
var e;
|
|
691
|
+
(e = this.db) == null || e.close(), this.db = null;
|
|
693
692
|
}
|
|
694
693
|
ensureDb() {
|
|
695
694
|
if (!this.db) throw new Error("[FirstLook] Storage not opened. Call open() first.");
|
|
696
695
|
return this.db;
|
|
697
696
|
}
|
|
698
|
-
async put(
|
|
697
|
+
async put(e, t) {
|
|
699
698
|
const s = this.ensureDb();
|
|
700
699
|
return new Promise((i, n) => {
|
|
701
|
-
const o = s.transaction(
|
|
702
|
-
o.objectStore(
|
|
700
|
+
const o = s.transaction(e, "readwrite");
|
|
701
|
+
o.objectStore(e).put(t), o.oncomplete = () => i(), o.onerror = () => n(o.error);
|
|
703
702
|
});
|
|
704
703
|
}
|
|
705
|
-
async get(
|
|
704
|
+
async get(e, t) {
|
|
706
705
|
const s = this.ensureDb();
|
|
707
706
|
return new Promise((i, n) => {
|
|
708
|
-
const l = s.transaction(
|
|
707
|
+
const l = s.transaction(e, "readonly").objectStore(e).get(t);
|
|
709
708
|
l.onsuccess = () => i(l.result), l.onerror = () => n(l.error);
|
|
710
709
|
});
|
|
711
710
|
}
|
|
712
|
-
async getAllByIndex(
|
|
711
|
+
async getAllByIndex(e, t, s) {
|
|
713
712
|
const i = this.ensureDb();
|
|
714
713
|
return new Promise((n, o) => {
|
|
715
|
-
const h = i.transaction(
|
|
714
|
+
const h = i.transaction(e, "readonly").objectStore(e).index(t).getAll(s);
|
|
716
715
|
h.onsuccess = () => n(h.result), h.onerror = () => o(h.error);
|
|
717
716
|
});
|
|
718
717
|
}
|
|
719
718
|
}
|
|
720
|
-
const
|
|
721
|
-
class
|
|
722
|
-
constructor(
|
|
723
|
-
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 = "";
|
|
724
723
|
}
|
|
725
724
|
async open() {
|
|
726
|
-
return this.screenshotDataUrl = await this.captureScreenshot(), new Promise((
|
|
725
|
+
return this.screenshotDataUrl = await this.captureScreenshot(), new Promise((e) => {
|
|
727
726
|
this.overlay = r("div", { className: "fl-annotation-overlay" });
|
|
728
|
-
const
|
|
729
|
-
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);
|
|
730
729
|
const s = r("input", {
|
|
731
730
|
className: "fl-annotation-comment",
|
|
732
731
|
type: "text",
|
|
733
732
|
placeholder: "Add a comment..."
|
|
734
733
|
}), i = r("div", { className: "fl-color-picker" });
|
|
735
|
-
for (const
|
|
736
|
-
const h = r("div", { className: `fl-color-swatch ${
|
|
737
|
-
h.style.background =
|
|
738
|
-
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");
|
|
739
738
|
}, i.appendChild(h);
|
|
740
739
|
}
|
|
741
740
|
const n = r("button", { className: "fl-btn fl-btn-ok" }, ["Submit"]);
|
|
742
741
|
n.onclick = () => {
|
|
743
|
-
const
|
|
742
|
+
const a = {
|
|
744
743
|
screenshotDataUrl: this.getAnnotatedImage(),
|
|
745
744
|
drawings: [...this.paths],
|
|
746
745
|
comment: s.value,
|
|
747
746
|
timestamp: Date.now()
|
|
748
747
|
};
|
|
749
|
-
this.close(),
|
|
748
|
+
this.close(), e(a);
|
|
750
749
|
};
|
|
751
750
|
const o = r("button", { className: "fl-btn fl-btn-ng" }, ["Cancel"]);
|
|
752
751
|
o.onclick = () => {
|
|
753
|
-
this.close(),
|
|
752
|
+
this.close(), e(null);
|
|
754
753
|
};
|
|
755
754
|
const l = r("div", { className: "fl-annotation-toolbar" }, [
|
|
756
755
|
i,
|
|
@@ -762,12 +761,12 @@ class K {
|
|
|
762
761
|
});
|
|
763
762
|
}
|
|
764
763
|
close() {
|
|
765
|
-
var
|
|
766
|
-
(
|
|
764
|
+
var e;
|
|
765
|
+
(e = this.overlay) == null || e.remove(), this.overlay = null, this.canvas = null, this.ctx = null, this.paths = [], this.currentPath = [];
|
|
767
766
|
}
|
|
768
767
|
async captureScreenshot() {
|
|
769
768
|
try {
|
|
770
|
-
const
|
|
769
|
+
const e = window.innerWidth, t = window.innerHeight, s = document.documentElement.cloneNode(!0), i = s.querySelector("#firstlook-sdk-root");
|
|
771
770
|
i == null || i.remove();
|
|
772
771
|
const n = s.querySelectorAll('[data-fl-masked], input[type="password"]');
|
|
773
772
|
for (const p of n)
|
|
@@ -778,47 +777,47 @@ class K {
|
|
|
778
777
|
const p = window.getComputedStyle(document.body);
|
|
779
778
|
o.style.backgroundColor = p.backgroundColor, o.style.color = p.color, o.style.fontFamily = p.fontFamily, o.style.margin = "0", o.style.overflow = "hidden";
|
|
780
779
|
}
|
|
781
|
-
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}">
|
|
782
781
|
<foreignObject width="100%" height="100%">
|
|
783
782
|
${l}
|
|
784
783
|
</foreignObject>
|
|
785
|
-
</svg>`, h = new Blob([
|
|
786
|
-
|
|
787
|
-
const f =
|
|
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
|
+
const f = m.getContext("2d");
|
|
788
787
|
return f.scale(window.devicePixelRatio, window.devicePixelRatio), new Promise((p) => {
|
|
789
|
-
const
|
|
790
|
-
|
|
791
|
-
f.drawImage(
|
|
792
|
-
},
|
|
793
|
-
URL.revokeObjectURL(d), p(this.captureScreenshotFallback(
|
|
794
|
-
},
|
|
788
|
+
const g = new Image();
|
|
789
|
+
g.onload = () => {
|
|
790
|
+
f.drawImage(g, 0, 0, e, t), URL.revokeObjectURL(d), p(m.toDataURL("image/png"));
|
|
791
|
+
}, g.onerror = () => {
|
|
792
|
+
URL.revokeObjectURL(d), p(this.captureScreenshotFallback(e, t));
|
|
793
|
+
}, g.src = d;
|
|
795
794
|
});
|
|
796
795
|
} catch {
|
|
797
796
|
return this.captureScreenshotFallback(window.innerWidth, window.innerHeight);
|
|
798
797
|
}
|
|
799
798
|
}
|
|
800
|
-
captureScreenshotFallback(
|
|
799
|
+
captureScreenshotFallback(e, t) {
|
|
801
800
|
const s = document.createElement("canvas");
|
|
802
|
-
s.width =
|
|
801
|
+
s.width = e, s.height = t;
|
|
803
802
|
const i = s.getContext("2d"), n = window.getComputedStyle(document.body);
|
|
804
|
-
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");
|
|
805
804
|
}
|
|
806
805
|
initCanvas() {
|
|
807
806
|
if (!this.canvas) return;
|
|
808
|
-
const
|
|
809
|
-
|
|
810
|
-
const
|
|
811
|
-
this.canvas.width =
|
|
812
|
-
},
|
|
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;
|
|
813
812
|
}
|
|
814
|
-
onDrawStart(
|
|
813
|
+
onDrawStart(e) {
|
|
815
814
|
this.drawing = !0;
|
|
816
|
-
const
|
|
817
|
-
this.currentPath = [{ x:
|
|
815
|
+
const t = this.canvas.getBoundingClientRect();
|
|
816
|
+
this.currentPath = [{ x: e.clientX - t.left, y: e.clientY - t.top }];
|
|
818
817
|
}
|
|
819
|
-
onDrawMove(
|
|
818
|
+
onDrawMove(e) {
|
|
820
819
|
if (!this.drawing || !this.ctx) return;
|
|
821
|
-
const
|
|
820
|
+
const t = this.canvas.getBoundingClientRect(), s = { x: e.clientX - t.left, y: e.clientY - t.top };
|
|
822
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) {
|
|
823
822
|
const i = this.currentPath[this.currentPath.length - 2];
|
|
824
823
|
this.ctx.beginPath(), this.ctx.moveTo(i.x, i.y), this.ctx.lineTo(s.x, s.y), this.ctx.stroke();
|
|
@@ -832,15 +831,15 @@ class K {
|
|
|
832
831
|
}), this.drawing = !1, this.currentPath = [];
|
|
833
832
|
}
|
|
834
833
|
getAnnotatedImage() {
|
|
835
|
-
var
|
|
836
|
-
return ((
|
|
834
|
+
var e;
|
|
835
|
+
return ((e = this.canvas) == null ? void 0 : e.toDataURL("image/png")) || this.screenshotDataUrl;
|
|
837
836
|
}
|
|
838
837
|
}
|
|
839
|
-
class
|
|
840
|
-
constructor(
|
|
841
|
-
this.shadowRoot =
|
|
838
|
+
class W {
|
|
839
|
+
constructor(e, t) {
|
|
840
|
+
this.shadowRoot = e, this.events = t, this.annotations = [], this.active = !1, this.onKeydown = (s) => {
|
|
842
841
|
s.ctrlKey && s.shiftKey && s.key === "R" && (s.preventDefault(), this.trigger());
|
|
843
|
-
}, this.annotationCanvas = new
|
|
842
|
+
}, this.annotationCanvas = new _(e);
|
|
844
843
|
}
|
|
845
844
|
start() {
|
|
846
845
|
document.addEventListener("keydown", this.onKeydown);
|
|
@@ -855,17 +854,17 @@ class H {
|
|
|
855
854
|
if (!this.active) {
|
|
856
855
|
this.active = !0;
|
|
857
856
|
try {
|
|
858
|
-
const
|
|
859
|
-
|
|
857
|
+
const e = await this.annotationCanvas.open();
|
|
858
|
+
e && (this.annotations.push(e), this.events.emit({ type: "report:submitted" }));
|
|
860
859
|
} finally {
|
|
861
860
|
this.active = !1;
|
|
862
861
|
}
|
|
863
862
|
}
|
|
864
863
|
}
|
|
865
864
|
}
|
|
866
|
-
class
|
|
867
|
-
constructor(
|
|
868
|
-
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;
|
|
869
868
|
}
|
|
870
869
|
start() {
|
|
871
870
|
this.handler = this.onTap.bind(this), document.addEventListener("pointerdown", this.handler, { passive: !0 });
|
|
@@ -873,17 +872,17 @@ class _ {
|
|
|
873
872
|
stop() {
|
|
874
873
|
this.handler && (document.removeEventListener("pointerdown", this.handler), this.handler = null), this.reset();
|
|
875
874
|
}
|
|
876
|
-
onTap(
|
|
875
|
+
onTap(e) {
|
|
877
876
|
this.tapCount++, this.tapTimer && clearTimeout(this.tapTimer), this.tapCount >= this.requiredTaps ? (this.reset(), this.onActivate()) : this.tapTimer = setTimeout(() => this.reset(), 800);
|
|
878
877
|
}
|
|
879
878
|
reset() {
|
|
880
879
|
this.tapCount = 0, this.tapTimer && (clearTimeout(this.tapTimer), this.tapTimer = null);
|
|
881
880
|
}
|
|
882
881
|
}
|
|
883
|
-
const
|
|
882
|
+
const y = "firstlook:uat-active";
|
|
884
883
|
class C {
|
|
885
|
-
constructor(
|
|
886
|
-
this.onActivate =
|
|
884
|
+
constructor(e) {
|
|
885
|
+
this.onActivate = e, this.handlers = [];
|
|
887
886
|
}
|
|
888
887
|
start() {
|
|
889
888
|
if (this.checkUrl()) {
|
|
@@ -894,29 +893,29 @@ class C {
|
|
|
894
893
|
this.onActivate();
|
|
895
894
|
return;
|
|
896
895
|
}
|
|
897
|
-
const
|
|
896
|
+
const e = () => {
|
|
898
897
|
this.checkUrl() && (this.persist(), this.onActivate());
|
|
899
898
|
};
|
|
900
|
-
window.addEventListener("hashchange",
|
|
899
|
+
window.addEventListener("hashchange", e), this.handlers.push(["hashchange", e]), window.addEventListener("popstate", e), this.handlers.push(["popstate", e]);
|
|
901
900
|
}
|
|
902
901
|
stop() {
|
|
903
|
-
for (const [
|
|
904
|
-
window.removeEventListener(
|
|
902
|
+
for (const [e, t] of this.handlers)
|
|
903
|
+
window.removeEventListener(e, t);
|
|
905
904
|
this.handlers = [];
|
|
906
905
|
}
|
|
907
906
|
static clearPersisted() {
|
|
908
907
|
try {
|
|
909
|
-
sessionStorage.removeItem(
|
|
908
|
+
sessionStorage.removeItem(y);
|
|
910
909
|
} catch {
|
|
911
910
|
}
|
|
912
911
|
}
|
|
913
912
|
checkUrl() {
|
|
914
|
-
const
|
|
915
|
-
if (
|
|
913
|
+
const e = new URLSearchParams(window.location.search);
|
|
914
|
+
if (e.has("firstlook") || e.has("uat-mode") || e.get("uat") === "1")
|
|
916
915
|
return !0;
|
|
917
|
-
const
|
|
916
|
+
const t = window.location.hash, s = t.indexOf("?");
|
|
918
917
|
if (s !== -1) {
|
|
919
|
-
const i = new URLSearchParams(
|
|
918
|
+
const i = new URLSearchParams(t.substring(s));
|
|
920
919
|
if (i.has("firstlook") || i.has("uat-mode") || i.get("uat") === "1")
|
|
921
920
|
return !0;
|
|
922
921
|
}
|
|
@@ -924,69 +923,90 @@ class C {
|
|
|
924
923
|
}
|
|
925
924
|
persist() {
|
|
926
925
|
try {
|
|
927
|
-
sessionStorage.setItem(
|
|
926
|
+
sessionStorage.setItem(y, "1");
|
|
928
927
|
} catch {
|
|
929
928
|
}
|
|
930
929
|
}
|
|
931
930
|
hasPersisted() {
|
|
932
931
|
try {
|
|
933
|
-
return sessionStorage.getItem(
|
|
932
|
+
return sessionStorage.getItem(y) === "1";
|
|
934
933
|
} catch {
|
|
935
934
|
return !1;
|
|
936
935
|
}
|
|
937
936
|
}
|
|
938
937
|
}
|
|
939
|
-
class
|
|
940
|
-
constructor(
|
|
941
|
-
this.onActivate =
|
|
938
|
+
const x = class x {
|
|
939
|
+
constructor(e) {
|
|
940
|
+
this.onActivate = e, this.handler = null, this.sequence = [], this.sequenceTimer = null;
|
|
942
941
|
}
|
|
943
942
|
start() {
|
|
944
|
-
this.handler = (
|
|
945
|
-
|
|
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
|
+
}
|
|
946
955
|
}, window.addEventListener("keydown", this.handler);
|
|
947
956
|
}
|
|
948
957
|
stop() {
|
|
949
|
-
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);
|
|
950
959
|
}
|
|
951
|
-
}
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
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;
|
|
955
975
|
}
|
|
956
976
|
show() {
|
|
957
977
|
if (this.root) return;
|
|
958
978
|
this.root = r("div", { className: "fl-debug-menu" }), this.root.style.pointerEvents = "auto";
|
|
959
|
-
for (const
|
|
960
|
-
this.root.addEventListener(
|
|
961
|
-
const
|
|
979
|
+
for (const t of ["click", "mousedown", "mouseup", "pointerdown", "pointerup", "touchstart", "touchend"])
|
|
980
|
+
this.root.addEventListener(t, (s) => s.stopPropagation());
|
|
981
|
+
const e = [
|
|
962
982
|
{ icon: "▶", label: "Start Session", action: this.callbacks.onStartSession },
|
|
963
983
|
{ icon: "📸", label: "Report Issue", action: this.callbacks.onReportIssue },
|
|
964
984
|
{ icon: "✖", label: "Close SDK", action: this.callbacks.onClose }
|
|
965
985
|
];
|
|
966
|
-
for (const
|
|
986
|
+
for (const t of e) {
|
|
967
987
|
const s = r("button", { className: "fl-debug-menu-item" }, [
|
|
968
|
-
r("span", { className: "fl-debug-menu-item-icon" }, [
|
|
969
|
-
|
|
988
|
+
r("span", { className: "fl-debug-menu-item-icon" }, [t.icon]),
|
|
989
|
+
t.label
|
|
970
990
|
]);
|
|
971
991
|
s.onclick = (i) => {
|
|
972
|
-
i.stopPropagation(), this.hide(),
|
|
992
|
+
i.stopPropagation(), this.hide(), t.action();
|
|
973
993
|
}, this.root.appendChild(s);
|
|
974
994
|
}
|
|
975
995
|
this.shadowRoot.appendChild(this.root), setTimeout(() => {
|
|
976
|
-
this.outsideClickHandler = (
|
|
977
|
-
this.root && !this.root.contains(
|
|
996
|
+
this.outsideClickHandler = (t) => {
|
|
997
|
+
this.root && !this.root.contains(t.target) && this.hide();
|
|
978
998
|
}, document.addEventListener("click", this.outsideClickHandler, { capture: !0 });
|
|
979
999
|
}, 100);
|
|
980
1000
|
}
|
|
981
1001
|
hide() {
|
|
982
|
-
var
|
|
983
|
-
(
|
|
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);
|
|
984
1004
|
}
|
|
985
1005
|
get visible() {
|
|
986
1006
|
return this.root !== null;
|
|
987
1007
|
}
|
|
988
1008
|
}
|
|
989
|
-
const
|
|
1009
|
+
const Y = (
|
|
990
1010
|
/* css */
|
|
991
1011
|
`
|
|
992
1012
|
:host {
|
|
@@ -1487,10 +1507,10 @@ const X = (
|
|
|
1487
1507
|
}
|
|
1488
1508
|
.fl-feedback-modal-actions { padding: 0 16px 16px; }
|
|
1489
1509
|
`
|
|
1490
|
-
),
|
|
1491
|
-
class
|
|
1510
|
+
), G = 5;
|
|
1511
|
+
class J {
|
|
1492
1512
|
constructor() {
|
|
1493
|
-
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;
|
|
1494
1514
|
}
|
|
1495
1515
|
// ----------------------------------------------------------------
|
|
1496
1516
|
// Public API
|
|
@@ -1499,12 +1519,12 @@ class G {
|
|
|
1499
1519
|
* Initialize the SDK with configuration.
|
|
1500
1520
|
* This does NOT activate the UI — it only sets up triggers.
|
|
1501
1521
|
*/
|
|
1502
|
-
async init(
|
|
1522
|
+
async init(e) {
|
|
1503
1523
|
if (this.state !== "idle") {
|
|
1504
1524
|
console.warn("[FirstLook] SDK already initialized.");
|
|
1505
1525
|
return;
|
|
1506
1526
|
}
|
|
1507
|
-
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);
|
|
1508
1528
|
}
|
|
1509
1529
|
/**
|
|
1510
1530
|
* Manually activate the SDK UI (bypassing triggers).
|
|
@@ -1519,15 +1539,15 @@ class G {
|
|
|
1519
1539
|
/**
|
|
1520
1540
|
* Start a UAT session with a list of quests.
|
|
1521
1541
|
*/
|
|
1522
|
-
async startSession(
|
|
1542
|
+
async startSession(e) {
|
|
1523
1543
|
var s;
|
|
1524
1544
|
if (this.state !== "active")
|
|
1525
1545
|
throw new Error("[FirstLook] Cannot start session: SDK not active. Call activate() first.");
|
|
1526
|
-
(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(
|
|
1527
1547
|
this.config,
|
|
1528
1548
|
this.events,
|
|
1529
1549
|
this.fieldMasker.getCombinedSelector()
|
|
1530
|
-
), 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, {
|
|
1531
1551
|
onOkWithFeedback: (i) => this.handleQuestOk(i),
|
|
1532
1552
|
onConcern: (i) => this.handleConcern(i),
|
|
1533
1553
|
onNg: () => this.handleQuestNg(),
|
|
@@ -1537,18 +1557,18 @@ class G {
|
|
|
1537
1557
|
onMinimize: () => {
|
|
1538
1558
|
}
|
|
1539
1559
|
}), this.questManager.startSession(this.sessionStartTime), this.renderCurrentQuest();
|
|
1540
|
-
const
|
|
1541
|
-
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;
|
|
1542
1562
|
}
|
|
1543
1563
|
/**
|
|
1544
1564
|
* Fetch quest definitions from the API and start a session.
|
|
1545
1565
|
*/
|
|
1546
|
-
async startSessionFromRemote(
|
|
1566
|
+
async startSessionFromRemote(e) {
|
|
1547
1567
|
if (this.state !== "active")
|
|
1548
1568
|
throw new Error("[FirstLook] Cannot start session: SDK not active. Call activate() first.");
|
|
1549
1569
|
if (!this.config)
|
|
1550
1570
|
throw new Error("[FirstLook] SDK not initialized.");
|
|
1551
|
-
const
|
|
1571
|
+
const t = this.config.endpoint.replace(/\/ingest-session$/, ""), s = await fetch(`${t}/export-quests?id=${encodeURIComponent(e)}`, {
|
|
1552
1572
|
headers: { "X-API-Key": this.config.apiKey }
|
|
1553
1573
|
});
|
|
1554
1574
|
if (!s.ok)
|
|
@@ -1560,12 +1580,12 @@ class G {
|
|
|
1560
1580
|
* End the current session, persist data, and trigger upload.
|
|
1561
1581
|
*/
|
|
1562
1582
|
async endSession() {
|
|
1563
|
-
var i, n, o, l,
|
|
1583
|
+
var i, n, o, l, a, h, d, m, f, p, g;
|
|
1564
1584
|
if (this.state !== "recording" || !this.sessionId) return null;
|
|
1565
|
-
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(), (
|
|
1566
|
-
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();
|
|
1567
1587
|
await Promise.allSettled(
|
|
1568
|
-
|
|
1588
|
+
e.map(async (b) => {
|
|
1569
1589
|
if (b.voiceMemoBlob) {
|
|
1570
1590
|
try {
|
|
1571
1591
|
b.voiceMemoBase64 = await this.blobToBase64(b.voiceMemoBlob);
|
|
@@ -1586,29 +1606,29 @@ class G {
|
|
|
1586
1606
|
);
|
|
1587
1607
|
})
|
|
1588
1608
|
);
|
|
1589
|
-
const
|
|
1609
|
+
const t = {
|
|
1590
1610
|
sessionId: this.sessionId,
|
|
1591
1611
|
projectId: this.config.projectId,
|
|
1592
1612
|
userId: this.config.userId,
|
|
1593
1613
|
role: this.config.role,
|
|
1594
|
-
deviceInfo:
|
|
1614
|
+
deviceInfo: q(),
|
|
1595
1615
|
startedAt: new Date(this.sessionStartTime).toISOString(),
|
|
1596
1616
|
endedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1597
1617
|
duration: Math.floor((Date.now() - this.sessionStartTime) / 1e3),
|
|
1598
|
-
quests:
|
|
1618
|
+
quests: e,
|
|
1599
1619
|
recordings: ((h = this.sessionRecorder) == null ? void 0 : h.getSnapshots()) ?? []
|
|
1600
1620
|
};
|
|
1601
|
-
await ((d = this.storage) == null ? void 0 : d.saveSession(
|
|
1602
|
-
const s = ((
|
|
1621
|
+
await ((d = this.storage) == null ? void 0 : d.saveSession(t));
|
|
1622
|
+
const s = ((m = this.issueReporter) == null ? void 0 : m.getAnnotations()) ?? [];
|
|
1603
1623
|
for (const b of s)
|
|
1604
1624
|
await ((f = this.storage) == null ? void 0 : f.saveAnnotation(this.sessionId, b));
|
|
1605
|
-
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;
|
|
1606
1626
|
}
|
|
1607
1627
|
/**
|
|
1608
1628
|
* Subscribe to SDK events.
|
|
1609
1629
|
*/
|
|
1610
|
-
on(
|
|
1611
|
-
return this.events.on(
|
|
1630
|
+
on(e, t) {
|
|
1631
|
+
return this.events.on(e, t);
|
|
1612
1632
|
}
|
|
1613
1633
|
/**
|
|
1614
1634
|
* Get current SDK state.
|
|
@@ -1620,26 +1640,26 @@ class G {
|
|
|
1620
1640
|
* Completely destroy the SDK instance and clean up all resources.
|
|
1621
1641
|
*/
|
|
1622
1642
|
destroy() {
|
|
1623
|
-
var
|
|
1624
|
-
this.maxDurationTimer && (clearTimeout(this.maxDurationTimer), this.maxDurationTimer = null), this.backupTimer && (clearInterval(this.backupTimer), this.backupTimer = null), (
|
|
1625
|
-
}), (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";
|
|
1626
1646
|
}
|
|
1627
1647
|
// ----------------------------------------------------------------
|
|
1628
1648
|
// Private methods
|
|
1629
1649
|
// ----------------------------------------------------------------
|
|
1630
1650
|
createShadowHost() {
|
|
1631
|
-
var
|
|
1632
|
-
(
|
|
1633
|
-
const
|
|
1634
|
-
|
|
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);
|
|
1635
1655
|
}
|
|
1636
1656
|
setupTriggers() {
|
|
1637
|
-
const
|
|
1638
|
-
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();
|
|
1639
1659
|
}
|
|
1640
1660
|
onActivate() {
|
|
1641
|
-
var
|
|
1642
|
-
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, {
|
|
1643
1663
|
onStartSession: () => {
|
|
1644
1664
|
this.events.emit({ type: "sdk:activated" });
|
|
1645
1665
|
},
|
|
@@ -1652,77 +1672,77 @@ class G {
|
|
|
1652
1672
|
}
|
|
1653
1673
|
}), this.debugMenu.show());
|
|
1654
1674
|
}
|
|
1655
|
-
handleQuestOk(
|
|
1675
|
+
handleQuestOk(e) {
|
|
1656
1676
|
var i;
|
|
1657
1677
|
if (!this.questManager || !this.sessionRecorder) return;
|
|
1658
|
-
const
|
|
1659
|
-
this.questManager.completeCurrentQuest(
|
|
1660
|
-
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,
|
|
1661
1681
|
checklist: s
|
|
1662
1682
|
}), this.renderCurrentQuest();
|
|
1663
1683
|
}
|
|
1664
|
-
handleConcern(
|
|
1684
|
+
handleConcern(e) {
|
|
1665
1685
|
var i;
|
|
1666
1686
|
if (!this.questManager || !this.sessionRecorder) return;
|
|
1667
|
-
const
|
|
1668
|
-
this.questManager.completeCurrentQuest(
|
|
1669
|
-
comment:
|
|
1687
|
+
const t = this.sessionRecorder.getActionLogs(), s = (i = this.questOverlay) == null ? void 0 : i.getChecklist();
|
|
1688
|
+
this.questManager.completeCurrentQuest(t, {
|
|
1689
|
+
comment: e,
|
|
1670
1690
|
concern: !0,
|
|
1671
1691
|
checklist: s
|
|
1672
1692
|
}), this.renderCurrentQuest();
|
|
1673
1693
|
}
|
|
1674
1694
|
handleQuestNg() {
|
|
1675
1695
|
if (!this.questManager || !this.questOverlay) return;
|
|
1676
|
-
const
|
|
1677
|
-
|
|
1696
|
+
const e = this.questManager.getCurrentQuest();
|
|
1697
|
+
e && this.questOverlay.renderFeedbackModal(e.title, "ng");
|
|
1678
1698
|
}
|
|
1679
|
-
handleNgFeedbackSubmit(
|
|
1699
|
+
handleNgFeedbackSubmit(e) {
|
|
1680
1700
|
var i;
|
|
1681
1701
|
if (!this.questManager || !this.sessionRecorder) return;
|
|
1682
|
-
const
|
|
1683
|
-
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();
|
|
1684
1704
|
}
|
|
1685
|
-
handleMemo(
|
|
1686
|
-
if (!this.questManager || !
|
|
1687
|
-
const
|
|
1688
|
-
comment:
|
|
1705
|
+
handleMemo(e) {
|
|
1706
|
+
if (!this.questManager || !e) return;
|
|
1707
|
+
const t = {
|
|
1708
|
+
comment: e,
|
|
1689
1709
|
timestamp: Date.now(),
|
|
1690
1710
|
relativeTime: Date.now() - this.sessionStartTime
|
|
1691
1711
|
};
|
|
1692
|
-
this.questManager.addFeedback(
|
|
1712
|
+
this.questManager.addFeedback(t);
|
|
1693
1713
|
}
|
|
1694
1714
|
renderCurrentQuest() {
|
|
1695
1715
|
var s;
|
|
1696
1716
|
if (!this.questManager || !this.questOverlay) return;
|
|
1697
|
-
const
|
|
1698
|
-
if (
|
|
1699
|
-
const
|
|
1700
|
-
this.questOverlay.renderBlocked(
|
|
1717
|
+
const e = this.questManager.getStatus();
|
|
1718
|
+
if (e.isBlocked) {
|
|
1719
|
+
const n = this.questManager.getResults().find((l) => l.status === "FAILED"), o = n ? n.questId : "Unknown";
|
|
1720
|
+
this.questOverlay.renderBlocked(o);
|
|
1701
1721
|
return;
|
|
1702
1722
|
}
|
|
1703
|
-
if (
|
|
1704
|
-
this.questOverlay.renderSummary(
|
|
1723
|
+
if (e.isFinished) {
|
|
1724
|
+
this.questOverlay.renderSummary(e.completed, e.failed, e.total);
|
|
1705
1725
|
return;
|
|
1706
1726
|
}
|
|
1707
|
-
const
|
|
1708
|
-
|
|
1709
|
-
|
|
1727
|
+
const t = this.questManager.getCurrentQuest();
|
|
1728
|
+
t && this.questOverlay.renderQuest(
|
|
1729
|
+
t,
|
|
1710
1730
|
this.questManager.getQuestStatuses(),
|
|
1711
1731
|
((s = this.voiceRecorder) == null ? void 0 : s.recording) ?? !1
|
|
1712
1732
|
);
|
|
1713
1733
|
}
|
|
1714
|
-
async flushUploadQueue(
|
|
1734
|
+
async flushUploadQueue(e) {
|
|
1715
1735
|
if (!(!this.storage || !this.config || this.isFlushing)) {
|
|
1716
1736
|
this.isFlushing = !0;
|
|
1717
1737
|
try {
|
|
1718
|
-
if (
|
|
1738
|
+
if (e) {
|
|
1719
1739
|
const s = navigator.connection;
|
|
1720
1740
|
if (s && s.type && s.type !== "wifi" && s.effectiveType !== "4g")
|
|
1721
1741
|
return;
|
|
1722
1742
|
}
|
|
1723
|
-
const
|
|
1724
|
-
for (const s of
|
|
1725
|
-
if (s.attempts >=
|
|
1743
|
+
const t = await this.storage.getPendingUploads();
|
|
1744
|
+
for (const s of t) {
|
|
1745
|
+
if (s.attempts >= G) {
|
|
1726
1746
|
await this.storage.removeFromQueue(s.key);
|
|
1727
1747
|
continue;
|
|
1728
1748
|
}
|
|
@@ -1750,32 +1770,32 @@ class G {
|
|
|
1750
1770
|
}
|
|
1751
1771
|
}
|
|
1752
1772
|
async backupSession() {
|
|
1753
|
-
var
|
|
1773
|
+
var e;
|
|
1754
1774
|
if (!(!this.storage || !this.sessionId || !this.config || !this.sessionRecorder))
|
|
1755
1775
|
try {
|
|
1756
|
-
const
|
|
1776
|
+
const t = {
|
|
1757
1777
|
sessionId: this.sessionId,
|
|
1758
1778
|
projectId: this.config.projectId,
|
|
1759
1779
|
userId: this.config.userId,
|
|
1760
1780
|
role: this.config.role,
|
|
1761
|
-
deviceInfo:
|
|
1781
|
+
deviceInfo: q(),
|
|
1762
1782
|
startedAt: new Date(this.sessionStartTime).toISOString(),
|
|
1763
1783
|
duration: Math.floor((Date.now() - this.sessionStartTime) / 1e3),
|
|
1764
|
-
quests: ((
|
|
1784
|
+
quests: ((e = this.questManager) == null ? void 0 : e.getResults()) ?? [],
|
|
1765
1785
|
recordings: this.sessionRecorder.getSnapshots()
|
|
1766
1786
|
};
|
|
1767
|
-
await this.storage.saveSession(
|
|
1787
|
+
await this.storage.saveSession(t);
|
|
1768
1788
|
} catch {
|
|
1769
1789
|
}
|
|
1770
1790
|
}
|
|
1771
|
-
blobToBase64(
|
|
1772
|
-
return new Promise((
|
|
1791
|
+
blobToBase64(e) {
|
|
1792
|
+
return new Promise((t, s) => {
|
|
1773
1793
|
const i = new FileReader();
|
|
1774
|
-
i.onloadend = () =>
|
|
1794
|
+
i.onloadend = () => t(i.result), i.onerror = () => s(i.error), i.readAsDataURL(e);
|
|
1775
1795
|
});
|
|
1776
1796
|
}
|
|
1777
1797
|
}
|
|
1778
1798
|
export {
|
|
1779
|
-
|
|
1799
|
+
J as FirstLookSDK
|
|
1780
1800
|
};
|
|
1781
1801
|
//# sourceMappingURL=firstlook.es.js.map
|