web.ts (5104B)
1 import { err_msg, schema_media_resource } from '@radroots/utils'; 2 import { type IHttpResponse, is_err_response, is_error_response, is_pass_response, WebHttp } from '@radroots/http'; 3 import { nostr_event_sign_attest } from "@radroots/nostr"; 4 import { cl_radroots_error } from "./error.js"; 5 import type { 6 IClientRadroots, 7 IClientRadrootsAccountsActivate, 8 IClientRadrootsAccountsActivateResolve, 9 IClientRadrootsAccountsCreate, 10 IClientRadrootsAccountsCreateResolve, 11 IClientRadrootsAccountsRequest, 12 IClientRadrootsAccountsRequestResolve, 13 IClientRadrootsMediaImageUpload, 14 IClientRadrootsMediaImageUploadResolve 15 } from "./types.js"; 16 17 export interface IWebClientRadroots extends IClientRadroots { } 18 19 export class WebClientRadroots implements IWebClientRadroots { 20 private _base_url: string 21 private _http_client: WebHttp 22 23 constructor(base_url: string) { 24 if (!base_url) throw new Error(cl_radroots_error.missing_base_url); 25 const parsed_url = new URL(base_url); 26 const sanitized_base_url = `${parsed_url.origin}${parsed_url.pathname}`.replace(/\/+$/, ``); 27 this._base_url = sanitized_base_url; 28 this._http_client = new WebHttp(); 29 } 30 31 private is_res_pass(res: IHttpResponse): boolean { 32 return is_pass_response(res.data); 33 } 34 35 private parse_res_field(field: unknown): string | undefined { 36 if (typeof field === `string` && field) return field; 37 } 38 39 private is_record(value: unknown): value is Record<string, unknown> { 40 return typeof value === "object" && value !== null; 41 } 42 43 private create_x_nostr_event(secret_key: string): string { 44 return JSON.stringify(nostr_event_sign_attest(secret_key)); 45 } 46 47 public async accounts_request(opts: IClientRadrootsAccountsRequest): Promise<IClientRadrootsAccountsRequestResolve> { 48 const { profile_name, secret_key } = opts 49 const res = await this._http_client.fetch({ 50 url: `${this._base_url}/v1/accounts/request`, 51 method: "post", 52 headers: { 53 "X-Nostr-Event": this.create_x_nostr_event(secret_key), 54 }, 55 data: { profile_name } 56 }); 57 if (is_err_response(res)) return res; 58 if (is_error_response(res)) return err_msg(res.error); 59 else if (this.is_res_pass(res)) { 60 const res_data = this.is_record(res.data) ? res.data : null; 61 const tok = res_data ? this.parse_res_field(res_data["tok"]) : undefined; 62 if (tok) return { result: tok }; 63 } 64 return err_msg(cl_radroots_error.account_registered); 65 } 66 67 public async accounts_create(opts: IClientRadrootsAccountsCreate): Promise<IClientRadrootsAccountsCreateResolve> { 68 const { tok, secret_key } = opts 69 const res = await this._http_client.fetch({ 70 url: `${this._base_url}/v1/accounts/create`, 71 method: "post", 72 headers: { 73 "X-Nostr-Event": this.create_x_nostr_event(secret_key), 74 }, 75 authorization: tok 76 }); 77 if (is_err_response(res)) return res; 78 if (is_error_response(res)) return err_msg(res.error); 79 else if (this.is_res_pass(res)) { 80 const res_data = this.is_record(res.data) ? res.data : null; 81 const id = res_data ? this.parse_res_field(res_data["id"]) : undefined; 82 if (id) return { result: id }; 83 } 84 return err_msg(cl_radroots_error.request_failure); 85 } 86 87 public async accounts_activate(opts: IClientRadrootsAccountsActivate): Promise<IClientRadrootsAccountsActivateResolve> { 88 const { id, secret_key } = opts 89 const res = await this._http_client.fetch({ 90 url: `${this._base_url}/v1/accounts/activate`, 91 method: "post", 92 headers: { 93 "X-Nostr-Event": this.create_x_nostr_event(secret_key), 94 }, 95 data: { id } 96 }); 97 if (is_err_response(res)) return res; 98 if (is_error_response(res)) return err_msg(res.error); 99 else if (this.is_res_pass(res)) return { result: id }; 100 return err_msg(cl_radroots_error.request_failure); 101 } 102 103 public async media_image_upload(opts: IClientRadrootsMediaImageUpload): Promise<IClientRadrootsMediaImageUploadResolve> { 104 const { mime_type, file_data, secret_key } = opts 105 const res = await this._http_client.fetch({ 106 url: `${this._base_url}/v1/media/image/upload`, 107 method: "put", 108 headers: { 109 "Content-Type": mime_type || "image/png", 110 "X-Nostr-Event": this.create_x_nostr_event(secret_key), 111 }, 112 data_bin: file_data 113 }); 114 if (is_err_response(res)) return res; 115 if (is_error_response(res)) return err_msg(res.error); 116 else if (this.is_res_pass(res)) { 117 const res_data = schema_media_resource.safeParse(res.data); 118 if (res_data.success && res_data.data) return res_data.data; 119 } 120 return err_msg(cl_radroots_error.request_failure); 121 } 122 }