diff --git a/README.md b/README.md index c65eb53..74cf195 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,6 @@ The release contains two builds: ```javascript const config = { - apiUrl: 'https://your-simface-api.run.app', projectId: 'your-project-id', apiKey: 'your-api-key', }; @@ -175,7 +174,7 @@ Parameters: | Parameter | Type | Description | |-----------|------|-------------| -| `config` | `SimFaceConfig` | SDK configuration (`apiUrl`, `projectId`, `apiKey`) | +| `config` | `SimFaceConfig` | SDK configuration (`projectId`, `apiKey`, optional `apiUrl`) | | `clientId` | `string` | Unique identifier for the user | | `workflowOptions` | `SimFaceWorkflowOptions` | Optional popup/embedded-agnostic capture behavior | | `captureElement` | `SimFaceCaptureElement` | Optional embedded `simface-capture` element | @@ -190,7 +189,7 @@ Parameters: | Parameter | Type | Description | |-----------|------|-------------| -| `config` | `SimFaceConfig` | SDK configuration (`apiUrl`, `projectId`, `apiKey`) | +| `config` | `SimFaceConfig` | SDK configuration (`projectId`, `apiKey`, optional `apiUrl`) | | `clientId` | `string` | Unique identifier for the user | | `workflowOptions` | `SimFaceWorkflowOptions` | Optional popup/embedded-agnostic capture behavior | | `captureElement` | `SimFaceCaptureElement` | Optional embedded `simface-capture` element | @@ -237,7 +236,6 @@ Use the `simface-capture` Web Component directly when you want the host applicat const captureEl = document.querySelector('simface-capture'); const client = new SimFaceAPIClient({ - apiUrl: 'https://your-simface-api.run.app', projectId: 'your-project-id', apiKey: 'your-api-key', }); @@ -296,9 +294,9 @@ This is more flexible, but it also means the host owns more of the workflow. ```typescript interface SimFaceConfig { - apiUrl: string; projectId: string; apiKey: string; + apiUrl?: string; // Defaults to the hosted SimFace backend } ``` diff --git a/src/index.test.ts b/src/index.test.ts index 42456b1..e914e8a 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -31,7 +31,6 @@ import { enroll, verify } from './index.js'; describe('SDK entrypoints', () => { const config = { - apiUrl: 'https://example.invalid', projectId: 'project-1', apiKey: 'api-key', }; @@ -60,6 +59,7 @@ describe('SDK entrypoints', () => { const result = await enroll(config, 'user-1', workflowOptions, captureComponent); + expect(apiClientConstructor).toHaveBeenCalledWith(config); expect(captureMocks.captureFromCamera).toHaveBeenCalledWith(workflowOptions, captureComponent); expect(apiClientMethodMocks.validateAPIKey).toHaveBeenCalledTimes(1); expect(apiClientMethodMocks.enroll).toHaveBeenCalledWith('user-1', blob); diff --git a/src/index.ts b/src/index.ts index 9766f5c..a45d8ed 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,11 +4,13 @@ * Usage: * import { enroll, verify } from '@simprints/simface-sdk'; * + * const config = { projectId: '...', apiKey: '...' }; + * * // Enroll a new user - * const result = await enroll({ apiUrl: '...', projectId: '...', apiKey: '...' }, 'user-123'); + * const result = await enroll(config, 'user-123'); * * // Verify an existing user - * const result = await verify({ apiUrl: '...', projectId: '...', apiKey: '...' }, 'user-123'); + * const result = await verify(config, 'user-123'); */ import type { diff --git a/src/services/api-client.test.ts b/src/services/api-client.test.ts index cb2ffef..2a000ed 100644 --- a/src/services/api-client.test.ts +++ b/src/services/api-client.test.ts @@ -1,10 +1,13 @@ import { describe, it, expect, vi } from 'vitest'; import { SimFaceAPIClient } from '../services/api-client.js'; +import { DEFAULT_SIMFACE_API_URL } from '../shared/api-url.js'; + +const DEFAULT_API_URL = DEFAULT_SIMFACE_API_URL; const mockConfig = { - apiUrl: 'https://api.example.com', projectId: 'test-project', apiKey: 'test-key', + apiUrl: 'https://api.example.com', }; describe('SimFaceAPIClient', () => { @@ -141,6 +144,39 @@ describe('SimFaceAPIClient', () => { }); describe('URL handling', () => { + it('should use the hosted backend when apiUrl is omitted', async () => { + global.fetch = vi.fn().mockResolvedValue({ + ok: true, + json: () => Promise.resolve({ valid: true }), + }); + + const client = new SimFaceAPIClient({ + projectId: mockConfig.projectId, + apiKey: mockConfig.apiKey, + }); + await client.validateAPIKey(); + + expect(fetch).toHaveBeenCalledWith( + `${DEFAULT_API_URL}/api/v1/auth/validate`, + expect.anything(), + ); + }); + + it('should treat a blank apiUrl as missing and use the hosted backend', async () => { + global.fetch = vi.fn().mockResolvedValue({ + ok: true, + json: () => Promise.resolve({ valid: true }), + }); + + const client = new SimFaceAPIClient({ ...mockConfig, apiUrl: ' ' }); + await client.validateAPIKey(); + + expect(fetch).toHaveBeenCalledWith( + `${DEFAULT_API_URL}/api/v1/auth/validate`, + expect.anything(), + ); + }); + it('should strip trailing slash from apiUrl', async () => { global.fetch = vi.fn().mockResolvedValue({ ok: true, diff --git a/src/services/api-client.ts b/src/services/api-client.ts index be5c9a2..9ff3255 100644 --- a/src/services/api-client.ts +++ b/src/services/api-client.ts @@ -1,4 +1,5 @@ import type { SimFaceConfig, ValidateResult, EnrollResult, VerifyResult, APIError } from '../types/index.js'; +import { resolveApiUrl } from '../shared/api-url.js'; async function getAPIErrorMessage(response: Response, fallback: string): Promise { try { @@ -19,7 +20,7 @@ export class SimFaceAPIClient { private readonly apiKey: string; constructor(config: SimFaceConfig) { - this.apiUrl = config.apiUrl.replace(/\/$/, ''); + this.apiUrl = resolveApiUrl(config.apiUrl); this.projectId = config.projectId; this.apiKey = config.apiKey; } diff --git a/src/shared/api-url.ts b/src/shared/api-url.ts new file mode 100644 index 0000000..5e5c568 --- /dev/null +++ b/src/shared/api-url.ts @@ -0,0 +1,10 @@ +export const DEFAULT_SIMFACE_API_URL = 'https://simface-api-85584555549.europe-west1.run.app'; + +export function resolveApiUrl(apiUrl?: string): string { + if (typeof apiUrl !== 'string') { + return DEFAULT_SIMFACE_API_URL; + } + + const normalizedApiUrl = apiUrl.trim().replace(/\/$/, ''); + return normalizedApiUrl || DEFAULT_SIMFACE_API_URL; +} diff --git a/src/types/index.ts b/src/types/index.ts index 4a9ca1b..6b905a6 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,11 +1,11 @@ /** Configuration for the SimFace SDK. */ export interface SimFaceConfig { - /** Base URL of the SimFace API backend. */ - apiUrl: string; /** Unique identifier for the project. */ projectId: string; /** Short-lived API credential for authentication. Do not hardcode long-lived secrets into public client bundles. */ apiKey: string; + /** Optional base URL of the SimFace API backend. Defaults to the hosted SimFace backend. */ + apiUrl?: string; } /** Result of an enrollment operation. */