From c1a650cbda403fae844a5748bd2244a327655201 Mon Sep 17 00:00:00 2001 From: Tristram Date: Tue, 17 Mar 2026 09:42:28 +0000 Subject: [PATCH 1/2] Make `apiUrl` optional and default to the hosted backend - Added `resolveApiUrl` helper to handle default API URL resolution and normalization. - Updated `SimFaceConfig` to make `apiUrl` optional. - Refactored `SimFaceAPIClient` to use `resolveApiUrl`. - Enhanced tests to validate default API URL behavior and trailing slash handling. - Updated documentation, examples, and types to reflect the new `apiUrl` feature. --- README.md | 8 +++---- src/index.test.ts | 2 +- src/index.ts | 6 ++++-- src/services/api-client.test.ts | 37 ++++++++++++++++++++++++++++++++- src/services/api-client.ts | 3 ++- src/shared/api-url.ts | 10 +++++++++ src/types/index.ts | 4 ++-- 7 files changed, 58 insertions(+), 12 deletions(-) create mode 100644 src/shared/api-url.ts diff --git a/README.md b/README.md index 39b10b3..b5fe05a 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,6 @@ The release contains two builds: ```javascript const config = { - apiUrl: 'https://your-simface-api.run.app', projectId: 'your-project-id', apiKey: 'your-api-key', }; @@ -166,7 +165,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 | @@ -181,7 +180,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 | @@ -228,7 +227,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', }); @@ -287,9 +285,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 a5fcfab..8587710 100644 --- a/src/services/api-client.test.ts +++ b/src/services/api-client.test.ts @@ -1,10 +1,12 @@ import { describe, it, expect, vi } from 'vitest'; import { SimFaceAPIClient } from '../services/api-client.js'; +const DEFAULT_API_URL = 'https://simface-api-85584555549.europe-west1.run.app'; + const mockConfig = { - apiUrl: 'https://api.example.com', projectId: 'test-project', apiKey: 'test-key', + apiUrl: 'https://api.example.com', }; describe('SimFaceAPIClient', () => { @@ -117,6 +119,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 b9a4ed8..4a8e8ab 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'; export class SimFaceAPIClient { private readonly apiUrl: string; @@ -6,7 +7,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 88ad685..5425963 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; /** API key for authentication. */ apiKey: string; + /** Optional base URL of the SimFace API backend. Defaults to the hosted SimFace backend. */ + apiUrl?: string; } /** Result of an enrollment operation. */ From 6db9beaf8a1a40aa57e37010a44ef45aa5339f73 Mon Sep 17 00:00:00 2001 From: Tristram Norman Date: Tue, 17 Mar 2026 09:49:39 +0000 Subject: [PATCH 2/2] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/services/api-client.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/services/api-client.test.ts b/src/services/api-client.test.ts index b9cc0a3..2a000ed 100644 --- a/src/services/api-client.test.ts +++ b/src/services/api-client.test.ts @@ -1,7 +1,8 @@ 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 = 'https://simface-api-85584555549.europe-west1.run.app'; +const DEFAULT_API_URL = DEFAULT_SIMFACE_API_URL; const mockConfig = { projectId: 'test-project',