import type { NbDnsNameserverGroup, NbEvent, NbGroup, NbNetwork, NbNetworkResource, NbNetworkRouter, NbPeer, NbPolicy, NbPostureCheck, NbRoute, NbSetupKey, NbUser, } from "./types.ts"; /** Narrowed fetch signature used for dependency injection. */ export type FetchFn = ( input: string | URL | Request, init?: RequestInit, ) => Promise; /** Thrown when the NetBird API returns a non-2xx status. */ export class NetbirdApiError extends Error { constructor( public readonly status: number, public readonly method: string, public readonly path: string, public readonly body: unknown, ) { super(`NetBird API error: ${method} ${path} returned ${status}`); this.name = "NetbirdApiError"; } } /** * Thin HTTP client for the NetBird Management API. * * Accepts an injectable fetch function so callers (and tests) can swap * the transport without touching the client logic. */ export class NetbirdClient { constructor( private readonly baseUrl: string, private readonly token: string, private readonly fetchFn: FetchFn = fetch, ) {} // --------------------------------------------------------------------------- // Internal // --------------------------------------------------------------------------- private async request( method: string, path: string, body?: unknown, ): Promise { const url = `${this.baseUrl}${path}`; const headers: Record = { "Authorization": `Token ${this.token}`, "Accept": "application/json", }; if (body !== undefined) { headers["Content-Type"] = "application/json"; } const resp = await this.fetchFn(url, { method, headers, body: body !== undefined ? JSON.stringify(body) : undefined, }); if (!resp.ok) { const text = await resp.text(); let errorBody: unknown; try { errorBody = JSON.parse(text); } catch { errorBody = text; } throw new NetbirdApiError(resp.status, method, path, errorBody); } // 204 No Content — nothing to parse if (resp.status === 204) { return undefined as T; } return (await resp.json()) as T; } // --------------------------------------------------------------------------- // Groups // --------------------------------------------------------------------------- listGroups(): Promise { return this.request("GET", "/groups"); } createGroup(data: { name: string; peers?: string[] }): Promise { return this.request("POST", "/groups", data); } updateGroup( id: string, data: { name?: string; peers?: string[] }, ): Promise { return this.request("PUT", `/groups/${id}`, data); } deleteGroup(id: string): Promise { return this.request("DELETE", `/groups/${id}`); } // --------------------------------------------------------------------------- // Setup Keys // --------------------------------------------------------------------------- listSetupKeys(): Promise { return this.request("GET", "/setup-keys"); } createSetupKey(data: { name: string; type: "one-off" | "reusable"; expires_in: number; auto_groups?: string[]; usage_limit?: number; }): Promise { return this.request("POST", "/setup-keys", data); } deleteSetupKey(id: number | string): Promise { return this.request("DELETE", `/setup-keys/${id}`); } // --------------------------------------------------------------------------- // Peers // --------------------------------------------------------------------------- listPeers(): Promise { return this.request("GET", "/peers"); } updatePeer( id: string, data: { name?: string; ssh_enabled?: boolean; login_expiration_enabled?: boolean; inactivity_expiration_enabled?: boolean; }, ): Promise { return this.request("PUT", `/peers/${id}`, data); } deletePeer(id: string): Promise { return this.request("DELETE", `/peers/${id}`); } // --------------------------------------------------------------------------- // Policies // --------------------------------------------------------------------------- listPolicies(): Promise { return this.request("GET", "/policies"); } createPolicy(data: Omit): Promise { return this.request("POST", "/policies", data); } updatePolicy(id: string, data: Omit): Promise { return this.request("PUT", `/policies/${id}`, data); } deletePolicy(id: string): Promise { return this.request("DELETE", `/policies/${id}`); } // --------------------------------------------------------------------------- // Routes // --------------------------------------------------------------------------- listRoutes(): Promise { return this.request("GET", "/routes"); } createRoute(data: Omit): Promise { return this.request("POST", "/routes", data); } updateRoute(id: string, data: Omit): Promise { return this.request("PUT", `/routes/${id}`, data); } deleteRoute(id: string): Promise { return this.request("DELETE", `/routes/${id}`); } // --------------------------------------------------------------------------- // DNS Nameserver Groups // --------------------------------------------------------------------------- listDnsNameserverGroups(): Promise { return this.request("GET", "/dns/nameservers"); } createDnsNameserverGroup( data: Omit, ): Promise { return this.request("POST", "/dns/nameservers", data); } updateDnsNameserverGroup( id: string, data: Omit, ): Promise { return this.request("PUT", `/dns/nameservers/${id}`, data); } deleteDnsNameserverGroup(id: string): Promise { return this.request("DELETE", `/dns/nameservers/${id}`); } // --------------------------------------------------------------------------- // Events // --------------------------------------------------------------------------- listEvents(): Promise { return this.request("GET", "/events/audit"); } // --------------------------------------------------------------------------- // Posture Checks // --------------------------------------------------------------------------- listPostureChecks(): Promise { return this.request("GET", "/posture-checks"); } createPostureCheck( data: Omit, ): Promise { return this.request("POST", "/posture-checks", data); } updatePostureCheck( id: string, data: Omit, ): Promise { return this.request("PUT", `/posture-checks/${id}`, data); } deletePostureCheck(id: string): Promise { return this.request("DELETE", `/posture-checks/${id}`); } // --------------------------------------------------------------------------- // Networks // --------------------------------------------------------------------------- listNetworks(): Promise { return this.request("GET", "/networks"); } createNetwork( data: { name: string; description?: string }, ): Promise { return this.request("POST", "/networks", data); } updateNetwork( id: string, data: { name: string; description?: string }, ): Promise { return this.request("PUT", `/networks/${id}`, data); } deleteNetwork(id: string): Promise { return this.request("DELETE", `/networks/${id}`); } // --------------------------------------------------------------------------- // Network Resources // --------------------------------------------------------------------------- listNetworkResources(networkId: string): Promise { return this.request("GET", `/networks/${networkId}/resources`); } createNetworkResource( networkId: string, data: { name: string; description?: string; address: string; enabled: boolean; groups: string[]; }, ): Promise { return this.request("POST", `/networks/${networkId}/resources`, data); } updateNetworkResource( networkId: string, resourceId: string, data: { name: string; description?: string; address: string; enabled: boolean; groups: string[]; }, ): Promise { return this.request( "PUT", `/networks/${networkId}/resources/${resourceId}`, data, ); } deleteNetworkResource( networkId: string, resourceId: string, ): Promise { return this.request( "DELETE", `/networks/${networkId}/resources/${resourceId}`, ); } // --------------------------------------------------------------------------- // Network Routers // --------------------------------------------------------------------------- listNetworkRouters(networkId: string): Promise { return this.request("GET", `/networks/${networkId}/routers`); } createNetworkRouter( networkId: string, data: Omit, ): Promise { return this.request("POST", `/networks/${networkId}/routers`, data); } updateNetworkRouter( networkId: string, routerId: string, data: Omit, ): Promise { return this.request( "PUT", `/networks/${networkId}/routers/${routerId}`, data, ); } deleteNetworkRouter( networkId: string, routerId: string, ): Promise { return this.request( "DELETE", `/networks/${networkId}/routers/${routerId}`, ); } // --------------------------------------------------------------------------- // Users // --------------------------------------------------------------------------- listUsers(): Promise { return this.request("GET", "/users"); } createUser(data: { email: string; name?: string; role: string; auto_groups: string[]; is_service_user: boolean; }): Promise { return this.request("POST", "/users", data); } updateUser( id: string, data: { name?: string; role?: string; auto_groups?: string[] }, ): Promise { return this.request("PUT", `/users/${id}`, data); } deleteUser(id: string): Promise { return this.request("DELETE", `/users/${id}`); } }