forked from coracle/caravel
c261d8a146
Co-authored-by: userAdityaa <aditya.chaudhary1558@gmail.com> Co-committed-by: userAdityaa <aditya.chaudhary1558@gmail.com>
279 lines
6.8 KiB
TypeScript
279 lines
6.8 KiB
TypeScript
const API_URL = import.meta.env.VITE_API_URL
|
|
|
|
// Populated by state.ts after it initializes, breaking the circular dependency
|
|
let getAccount: () => unknown = () => undefined
|
|
export function registerAccountGetter(fn: () => unknown) {
|
|
getAccount = fn
|
|
}
|
|
|
|
type ApiOk<T> = {
|
|
data: T
|
|
code: string
|
|
}
|
|
|
|
type UpdateTenantInput = {
|
|
nwc_url?: string
|
|
}
|
|
|
|
type AuthCache = {
|
|
pubkey: string
|
|
value: string
|
|
expiresAt: number
|
|
}
|
|
|
|
export class ApiError extends Error {
|
|
status: number
|
|
|
|
constructor(message: string, status: number) {
|
|
super(message)
|
|
this.name = "ApiError"
|
|
this.status = status
|
|
}
|
|
}
|
|
|
|
export type Plan = {
|
|
id: string
|
|
name: string
|
|
amount: number
|
|
stripe_price_id: string | null
|
|
members: number | null
|
|
blossom: boolean
|
|
livekit: boolean
|
|
}
|
|
|
|
export type PlanId = string
|
|
|
|
export type Relay = {
|
|
id: string
|
|
tenant: string
|
|
schema: string
|
|
subdomain: string
|
|
plan: PlanId
|
|
status: string
|
|
sync_error: string
|
|
stripe_subscription_item_id: string | null
|
|
synced: number
|
|
info_name: string
|
|
info_icon: string
|
|
info_description: string
|
|
policy_public_join: number
|
|
policy_strip_signatures: number
|
|
groups_enabled: number
|
|
management_enabled: number
|
|
blossom_enabled: number
|
|
livekit_enabled: number
|
|
push_enabled: number
|
|
}
|
|
|
|
export type CreateRelayInput = {
|
|
tenant?: string
|
|
subdomain: string
|
|
plan: string
|
|
info_name?: string
|
|
info_icon?: string
|
|
info_description?: string
|
|
policy_public_join?: number
|
|
policy_strip_signatures?: number
|
|
groups_enabled?: number
|
|
management_enabled?: number
|
|
blossom_enabled?: number
|
|
livekit_enabled?: number
|
|
push_enabled?: number
|
|
}
|
|
|
|
export type UpdateRelayInput = {
|
|
subdomain?: string
|
|
plan?: string
|
|
info_name?: string
|
|
info_icon?: string
|
|
info_description?: string
|
|
policy_public_join?: number
|
|
policy_strip_signatures?: number
|
|
groups_enabled?: number
|
|
management_enabled?: number
|
|
blossom_enabled?: number
|
|
livekit_enabled?: number
|
|
push_enabled?: number
|
|
}
|
|
|
|
export type Tenant = {
|
|
pubkey: string
|
|
nwc_url: string
|
|
created_at: number
|
|
stripe_customer_id: string
|
|
stripe_subscription_id: string | null
|
|
past_due_at: number | null
|
|
nwc_error: string | null
|
|
}
|
|
|
|
export type Invoice = {
|
|
id: string
|
|
status: string
|
|
amount_due: number
|
|
currency: string
|
|
hosted_invoice_url: string
|
|
period_start: number
|
|
period_end: number
|
|
}
|
|
|
|
export type Activity = {
|
|
id: string
|
|
tenant: string
|
|
created_at: number
|
|
activity_type: string
|
|
resource_type: string
|
|
resource_id: string
|
|
}
|
|
|
|
export type Identity = {
|
|
pubkey: string
|
|
is_admin: boolean
|
|
}
|
|
|
|
let authCache: AuthCache | undefined
|
|
|
|
export async function makeAuth(): Promise<string | undefined> {
|
|
const current = getAccount() as { pubkey: string; signer: { signEvent: (e: unknown) => Promise<{ pubkey: string; sig: string; [k: string]: unknown }> } } | undefined
|
|
if (!current) return undefined
|
|
|
|
const now = Date.now()
|
|
if (authCache && authCache.pubkey === current.pubkey && authCache.expiresAt > now) {
|
|
return authCache.value
|
|
}
|
|
|
|
const event = await current.signer.signEvent({
|
|
kind: 27235,
|
|
content: "",
|
|
created_at: Math.floor(now / 1000),
|
|
// Intentional session-style auth: sign the API base URL once, then reuse
|
|
// the header briefly to avoid prompting the signer on every request.
|
|
tags: [["u", API_URL]],
|
|
})
|
|
|
|
const value = `Nostr ${btoa(JSON.stringify(event))}`
|
|
authCache = {
|
|
pubkey: current.pubkey,
|
|
value,
|
|
expiresAt: now + 10 * 60 * 1000,
|
|
}
|
|
return value
|
|
}
|
|
|
|
export async function callApi<TRequest = unknown, TResponse = unknown>(
|
|
method: string,
|
|
path: string,
|
|
body?: TRequest,
|
|
): Promise<TResponse> {
|
|
const auth = await makeAuth()
|
|
const url = new URL(path, API_URL).toString()
|
|
|
|
const response = await fetch(url, {
|
|
method,
|
|
headers: {
|
|
...(auth ? { Authorization: auth } : {}),
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: body === undefined ? undefined : JSON.stringify(body),
|
|
})
|
|
|
|
if (!response.ok) {
|
|
let message = `Request failed (${response.status})`
|
|
try {
|
|
const payload = await response.json() as { error?: string }
|
|
if (payload.error) message = payload.error
|
|
} catch {
|
|
// ignore invalid/non-json body
|
|
}
|
|
throw new ApiError(message, response.status)
|
|
}
|
|
|
|
if (response.status === 204) return undefined as TResponse
|
|
|
|
const payload = await response.json() as ApiOk<TResponse>
|
|
return payload.data
|
|
}
|
|
|
|
export async function listPlans(): Promise<Plan[]> {
|
|
const url = new URL("/plans", API_URL).toString()
|
|
const response = await fetch(url)
|
|
if (!response.ok) throw new ApiError(`Failed to load plans (${response.status})`, response.status)
|
|
const payload = await response.json() as { data: Plan[] }
|
|
return payload.data
|
|
}
|
|
|
|
export function getIdentity() {
|
|
return callApi<undefined, Identity>("GET", "/identity")
|
|
}
|
|
|
|
export function createTenant() {
|
|
return callApi<undefined, Tenant>("POST", "/tenants")
|
|
}
|
|
|
|
export function getPlan(id: string) {
|
|
return callApi<undefined, Plan>("GET", `/plans/${id}`)
|
|
}
|
|
|
|
export function listTenants() {
|
|
return callApi<undefined, Tenant[]>("GET", "/tenants")
|
|
}
|
|
|
|
export function getTenant(pubkey: string) {
|
|
return callApi<undefined, Tenant>("GET", `/tenants/${pubkey}`)
|
|
}
|
|
|
|
export function listTenantRelays(pubkey: string) {
|
|
return callApi<undefined, Relay[]>("GET", `/tenants/${pubkey}/relays`)
|
|
}
|
|
|
|
export function listTenantInvoices(pubkey: string) {
|
|
return callApi<undefined, Invoice[]>("GET", `/tenants/${pubkey}/invoices`)
|
|
}
|
|
|
|
export function updateTenant(pubkey: string, input: UpdateTenantInput) {
|
|
return callApi<UpdateTenantInput, Tenant>("PUT", `/tenants/${pubkey}`, input)
|
|
}
|
|
|
|
export function listRelays() {
|
|
return callApi<undefined, Relay[]>("GET", "/relays")
|
|
}
|
|
|
|
export function getRelay(id: string) {
|
|
return callApi<undefined, Relay>("GET", `/relays/${id}`)
|
|
}
|
|
|
|
export function listRelayMembers(id: string) {
|
|
return callApi<undefined, { members: string[] }>("GET", `/relays/${id}/members`)
|
|
}
|
|
|
|
export function listRelayActivity(id: string) {
|
|
return callApi<undefined, { activity: Activity[] }>("GET", `/relays/${id}/activity`)
|
|
}
|
|
|
|
export function reactivateRelay(id: string) {
|
|
return callApi<undefined, void>("POST", `/relays/${id}/reactivate`)
|
|
}
|
|
|
|
export function createPortalSession(pubkey: string) {
|
|
return callApi<undefined, { url: string }>("GET", `/tenants/${pubkey}/stripe/session`)
|
|
}
|
|
|
|
export function getInvoiceBolt11(invoiceId: string) {
|
|
return callApi<undefined, { bolt11: string }>("GET", `/invoices/${invoiceId}/bolt11`)
|
|
}
|
|
|
|
export function createRelay(input: CreateRelayInput) {
|
|
return callApi<CreateRelayInput, Relay>("POST", "/relays", input)
|
|
}
|
|
|
|
export function updateRelay(id: string, input: UpdateRelayInput) {
|
|
return callApi<UpdateRelayInput, Relay>("PUT", `/relays/${id}`, input)
|
|
}
|
|
|
|
export function deactivateRelay(id: string) {
|
|
return callApi<undefined, void>("POST", `/relays/${id}/deactivate`)
|
|
}
|
|
|
|
export function getInvoice(id: string) {
|
|
return callApi<undefined, Invoice>("GET", `/invoices/${id}`)
|
|
}
|