export function normalizeTargetUrl(raw) { const url = new URL(raw) if (!['http:', 'https:'].includes(url.protocol)) throw new Error('target URL must use http or https') url.hash = ''; url.search = '' return url.toString().replace(/\/$/, '') } export function createApiClient({ fetchImpl = globalThis.fetch } = {}) { const request = async (url, options) => { const response = await fetchImpl(url, options) const data = await response.json().catch(() => ({})) if (!response.ok) throw new Error(data.error || `HTTP ${response.status}`) return data } return { request, config: () => request('/api/config'), catalog: () => request('/api/catalog'), status: () => request('/api/status'), proxy: (id, endpoint, options) => fetchImpl(`/api/proxy/${encodeURIComponent(id)}${endpoint}`, options), prepareConfirmation: payload => request('/api/proxy-confirmations/prepare', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(payload) }), createInstance: payload => request('/api/instances', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(payload) }), updateInstance: (id, payload) => request(`/api/instances/${encodeURIComponent(id)}`, { method: 'PUT', headers: { 'content-type': 'application/json' }, body: JSON.stringify(payload) }), deleteInstance: id => request(`/api/instances/${encodeURIComponent(id)}`, { method: 'DELETE' }), login: (id, password) => request(`/api/instances/${encodeURIComponent(id)}/login`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(password === undefined ? {} : { password }) }), logout: id => request(`/api/instances/${encodeURIComponent(id)}/logout`, { method: 'POST' }), } }