import type { HttpClient, ReportCreateIn, ReportDetail, AnnotationShape } from "@ltt/shared";
import { getHttpClient, getSettings, type Settings } from "./settings";
import type { SubmitNotePayload } from "../lib/messages";
async function dataUrlToFile(dataUrl: string, filename: string): Promise<File> {
const response = await fetch(dataUrl);
const blob = await response.blob();
return new File([blob], filename, { type: blob.type || "image/png" });
}
async function uploadFile(client: HttpClient, dataUrl: string, filename: string): Promise<string> {
const file = await dataUrlToFile(dataUrl, filename);
const result = await client.upload<{ file_id: string }>("/api/uploads", file);
return result.file_id;
}
/** Prepares and submits an element-note report from the NoteComposer payload. */
export async function submitNoteReport(payload: SubmitNotePayload): Promise<{ report_token: string }> {
const settings = await getSettings();
if (!settings.token) throw new Error("Not signed in — open the extension settings");
if (!settings.defaultProjectId) throw new Error("No default project selected — open the extension settings");
const client = await getHttpClient();
const attachmentIds: string[] = [];
const shapesByFile: Record<string, AnnotationShape[]> = {};
const screenshotId = await uploadFile(client, payload.screenshotDataUrl, "screenshot.png");
attachmentIds.push(screenshotId);
if (payload.annotationShapes?.length) {
shapesByFile[screenshotId] = payload.annotationShapes;
}
for (const [index, file] of payload.files.entries()) {
attachmentIds.push(await uploadFile(client, file.dataUrl, file.filename || `attachment-${index + 1}`));
}
const body: ReportCreateIn = {
project_id: settings.defaultProjectId,
type: "element_note",
title: payload.title || "Element note",
description: [payload.comment, ...payload.links.map((l) => l)].filter(Boolean).join("\n\n") || null,
page_url: payload.pageUrl || null,
page_title: payload.pageTitle || null,
environment: payload.environment,
element: payload.element,
attachment_ids: attachmentIds,
annotation_shapes: Object.keys(shapesByFile).length ? shapesByFile : null,
};
const report = await client.post<ReportDetail>("/api/reports", body);
return { report_token: report.share_token };
}
/** Prepares and submits a recording report from buffered recorder steps. */
export async function submitRecordingReport(input: {
payload: Omit<ReportCreateIn, "project_id">;
settings?: Settings;
}): Promise<{ report_token: string }> {
const settings = input.settings ?? (await getSettings());
if (!settings.token) throw new Error("Not signed in — open the extension settings");
if (!settings.defaultProjectId) throw new Error("No default project selected — open the extension settings");
const client = await getHttpClient();
const report = await client.post<ReportDetail>("/api/reports", {
...input.payload,
project_id: settings.defaultProjectId,
});
return { report_token: report.share_token };
}