diff --git a/src/services/idnot/index.ts b/src/services/idnot/index.ts index aefc7fa..a3d49c7 100644 --- a/src/services/idnot/index.ts +++ b/src/services/idnot/index.ts @@ -1,7 +1,39 @@ import fetch from 'node-fetch'; import { IdNotUser, ECivility, EOfficeStatus, EIdnotRole } from '../../types'; +import { ExternalServiceError } from '../../types/errors'; +import { Logger } from '../../utils/logger'; export class IdNotService { + private static async parseJsonOrThrow(response: Response, context: string): Promise { + const contentType = response.headers.get('content-type') || ''; + try { + if (contentType.includes('application/json')) { + return await (response as any).json(); + } + const text = await (response as any).text(); + Logger.error('IdNot non-JSON response', { + context, + status: response.status, + statusText: response.statusText, + contentType, + bodySnippet: text?.slice(0, 200) + }); + throw new ExternalServiceError('IdNot', `Non-JSON response (${response.status} ${response.statusText}): ${text?.slice(0, 120) || 'no body'}`); + } catch (err) { + if (err instanceof ExternalServiceError) throw err; + // json() may throw on invalid JSON + const text = await (response as any).text().catch(() => undefined); + Logger.error('IdNot JSON parse failed', { + context, + status: response.status, + statusText: response.statusText, + contentType, + parseError: err instanceof Error ? err.message : String(err), + bodySnippet: text?.slice(0, 200) + }); + throw new ExternalServiceError('IdNot', `Invalid JSON response (${response.status} ${response.statusText}): ${text?.slice(0, 120) || 'no body'}`); + } + } static async exchangeCodeForTokens(code: string) { const { IDNOT_CLIENT_ID, @@ -67,12 +99,10 @@ export class IdNotService { const url = `${IDNOT_ANNUARY_BASE_URL}/api/pp/v2/personnes/${idNot}/rattachements?${searchParams}`; const response = await fetch(url, { method: 'GET' }); - if (!response.ok) { - throw new Error(`Failed to fetch rattachements: ${response.status} ${response.statusText}`); + throw new ExternalServiceError('IdNot', `Failed to fetch rattachements: ${response.status} ${response.statusText}`); } - - return response.json(); + return await IdNotService.parseJsonOrThrow(response as any, 'getUserRattachements'); } static async getOfficeRattachements(idNot: string) { @@ -89,13 +119,11 @@ export class IdNotService { const url = `${IDNOT_ANNUARY_BASE_URL}/api/pp/v2/entites/${idNot}/personnes?` + searchParams; - const json = await ( - await fetch(url, { - method: 'GET' - }) - ).json(); - - return json; + const response = await fetch(url, { method: 'GET' }); + if (!response.ok) { + throw new ExternalServiceError('IdNot', `Failed to fetch office rattachements: ${response.status} ${response.statusText}`); + } + return await IdNotService.parseJsonOrThrow(response as any, 'getOfficeRattachements'); } static async getUserData(profileIdn: string) { @@ -109,13 +137,11 @@ export class IdNotService { key: IDNOT_API_KEY }); - const userData = await ( - await fetch(`${IDNOT_API_BASE_URL}/api/pp/v2/rattachements/${profileIdn}?` + searchParams, { - method: 'GET' - }) - ).json(); - - return userData; + const response = await fetch(`${IDNOT_API_BASE_URL}/api/pp/v2/rattachements/${profileIdn}?` + searchParams, { method: 'GET' }); + if (!response.ok) { + throw new ExternalServiceError('IdNot', `Failed to fetch user data: ${response.status} ${response.statusText}`); + } + return await IdNotService.parseJsonOrThrow(response as any, 'getUserData'); } static async getOfficeLocationData(locationsUrl: string) { @@ -129,13 +155,11 @@ export class IdNotService { key: IDNOT_API_KEY }); - const officeLocationData = await ( - await fetch(`${IDNOT_API_BASE_URL}${locationsUrl}?` + searchParams, { - method: 'GET' - }) - ).json(); - - return officeLocationData; + const response = await fetch(`${IDNOT_API_BASE_URL}${locationsUrl}?` + searchParams, { method: 'GET' }); + if (!response.ok) { + throw new ExternalServiceError('IdNot', `Failed to fetch office location data: ${response.status} ${response.statusText}`); + } + return await IdNotService.parseJsonOrThrow(response as any, 'getOfficeLocationData'); } static getOfficeStatus(statusName: string): EOfficeStatus {