storage/src/remote.ts

65 lines
2 KiB
TypeScript

import { KeyValueStorage } from "./basic";
function protectKey(key: string): string {
return encodeURIComponent(key);
}
type RestClient = (method: "GET" | "POST" | "PUT" | "DELETE", uri: string, body?: string) => Promise<string | null>
export const HEADER_REQUESTER = "X-TK-Storage-App"
export const HEADER_REPLYIER = "X-TK-Storage-Control"
/**
* Storage on a remote server, directly usable using HTTP REST verbs
*/
export class RestRemoteStorage implements KeyValueStorage {
readonly appname: string
readonly base_url: string
readonly client: RestClient
constructor(appname: string, url: string) {
this.appname = appname;
this.base_url = (url[url.length - 1] != "/") ? url + "/" : url;
this.client = this.createClient();
}
async get(key: string): Promise<string | null> {
key = protectKey(key);
return await this.client("GET", key);
}
async set(key: string, value: string | null): Promise<void> {
key = protectKey(key);
if (value === null) {
await this.client("DELETE", key);
} else {
await this.client("PUT", key, value);
}
}
private createClient(): RestClient {
const Request: typeof XMLHttpRequest = (typeof XMLHttpRequest === "undefined") ? require("xmlhttprequest").XMLHttpRequest : XMLHttpRequest;
const client: RestClient = (method, path, body?) => new Promise((resolve, reject) => {
const xhr = new Request();
xhr.open(method, `${this.base_url}${path}`);
xhr.responseType = 'text';
xhr.setRequestHeader(HEADER_REQUESTER, this.appname);
xhr.onload = function () {
if (xhr.getResponseHeader(HEADER_REPLYIER) != "ok") {
reject("storage not compatible with tk-storage");
} else if (xhr.status == 200) {
resolve(xhr.responseText);
} else if (xhr.status == 404) {
resolve(null);
} else {
reject(`http error ${xhr.status}: ${xhr.statusText}`);
}
};
xhr.onerror = function () {
reject("unknown error");
};
xhr.send(body);
});
return client;
}
}