diff options
Diffstat (limited to 'kittybox-rs/companion-lite/src/micropub_api.ts')
-rw-r--r-- | kittybox-rs/companion-lite/src/micropub_api.ts | 125 |
1 files changed, 96 insertions, 29 deletions
diff --git a/kittybox-rs/companion-lite/src/micropub_api.ts b/kittybox-rs/companion-lite/src/micropub_api.ts index 9eb65a2..fa1c431 100644 --- a/kittybox-rs/companion-lite/src/micropub_api.ts +++ b/kittybox-rs/companion-lite/src/micropub_api.ts @@ -1,6 +1,6 @@ export interface MicropubChannel { - uid: string, - name: string + readonly uid: string, + readonly name: string } export interface MF2 { @@ -9,50 +9,117 @@ export interface MF2 { } export interface MicropubConfig { - channels: MicropubChannel[], - "media-endpoint": string + readonly channels?: MicropubChannel[], + readonly "media-endpoint"?: string } -export async function query_channels(endpoint: string, token: string): Promise<MicropubChannel[]> { - const response = await fetch(endpoint + "?q=config", { - headers: { - "Authorization": `Bearer ${token}` - } - }) +export interface MicropubErrorMessage { + readonly error: string, + readonly error_description: string | undefined +} + +export class MicropubError extends Error { + readonly status: number | null + readonly response: MicropubErrorMessage | null - if (response.ok) { - const config = await response.json() as MicropubConfig; + constructor(status: number | null, response: MicropubErrorMessage | null, cause: Error | null = null) { + // Needs to pass both `message` and `options` to install the "cause" property. + if (status == null) { + super("Micropub endpoint didn't respond properly", { cause }); + } else if (response == null) { + super(`Micropub endpoint returned HTTP ${status}`, { cause }); + } else { + super( + `Micropub endpoint returned ${response.error}: ${response.error_description ?? "(no description was provided)"}`, + { cause } + ) + } - return config["channels"] - } else { - throw new Error(`Micropub endpoint returned ${response.status}: ${await response.json()}`) + this.status = status; + this.response = response; } - } -export async function submit(endpoint: string, token: string, mf2: MF2) { - try { - const response = await fetch(endpoint, { +export class Micropub { + readonly token: string + readonly micropub_endpoint: URL + private config_response: MicropubConfig | null + + constructor({ endpoint, token }: { endpoint: URL, token: string }) { + this.micropub_endpoint = endpoint; + this.token = token; + this.config_response = null; + } + + async config(): Promise<MicropubConfig> { + if (this.config_response != null) { + return this.config_response + } + let url = this.micropub_endpoint; + let params = new URLSearchParams(); + for (const [key, val] of url.searchParams) { + params.append(key, val) + } + params.set("q", "config") + + url.search = "?" + params.toString(); + + const response = await fetch(url, { + headers: { + "Authorization": `Bearer ${this.token}` + } + }); + if (response.ok) { + const config = await response.json() as MicropubConfig; + this.config_response = config + + return config + } else { + throw new MicropubError(response.status, await response.json() as MicropubErrorMessage); + } + } + + async submit(mf2: MF2): Promise<URL> { + const response = await fetch(this.micropub_endpoint, { method: "POST", headers: { - "Authorization": `Bearer ${token}`, + "Authorization": `Bearer ${this.token}`, "Content-Type": "application/json" }, body: JSON.stringify(mf2) }) if (response.status != 201 && response.status != 202) { - let err = await response.json(); - console.error("Micropub error!", err); + let err = await response.json() as MicropubErrorMessage; - return err; + throw new MicropubError(response.status, err) } else { - return { - "location": response.headers.get("Location") - } + return new URL(response.headers.get("Location") as string) + } + } + + async upload(file: File): Promise<URL> { + const config = await this.config(); + const media = config["media-endpoint"]; + if (media == null) { + throw new Error("Micropub endpoint doesn't support file uploads") + } + + const form = new FormData(); + form.set("file", file); + + const response = await fetch(media, { + method: "POST", + headers: { + "Authorization": `Bearer ${this.token}`, + }, + body: form + }) + + if (response.ok) { + return new URL(response.headers.get("Location") as string) + } else { + throw new MicropubError(response.status, await response.json()); } - } catch (e) { - console.error("Network error!", e) - throw e } } |