@h-ai/feedback 1.0.0-beta.17 → 1.0.0-beta.19
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/README.en.md +13 -7
- package/README.ja.md +13 -7
- package/README.md +13 -7
- package/dist/bundle/index.d.ts +2 -1
- package/dist/bundle/index.js +1 -1
- package/dist/capacitor.d.ts +1 -1
- package/dist/capacitor.js +18 -4
- package/dist/capacitor.js.map +1 -1
- package/dist/chunk-IFJHL5VK.js +442 -0
- package/dist/chunk-IFJHL5VK.js.map +1 -0
- package/dist/client/index.d.ts +4 -3
- package/dist/client/index.js +323 -68
- package/dist/client/index.js.map +1 -1
- package/dist/{feedback-client-files-B88nCOo_.d.ts → feedback-client-files-BlNJ94nn.d.ts} +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +788 -169
- package/dist/index.js.map +1 -1
- package/dist/tauri.d.ts +1 -1
- package/package.json +18 -18
- package/dist/chunk-KGMLTOMM.js +0 -340
- package/dist/chunk-KGMLTOMM.js.map +0 -1
package/dist/client/index.js
CHANGED
|
@@ -36,7 +36,10 @@ async function saveFeedbackFile(bytes, filename, contentType = "application/zip"
|
|
|
36
36
|
}
|
|
37
37
|
return ok({ status: "saved" });
|
|
38
38
|
} catch (cause) {
|
|
39
|
-
const error = core.error.toPublic({
|
|
39
|
+
const error = core.error.toPublic({
|
|
40
|
+
...HaiFeedbackError.OPERATION_FAILED,
|
|
41
|
+
message: feedbackM("feedback_operationFailed")
|
|
42
|
+
});
|
|
40
43
|
core.logger.error("Feedback file save failed", { traceId: error.traceId, error: cause });
|
|
41
44
|
return err(error);
|
|
42
45
|
}
|
|
@@ -56,7 +59,10 @@ async function captureFeedbackScreenshot(signal) {
|
|
|
56
59
|
if (document.fonts) {
|
|
57
60
|
let timeout;
|
|
58
61
|
try {
|
|
59
|
-
await Promise.race([
|
|
62
|
+
await Promise.race([
|
|
63
|
+
document.fonts.ready,
|
|
64
|
+
new Promise((resolve) => timeout = setTimeout(resolve, 500))
|
|
65
|
+
]);
|
|
60
66
|
} finally {
|
|
61
67
|
clearTimeout(timeout);
|
|
62
68
|
}
|
|
@@ -88,7 +94,17 @@ async function captureFeedbackScreenshot(signal) {
|
|
|
88
94
|
if (paint && clone.defaultView) {
|
|
89
95
|
for (const element of clone.querySelectorAll("*")) {
|
|
90
96
|
const computed = clone.defaultView.getComputedStyle(element);
|
|
91
|
-
for (const property of [
|
|
97
|
+
for (const property of [
|
|
98
|
+
"color",
|
|
99
|
+
"background-color",
|
|
100
|
+
"border-top-color",
|
|
101
|
+
"border-bottom-color",
|
|
102
|
+
"border-left-color",
|
|
103
|
+
"border-right-color",
|
|
104
|
+
"outline-color",
|
|
105
|
+
"text-decoration-color",
|
|
106
|
+
"-webkit-text-stroke-color"
|
|
107
|
+
]) {
|
|
92
108
|
const raw = computed.getPropertyValue(property);
|
|
93
109
|
if (!/oklch|oklab|color\(|lab\(|lch\(/i.test(raw))
|
|
94
110
|
continue;
|
|
@@ -143,18 +159,38 @@ async function captureFeedbackScreenshot(signal) {
|
|
|
143
159
|
}
|
|
144
160
|
}
|
|
145
161
|
async function annotateFeedbackImage(bytes, annotation) {
|
|
146
|
-
if (![
|
|
162
|
+
if (![
|
|
163
|
+
annotation.x,
|
|
164
|
+
annotation.y,
|
|
165
|
+
annotation.width,
|
|
166
|
+
annotation.height
|
|
167
|
+
].every(Number.isFinite) || annotation.width <= 0 || annotation.height <= 0) {
|
|
147
168
|
throw new Error("Invalid annotation rectangle");
|
|
169
|
+
}
|
|
148
170
|
const image = new Image();
|
|
149
|
-
const imageUrl = URL.createObjectURL(new Blob([
|
|
171
|
+
const imageUrl = URL.createObjectURL(new Blob([
|
|
172
|
+
new Uint8Array(bytes)
|
|
173
|
+
], { type: "image/png" }));
|
|
150
174
|
const canvas = document.createElement("canvas");
|
|
151
175
|
try {
|
|
152
176
|
image.src = imageUrl;
|
|
153
177
|
await image.decode();
|
|
154
178
|
if (annotation.coordinateSpace === "ratio") {
|
|
155
|
-
if ([
|
|
179
|
+
if ([
|
|
180
|
+
annotation.x,
|
|
181
|
+
annotation.y,
|
|
182
|
+
annotation.width,
|
|
183
|
+
annotation.height
|
|
184
|
+
].some((value) => value < 0 || value > 1)) {
|
|
156
185
|
throw new Error("Invalid annotation ratio");
|
|
157
|
-
|
|
186
|
+
}
|
|
187
|
+
annotation = {
|
|
188
|
+
...annotation,
|
|
189
|
+
x: annotation.x * image.naturalWidth,
|
|
190
|
+
y: annotation.y * image.naturalHeight,
|
|
191
|
+
width: annotation.width * image.naturalWidth,
|
|
192
|
+
height: annotation.height * image.naturalHeight
|
|
193
|
+
};
|
|
158
194
|
}
|
|
159
195
|
canvas.width = image.naturalWidth;
|
|
160
196
|
canvas.height = image.naturalHeight;
|
|
@@ -196,8 +232,9 @@ function sanitizeFeedbackData(value) {
|
|
|
196
232
|
return sanitizeFeedbackText(input).slice(0, 8192);
|
|
197
233
|
if (input instanceof Error)
|
|
198
234
|
return visit({ name: input.name, message: input.message, stack: input.stack }, depth + 1);
|
|
199
|
-
if (!input || typeof input !== "object")
|
|
235
|
+
if (!input || typeof input !== "object") {
|
|
200
236
|
return input === void 0 || typeof input === "function" || typeof input === "symbol" ? null : typeof input === "bigint" ? String(input) : input;
|
|
237
|
+
}
|
|
201
238
|
if (seen.has(input))
|
|
202
239
|
return "[CIRCULAR]";
|
|
203
240
|
seen.add(input);
|
|
@@ -356,7 +393,9 @@ function feedbackFileContentType(file) {
|
|
|
356
393
|
["application/x-gzip", "application/gzip"]
|
|
357
394
|
]);
|
|
358
395
|
const normalized = aliases.get(type) ?? type;
|
|
359
|
-
return [
|
|
396
|
+
return [
|
|
397
|
+
...extensions.values()
|
|
398
|
+
].includes(normalized) ? normalized : void 0;
|
|
360
399
|
}
|
|
361
400
|
function createFeedbackClient() {
|
|
362
401
|
let config;
|
|
@@ -380,14 +419,28 @@ function createFeedbackClient() {
|
|
|
380
419
|
previews.delete(id);
|
|
381
420
|
}
|
|
382
421
|
}
|
|
383
|
-
return {
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
422
|
+
return {
|
|
423
|
+
attachments: files.map((file) => {
|
|
424
|
+
let preview = previews.get(file.id);
|
|
425
|
+
if (!preview) {
|
|
426
|
+
preview = {
|
|
427
|
+
checksum: file.checksum,
|
|
428
|
+
url: URL.createObjectURL(new Blob([new Uint8Array(file.bytes).buffer], { type: file.contentType }))
|
|
429
|
+
};
|
|
430
|
+
previews.set(file.id, preview);
|
|
431
|
+
}
|
|
432
|
+
return {
|
|
433
|
+
id: file.id,
|
|
434
|
+
name: file.name,
|
|
435
|
+
size: file.size,
|
|
436
|
+
mimeType: file.contentType,
|
|
437
|
+
url: preview.url
|
|
438
|
+
};
|
|
439
|
+
}),
|
|
440
|
+
evidence: draft?.evidence ?? [],
|
|
441
|
+
missing: draft?.missing ?? [],
|
|
442
|
+
budget: { ...budget }
|
|
443
|
+
};
|
|
391
444
|
}
|
|
392
445
|
let initializing = false;
|
|
393
446
|
let sequence = 0;
|
|
@@ -427,13 +480,28 @@ function createFeedbackClient() {
|
|
|
427
480
|
function failure(code = "OPERATION_FAILED", cause, message) {
|
|
428
481
|
const key = code === "NOT_INITIALIZED" ? "feedback_notInitialized" : code === "LIMIT_EXCEEDED" ? "feedback_limitExceeded" : code === "INVALID_INPUT" ? "feedback_invalidInput" : "feedback_operationFailed";
|
|
429
482
|
const traceId = core.context.get().traceId ?? core.context.get().requestId ?? core.id.uuid();
|
|
430
|
-
const error = {
|
|
431
|
-
|
|
483
|
+
const error = {
|
|
484
|
+
...HaiFeedbackError[code],
|
|
485
|
+
traceId,
|
|
486
|
+
message: message ?? feedbackM(key),
|
|
487
|
+
cause
|
|
488
|
+
};
|
|
489
|
+
if (!(closing && code === "NOT_INITIALIZED")) {
|
|
432
490
|
core.logger[cause === void 0 ? "warn" : "error"]("Feedback client operation failed", { module: "feedback", traceId, error });
|
|
433
|
-
|
|
491
|
+
}
|
|
492
|
+
return err({
|
|
493
|
+
...error,
|
|
494
|
+
message: core.error.formatMessage(error)
|
|
495
|
+
});
|
|
434
496
|
}
|
|
435
497
|
function limits() {
|
|
436
|
-
return {
|
|
498
|
+
return {
|
|
499
|
+
events: Math.min(config?.maxEvents ?? 1e3, budget.maxEvidenceCount),
|
|
500
|
+
bytes: Math.min(config?.maxBytes ?? 2 * 1024 * 1024, budget.maxEvidenceBytes),
|
|
501
|
+
age: config?.maxAgeMs ?? 3e5,
|
|
502
|
+
before: config?.beforeCount ?? 100,
|
|
503
|
+
after: config?.afterCount ?? 100
|
|
504
|
+
};
|
|
437
505
|
}
|
|
438
506
|
function schedulePersist() {
|
|
439
507
|
if (!config?.persistDraft || persistTimer)
|
|
@@ -489,8 +557,17 @@ function createFeedbackClient() {
|
|
|
489
557
|
if (item.kind === "error" || item.kind === "request" && item.data.success === false) {
|
|
490
558
|
lastError = item;
|
|
491
559
|
const before = buffer.filter((entry2) => (entry2.kind === "log" || entry2.kind === "error") && entry2.id !== item.id);
|
|
492
|
-
const evidence = [
|
|
493
|
-
|
|
560
|
+
const evidence = [
|
|
561
|
+
...before.slice(Math.max(0, before.length - bounds.before)),
|
|
562
|
+
item
|
|
563
|
+
];
|
|
564
|
+
errorWindows.push({
|
|
565
|
+
anchor: item,
|
|
566
|
+
evidence,
|
|
567
|
+
after: 0,
|
|
568
|
+
deadline: item.timestamp + (config.afterTimeoutMs ?? 3e4),
|
|
569
|
+
bytes: new TextEncoder().encode(JSON.stringify(evidence)).byteLength
|
|
570
|
+
});
|
|
494
571
|
void captureKeyScreenshot();
|
|
495
572
|
}
|
|
496
573
|
errorWindows = errorWindows.filter((window2) => window2.anchor.timestamp >= Date.now() - bounds.age);
|
|
@@ -522,15 +599,42 @@ function createFeedbackClient() {
|
|
|
522
599
|
try {
|
|
523
600
|
if (config || closePromise)
|
|
524
601
|
await close();
|
|
525
|
-
if (typeof window === "undefined" || !input.appId || !input.actions || [
|
|
602
|
+
if (typeof window === "undefined" || !input.appId || !input.actions || [
|
|
603
|
+
"submit",
|
|
604
|
+
"appendEvidence",
|
|
605
|
+
"completeCollection",
|
|
606
|
+
"upload"
|
|
607
|
+
].some((key) => typeof input.actions[key] !== "function")) {
|
|
526
608
|
return failure("INVALID_INPUT");
|
|
527
|
-
|
|
609
|
+
}
|
|
610
|
+
const numeric = [
|
|
611
|
+
input.maxEvents,
|
|
612
|
+
input.maxBytes,
|
|
613
|
+
input.maxAgeMs,
|
|
614
|
+
input.beforeCount,
|
|
615
|
+
input.afterCount,
|
|
616
|
+
input.afterTimeoutMs,
|
|
617
|
+
input.screenshotTimeoutMs,
|
|
618
|
+
input.maxKeyScreenshots,
|
|
619
|
+
input.keyScreenshotThrottleMs
|
|
620
|
+
];
|
|
528
621
|
if (numeric.some((value) => value !== void 0 && (!Number.isSafeInteger(value) || value < 0)) || input.persistDraft && !input.persistenceKey)
|
|
529
622
|
return failure("INVALID_INPUT");
|
|
530
623
|
if (input.replayStart !== void 0 && !["init", "prepare"].includes(input.replayStart))
|
|
531
624
|
return failure("INVALID_INPUT");
|
|
532
|
-
if (!FeedbackSubmitInputSchema.pick({
|
|
625
|
+
if (!FeedbackSubmitInputSchema.pick({
|
|
626
|
+
appId: true,
|
|
627
|
+
appVersion: true,
|
|
628
|
+
buildId: true,
|
|
629
|
+
commit: true
|
|
630
|
+
}).safeParse({
|
|
631
|
+
appId: input.appId,
|
|
632
|
+
appVersion: input.appVersion,
|
|
633
|
+
buildId: input.buildId,
|
|
634
|
+
commit: input.commit
|
|
635
|
+
}).success) {
|
|
533
636
|
return failure("INVALID_INPUT");
|
|
637
|
+
}
|
|
534
638
|
const initVersion = sessionVersion;
|
|
535
639
|
const policy = input.actions.policy ? await input.actions.policy().catch(() => failure("OPERATION_FAILED")) : ok(input.budget ?? resolveFeedbackBudget());
|
|
536
640
|
if (initVersion !== sessionVersion)
|
|
@@ -578,16 +682,35 @@ function createFeedbackClient() {
|
|
|
578
682
|
unsubscribeLogger = core.logger.observe((entry) => {
|
|
579
683
|
if (entry.context.module === "feedback" || entry.context.feedback === true)
|
|
580
684
|
return;
|
|
581
|
-
record({
|
|
685
|
+
record({
|
|
686
|
+
kind: ["error", "fatal"].includes(entry.level) ? "error" : "log",
|
|
687
|
+
requestId: typeof entry.context.requestId === "string" ? entry.context.requestId : typeof entry.context.traceId === "string" ? entry.context.traceId : void 0,
|
|
688
|
+
data: { level: entry.level, message: entry.message, context: entry.context }
|
|
689
|
+
});
|
|
582
690
|
});
|
|
583
|
-
listen("error", (event) => record({
|
|
584
|
-
|
|
691
|
+
listen("error", (event) => record({
|
|
692
|
+
kind: "error",
|
|
693
|
+
data: {
|
|
694
|
+
message: event.message,
|
|
695
|
+
stack: event.error instanceof Error ? event.error.stack : void 0,
|
|
696
|
+
filename: feedbackPageUrl(event.filename || location.href),
|
|
697
|
+
line: event.lineno,
|
|
698
|
+
column: event.colno
|
|
699
|
+
}
|
|
700
|
+
}));
|
|
701
|
+
listen("unhandledrejection", (event) => record({
|
|
702
|
+
kind: "error",
|
|
703
|
+
data: { reason: event.reason }
|
|
704
|
+
}));
|
|
585
705
|
const click = (event) => {
|
|
586
706
|
if (!(event.target instanceof Element) || event.target.closest("[data-feedback-ignore]"))
|
|
587
707
|
return;
|
|
588
708
|
const target = event.target.closest("[data-feedback-action],button,a");
|
|
589
709
|
if (target && !target.closest("[data-feedback-private]")) {
|
|
590
|
-
record({
|
|
710
|
+
record({
|
|
711
|
+
kind: "interaction",
|
|
712
|
+
data: { action: target.getAttribute("data-feedback-action") ?? target.tagName.toLowerCase(), route: location.pathname }
|
|
713
|
+
});
|
|
591
714
|
if (target.hasAttribute("data-feedback-action"))
|
|
592
715
|
void captureKeyScreenshot();
|
|
593
716
|
}
|
|
@@ -598,7 +721,10 @@ function createFeedbackClient() {
|
|
|
598
721
|
for (const level of ["debug", "info", "warn", "error"]) {
|
|
599
722
|
const original = globalThis.console[level];
|
|
600
723
|
const wrapper = (...args) => {
|
|
601
|
-
record({
|
|
724
|
+
record({
|
|
725
|
+
kind: level === "error" ? "error" : "log",
|
|
726
|
+
data: { channel: "console", level, arguments: args }
|
|
727
|
+
});
|
|
602
728
|
original.apply(globalThis.console, args);
|
|
603
729
|
};
|
|
604
730
|
globalThis.console[level] = wrapper;
|
|
@@ -668,7 +794,14 @@ function createFeedbackClient() {
|
|
|
668
794
|
missing.add("replay-snapshot-truncated");
|
|
669
795
|
return;
|
|
670
796
|
}
|
|
671
|
-
const safe = FeedbackEvidenceInputSchema.safeParse({
|
|
797
|
+
const safe = FeedbackEvidenceInputSchema.safeParse({
|
|
798
|
+
id: core.id.uuid(),
|
|
799
|
+
source,
|
|
800
|
+
sequence: ++sequence,
|
|
801
|
+
kind: "replay",
|
|
802
|
+
timestamp: Date.now(),
|
|
803
|
+
data: { event: payload }
|
|
804
|
+
});
|
|
672
805
|
if (!safe.success)
|
|
673
806
|
return;
|
|
674
807
|
replayChunks.at(-1)?.push(safe.data);
|
|
@@ -800,7 +933,20 @@ function createFeedbackClient() {
|
|
|
800
933
|
if (closing || config !== activeConfig)
|
|
801
934
|
return failure("NOT_INITIALIZED");
|
|
802
935
|
keyScreenshots = keyScreenshots.filter((item) => item.timestamp >= now - limits().age);
|
|
803
|
-
draft = {
|
|
936
|
+
draft = {
|
|
937
|
+
submissionId: core.id.uuid(),
|
|
938
|
+
anchorTime: anchor?.timestamp ?? now,
|
|
939
|
+
anchorKind: anchor ? "error" : "feedback",
|
|
940
|
+
errorId: anchor?.errorId,
|
|
941
|
+
evidence: [
|
|
942
|
+
...selected,
|
|
943
|
+
...other,
|
|
944
|
+
...replayChunks.flat()
|
|
945
|
+
].sort((a, b) => a.sequence - b.sequence),
|
|
946
|
+
attachments: [],
|
|
947
|
+
missing: [...missing],
|
|
948
|
+
expiresAt: now + 864e5
|
|
949
|
+
};
|
|
804
950
|
while (draft.evidence.length > limits().events || new TextEncoder().encode(JSON.stringify(draft.evidence)).byteLength > limits().bytes) {
|
|
805
951
|
draft.evidence.shift();
|
|
806
952
|
missing.add("browser-draft-truncated");
|
|
@@ -842,7 +988,22 @@ function createFeedbackClient() {
|
|
|
842
988
|
function recordEnvironment() {
|
|
843
989
|
if (!config)
|
|
844
990
|
return;
|
|
845
|
-
record({
|
|
991
|
+
record({
|
|
992
|
+
kind: "environment",
|
|
993
|
+
data: {
|
|
994
|
+
appId: config.appId,
|
|
995
|
+
appVersion: config.appVersion,
|
|
996
|
+
buildId: config.buildId,
|
|
997
|
+
commit: config.commit,
|
|
998
|
+
url: feedbackPageUrl(location.href),
|
|
999
|
+
browser: navigator.userAgent,
|
|
1000
|
+
language: navigator.language,
|
|
1001
|
+
timezone: new Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
1002
|
+
viewport: { width: innerWidth, height: innerHeight, pixelRatio: devicePixelRatio },
|
|
1003
|
+
online: navigator.onLine,
|
|
1004
|
+
cookies: collectFeedbackCookies(document.cookie, config.cookieAllowlist ?? [])
|
|
1005
|
+
}
|
|
1006
|
+
});
|
|
846
1007
|
}
|
|
847
1008
|
function pageIdentity() {
|
|
848
1009
|
return `${location.origin}${location.pathname}${location.hash.startsWith("#/") ? location.hash.split("?")[0] : ""}`;
|
|
@@ -868,14 +1029,16 @@ function createFeedbackClient() {
|
|
|
868
1029
|
const image = await attachment(name ?? previousName ?? `screenshot-${core.id.uuid()}.png`, "image/png", bytes);
|
|
869
1030
|
if (closing || config !== activeConfig || draft !== current || current.submission || previous && (!current.attachments.includes(previous) || previous.checksum !== previousChecksum))
|
|
870
1031
|
return err(HaiFeedbackError.CONFLICT, feedbackM("feedback_conflict"));
|
|
871
|
-
if (current.attachments.reduce((total, item) => total + (item === previous ? 0 : item.size), image.size) > budget.maxAttachmentsBytes)
|
|
1032
|
+
if (current.attachments.reduce((total, item) => total + (item === previous ? 0 : item.size), image.size) > budget.maxAttachmentsBytes) {
|
|
872
1033
|
return failure("LIMIT_EXCEEDED");
|
|
873
|
-
|
|
1034
|
+
}
|
|
1035
|
+
if (previous) {
|
|
874
1036
|
current.attachments = current.attachments.map((item) => item === previous ? { ...image, id: previous.id } : item);
|
|
875
|
-
else if (current.attachments.length < budget.maxAttachmentCount)
|
|
1037
|
+
} else if (current.attachments.length < budget.maxAttachmentCount) {
|
|
876
1038
|
current.attachments.unshift(image);
|
|
877
|
-
else
|
|
1039
|
+
} else {
|
|
878
1040
|
return failure("LIMIT_EXCEEDED");
|
|
1041
|
+
}
|
|
879
1042
|
if (!name)
|
|
880
1043
|
recordEnvironment();
|
|
881
1044
|
preparedPage = pageIdentity();
|
|
@@ -911,7 +1074,15 @@ function createFeedbackClient() {
|
|
|
911
1074
|
}
|
|
912
1075
|
draftBytes = new TextEncoder().encode(JSON.stringify(current.evidence)).byteLength;
|
|
913
1076
|
}
|
|
914
|
-
const fingerprint = await digestHex(new TextEncoder().encode(JSON.stringify({
|
|
1077
|
+
const fingerprint = await digestHex(new TextEncoder().encode(JSON.stringify({
|
|
1078
|
+
description: sanitizeFeedbackData({ text: input.description ?? "" }).text,
|
|
1079
|
+
category: input.category ?? "",
|
|
1080
|
+
contact: sanitizeFeedbackData({ text: input.contact ?? "" }).text,
|
|
1081
|
+
files: (input.files ?? []).map((item) => ({ name: item.name, size: item.size, lastModified: item.lastModified })),
|
|
1082
|
+
removed: [
|
|
1083
|
+
...input.removedAttachmentIds ?? []
|
|
1084
|
+
].sort()
|
|
1085
|
+
})));
|
|
915
1086
|
if (closing || config !== activeConfig || draft !== current)
|
|
916
1087
|
return failure("NOT_INITIALIZED");
|
|
917
1088
|
if (current.submission && current.userInputFingerprint && fingerprint !== current.userInputFingerprint)
|
|
@@ -925,7 +1096,20 @@ function createFeedbackClient() {
|
|
|
925
1096
|
}
|
|
926
1097
|
if (closing || config !== activeConfig || draft !== current)
|
|
927
1098
|
return failure("NOT_INITIALIZED");
|
|
928
|
-
current.submission ??= FeedbackSubmitInputSchema.parse({
|
|
1099
|
+
current.submission ??= FeedbackSubmitInputSchema.parse({
|
|
1100
|
+
submissionId: current.submissionId,
|
|
1101
|
+
appId: config.appId,
|
|
1102
|
+
appVersion: config.appVersion,
|
|
1103
|
+
buildId: config.buildId,
|
|
1104
|
+
commit: config.commit,
|
|
1105
|
+
description: typeof input.description === "string" ? String(sanitizeFeedbackData({ text: input.description }).text) : void 0,
|
|
1106
|
+
category: input.category,
|
|
1107
|
+
contact: input.contact,
|
|
1108
|
+
url: feedbackPageUrl(location.href),
|
|
1109
|
+
anchor: { kind: current.anchorKind, timestamp: current.anchorTime, errorId: current.errorId },
|
|
1110
|
+
evidence: current.evidence,
|
|
1111
|
+
attachments: current.attachments.map(({ bytes: _bytes, ...metadata }) => metadata)
|
|
1112
|
+
});
|
|
929
1113
|
current.userInputFingerprint = fingerprint;
|
|
930
1114
|
schedulePersist();
|
|
931
1115
|
const result = await activeConfig.actions.submit(current.submission);
|
|
@@ -953,7 +1137,11 @@ function createFeedbackClient() {
|
|
|
953
1137
|
return ok(void 0);
|
|
954
1138
|
if (uploadedIds.has(item.id))
|
|
955
1139
|
continue;
|
|
956
|
-
const result = await config.actions.upload({
|
|
1140
|
+
const result = await config.actions.upload({
|
|
1141
|
+
id: draft.report.id,
|
|
1142
|
+
attachmentId: item.id,
|
|
1143
|
+
dataBase64: base64(item.bytes)
|
|
1144
|
+
});
|
|
957
1145
|
if (!result.success)
|
|
958
1146
|
return result;
|
|
959
1147
|
uploadedIds.add(item.id);
|
|
@@ -979,7 +1167,15 @@ function createFeedbackClient() {
|
|
|
979
1167
|
return ok(void 0);
|
|
980
1168
|
}
|
|
981
1169
|
if (collectionSealed && !discardOnClose) {
|
|
982
|
-
const result = await config.actions.completeCollection({
|
|
1170
|
+
const result = await config.actions.completeCollection({
|
|
1171
|
+
id: draft.report.id,
|
|
1172
|
+
missing: [
|
|
1173
|
+
.../* @__PURE__ */ new Set([
|
|
1174
|
+
...draft.missing,
|
|
1175
|
+
...missing
|
|
1176
|
+
])
|
|
1177
|
+
]
|
|
1178
|
+
});
|
|
983
1179
|
if (!result.success)
|
|
984
1180
|
return result;
|
|
985
1181
|
collectionFinished = true;
|
|
@@ -1031,8 +1227,9 @@ function createFeedbackClient() {
|
|
|
1031
1227
|
const bytes = await annotateFeedbackImage(originalBytes, mark);
|
|
1032
1228
|
const name = /\.[^.]+$/.test(item.name) ? item.name.replace(/\.[^.]+$/, ".png") : `${item.name}.png`;
|
|
1033
1229
|
const updated = await attachment(name, "image/png", bytes);
|
|
1034
|
-
if (updated.size > budget.maxAttachmentBytes || current.attachments.reduce((sum, file) => sum + (file === item ? 0 : file.size), updated.size) > budget.maxAttachmentsBytes)
|
|
1230
|
+
if (updated.size > budget.maxAttachmentBytes || current.attachments.reduce((sum, file) => sum + (file === item ? 0 : file.size), updated.size) > budget.maxAttachmentsBytes) {
|
|
1035
1231
|
return failure("LIMIT_EXCEEDED");
|
|
1232
|
+
}
|
|
1036
1233
|
if (closing || draft !== current || current.submission || !current.attachments.includes(item) || item.bytes !== originalBytes)
|
|
1037
1234
|
return err(HaiFeedbackError.CONFLICT, feedbackM("feedback_conflict"));
|
|
1038
1235
|
Object.assign(item, updated, { id });
|
|
@@ -1117,7 +1314,31 @@ function createFeedbackClient() {
|
|
|
1117
1314
|
attachments.push(item);
|
|
1118
1315
|
}
|
|
1119
1316
|
}
|
|
1120
|
-
return createFeedbackBundle({
|
|
1317
|
+
return createFeedbackBundle({
|
|
1318
|
+
maxBytes: budget.maxTotalBytes,
|
|
1319
|
+
report: {
|
|
1320
|
+
...draft.report ?? {
|
|
1321
|
+
id: draft.submissionId,
|
|
1322
|
+
appId: config?.appId,
|
|
1323
|
+
appVersion: config?.appVersion,
|
|
1324
|
+
buildId: config?.buildId,
|
|
1325
|
+
commit: config?.commit,
|
|
1326
|
+
url: draft.evidence.filter((item) => item.kind === "environment").at(-1)?.data.url,
|
|
1327
|
+
anchor: { kind: draft.anchorKind, timestamp: draft.anchorTime },
|
|
1328
|
+
evidenceRevision: 1
|
|
1329
|
+
},
|
|
1330
|
+
...sanitizeFeedbackData({ description: input.description ?? draft.report?.description ?? "", category: input.category, contact: input.contact })
|
|
1331
|
+
},
|
|
1332
|
+
evidence: draft.evidence,
|
|
1333
|
+
attachments: attachments.filter((item) => !input.removedAttachmentIds?.includes(item.id)),
|
|
1334
|
+
missing: [
|
|
1335
|
+
.../* @__PURE__ */ new Set([
|
|
1336
|
+
...draft.missing,
|
|
1337
|
+
...missing,
|
|
1338
|
+
...!draft.report ? ["server-evidence-not-obtained", "feedback-not-yet-accepted"] : []
|
|
1339
|
+
])
|
|
1340
|
+
]
|
|
1341
|
+
});
|
|
1121
1342
|
} catch (cause) {
|
|
1122
1343
|
return failure("OPERATION_FAILED", cause);
|
|
1123
1344
|
}
|
|
@@ -1184,33 +1405,53 @@ function createFeedbackClient() {
|
|
|
1184
1405
|
flushFailures = 0;
|
|
1185
1406
|
nextBackgroundFlush = 0;
|
|
1186
1407
|
}
|
|
1187
|
-
return {
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1408
|
+
return {
|
|
1409
|
+
init,
|
|
1410
|
+
prepare,
|
|
1411
|
+
pause,
|
|
1412
|
+
captureScreenshot,
|
|
1413
|
+
submit,
|
|
1414
|
+
flush,
|
|
1415
|
+
record,
|
|
1416
|
+
getDraft: () => draft,
|
|
1417
|
+
getView,
|
|
1418
|
+
releasePreviews,
|
|
1419
|
+
annotate,
|
|
1420
|
+
addFiles,
|
|
1421
|
+
exportDraft,
|
|
1422
|
+
async saveDraft(input) {
|
|
1423
|
+
const session = config;
|
|
1424
|
+
const exported = await exportDraft(input);
|
|
1425
|
+
if (!exported.success)
|
|
1426
|
+
return exported;
|
|
1427
|
+
if (closing || !session || session !== config)
|
|
1428
|
+
return failure("NOT_INITIALIZED");
|
|
1429
|
+
return saveFeedbackFile(exported.data, `feedback-${draft?.submissionId ?? "local"}.zip`, "application/zip", saveController.signal);
|
|
1430
|
+
},
|
|
1431
|
+
close,
|
|
1432
|
+
get budget() {
|
|
1433
|
+
return { ...budget };
|
|
1434
|
+
}
|
|
1435
|
+
};
|
|
1198
1436
|
}
|
|
1199
1437
|
var feedbackClient = createFeedbackClient();
|
|
1200
1438
|
async function bounded(promise, ms, controller) {
|
|
1201
1439
|
let timer;
|
|
1202
1440
|
let aborted;
|
|
1203
1441
|
try {
|
|
1204
|
-
return await Promise.race([
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
aborted();
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1442
|
+
return await Promise.race([
|
|
1443
|
+
promise,
|
|
1444
|
+
new Promise((_resolve, reject) => {
|
|
1445
|
+
aborted = () => reject(new Error("Capture cancelled"));
|
|
1446
|
+
if (controller.signal.aborted)
|
|
1447
|
+
aborted();
|
|
1448
|
+
else controller.signal.addEventListener("abort", aborted, { once: true });
|
|
1449
|
+
timer = setTimeout(() => {
|
|
1450
|
+
reject(new Error("Capture timed out"));
|
|
1451
|
+
controller.abort();
|
|
1452
|
+
}, ms);
|
|
1453
|
+
})
|
|
1454
|
+
]);
|
|
1214
1455
|
} finally {
|
|
1215
1456
|
clearTimeout(timer);
|
|
1216
1457
|
if (aborted)
|
|
@@ -1219,11 +1460,20 @@ async function bounded(promise, ms, controller) {
|
|
|
1219
1460
|
}
|
|
1220
1461
|
async function attachment(name, contentType, bytes) {
|
|
1221
1462
|
const checksum = await digestHex(bytes);
|
|
1222
|
-
return {
|
|
1463
|
+
return {
|
|
1464
|
+
id: core.id.uuid(),
|
|
1465
|
+
name: String(sanitizeFeedbackData({ text: name }).text).slice(0, 255),
|
|
1466
|
+
contentType,
|
|
1467
|
+
size: bytes.byteLength,
|
|
1468
|
+
checksum,
|
|
1469
|
+
bytes
|
|
1470
|
+
};
|
|
1223
1471
|
}
|
|
1224
1472
|
async function digestHex(bytes) {
|
|
1225
1473
|
const digest = await globalThis.crypto.subtle.digest("SHA-256", new Uint8Array(bytes));
|
|
1226
|
-
return [
|
|
1474
|
+
return [
|
|
1475
|
+
...new Uint8Array(digest)
|
|
1476
|
+
].map((value) => value.toString(16).padStart(2, "0")).join("");
|
|
1227
1477
|
}
|
|
1228
1478
|
function base64(bytes) {
|
|
1229
1479
|
let value = "";
|
|
@@ -1273,7 +1523,12 @@ function createFeedbackReview(actions) {
|
|
|
1273
1523
|
const events = [];
|
|
1274
1524
|
try {
|
|
1275
1525
|
for (let page = 1; page <= 100; page++) {
|
|
1276
|
-
const result = await actions.getEvidence({
|
|
1526
|
+
const result = await actions.getEvidence({
|
|
1527
|
+
id,
|
|
1528
|
+
kind: "replay",
|
|
1529
|
+
page,
|
|
1530
|
+
pageSize: 100
|
|
1531
|
+
});
|
|
1277
1532
|
if (signal.aborted)
|
|
1278
1533
|
return failed();
|
|
1279
1534
|
if (!result.success)
|