import { intercomFetch, resolveIntercomAuth, type ResolvedIntercomAuth } from './auth.ts' import { extractPageInfo } from './models.ts' import type { DryRunResult, IntercomAuthInput, IntercomOperation, IntercomPageInfo, JsonRecord, MutationInput, } from './types.ts' import { requireString } from './types.ts' import { INTERCOM_API_VERSION, inferOperationFromPath, intercomUrl, isMutatingMethod, isSearchPath, mutationPreview, nextStepForOperation, normalizeIntercomPath, permissionForOperation, } from './setup.ts' export class IntercomApiError extends Error { readonly status: number readonly operation: IntercomOperation readonly details: unknown readonly permission: string constructor( message: string, options: { status: number operation: IntercomOperation details: unknown permission: string }, ) { super(message) this.name = 'IntercomApiError' this.status = options.status this.operation = options.operation this.details = options.details this.permission = options.permission } } export type IntercomRequestInput = IntercomAuthInput & MutationInput & { path: string method?: string query?: JsonRecord body?: JsonRecord operation?: IntercomOperation } export type IntercomResponse = { data: T pageInfo: IntercomPageInfo status: number } function intercomErrorMessage(body: unknown): string | null { if (typeof body === 'string' && body.trim().length > 0) { return body.trim().slice(0, 400) } if (!body || typeof body !== 'object') return null const record = body as { errors?: unknown; message?: unknown; type?: unknown } if (Array.isArray(record.errors) && record.errors[0]) { const first = record.errors[0] as { message?: unknown; code?: unknown } if (typeof first.message === 'string' && first.message.length > 0) return first.message if (typeof first.code === 'string' && first.code.length > 0) return first.code } if (typeof record.message === 'string' && record.message.length > 0) return record.message return null } function looksLikeInsufficientPermission(status: number, body: unknown): boolean { if (status === 401 || status === 403) return true const message = (intercomErrorMessage(body) ?? '').toLowerCase() return ( message.includes('unauthorized') || message.includes('forbidden') || message.includes('not authorized') || message.includes('permission') || message.includes('access token') || message.includes('scope') ) } export async function intercomRequest( input: IntercomRequestInput, ): Promise | DryRunResult> { const method = (input.method ?? 'GET').toUpperCase() const path = normalizeIntercomPath(requireString(input.path, 'path')) const operation = inferOperationFromPath(method, path, input.operation) const mutating = isMutatingMethod(method) && !isSearchPath(path) const url = intercomUrl(path, input.query, input) if (mutating) { const preview = mutationPreview(input, { method, path, url, body: input.body, }) if (preview) return preview } const auth = await resolveIntercomAuth(input) return intercomRequestWithAuth(auth, { method, path, query: input.query, body: input.body, operation, apiVersion: input.apiVersion, }) } export async function intercomRequestWithAuth( auth: ResolvedIntercomAuth, input: { method: string path: string query?: JsonRecord body?: JsonRecord operation: IntercomOperation apiVersion?: string }, ): Promise> { const url = intercomUrl(input.path, input.query, { apiBaseUrl: auth.apiBaseUrl }) const headers = new Headers({ Accept: 'application/json', 'Intercom-Version': input.apiVersion ?? INTERCOM_API_VERSION, 'User-Agent': 'kody-intercom/1.0', }) const init: RequestInit = { method: input.method, headers } if (input.body !== undefined) { headers.set('Content-Type', 'application/json') init.body = JSON.stringify(input.body) } const response = await intercomFetch(auth, url, init) const text = await response.text() let parsed: unknown = text try { parsed = text ? JSON.parse(text) : null } catch { parsed = text } if (!response.ok || looksLikeInsufficientPermission(response.status, parsed)) { const permission = permissionForOperation(input.operation) const firstMessage = intercomErrorMessage(parsed) || (typeof parsed === 'string' ? parsed.slice(0, 400) : response.statusText) throw new IntercomApiError( [ `Intercom ${input.operation} failed (${response.status}): ${firstMessage}.`, `This call needs the "${permission}" permission.`, nextStepForOperation(input.operation, { authMode: auth.mode, integrationName: auth.integrationName, secretName: auth.secretName, }), ].join(' '), { status: response.status, operation: input.operation, details: parsed, permission, }, ) } return { data: parsed as T, pageInfo: extractPageInfo(parsed), status: response.status, } } export { inferOperationFromPath, isMutatingMethod, isSearchPath }