@simprints/simface-sdk 0.15.1 → 0.17.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +14 -5
- package/dist/index.d.ts +4 -2
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/services/api-client.js +17 -7
- package/dist/services/api-client.js.map +1 -1
- package/dist/shared/api-url.d.ts +2 -0
- package/dist/shared/api-url.js +9 -0
- package/dist/shared/api-url.js.map +1 -0
- package/dist/simface-sdk.js +1008 -1001
- package/dist/simface-sdk.umd.cjs +26 -26
- package/dist/types/index.d.ts +3 -3
- package/package.json +22 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Simprints
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -6,6 +6,8 @@ Works in: all modern browsers, WhatsApp in-app browser, and mobile WebViews.
|
|
|
6
6
|
|
|
7
7
|
This repository is the public frontend SDK and demo repo for SimFace. For frontend architecture and contributor setup, see [docs/architecture.md](docs/architecture.md) and [docs/development.md](docs/development.md).
|
|
8
8
|
|
|
9
|
+
For repository policies and contribution guidance, see [SECURITY.md](SECURITY.md), [CONTRIBUTING.md](CONTRIBUTING.md), and [LICENSE](LICENSE).
|
|
10
|
+
|
|
9
11
|
The backend API, infrastructure, and TensorFlow Lite runtime live in the separate private backend repository.
|
|
10
12
|
|
|
11
13
|
The capture flow is planned explicitly as: auto camera -> manual camera -> media picker. The primary API supports two UI modes:
|
|
@@ -50,12 +52,18 @@ The release contains two builds:
|
|
|
50
52
|
|
|
51
53
|
```javascript
|
|
52
54
|
const config = {
|
|
53
|
-
apiUrl: 'https://your-simface-api.run.app',
|
|
54
55
|
projectId: 'your-project-id',
|
|
55
56
|
apiKey: 'your-api-key',
|
|
56
57
|
};
|
|
57
58
|
```
|
|
58
59
|
|
|
60
|
+
### Security considerations
|
|
61
|
+
|
|
62
|
+
- Treat `apiKey` as a browser-visible credential. Do not hardcode long-lived secrets into shipped frontend bundles.
|
|
63
|
+
- Prefer issuing short-lived credentials or session-bound tokens from your own backend for each capture session.
|
|
64
|
+
- Keep `apiUrl` on HTTPS in production and make sure the backend only accepts trusted origins and authorized project IDs.
|
|
65
|
+
- The local demo does **not** persist project IDs or API keys to `localStorage`; it only remembers the API URL, presentation mode, and client ID between reloads.
|
|
66
|
+
|
|
59
67
|
### 3. Enroll a User
|
|
60
68
|
|
|
61
69
|
```javascript
|
|
@@ -166,7 +174,7 @@ Parameters:
|
|
|
166
174
|
|
|
167
175
|
| Parameter | Type | Description |
|
|
168
176
|
|-----------|------|-------------|
|
|
169
|
-
| `config` | `SimFaceConfig` | SDK configuration (`
|
|
177
|
+
| `config` | `SimFaceConfig` | SDK configuration (`projectId`, `apiKey`, optional `apiUrl`) |
|
|
170
178
|
| `clientId` | `string` | Unique identifier for the user |
|
|
171
179
|
| `workflowOptions` | `SimFaceWorkflowOptions` | Optional popup/embedded-agnostic capture behavior |
|
|
172
180
|
| `captureElement` | `SimFaceCaptureElement` | Optional embedded `simface-capture` element |
|
|
@@ -181,7 +189,7 @@ Parameters:
|
|
|
181
189
|
|
|
182
190
|
| Parameter | Type | Description |
|
|
183
191
|
|-----------|------|-------------|
|
|
184
|
-
| `config` | `SimFaceConfig` | SDK configuration (`
|
|
192
|
+
| `config` | `SimFaceConfig` | SDK configuration (`projectId`, `apiKey`, optional `apiUrl`) |
|
|
185
193
|
| `clientId` | `string` | Unique identifier for the user |
|
|
186
194
|
| `workflowOptions` | `SimFaceWorkflowOptions` | Optional popup/embedded-agnostic capture behavior |
|
|
187
195
|
| `captureElement` | `SimFaceCaptureElement` | Optional embedded `simface-capture` element |
|
|
@@ -228,7 +236,6 @@ Use the `simface-capture` Web Component directly when you want the host applicat
|
|
|
228
236
|
|
|
229
237
|
const captureEl = document.querySelector('simface-capture');
|
|
230
238
|
const client = new SimFaceAPIClient({
|
|
231
|
-
apiUrl: 'https://your-simface-api.run.app',
|
|
232
239
|
projectId: 'your-project-id',
|
|
233
240
|
apiKey: 'your-api-key',
|
|
234
241
|
});
|
|
@@ -287,9 +294,9 @@ This is more flexible, but it also means the host owns more of the workflow.
|
|
|
287
294
|
|
|
288
295
|
```typescript
|
|
289
296
|
interface SimFaceConfig {
|
|
290
|
-
apiUrl: string;
|
|
291
297
|
projectId: string;
|
|
292
298
|
apiKey: string;
|
|
299
|
+
apiUrl?: string; // Defaults to the hosted SimFace backend
|
|
293
300
|
}
|
|
294
301
|
```
|
|
295
302
|
|
|
@@ -378,3 +385,5 @@ npm run dev
|
|
|
378
385
|
```
|
|
379
386
|
|
|
380
387
|
The demo runs at `http://localhost:4173` and consumes the built SDK artifact from `dist/`. To enable HTTPS (required for camera access from other devices on the local network), set `DEMO_USE_HTTPS=true` before starting the demo.
|
|
388
|
+
|
|
389
|
+
The published demo starts with the hosted demo backend URL by default so GitHub Pages users can try the flow immediately. Override the API URL if you want to point it at a local or alternate backend.
|
package/dist/index.d.ts
CHANGED
|
@@ -4,11 +4,13 @@
|
|
|
4
4
|
* Usage:
|
|
5
5
|
* import { enroll, verify } from '@simprints/simface-sdk';
|
|
6
6
|
*
|
|
7
|
+
* const config = { projectId: '...', apiKey: '...' };
|
|
8
|
+
*
|
|
7
9
|
* // Enroll a new user
|
|
8
|
-
* const result = await enroll(
|
|
10
|
+
* const result = await enroll(config, 'user-123');
|
|
9
11
|
*
|
|
10
12
|
* // Verify an existing user
|
|
11
|
-
* const result = await verify(
|
|
13
|
+
* const result = await verify(config, 'user-123');
|
|
12
14
|
*/
|
|
13
15
|
import type { SimFaceCaptureElement, SimFaceWorkflowOptions, SimFaceConfig, EnrollResult, VerifyResult } from './types/index.js';
|
|
14
16
|
export type { CapturePreference, SimFaceCaptureElement, SimFaceWorkflowOptions, SimFaceConfig, EnrollResult, VerifyResult, FaceQualityResult, ValidateResult, } from './types/index.js';
|
package/dist/index.js
CHANGED
|
@@ -4,11 +4,13 @@
|
|
|
4
4
|
* Usage:
|
|
5
5
|
* import { enroll, verify } from '@simprints/simface-sdk';
|
|
6
6
|
*
|
|
7
|
+
* const config = { projectId: '...', apiKey: '...' };
|
|
8
|
+
*
|
|
7
9
|
* // Enroll a new user
|
|
8
|
-
* const result = await enroll(
|
|
10
|
+
* const result = await enroll(config, 'user-123');
|
|
9
11
|
*
|
|
10
12
|
* // Verify an existing user
|
|
11
|
-
* const result = await verify(
|
|
13
|
+
* const result = await verify(config, 'user-123');
|
|
12
14
|
*/
|
|
13
15
|
import { SimFaceAPIClient } from './services/api-client.js';
|
|
14
16
|
import { captureFromCamera } from './services/camera.js';
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AASH,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAazD,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EAAE,iBAAiB,EAAE,yBAAyB,EAAE,MAAM,8BAA8B,CAAC;AAC5F,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrF,OAAO,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAEjE;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,MAAM,CAC1B,MAAqB,EACrB,QAAgB,EAChB,eAAwC,EACxC,cAAsC;IAEtC,MAAM,MAAM,GAAG,IAAI,gBAAgB,CAAC,MAAM,CAAC,CAAC;IAE5C,yBAAyB;IACzB,MAAM,MAAM,CAAC,cAAc,EAAE,CAAC;IAE9B,qBAAqB;IACrB,MAAM,IAAI,GAAG,MAAM,uBAAuB,CAAC,eAAe,EAAE,cAAc,CAAC,CAAC;IAC5E,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,2BAA2B,EAAE,CAAC;IAC5E,CAAC;IAED,kBAAkB;IAClB,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IAEnD,+CAA+C;IAC/C,IAAI,MAAM,CAAC,eAAe,EAAE,CAAC;QAC3B,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,MAAM,CAC1B,MAAqB,EACrB,QAAgB,EAChB,eAAwC,EACxC,cAAsC;IAEtC,MAAM,MAAM,GAAG,IAAI,gBAAgB,CAAC,MAAM,CAAC,CAAC;IAE5C,yBAAyB;IACzB,MAAM,MAAM,CAAC,cAAc,EAAE,CAAC;IAE9B,qBAAqB;IACrB,MAAM,IAAI,GAAG,MAAM,uBAAuB,CAAC,eAAe,EAAE,cAAc,CAAC,CAAC;IAC5E,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,OAAO,EAAE,2BAA2B,EAAE,CAAC;IACxF,CAAC;IAED,OAAO,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;AACvC,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,uBAAuB,CACpC,eAAwC,EACxC,cAAsC;IAEtC,OAAO,iBAAiB,CAAC,eAAe,EAAE,cAAc,CAAC,CAAC;AAC5D,CAAC"}
|
|
@@ -1,6 +1,19 @@
|
|
|
1
|
+
import { resolveApiUrl } from '../shared/api-url.js';
|
|
2
|
+
async function getAPIErrorMessage(response, fallback) {
|
|
3
|
+
try {
|
|
4
|
+
const err = await response.json();
|
|
5
|
+
if (typeof err.error === 'string' && err.error.trim()) {
|
|
6
|
+
return err.error.trim();
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
// Ignore malformed or empty error payloads and fall back to a stable message.
|
|
11
|
+
}
|
|
12
|
+
return `${fallback} (HTTP ${response.status})`;
|
|
13
|
+
}
|
|
1
14
|
export class SimFaceAPIClient {
|
|
2
15
|
constructor(config) {
|
|
3
|
-
this.apiUrl = config.apiUrl
|
|
16
|
+
this.apiUrl = resolveApiUrl(config.apiUrl);
|
|
4
17
|
this.projectId = config.projectId;
|
|
5
18
|
this.apiKey = config.apiKey;
|
|
6
19
|
}
|
|
@@ -14,8 +27,7 @@ export class SimFaceAPIClient {
|
|
|
14
27
|
}),
|
|
15
28
|
});
|
|
16
29
|
if (!response.ok) {
|
|
17
|
-
|
|
18
|
-
throw new Error(err.error || 'API key validation failed');
|
|
30
|
+
throw new Error(await getAPIErrorMessage(response, 'API key validation failed'));
|
|
19
31
|
}
|
|
20
32
|
return response.json();
|
|
21
33
|
}
|
|
@@ -33,8 +45,7 @@ export class SimFaceAPIClient {
|
|
|
33
45
|
return { success: false, clientId, alreadyEnrolled: true, message: 'User already enrolled' };
|
|
34
46
|
}
|
|
35
47
|
if (!response.ok) {
|
|
36
|
-
|
|
37
|
-
throw new Error(err.error || 'Enrollment failed');
|
|
48
|
+
throw new Error(await getAPIErrorMessage(response, 'Enrollment failed'));
|
|
38
49
|
}
|
|
39
50
|
return response.json();
|
|
40
51
|
}
|
|
@@ -52,8 +63,7 @@ export class SimFaceAPIClient {
|
|
|
52
63
|
return { match: false, score: 0, threshold: 0, notEnrolled: true, message: 'User not enrolled' };
|
|
53
64
|
}
|
|
54
65
|
if (!response.ok) {
|
|
55
|
-
|
|
56
|
-
throw new Error(err.error || 'Verification failed');
|
|
66
|
+
throw new Error(await getAPIErrorMessage(response, 'Verification failed'));
|
|
57
67
|
}
|
|
58
68
|
return response.json();
|
|
59
69
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-client.js","sourceRoot":"","sources":["../../src/services/api-client.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"api-client.js","sourceRoot":"","sources":["../../src/services/api-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export const DEFAULT_SIMFACE_API_URL = 'https://simface-api-85584555549.europe-west1.run.app';
|
|
2
|
+
export function resolveApiUrl(apiUrl) {
|
|
3
|
+
if (typeof apiUrl !== 'string') {
|
|
4
|
+
return DEFAULT_SIMFACE_API_URL;
|
|
5
|
+
}
|
|
6
|
+
const normalizedApiUrl = apiUrl.trim().replace(/\/$/, '');
|
|
7
|
+
return normalizedApiUrl || DEFAULT_SIMFACE_API_URL;
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=api-url.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-url.js","sourceRoot":"","sources":["../../src/shared/api-url.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,uBAAuB,GAAG,sDAAsD,CAAC;AAE9F,MAAM,UAAU,aAAa,CAAC,MAAe;IAC3C,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;QAC/B,OAAO,uBAAuB,CAAC;IACjC,CAAC;IAED,MAAM,gBAAgB,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IAC1D,OAAO,gBAAgB,IAAI,uBAAuB,CAAC;AACrD,CAAC"}
|