import { KeyValueStorage } from "./basic"; function protectKey(key: string): string { return encodeURIComponent(key); } type RestClient = (method: "GET" | "POST" | "PUT" | "DELETE", uri: string, body?: string) => Promise 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 { key = protectKey(key); return await this.client("GET", key); } async set(key: string, value: string | null): Promise { 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; } }