Skip to content
← Public packages

@kentcdodds/wise

Call Wise profiles, balances, quotes, recipients, and transfers with wiseApiToken.

src/core.ts

78 lines · 2.5 KB · TypeScript
const wiseApiToken = '{{secret:wiseApiToken|scope=user}}'
const wiseApiBase = 'https://api.wise.com'

export function clean(value: unknown) {
	return String(value ?? '').trim()
}

export async function parseJsonResponse(response: Response, label = 'Wise API') {
	const text = await response.text()
	let body: unknown = null
	try {
		body = text ? JSON.parse(text) : null
	} catch {
		body = { raw: text }
	}
	if (!response.ok) {
		throw new Error(
			label + ' HTTP ' + response.status + ': ' + JSON.stringify(body).slice(0, 800),
		)
	}
	return body
}

export type WiseRequestInput = {
	method?: string
	path: string
	query?: Record<string, unknown>
	body?: unknown
	label?: string
}

/**
 * Make one authenticated Wise REST request with wiseApiToken.
 * Prefer the typed exports (list-profiles, create-quote, …) over calling this directly.
 */
export async function wiseRequest(input: WiseRequestInput) {
	const path = clean(input.path)
	if (!path.startsWith('/')) throw new Error('path must start with /')
	const url = new URL(path, wiseApiBase)
	for (const [key, value] of Object.entries(input.query || {})) {
		if (value === undefined || value === null || value === '') continue
		url.searchParams.set(key, String(value))
	}
	const method = clean(input.method || 'GET').toUpperCase() || 'GET'
	const headers: Record<string, string> = {
		Authorization: 'Bearer ' + wiseApiToken,
		Accept: 'application/json',
	}
	let body: string | undefined
	if (input.body !== undefined && method !== 'GET' && method !== 'HEAD') {
		headers['Content-Type'] = 'application/json'
		body = JSON.stringify(input.body)
	}
	const response = await fetch(url, { method, headers, body })
	return await parseJsonResponse(response, input.label || 'Wise API')
}

export type ProfileRef = {
	profileId?: number | string
	/** When profileId is omitted, pick the first profile of this type (default business). */
	profileType?: 'personal' | 'business'
}

export async function resolveProfileId(input: ProfileRef = {}) {
	if (input.profileId !== undefined && input.profileId !== null && clean(input.profileId)) {
		const id = Number(input.profileId)
		if (!Number.isFinite(id)) throw new Error('profileId must be a number')
		return id
	}
	const profiles = (await wiseRequest({
		path: '/v1/profiles',
		label: 'Wise profiles',
	})) as Array<{ id: number; type?: string }>
	const wanted = input.profileType || 'business'
	const hit = profiles.find((p) => p.type === wanted) || profiles[0]
	if (!hit?.id) throw new Error('No Wise profiles available for this token')
	return hit.id
}