import { html, css, wrapCss, apiPrefix, LitElement } from "replaywebpage"; import prettyBytes from "pretty-bytes"; import fasSync from "@fortawesome/fontawesome-free/svgs/solid/sync-alt.svg"; import fasCheck from "@fortawesome/fontawesome-free/svgs/solid/check-circle.svg"; import fasExternal from "@fortawesome/fontawesome-free/svgs/solid/external-link-alt.svg"; import fasX from "@fortawesome/fontawesome-free/svgs/solid/times-circle.svg"; import { type BtrixOpts } from "../types"; const VERSION = __AWP_VERSION__; class BtrixUploader extends LitElement { btrixOpts: BtrixOpts | null = null; static get properties() { return { btrixOpts: { type: Object }, coll: { type: Object }, uploadColl: { type: Object }, status: { type: String }, uploadId: { type: String }, uploadTime: { type: Number }, isUploadNeeded: { type: Boolean }, actualSize: { type: Number }, uploadSize: { type: Number }, uploadTotal: { type: Number }, }; } static get styles() { return wrapCss(css``); } // @ts-expect-error - TS7006 - Parameter 'changedProps' implicitly has an 'any' type. updated(changedProps) { if (changedProps.has("uploadColl")) { // @ts-expect-error - TS2339 - Property 'uploadColl' does not exist on type 'BtrixUploader'. const { coll, isUploadNeeded } = this.uploadColl; // @ts-expect-error - TS2339 - Property 'coll' does not exist on type 'BtrixUploader'. this.coll = coll; // @ts-expect-error - TS2339 - Property 'actualSize' does not exist on type 'BtrixUploader'. this.actualSize = 0; // @ts-expect-error - TS2339 - Property 'isUploadNeeded' does not exist on type 'BtrixUploader'. this.isUploadNeeded = isUploadNeeded; // @ts-expect-error - TS2339 - Property 'uploadTime' does not exist on type 'BtrixUploader'. | TS2339 - Property 'coll' does not exist on type 'BtrixUploader'. this.uploadTime = this.coll.uploadTime; // @ts-expect-error - TS2339 - Property 'uploadId' does not exist on type 'BtrixUploader'. | TS2339 - Property 'coll' does not exist on type 'BtrixUploader'. this.uploadId = this.coll.uploadId; } // @ts-expect-error - TS2339 - Property 'coll' does not exist on type 'BtrixUploader'. if (changedProps.has("coll") && this.coll) { this.pollUploadState(); } } async pollUploadState() { // @ts-expect-error - TS2339 - Property 'pollingUploadState' does not exist on type 'BtrixUploader'. if (this.pollingUploadState) { return; } // @ts-expect-error - TS2339 - Property 'pollingUploadState' does not exist on type 'BtrixUploader'. this.pollingUploadState = true; const loop = true; while (loop) { // @ts-expect-error - TS2339 - Property 'coll' does not exist on type 'BtrixUploader'. const resp = await fetch(`${apiPrefix}/c/${this.coll.id}/upload`); const json = await resp.json(); // @ts-expect-error - TS2339 - Property 'status' does not exist on type 'BtrixUploader'. this.status = json.status; // @ts-expect-error - TS2339 - Property 'uploadTime' does not exist on type 'BtrixUploader'. this.uploadTime = json.uploadTime; // @ts-expect-error - TS2339 - Property 'uploadId' does not exist on type 'BtrixUploader'. this.uploadId = json.uploadId; // @ts-expect-error - TS2339 - Property 'status' does not exist on type 'BtrixUploader'. if (this.status === "uploading") { // @ts-expect-error - TS2339 - Property 'isUploadNeeded' does not exist on type 'BtrixUploader'. this.isUploadNeeded = false; } else if ( // @ts-expect-error - TS2339 - Property 'status' does not exist on type 'BtrixUploader'. this.status === "idle" && this.btrixOpts?.client && json.uploadTime && json.uploadId && json.mtime <= json.uploadTime ) { this.getRemoteUpload(); // @ts-expect-error - TS2339 - Property 'uploadId' does not exist on type 'BtrixUploader'. } else if (!this.uploadId) { // @ts-expect-error - TS2339 - Property 'isUploadNeeded' does not exist on type 'BtrixUploader'. this.isUploadNeeded = true; } // @ts-expect-error - TS2339 - Property 'uploadSize' does not exist on type 'BtrixUploader'. this.uploadSize = json.size; // @ts-expect-error - TS2339 - Property 'uploadTotal' does not exist on type 'BtrixUploader'. this.uploadTotal = json.totalSize; // @ts-expect-error - TS2339 - Property 'status' does not exist on type 'BtrixUploader'. if (this.status !== "uploading") { break; } await new Promise((resolve) => setTimeout(resolve, 1000)); } // @ts-expect-error - TS2339 - Property 'pollingUploadState' does not exist on type 'BtrixUploader'. this.pollingUploadState = false; } async getRemoteUpload() { try { // @ts-expect-error - TS2339 - Property 'btrixOpts' does not exist on type 'BtrixUploader'. | TS2339 - Property 'uploadId' does not exist on type 'BtrixUploader'. const upload = await this.btrixOpts.client.getRemoteUpload(this.uploadId); //this.coll.title = upload.name; // @ts-expect-error - TS2339 - Property 'actualSize' does not exist on type 'BtrixUploader'. this.actualSize = upload.fileSize; } catch (e) { // @ts-expect-error - TS2339 - Property 'isUploadNeeded' does not exist on type 'BtrixUploader'. this.isUploadNeeded = true; // @ts-expect-error - TS2339 - Property 'status' does not exist on type 'BtrixUploader'. this.status = "missing"; } } render() { // @ts-expect-error - TS2339 - Property 'coll' does not exist on type 'BtrixUploader'. if (!this.coll) { return html``; } // @ts-expect-error - TS2339 - Property 'uploadTime' does not exist on type 'BtrixUploader'. const uploadTime = this.uploadTime; // @ts-expect-error - TS2339 - Property 'isUploadNeeded' does not exist on type 'BtrixUploader'. const alreadyUploaded = !this.isUploadNeeded && uploadTime; let btrixUploadUrl = ""; try { // @ts-expect-error - TS2339 - Property 'btrixOpts' does not exist on type 'BtrixUploader'. | TS2339 - Property 'uploadId' does not exist on type 'BtrixUploader'. if (this.btrixOpts.client && this.uploadId) { // @ts-expect-error - TS2339 - Property 'btrixOpts' does not exist on type 'BtrixUploader'. const { client } = this.btrixOpts; btrixUploadUrl = new URL( // @ts-expect-error - TS2339 - Property 'uploadId' does not exist on type 'BtrixUploader'. `/orgs/${client.defaultOrg}/artifacts/upload/${this.uploadId}`, client.url, ).href; } } catch (e) { // ignore } return html` ${ // @ts-expect-error - TS2339 - Property 'actualSize' does not exist on type 'BtrixUploader'. this.actualSize ? html` ` : "" } ${uploadTime ? html` ` : ""} ${btrixUploadUrl ? html` ` : ""}
Collection: ${ // @ts-expect-error - TS2339 - Property 'coll' does not exist on type 'BtrixUploader'. this.coll.title }
Local Size: ${ // @ts-expect-error - TS2339 - Property 'coll' does not exist on type 'BtrixUploader'. prettyBytes(this.coll.size) }
Uploaded Size: ${ // @ts-expect-error - TS2339 - Property 'actualSize' does not exist on type 'BtrixUploader'. prettyBytes(this.actualSize) }
Last Uploaded At: ${new Date(uploadTime).toLocaleString()}
Link: View in Browsertrix
${this.renderUploadStatus()}
${ // @ts-expect-error - TS2339 - Property 'status' does not exist on type 'BtrixUploader'. this.status === "uploading" ? html` ` : html` ` }
`; } renderUploadStatus() { // @ts-expect-error - TS2339 - Property 'status' does not exist on type 'BtrixUploader'. switch (this.status) { case "done": return html`

Upload Finished

`; case "failed": return html`

Sorry, Upload Failed, or, the Browsertrix credentials may be incorrect.

;

Check your credentials in Settings and then click Upload to try again.

`; case "aborted": return html`

Upload has been canceled

`; case "idle": // @ts-expect-error - TS2339 - Property 'isUploadNeeded' does not exist on type 'BtrixUploader'. if (!this.isUploadNeeded) { return html`

Archive already uploaded to Browsertrix. ${this.renderDeleteUploaded()}

`; // @ts-expect-error - TS2339 - Property 'uploadId' does not exist on type 'BtrixUploader'. } else if (this.uploadId) { return html`

Archive updated since last upload. Click "Upload" below to upload latest. ${this.renderDeleteUploaded()}

`; } else { return html`

Archive not yet uploaded. Click "Upload" below to start.

`; } case "deleted": return html`

Upload to Browsertrix has been deleted.

(Data is still saved locally in your browser)

`; case "deleteFailed": return html`

Sorry, deleting upload has failed, or, the Browsertrix credentials may be incorrect.

;

Check your credentials in Settings and then click Delete to try again. ${this.renderDeleteUploaded()}

`; default: return ""; } } renderDeleteUploaded() { return html` `; } async onUpload() { const client = this.btrixOpts!.client; // @ts-expect-error - TS2339 - Property 'btrixOpts' does not exist on type 'BtrixUploader'. const org = await client.getOrg(this.btrixOpts.orgName); const urlObj = new URL(`/api/orgs/${org}/uploads/stream`, client!.url); // @ts-expect-error - TS2339 - Property 'uploadId' does not exist on type 'BtrixUploader'. if (this.uploadId) { // @ts-expect-error - TS2339 - Property 'uploadId' does not exist on type 'BtrixUploader'. urlObj.searchParams.set("replaceId", this.uploadId); } const now = new Date().toLocaleString(); urlObj.searchParams.set( "notes", `Uploaded by ArchiveWeb.page ${VERSION} at ${now}`, ); const url = urlObj.href; const headers = { Authorization: client!.auth }; const body = JSON.stringify({ url, headers }); const method = "POST"; // @ts-expect-error - TS2339 - Property 'status' does not exist on type 'BtrixUploader'. this.status = "uploading"; // @ts-expect-error - TS2339 - Property 'uploadSize' does not exist on type 'BtrixUploader'. this.uploadSize = 0; // @ts-expect-error - TS2339 - Property 'uploadTotal' does not exist on type 'BtrixUploader'. this.uploadTotal = 0; const resp = await fetch( // @ts-expect-error - TS2339 - Property 'coll' does not exist on type 'BtrixUploader'. `${apiPrefix}/c/${this.coll.id}/upload?format=wacz&pages=all`, { method, body }, ); const json = await resp.json(); if (json.uploading) { this.pollUploadState(); } } async onCancelUpload() { const method = "POST"; const body = JSON.stringify({ abortUpload: true }); // @ts-expect-error - TS2339 - Property 'coll' does not exist on type 'BtrixUploader'. await fetch(`${apiPrefix}/c/${this.coll.id}/upload`, { method, body }); this.pollUploadState(); } async onDeleteUpload() { try { // @ts-expect-error - TS2339 - Property 'btrixOpts' does not exist on type 'BtrixUploader'. const { client } = this.btrixOpts; if (!client) { return; } // @ts-expect-error - TS2339 - Property 'uploadId' does not exist on type 'BtrixUploader'. await client.deleteUpload(this.uploadId); // @ts-expect-error - TS2339 - Property 'coll' does not exist on type 'BtrixUploader'. await fetch(`${apiPrefix}/c/${this.coll.id}/upload`, { method: "DELETE", }); // @ts-expect-error - TS2339 - Property 'status' does not exist on type 'BtrixUploader'. this.status = "deleted"; // @ts-expect-error - TS2339 - Property 'isUploadNeeded' does not exist on type 'BtrixUploader'. this.isUploadNeeded = true; // @ts-expect-error - TS2339 - Property 'uploadTime' does not exist on type 'BtrixUploader'. | TS2339 - Property 'btrixOpts' does not exist on type 'BtrixUploader'. this.uploadTime = this.btrixOpts.uploadTime = null; // @ts-expect-error - TS2339 - Property 'uploadId' does not exist on type 'BtrixUploader'. | TS2339 - Property 'btrixOpts' does not exist on type 'BtrixUploader'. this.uploadId = this.btrixOpts.uploadId = null; } catch (e) { // @ts-expect-error - TS2339 - Property 'status' does not exist on type 'BtrixUploader'. this.status = "deleteFailed"; } } } customElements.define("wr-btrix-upload", BtrixUploader); export class BtrixClient { url: string | URL | undefined; auth: TODOFixMe; defaultOrg: null; // @ts-expect-error - TS7031 - Binding element 'url' implicitly has an 'any' type. | TS7031 - Binding element 'username' implicitly has an 'any' type. | TS7031 - Binding element 'password' implicitly has an 'any' type. | TS7031 - Binding element 'orgName' implicitly has an 'any' type. static async login({ url, username, password, orgName }) { const loginUrl = url + "/api/auth/jwt/login"; const form = new URLSearchParams(); form.append("username", username); form.append("password", password); const headers = new Headers(); headers.set("Content-Type", "application/x-www-form-urlencoded"); const res = await fetch(loginUrl, { method: "POST", body: form, headers }); const auth = await res.json(); const { token_type, access_token } = auth; if (!access_token || !token_type) { throw new Error("Invalid login"); } const authToken = token_type + " " + access_token; const client = new BtrixClient(url, authToken); const org = await client.getOrg(orgName); client.defaultOrg = org; return client; } // @ts-expect-error - TS7006 - Parameter 'url' implicitly has an 'any' type. | TS7006 - Parameter 'auth' implicitly has an 'any' type. constructor(url, auth) { this.url = url; this.auth = auth; this.defaultOrg = null; } // @ts-expect-error - TS7006 - Parameter 'endpoint' implicitly has an 'any' type. async fetchAPI(endpoint, method = "GET", body = null) { const headers = { Authorization: this.auth }; if (method !== "GET") { // @ts-expect-error - TS7053 - Element implicitly has an 'any' type because expression of type '"Content-Type"' can't be used to index type '{ Authorization: any; }'. headers["Content-Type"] = "application/json"; } try { const resp = await fetch(this.url + endpoint, { headers, method, body, // @ts-expect-error - TS2345 - Argument of type '{ headers: { Authorization: any; }; method: string; body: null; duplex: string; }' is not assignable to parameter of type 'RequestInit'. duplex: "half", }); return await resp.json(); } catch (e) { console.warn(e); return {}; } } async getOrg(name = "") { const json = await this.fetchAPI("/api/users/me"); const { orgs } = json; if (!orgs?.length) { return null; } if (!name) { return orgs[0].id; } for (const org of orgs) { if (org.slug === name || org.name === name) { return org.id; } } return orgs[0].id; } // @ts-expect-error - TS7006 - Parameter 'uploadId' implicitly has an 'any' type. async getRemoteUpload(uploadId, orgId = null) { const org = this.defaultOrg || orgId; const res = await this.fetchAPI(`/api/orgs/${org}/uploads/${uploadId}`); if (!res.name) { throw new Error("upload_missing"); } return res; } // @ts-expect-error - TS7006 - Parameter 'uploadId' implicitly has an 'any' type. async deleteUpload(uploadId, orgId = null) { const org = this.defaultOrg || orgId; const deleteStr = JSON.stringify({ crawl_ids: [uploadId] }); const res = await this.fetchAPI( `/api/orgs/${org}/uploads/delete`, "POST", // @ts-expect-error - TS2345 - Argument of type 'string' is not assignable to parameter of type 'null | undefined'. deleteStr, ); if (!res.deleted) { throw new Error("delete_failed"); } } }