2022-10-15 17:02:56 +00:00
|
|
|
import { FileMeta } from "../types.ts";
|
2023-05-23 18:53:53 +00:00
|
|
|
import { SpacePrimitives } from "./space_primitives.ts";
|
|
|
|
import { flushCachesAndUnregisterServiceWorker } from "../sw_util.ts";
|
2022-03-20 08:56:28 +00:00
|
|
|
|
2022-04-07 13:21:30 +00:00
|
|
|
export class HttpSpacePrimitives implements SpacePrimitives {
|
2023-01-13 14:41:29 +00:00
|
|
|
constructor(
|
2023-05-23 18:53:53 +00:00
|
|
|
readonly url: string,
|
|
|
|
readonly expectedSpacePath?: string,
|
|
|
|
readonly syncMode = false,
|
2023-01-13 14:41:29 +00:00
|
|
|
) {
|
2022-04-29 16:54:27 +00:00
|
|
|
}
|
|
|
|
|
2023-05-23 18:53:53 +00:00
|
|
|
public async authenticatedFetch(
|
2022-04-29 16:54:27 +00:00
|
|
|
url: string,
|
2023-05-23 18:53:53 +00:00
|
|
|
options: RequestInit,
|
2022-04-29 16:54:27 +00:00
|
|
|
): Promise<Response> {
|
2023-05-23 18:53:53 +00:00
|
|
|
if (!options.headers) {
|
|
|
|
options.headers = {};
|
|
|
|
}
|
|
|
|
if (this.syncMode) {
|
|
|
|
options.headers = { ...options.headers, ...{ "X-Sync-Mode": "true" } };
|
2023-01-13 14:41:29 +00:00
|
|
|
}
|
|
|
|
|
2023-05-23 18:53:53 +00:00
|
|
|
const result = await fetch(url, { ...options });
|
|
|
|
if (
|
|
|
|
result.status === 401
|
|
|
|
) {
|
|
|
|
// Invalid credentials, reloading the browser should trigger authentication
|
|
|
|
console.log("Going to redirect after", url);
|
|
|
|
location.href = "/.auth?refer=" + location.pathname;
|
|
|
|
throw new Error("Invalid credentials");
|
2022-04-29 16:54:27 +00:00
|
|
|
}
|
|
|
|
return result;
|
2022-03-20 08:56:28 +00:00
|
|
|
}
|
|
|
|
|
2023-01-13 14:41:29 +00:00
|
|
|
async fetchFileList(): Promise<FileMeta[]> {
|
2023-05-23 18:53:53 +00:00
|
|
|
const resp = await this.authenticatedFetch(this.url, {
|
2022-04-06 13:39:20 +00:00
|
|
|
method: "GET",
|
|
|
|
});
|
2022-03-31 12:28:07 +00:00
|
|
|
|
2023-05-23 18:53:53 +00:00
|
|
|
if (
|
|
|
|
resp.status === 200 &&
|
|
|
|
this.expectedSpacePath &&
|
|
|
|
resp.headers.get("X-Space-Path") !== this.expectedSpacePath
|
|
|
|
) {
|
|
|
|
await flushCachesAndUnregisterServiceWorker();
|
|
|
|
alert("Space folder path different on server, reloading the page");
|
|
|
|
location.reload();
|
|
|
|
}
|
|
|
|
|
|
|
|
return resp.json();
|
2022-03-20 08:56:28 +00:00
|
|
|
}
|
|
|
|
|
2022-09-12 12:50:37 +00:00
|
|
|
async readFile(
|
|
|
|
name: string,
|
2023-05-23 18:53:53 +00:00
|
|
|
): Promise<{ data: Uint8Array; meta: FileMeta }> {
|
2023-01-13 14:41:29 +00:00
|
|
|
const res = await this.authenticatedFetch(
|
2023-05-23 18:53:53 +00:00
|
|
|
`${this.url}/${encodeURI(name)}`,
|
2023-01-13 14:41:29 +00:00
|
|
|
{
|
|
|
|
method: "GET",
|
|
|
|
},
|
|
|
|
);
|
2022-09-12 12:50:37 +00:00
|
|
|
if (res.status === 404) {
|
2023-05-23 18:53:53 +00:00
|
|
|
throw new Error(`Not found`);
|
2022-09-12 12:50:37 +00:00
|
|
|
}
|
2022-03-31 12:28:07 +00:00
|
|
|
return {
|
2023-05-23 18:53:53 +00:00
|
|
|
data: new Uint8Array(await res.arrayBuffer()),
|
2022-09-12 12:50:37 +00:00
|
|
|
meta: this.responseToMeta(name, res),
|
2022-03-31 12:28:07 +00:00
|
|
|
};
|
2022-03-20 08:56:28 +00:00
|
|
|
}
|
|
|
|
|
2022-09-12 12:50:37 +00:00
|
|
|
async writeFile(
|
2022-03-31 12:28:07 +00:00
|
|
|
name: string,
|
2023-05-23 18:53:53 +00:00
|
|
|
data: Uint8Array,
|
|
|
|
_selfUpdate?: boolean,
|
|
|
|
lastModified?: number,
|
2022-09-12 12:50:37 +00:00
|
|
|
): Promise<FileMeta> {
|
2023-01-13 14:41:29 +00:00
|
|
|
const headers: Record<string, string> = {
|
|
|
|
"Content-Type": "application/octet-stream",
|
|
|
|
};
|
2023-05-23 18:53:53 +00:00
|
|
|
if (lastModified) {
|
|
|
|
headers["X-Last-Modified"] = "" + lastModified;
|
2023-01-13 14:41:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const res = await this.authenticatedFetch(
|
2023-05-23 18:53:53 +00:00
|
|
|
`${this.url}/${encodeURI(name)}`,
|
2023-01-13 14:41:29 +00:00
|
|
|
{
|
|
|
|
method: "PUT",
|
|
|
|
headers,
|
2023-05-23 18:53:53 +00:00
|
|
|
body: data,
|
2022-09-12 12:50:37 +00:00
|
|
|
},
|
2023-01-13 14:41:29 +00:00
|
|
|
);
|
2022-09-12 12:50:37 +00:00
|
|
|
const newMeta = this.responseToMeta(name, res);
|
2022-04-06 13:39:20 +00:00
|
|
|
return newMeta;
|
2022-03-31 12:28:07 +00:00
|
|
|
}
|
2022-03-20 08:56:28 +00:00
|
|
|
|
2022-09-12 12:50:37 +00:00
|
|
|
async deleteFile(name: string): Promise<void> {
|
2023-01-13 14:41:29 +00:00
|
|
|
const req = await this.authenticatedFetch(
|
2023-05-23 18:53:53 +00:00
|
|
|
`${this.url}/${encodeURI(name)}`,
|
2023-01-13 14:41:29 +00:00
|
|
|
{
|
|
|
|
method: "DELETE",
|
|
|
|
},
|
|
|
|
);
|
2022-03-31 12:28:07 +00:00
|
|
|
if (req.status !== 200) {
|
2022-09-12 12:50:37 +00:00
|
|
|
throw Error(`Failed to delete file: ${req.statusText}`);
|
2022-03-31 12:28:07 +00:00
|
|
|
}
|
2022-03-20 08:56:28 +00:00
|
|
|
}
|
|
|
|
|
2022-09-12 12:50:37 +00:00
|
|
|
async getFileMeta(name: string): Promise<FileMeta> {
|
2023-01-13 14:41:29 +00:00
|
|
|
const res = await this.authenticatedFetch(
|
2023-05-23 18:53:53 +00:00
|
|
|
`${this.url}/${encodeURI(name)}`,
|
2023-01-13 14:41:29 +00:00
|
|
|
{
|
|
|
|
method: "OPTIONS",
|
|
|
|
},
|
|
|
|
);
|
2022-09-12 12:50:37 +00:00
|
|
|
if (res.status === 404) {
|
2023-05-23 18:53:53 +00:00
|
|
|
throw new Error(`Not found`);
|
2022-09-12 12:50:37 +00:00
|
|
|
}
|
|
|
|
return this.responseToMeta(name, res);
|
|
|
|
}
|
|
|
|
|
|
|
|
private responseToMeta(name: string, res: Response): FileMeta {
|
|
|
|
return {
|
|
|
|
name,
|
2022-10-10 14:20:29 +00:00
|
|
|
size: +res.headers.get("X-Content-Length")!,
|
2022-09-12 12:50:37 +00:00
|
|
|
contentType: res.headers.get("Content-type")!,
|
2022-10-10 14:20:29 +00:00
|
|
|
lastModified: +(res.headers.get("X-Last-Modified") || "0"),
|
2022-09-12 12:50:37 +00:00
|
|
|
perm: (res.headers.get("X-Permission") as "rw" | "ro") || "rw",
|
|
|
|
};
|
|
|
|
}
|
2022-03-20 08:56:28 +00:00
|
|
|
}
|