@authenta/core 1.0.0
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/README.md +324 -0
- package/dist/client.d.ts +62 -0
- package/dist/client.js +234 -0
- package/dist/errors.d.ts +24 -0
- package/dist/errors.js +55 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +26 -0
- package/dist/types/index.d.ts +85 -0
- package/dist/types/index.js +2 -0
- package/dist/utils/helpers.d.ts +4 -0
- package/dist/utils/helpers.js +53 -0
- package/package.json +29 -0
package/README.md
ADDED
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
# @authenta/core
|
|
2
|
+
|
|
3
|
+
Pure TypeScript API client for the [Authenta](https://authenta.ai) eKYC platform. Works in **Node.js** and **React Native** — no native modules or UI dependencies.
|
|
4
|
+
|
|
5
|
+
Use this package directly if you want headless control over uploads, polling, and result retrieval. For a ready-made camera capture UI, use [`@authenta/react-native`](https://www.npmjs.com/package/@authenta/react-native).
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Table of Contents
|
|
10
|
+
|
|
11
|
+
- [Installation](#installation)
|
|
12
|
+
- [Quick Start](#quick-start)
|
|
13
|
+
- [AuthentaClient](#authentaclient)
|
|
14
|
+
- [Configuration](#configuration)
|
|
15
|
+
- [faceIntelligence()](#faceintelligence)
|
|
16
|
+
- [RunOptions](#runoptions)
|
|
17
|
+
- [Low-level API](#low-level-api)
|
|
18
|
+
- [Models](#models)
|
|
19
|
+
- [Error Handling](#error-handling)
|
|
20
|
+
- [TypeScript Types](#typescript-types)
|
|
21
|
+
- [Contributing](#contributing)
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install @authenta/core
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
No peer dependencies. Works out of the box in Node.js >= 16 and React Native >= 0.72.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Quick Start
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
import { AuthentaClient } from '@authenta/core';
|
|
39
|
+
|
|
40
|
+
const client = new AuthentaClient({
|
|
41
|
+
clientId: 'YOUR_CLIENT_ID',
|
|
42
|
+
clientSecret: 'YOUR_CLIENT_SECRET',
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const result = await client.faceIntelligence('file:///path/to/selfie.jpg', 'FI-1', {
|
|
46
|
+
livenessCheck: true,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
console.log(result.result?.isLiveness); // true | false
|
|
50
|
+
console.log(result.status); // "PROCESSED"
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## AuthentaClient
|
|
56
|
+
|
|
57
|
+
### Configuration
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
const client = new AuthentaClient({
|
|
61
|
+
clientId: 'YOUR_CLIENT_ID', // required
|
|
62
|
+
clientSecret: 'YOUR_CLIENT_SECRET', // required
|
|
63
|
+
baseUrl: 'https://platform.authenta.ai', // optional — default shown
|
|
64
|
+
});
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
| Option | Type | Required | Description |
|
|
68
|
+
|---|---|---|---|
|
|
69
|
+
| `clientId` | `string` | Yes | Your Authenta client ID |
|
|
70
|
+
| `clientSecret` | `string` | Yes | Your Authenta client secret |
|
|
71
|
+
| `baseUrl` | `string` | No | API base URL (default: `https://platform.authenta.ai`) |
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
### faceIntelligence()
|
|
76
|
+
|
|
77
|
+
The primary high-level method. Uploads the file, polls until processing is complete, fetches the detection result, and returns a `ProcessedMedia` object.
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
const result = await client.faceIntelligence(uri, modelType, options);
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**Parameters**
|
|
84
|
+
|
|
85
|
+
| Parameter | Type | Description |
|
|
86
|
+
|---|---|---|
|
|
87
|
+
| `uri` | `string` | `file://` URI of the photo or video to analyse |
|
|
88
|
+
| `modelType` | `ModelType` | Model to run (e.g. `'FI-1'`) |
|
|
89
|
+
| `options` | `RunOptions` | Detection options — see [RunOptions](#runoptions) |
|
|
90
|
+
|
|
91
|
+
**Examples**
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
// Liveness check — photo
|
|
95
|
+
const result = await client.faceIntelligence('file:///path/to/selfie.jpg', 'FI-1', {
|
|
96
|
+
livenessCheck: true,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// Faceswap check — video
|
|
100
|
+
const result = await client.faceIntelligence('file:///path/to/clip.mp4', 'FI-1', {
|
|
101
|
+
faceswapCheck: true,
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// Liveness + faceswap — video
|
|
105
|
+
const result = await client.faceIntelligence('file:///path/to/clip.mp4', 'FI-1', {
|
|
106
|
+
livenessCheck: true,
|
|
107
|
+
faceswapCheck: true,
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// Face similarity — photo + reference
|
|
111
|
+
const result = await client.faceIntelligence('file:///path/to/selfie.jpg', 'FI-1', {
|
|
112
|
+
faceSimilarityCheck: true,
|
|
113
|
+
referenceImage: 'file:///path/to/id-photo.jpg',
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// Liveness + face similarity — photo
|
|
117
|
+
const result = await client.faceIntelligence('file:///path/to/selfie.jpg', 'FI-1', {
|
|
118
|
+
livenessCheck: true,
|
|
119
|
+
faceSimilarityCheck: true,
|
|
120
|
+
referenceImage: 'file:///path/to/id-photo.jpg',
|
|
121
|
+
});
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
**Returns** `Promise<ProcessedMedia>`
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
{
|
|
128
|
+
mid: string; // unique media ID
|
|
129
|
+
name: string;
|
|
130
|
+
status: 'PROCESSED'; // always PROCESSED on success
|
|
131
|
+
modelType: string; // e.g. "FI-1"
|
|
132
|
+
contentType: string; // MIME type of the uploaded file
|
|
133
|
+
size: number; // file size in bytes
|
|
134
|
+
createdAt: string; // ISO 8601
|
|
135
|
+
srcURL?: string;
|
|
136
|
+
resultURL?: string;
|
|
137
|
+
result?: {
|
|
138
|
+
resultType: string;
|
|
139
|
+
isLiveness?: boolean | string; // liveness check result
|
|
140
|
+
isDeepFake?: boolean | string; // faceswap / deepfake result
|
|
141
|
+
isSimilar?: boolean | string; // face similarity result
|
|
142
|
+
similarityScore?: number | string; // 0–100
|
|
143
|
+
[key: string]: any;
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
### RunOptions
|
|
151
|
+
|
|
152
|
+
| Option | Type | Default | Description |
|
|
153
|
+
|---|---|---|---|
|
|
154
|
+
| `livenessCheck` | `boolean` | `false` | Run liveness check (FI-1) |
|
|
155
|
+
| `faceswapCheck` | `boolean` | `false` | Run faceswap / deepfake check (FI-1, video required) |
|
|
156
|
+
| `faceSimilarityCheck` | `boolean` | `false` | Run face similarity check (FI-1, photo + reference required) |
|
|
157
|
+
| `referenceImage` | `string` | — | `file://` URI of reference face image (required when `faceSimilarityCheck: true`) |
|
|
158
|
+
| `isSingleFace` | `boolean` | `true` | Hint that only one face is present |
|
|
159
|
+
| `autoPolling` | `boolean` | `true` | Wait for result before returning. Set `false` to return immediately after upload |
|
|
160
|
+
| `interval` | `number` | `5000` | Polling interval in milliseconds |
|
|
161
|
+
| `timeout` | `number` | `600000` | Max polling duration in milliseconds (10 min) |
|
|
162
|
+
|
|
163
|
+
**Check compatibility**
|
|
164
|
+
|
|
165
|
+
| Check | Input required | Can combine with |
|
|
166
|
+
|---|---|---|
|
|
167
|
+
| `livenessCheck` | Photo **or** video | `faceSimilarityCheck` |
|
|
168
|
+
| `faceswapCheck` | Video (max 10 s) | `livenessCheck` |
|
|
169
|
+
| `faceSimilarityCheck` | Photo + `referenceImage` | `livenessCheck` |
|
|
170
|
+
| `faceswapCheck` + `faceSimilarityCheck` | — | **Not allowed** |
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
### Low-level API
|
|
175
|
+
|
|
176
|
+
Call each step individually for full control:
|
|
177
|
+
|
|
178
|
+
```ts
|
|
179
|
+
// 1. Upload — creates a media record and uploads the file to S3
|
|
180
|
+
const media = await client.upload('file:///path/to/selfie.jpg', 'FI-1', {
|
|
181
|
+
livenessCheck: true,
|
|
182
|
+
});
|
|
183
|
+
console.log(media.mid); // "abc-123"
|
|
184
|
+
|
|
185
|
+
// 2. Poll until processing completes
|
|
186
|
+
const processed = await client.waitForMedia(media.mid, {
|
|
187
|
+
interval: 3000, // poll every 3 s
|
|
188
|
+
timeout: 120000, // give up after 2 min
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
// 3. Fetch the detection result from resultURL
|
|
192
|
+
const result = await client.getResult(processed);
|
|
193
|
+
console.log(result.isLiveness, result.isDeepFake);
|
|
194
|
+
|
|
195
|
+
// CRUD helpers
|
|
196
|
+
const record = await client.getMedia(mid);
|
|
197
|
+
const list = await client.listMedia({ page: 1, pageSize: 20 });
|
|
198
|
+
await client.deleteMedia(mid);
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## Models
|
|
204
|
+
|
|
205
|
+
| Model ID | Description | Input |
|
|
206
|
+
|---|---|---|
|
|
207
|
+
| `FI-1` | Face Intelligence — liveness, faceswap, face similarity | Photo or video |
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## Error Handling
|
|
212
|
+
|
|
213
|
+
All errors extend `AuthentaError`. Import and catch specifically:
|
|
214
|
+
|
|
215
|
+
```ts
|
|
216
|
+
import {
|
|
217
|
+
AuthentaError,
|
|
218
|
+
AuthenticationError,
|
|
219
|
+
AuthorizationError,
|
|
220
|
+
QuotaExceededError,
|
|
221
|
+
InsufficientCreditsError,
|
|
222
|
+
ValidationError,
|
|
223
|
+
ServerError,
|
|
224
|
+
} from '@authenta/core';
|
|
225
|
+
|
|
226
|
+
try {
|
|
227
|
+
const result = await client.faceIntelligence(uri, 'FI-1', { livenessCheck: true });
|
|
228
|
+
} catch (err) {
|
|
229
|
+
if (err instanceof AuthenticationError) {
|
|
230
|
+
// Invalid clientId / clientSecret — check your credentials
|
|
231
|
+
} else if (err instanceof AuthorizationError) {
|
|
232
|
+
// Account lacks permission for this operation
|
|
233
|
+
} else if (err instanceof QuotaExceededError) {
|
|
234
|
+
// Monthly quota exceeded
|
|
235
|
+
} else if (err instanceof InsufficientCreditsError) {
|
|
236
|
+
// No remaining credits
|
|
237
|
+
} else if (err instanceof ValidationError) {
|
|
238
|
+
// Bad input — see err.message for details
|
|
239
|
+
console.error(err.message, err.code, err.statusCode);
|
|
240
|
+
} else if (err instanceof ServerError) {
|
|
241
|
+
// Platform error — safe to retry
|
|
242
|
+
} else if (err instanceof AuthentaError) {
|
|
243
|
+
// Base class catch-all
|
|
244
|
+
console.error(err.message, err.code, err.statusCode, err.details);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
**Error properties**
|
|
250
|
+
|
|
251
|
+
| Property | Type | Description |
|
|
252
|
+
|---|---|---|
|
|
253
|
+
| `message` | `string` | Human-readable description |
|
|
254
|
+
| `code` | `string?` | API error code (e.g. `IAM001`) |
|
|
255
|
+
| `statusCode` | `number?` | HTTP status code |
|
|
256
|
+
| `details` | `object?` | Raw API response body |
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
## TypeScript Types
|
|
261
|
+
|
|
262
|
+
All public types are exported from the package entry point:
|
|
263
|
+
|
|
264
|
+
```ts
|
|
265
|
+
import type {
|
|
266
|
+
AuthentaClientConfig,
|
|
267
|
+
ModelType,
|
|
268
|
+
MediaStatus,
|
|
269
|
+
FileInfo,
|
|
270
|
+
FIOptions,
|
|
271
|
+
RunOptions,
|
|
272
|
+
PollingOptions,
|
|
273
|
+
CreateMediaResponse,
|
|
274
|
+
MediaRecord,
|
|
275
|
+
ListMediaResponse,
|
|
276
|
+
DetectionResult,
|
|
277
|
+
ProcessedMedia,
|
|
278
|
+
} from '@authenta/core';
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
---
|
|
282
|
+
|
|
283
|
+
## Contributing
|
|
284
|
+
|
|
285
|
+
### Setup
|
|
286
|
+
|
|
287
|
+
```bash
|
|
288
|
+
git clone https://github.com/phospheneai/authenta-reactnative-sdk.git
|
|
289
|
+
cd authenta-reactnative-sdk
|
|
290
|
+
npm install
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### Build
|
|
294
|
+
|
|
295
|
+
```bash
|
|
296
|
+
npm run build --workspace=packages/core
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### Test
|
|
300
|
+
|
|
301
|
+
```bash
|
|
302
|
+
npm test --workspace=packages/core
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### Guidelines
|
|
306
|
+
|
|
307
|
+
- **No native modules** — this package must work in plain Node.js. Do not import React, React Native, or any native module.
|
|
308
|
+
- **No Node.js built-ins at the top level** — Metro (React Native bundler) cannot resolve `fs`, `path`, etc. Use the `_require = require` alias trick only inside runtime guards (`typeof XMLHttpRequest === 'undefined'`).
|
|
309
|
+
- **Typed errors** — all thrown values must extend `AuthentaError`. New error types go in `errors.ts` and must be exported from `index.ts`.
|
|
310
|
+
- **Types in one place** — all interfaces belong in `src/types/index.ts`.
|
|
311
|
+
- **No breaking changes** — `AuthentaClient` and all exported types are the stable public surface.
|
|
312
|
+
|
|
313
|
+
### Publish
|
|
314
|
+
|
|
315
|
+
```bash
|
|
316
|
+
# Bump version in packages/core/package.json, then:
|
|
317
|
+
npm publish --workspace=packages/core --access public
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
---
|
|
321
|
+
|
|
322
|
+
## License
|
|
323
|
+
|
|
324
|
+
MIT © Authenta
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { ModelType, FIOptions, PollingOptions, RunOptions, CreateMediaResponse, MediaRecord, ListMediaParams, ListMediaResponse, DetectionResult, ProcessedMedia } from './types';
|
|
2
|
+
export interface AuthentaClientConfig {
|
|
3
|
+
baseUrl?: string;
|
|
4
|
+
clientId: string;
|
|
5
|
+
clientSecret: string;
|
|
6
|
+
}
|
|
7
|
+
export declare class AuthentaClient {
|
|
8
|
+
private readonly baseUrl;
|
|
9
|
+
private readonly clientId;
|
|
10
|
+
private readonly clientSecret;
|
|
11
|
+
constructor({ baseUrl, clientId, clientSecret, }: AuthentaClientConfig);
|
|
12
|
+
private get authHeaders();
|
|
13
|
+
private request;
|
|
14
|
+
private throwApiError;
|
|
15
|
+
/** Fetch a local URI once — derives name, type, size, and blob for upload.
|
|
16
|
+
* In React Native uses XMLHttpRequest (fetch('file://...') fails on Android).
|
|
17
|
+
* In Node.js (tests) uses fs since XMLHttpRequest is not available. */
|
|
18
|
+
private resolveUri;
|
|
19
|
+
private uploadToS3;
|
|
20
|
+
createMedia(params: {
|
|
21
|
+
name: string;
|
|
22
|
+
contentType: string;
|
|
23
|
+
size: number;
|
|
24
|
+
modelType: ModelType;
|
|
25
|
+
metadata?: Record<string, any>;
|
|
26
|
+
}): Promise<CreateMediaResponse>;
|
|
27
|
+
getMedia(mid: string): Promise<MediaRecord>;
|
|
28
|
+
listMedia(params?: ListMediaParams): Promise<ListMediaResponse>;
|
|
29
|
+
deleteMedia(mid: string): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* Two-step upload: derives file info from the URI, creates a media record,
|
|
32
|
+
* then PUTs the file blob to S3. Works for all model types.
|
|
33
|
+
* Pass `fiOptions` only when modelType is "FI-1".
|
|
34
|
+
*/
|
|
35
|
+
upload(uri: string, modelType: ModelType, fiOptions?: FIOptions): Promise<CreateMediaResponse>;
|
|
36
|
+
waitForMedia(mid: string, { interval, timeout }?: PollingOptions): Promise<MediaRecord>;
|
|
37
|
+
getResult(media: MediaRecord): Promise<DetectionResult>;
|
|
38
|
+
/**
|
|
39
|
+
* Upload a file URI and process it with the given model.
|
|
40
|
+
* The SDK automatically derives the file name, type, and size from the URI.
|
|
41
|
+
*
|
|
42
|
+
* - For all models: uploads, polls until complete, and fetches the result.
|
|
43
|
+
* - For FI-1: pass any face-check flags; unset flags default to false.
|
|
44
|
+
* - Set `autoPolling: false` to return immediately after upload.
|
|
45
|
+
*
|
|
46
|
+
* @example DF-1 / AC-1
|
|
47
|
+
* const result = await client.faceIntelligence('file:///path/to/video.mp4', 'DF-1');
|
|
48
|
+
*
|
|
49
|
+
* @example FI-1 liveness
|
|
50
|
+
* const result = await client.faceIntelligence('file:///path/to/selfie.jpg', 'FI-1', { livenessCheck: true });
|
|
51
|
+
*
|
|
52
|
+
* @example FI-1 faceswap (video only)
|
|
53
|
+
* const result = await client.faceIntelligence('file:///path/to/video.mp4', 'FI-1', { faceswapCheck: true });
|
|
54
|
+
*
|
|
55
|
+
* @example FI-1 similarity
|
|
56
|
+
* const result = await client.faceIntelligence('file:///path/to/selfie.jpg', 'FI-1', {
|
|
57
|
+
* faceSimilarityCheck: true,
|
|
58
|
+
* referenceImage: 'file:///path/to/id-photo.jpg',
|
|
59
|
+
* });
|
|
60
|
+
*/
|
|
61
|
+
faceIntelligence(uri: string, modelType: ModelType, { autoPolling, interval, timeout, isSingleFace, faceswapCheck, livenessCheck, faceSimilarityCheck, referenceImage, }?: RunOptions): Promise<ProcessedMedia>;
|
|
62
|
+
}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AuthentaClient = void 0;
|
|
4
|
+
const errors_1 = require("./errors");
|
|
5
|
+
const helpers_1 = require("./utils/helpers");
|
|
6
|
+
const TERMINAL_STATUSES = new Set(['PROCESSED', 'FAILED', 'ERROR']);
|
|
7
|
+
class AuthentaClient {
|
|
8
|
+
constructor({ baseUrl = 'https://platform.authenta.ai', clientId, clientSecret, }) {
|
|
9
|
+
this.baseUrl = baseUrl.replace(/\/$/, '');
|
|
10
|
+
this.clientId = clientId;
|
|
11
|
+
this.clientSecret = clientSecret;
|
|
12
|
+
}
|
|
13
|
+
// ─── Private helpers ───────────────────────────────────────────────────────
|
|
14
|
+
get authHeaders() {
|
|
15
|
+
return {
|
|
16
|
+
'x-client-id': this.clientId,
|
|
17
|
+
'x-client-secret': this.clientSecret,
|
|
18
|
+
'Content-Type': 'application/json',
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
async request(method, path, body, queryParams) {
|
|
22
|
+
let url = `${this.baseUrl}${path}`;
|
|
23
|
+
if (queryParams) {
|
|
24
|
+
const qs = Object.entries(queryParams)
|
|
25
|
+
.filter(([, v]) => v !== undefined && v !== null)
|
|
26
|
+
.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`)
|
|
27
|
+
.join('&');
|
|
28
|
+
if (qs)
|
|
29
|
+
url += `?${qs}`;
|
|
30
|
+
}
|
|
31
|
+
const response = await fetch(url, {
|
|
32
|
+
method,
|
|
33
|
+
headers: this.authHeaders,
|
|
34
|
+
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
35
|
+
});
|
|
36
|
+
if (!response.ok) {
|
|
37
|
+
await this.throwApiError(response);
|
|
38
|
+
}
|
|
39
|
+
const text = await response.text();
|
|
40
|
+
if (!text.trim())
|
|
41
|
+
return {};
|
|
42
|
+
try {
|
|
43
|
+
return JSON.parse(text);
|
|
44
|
+
}
|
|
45
|
+
catch (_a) {
|
|
46
|
+
throw new errors_1.ValidationError('Expected JSON but received non-JSON response', undefined, response.status, { body: text.slice(0, 200) });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
async throwApiError(response) {
|
|
50
|
+
var _a, _b, _c;
|
|
51
|
+
const status = response.status;
|
|
52
|
+
let data;
|
|
53
|
+
try {
|
|
54
|
+
data = await response.json();
|
|
55
|
+
}
|
|
56
|
+
catch (_d) {
|
|
57
|
+
const text = await response.text().catch(() => '');
|
|
58
|
+
if (status >= 500)
|
|
59
|
+
throw new errors_1.ServerError(text || 'Server error', undefined, status);
|
|
60
|
+
throw new errors_1.ValidationError(text || 'Client error', undefined, status);
|
|
61
|
+
}
|
|
62
|
+
const code = (_a = data === null || data === void 0 ? void 0 : data.code) !== null && _a !== void 0 ? _a : 'unknown';
|
|
63
|
+
const message = (_c = (_b = data === null || data === void 0 ? void 0 : data.message) !== null && _b !== void 0 ? _b : response.statusText) !== null && _c !== void 0 ? _c : 'Unknown error';
|
|
64
|
+
if (code === 'IAM001')
|
|
65
|
+
throw new errors_1.AuthenticationError(message, status, data);
|
|
66
|
+
if (code === 'IAM002')
|
|
67
|
+
throw new errors_1.AuthorizationError(message, status, data);
|
|
68
|
+
if (code === 'AA001')
|
|
69
|
+
throw new errors_1.QuotaExceededError(message, status, data);
|
|
70
|
+
if (code === 'U007')
|
|
71
|
+
throw new errors_1.InsufficientCreditsError(message, status, data);
|
|
72
|
+
if (status >= 500)
|
|
73
|
+
throw new errors_1.ServerError(message, code, status, data);
|
|
74
|
+
if (status >= 400)
|
|
75
|
+
throw new errors_1.ValidationError(message, code, status, data);
|
|
76
|
+
throw new errors_1.AuthentaError(message, code, status, data);
|
|
77
|
+
}
|
|
78
|
+
/** Fetch a local URI once — derives name, type, size, and blob for upload.
|
|
79
|
+
* In React Native uses XMLHttpRequest (fetch('file://...') fails on Android).
|
|
80
|
+
* In Node.js (tests) uses fs since XMLHttpRequest is not available. */
|
|
81
|
+
resolveUri(uri) {
|
|
82
|
+
var _a, _b;
|
|
83
|
+
const name = (_b = (_a = uri.split('/').pop()) === null || _a === void 0 ? void 0 : _a.split('?')[0]) !== null && _b !== void 0 ? _b : 'file';
|
|
84
|
+
const type = (0, helpers_1.getMimeType)(name);
|
|
85
|
+
// Node.js environment — XMLHttpRequest does not exist
|
|
86
|
+
if (typeof XMLHttpRequest === 'undefined') {
|
|
87
|
+
// Use aliased require so Metro's static analyser does not try to bundle 'fs'
|
|
88
|
+
const _require = require;
|
|
89
|
+
const fs = _require('fs');
|
|
90
|
+
const filePath = uri.replace(/^file:\/\//, '');
|
|
91
|
+
const buffer = fs.readFileSync(filePath);
|
|
92
|
+
const blob = new Blob([buffer], { type });
|
|
93
|
+
return Promise.resolve({ name, type, size: buffer.byteLength, blob });
|
|
94
|
+
}
|
|
95
|
+
// React Native — use XHR
|
|
96
|
+
return new Promise((resolve, reject) => {
|
|
97
|
+
const xhr = new XMLHttpRequest();
|
|
98
|
+
xhr.responseType = 'blob';
|
|
99
|
+
xhr.onload = () => resolve({ name, type, size: xhr.response.size, blob: xhr.response });
|
|
100
|
+
xhr.onerror = () => reject(new errors_1.AuthentaError(`Could not read file at URI: ${uri}`));
|
|
101
|
+
xhr.open('GET', uri);
|
|
102
|
+
xhr.send();
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
async uploadToS3(uploadUrl, blob, contentType) {
|
|
106
|
+
const putResponse = await fetch(uploadUrl, {
|
|
107
|
+
method: 'PUT',
|
|
108
|
+
headers: { 'Content-Type': contentType },
|
|
109
|
+
body: blob,
|
|
110
|
+
});
|
|
111
|
+
if (!putResponse.ok) {
|
|
112
|
+
throw new errors_1.AuthentaError(`S3 upload failed: HTTP ${putResponse.status}`, undefined, putResponse.status);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
// ─── Core media CRUD ───────────────────────────────────────────────────────
|
|
116
|
+
async createMedia(params) {
|
|
117
|
+
return this.request('POST', '/api/media', params);
|
|
118
|
+
}
|
|
119
|
+
async getMedia(mid) {
|
|
120
|
+
return this.request('GET', `/api/media/${mid}`);
|
|
121
|
+
}
|
|
122
|
+
async listMedia(params) {
|
|
123
|
+
return this.request('GET', '/api/media', undefined, params);
|
|
124
|
+
}
|
|
125
|
+
async deleteMedia(mid) {
|
|
126
|
+
await this.request('DELETE', `/api/media/${mid}`);
|
|
127
|
+
}
|
|
128
|
+
// ─── Upload (common for all models) ───────────────────────────────────────
|
|
129
|
+
/**
|
|
130
|
+
* Two-step upload: derives file info from the URI, creates a media record,
|
|
131
|
+
* then PUTs the file blob to S3. Works for all model types.
|
|
132
|
+
* Pass `fiOptions` only when modelType is "FI-1".
|
|
133
|
+
*/
|
|
134
|
+
async upload(uri, modelType, fiOptions) {
|
|
135
|
+
const { name, type, size, blob } = await this.resolveUri(uri);
|
|
136
|
+
const payload = {
|
|
137
|
+
name,
|
|
138
|
+
contentType: type,
|
|
139
|
+
size,
|
|
140
|
+
modelType,
|
|
141
|
+
};
|
|
142
|
+
if (modelType.toUpperCase() === 'FI-1' && fiOptions) {
|
|
143
|
+
const { isSingleFace = true, faceswapCheck = false, livenessCheck = false, faceSimilarityCheck = false, } = fiOptions;
|
|
144
|
+
payload.metadata = { isSingleFace, faceswapCheck, livenessCheck, faceSimilarityCheck };
|
|
145
|
+
}
|
|
146
|
+
const media = await this.createMedia(payload);
|
|
147
|
+
await this.uploadToS3(media.uploadUrl, blob, type);
|
|
148
|
+
if (modelType.toUpperCase() === 'FI-1' && (fiOptions === null || fiOptions === void 0 ? void 0 : fiOptions.faceSimilarityCheck)) {
|
|
149
|
+
if (!fiOptions.referenceImage) {
|
|
150
|
+
throw new errors_1.ValidationError('referenceImage is required when faceSimilarityCheck is true');
|
|
151
|
+
}
|
|
152
|
+
if (!media.referenceUploadUrl) {
|
|
153
|
+
throw new errors_1.AuthentaError('No referenceUploadUrl returned from server');
|
|
154
|
+
}
|
|
155
|
+
const { blob: refBlob, type: refType } = await this.resolveUri(fiOptions.referenceImage);
|
|
156
|
+
await this.uploadToS3(media.referenceUploadUrl, refBlob, refType);
|
|
157
|
+
}
|
|
158
|
+
return media;
|
|
159
|
+
}
|
|
160
|
+
// ─── Polling ───────────────────────────────────────────────────────────────
|
|
161
|
+
async waitForMedia(mid, { interval = 5000, timeout = 600000 } = {}) {
|
|
162
|
+
const deadline = Date.now() + timeout;
|
|
163
|
+
while (true) {
|
|
164
|
+
const media = await this.getMedia(mid);
|
|
165
|
+
if (TERMINAL_STATUSES.has(media.status.toUpperCase()))
|
|
166
|
+
return media;
|
|
167
|
+
if (Date.now() >= deadline) {
|
|
168
|
+
throw new errors_1.AuthentaError(`Timed out waiting for media ${mid} — last status: ${media.status}`);
|
|
169
|
+
}
|
|
170
|
+
await new Promise(resolve => setTimeout(resolve, interval));
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
// ─── Result ────────────────────────────────────────────────────────────────
|
|
174
|
+
async getResult(media) {
|
|
175
|
+
if (!media.resultURL) {
|
|
176
|
+
throw new errors_1.ValidationError('media has no resultURL — ensure processing is complete (status=PROCESSED)');
|
|
177
|
+
}
|
|
178
|
+
const response = await fetch(media.resultURL);
|
|
179
|
+
if (!response.ok) {
|
|
180
|
+
throw new errors_1.AuthentaError(`Failed to fetch resultURL: HTTP ${response.status}`, undefined, response.status);
|
|
181
|
+
}
|
|
182
|
+
return response.json();
|
|
183
|
+
}
|
|
184
|
+
// ─── High-level: one function for all models ──────────────────────────────
|
|
185
|
+
/**
|
|
186
|
+
* Upload a file URI and process it with the given model.
|
|
187
|
+
* The SDK automatically derives the file name, type, and size from the URI.
|
|
188
|
+
*
|
|
189
|
+
* - For all models: uploads, polls until complete, and fetches the result.
|
|
190
|
+
* - For FI-1: pass any face-check flags; unset flags default to false.
|
|
191
|
+
* - Set `autoPolling: false` to return immediately after upload.
|
|
192
|
+
*
|
|
193
|
+
* @example DF-1 / AC-1
|
|
194
|
+
* const result = await client.faceIntelligence('file:///path/to/video.mp4', 'DF-1');
|
|
195
|
+
*
|
|
196
|
+
* @example FI-1 liveness
|
|
197
|
+
* const result = await client.faceIntelligence('file:///path/to/selfie.jpg', 'FI-1', { livenessCheck: true });
|
|
198
|
+
*
|
|
199
|
+
* @example FI-1 faceswap (video only)
|
|
200
|
+
* const result = await client.faceIntelligence('file:///path/to/video.mp4', 'FI-1', { faceswapCheck: true });
|
|
201
|
+
*
|
|
202
|
+
* @example FI-1 similarity
|
|
203
|
+
* const result = await client.faceIntelligence('file:///path/to/selfie.jpg', 'FI-1', {
|
|
204
|
+
* faceSimilarityCheck: true,
|
|
205
|
+
* referenceImage: 'file:///path/to/id-photo.jpg',
|
|
206
|
+
* });
|
|
207
|
+
*/
|
|
208
|
+
async faceIntelligence(uri, modelType, { autoPolling = true, interval, timeout, isSingleFace = true, faceswapCheck = false, livenessCheck = false, faceSimilarityCheck = false, referenceImage, } = {}) {
|
|
209
|
+
var _a;
|
|
210
|
+
const isFI = modelType.toUpperCase() === 'FI-1';
|
|
211
|
+
if (isFI) {
|
|
212
|
+
const type = (0, helpers_1.getMimeType)((_a = uri.split('/').pop()) !== null && _a !== void 0 ? _a : '');
|
|
213
|
+
if ((0, helpers_1.isImage)(type) && faceswapCheck) {
|
|
214
|
+
throw new errors_1.ValidationError('faceswapCheck cannot be true for image files');
|
|
215
|
+
}
|
|
216
|
+
if ((0, helpers_1.isVideo)(type) && faceSimilarityCheck) {
|
|
217
|
+
throw new errors_1.ValidationError('faceSimilarityCheck cannot be true for video files');
|
|
218
|
+
}
|
|
219
|
+
if (faceSimilarityCheck && !referenceImage) {
|
|
220
|
+
throw new errors_1.ValidationError('referenceImage is required when faceSimilarityCheck is true');
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
const fiOptions = isFI
|
|
224
|
+
? { isSingleFace, faceswapCheck, livenessCheck, faceSimilarityCheck, referenceImage }
|
|
225
|
+
: undefined;
|
|
226
|
+
const meta = await this.upload(uri, modelType, fiOptions);
|
|
227
|
+
if (!autoPolling)
|
|
228
|
+
return meta;
|
|
229
|
+
const media = await this.waitForMedia(meta.mid, { interval, timeout });
|
|
230
|
+
const result = media.resultURL ? await this.getResult(media) : undefined;
|
|
231
|
+
return Object.assign(Object.assign({}, media), { result });
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
exports.AuthentaClient = AuthentaClient;
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export declare class AuthentaError extends Error {
|
|
2
|
+
readonly code?: string | undefined;
|
|
3
|
+
readonly statusCode?: number | undefined;
|
|
4
|
+
readonly details?: Record<string, any> | undefined;
|
|
5
|
+
constructor(message: string, code?: string | undefined, statusCode?: number | undefined, details?: Record<string, any> | undefined);
|
|
6
|
+
}
|
|
7
|
+
export declare class AuthenticationError extends AuthentaError {
|
|
8
|
+
constructor(message: string, statusCode?: number, details?: Record<string, any>);
|
|
9
|
+
}
|
|
10
|
+
export declare class AuthorizationError extends AuthentaError {
|
|
11
|
+
constructor(message: string, statusCode?: number, details?: Record<string, any>);
|
|
12
|
+
}
|
|
13
|
+
export declare class QuotaExceededError extends AuthentaError {
|
|
14
|
+
constructor(message: string, statusCode?: number, details?: Record<string, any>);
|
|
15
|
+
}
|
|
16
|
+
export declare class InsufficientCreditsError extends AuthentaError {
|
|
17
|
+
constructor(message: string, statusCode?: number, details?: Record<string, any>);
|
|
18
|
+
}
|
|
19
|
+
export declare class ValidationError extends AuthentaError {
|
|
20
|
+
constructor(message: string, code?: string, statusCode?: number, details?: Record<string, any>);
|
|
21
|
+
}
|
|
22
|
+
export declare class ServerError extends AuthentaError {
|
|
23
|
+
constructor(message: string, code?: string, statusCode?: number, details?: Record<string, any>);
|
|
24
|
+
}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ServerError = exports.ValidationError = exports.InsufficientCreditsError = exports.QuotaExceededError = exports.AuthorizationError = exports.AuthenticationError = exports.AuthentaError = void 0;
|
|
4
|
+
class AuthentaError extends Error {
|
|
5
|
+
constructor(message, code, statusCode, details) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.code = code;
|
|
8
|
+
this.statusCode = statusCode;
|
|
9
|
+
this.details = details;
|
|
10
|
+
this.name = 'AuthentaError';
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
exports.AuthentaError = AuthentaError;
|
|
14
|
+
class AuthenticationError extends AuthentaError {
|
|
15
|
+
constructor(message, statusCode, details) {
|
|
16
|
+
super(message, 'IAM001', statusCode, details);
|
|
17
|
+
this.name = 'AuthenticationError';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
exports.AuthenticationError = AuthenticationError;
|
|
21
|
+
class AuthorizationError extends AuthentaError {
|
|
22
|
+
constructor(message, statusCode, details) {
|
|
23
|
+
super(message, 'IAM002', statusCode, details);
|
|
24
|
+
this.name = 'AuthorizationError';
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
exports.AuthorizationError = AuthorizationError;
|
|
28
|
+
class QuotaExceededError extends AuthentaError {
|
|
29
|
+
constructor(message, statusCode, details) {
|
|
30
|
+
super(message, 'AA001', statusCode, details);
|
|
31
|
+
this.name = 'QuotaExceededError';
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
exports.QuotaExceededError = QuotaExceededError;
|
|
35
|
+
class InsufficientCreditsError extends AuthentaError {
|
|
36
|
+
constructor(message, statusCode, details) {
|
|
37
|
+
super(message, 'U007', statusCode, details);
|
|
38
|
+
this.name = 'InsufficientCreditsError';
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
exports.InsufficientCreditsError = InsufficientCreditsError;
|
|
42
|
+
class ValidationError extends AuthentaError {
|
|
43
|
+
constructor(message, code, statusCode, details) {
|
|
44
|
+
super(message, code, statusCode, details);
|
|
45
|
+
this.name = 'ValidationError';
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
exports.ValidationError = ValidationError;
|
|
49
|
+
class ServerError extends AuthentaError {
|
|
50
|
+
constructor(message, code, statusCode, details) {
|
|
51
|
+
super(message, code, statusCode, details);
|
|
52
|
+
this.name = 'ServerError';
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
exports.ServerError = ServerError;
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.safeJsonParse = exports.isVideo = exports.isImage = exports.getMimeType = exports.AuthentaClient = void 0;
|
|
18
|
+
var client_1 = require("./client");
|
|
19
|
+
Object.defineProperty(exports, "AuthentaClient", { enumerable: true, get: function () { return client_1.AuthentaClient; } });
|
|
20
|
+
__exportStar(require("./types"), exports);
|
|
21
|
+
__exportStar(require("./errors"), exports);
|
|
22
|
+
var helpers_1 = require("./utils/helpers");
|
|
23
|
+
Object.defineProperty(exports, "getMimeType", { enumerable: true, get: function () { return helpers_1.getMimeType; } });
|
|
24
|
+
Object.defineProperty(exports, "isImage", { enumerable: true, get: function () { return helpers_1.isImage; } });
|
|
25
|
+
Object.defineProperty(exports, "isVideo", { enumerable: true, get: function () { return helpers_1.isVideo; } });
|
|
26
|
+
Object.defineProperty(exports, "safeJsonParse", { enumerable: true, get: function () { return helpers_1.safeJsonParse; } });
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
export type ModelType = 'DF-1' | 'AC-1' | 'FI-1' | (string & {});
|
|
2
|
+
export type MediaStatus = 'PENDING' | 'PROCESSING' | 'PROCESSED' | 'FAILED' | 'ERROR';
|
|
3
|
+
export interface FileInfo {
|
|
4
|
+
uri: string;
|
|
5
|
+
name: string;
|
|
6
|
+
type: string;
|
|
7
|
+
size: number;
|
|
8
|
+
}
|
|
9
|
+
export interface FIOptions {
|
|
10
|
+
isSingleFace?: boolean;
|
|
11
|
+
faceswapCheck?: boolean;
|
|
12
|
+
livenessCheck?: boolean;
|
|
13
|
+
faceSimilarityCheck?: boolean;
|
|
14
|
+
referenceImage?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface PollingOptions {
|
|
17
|
+
interval?: number;
|
|
18
|
+
timeout?: number;
|
|
19
|
+
}
|
|
20
|
+
export interface RunOptions extends FIOptions, PollingOptions {
|
|
21
|
+
autoPolling?: boolean;
|
|
22
|
+
}
|
|
23
|
+
export interface CreateMediaResponse {
|
|
24
|
+
mid: string;
|
|
25
|
+
name: string;
|
|
26
|
+
status: MediaStatus;
|
|
27
|
+
modelType: string;
|
|
28
|
+
contentType: string;
|
|
29
|
+
size: number;
|
|
30
|
+
createdAt: string;
|
|
31
|
+
uploadUrl: string;
|
|
32
|
+
referenceUploadUrl?: string;
|
|
33
|
+
}
|
|
34
|
+
export interface MediaRecord {
|
|
35
|
+
mid: string;
|
|
36
|
+
name: string;
|
|
37
|
+
status: MediaStatus;
|
|
38
|
+
modelType: string;
|
|
39
|
+
contentType: string;
|
|
40
|
+
size: number;
|
|
41
|
+
createdAt: string;
|
|
42
|
+
srcURL?: string;
|
|
43
|
+
resultURL?: string;
|
|
44
|
+
}
|
|
45
|
+
export interface ListMediaParams {
|
|
46
|
+
page?: number;
|
|
47
|
+
pageSize?: number;
|
|
48
|
+
[key: string]: any;
|
|
49
|
+
}
|
|
50
|
+
export interface ListMediaResponse {
|
|
51
|
+
items: MediaRecord[];
|
|
52
|
+
total?: number;
|
|
53
|
+
page?: number;
|
|
54
|
+
pageSize?: number;
|
|
55
|
+
}
|
|
56
|
+
export interface DetectionResult {
|
|
57
|
+
resultType?: string;
|
|
58
|
+
isDeepFake?: string | boolean;
|
|
59
|
+
RealConfidencePercent?: string | number;
|
|
60
|
+
isLiveness?: string | boolean;
|
|
61
|
+
isSimilar?: string | boolean;
|
|
62
|
+
similarityScore?: string | number;
|
|
63
|
+
identityPredictions?: IdentityPrediction[];
|
|
64
|
+
boundingBoxes?: BoundingBoxesMap;
|
|
65
|
+
[key: string]: any;
|
|
66
|
+
}
|
|
67
|
+
export interface IdentityPrediction {
|
|
68
|
+
identityId: number;
|
|
69
|
+
isDeepFake: boolean;
|
|
70
|
+
}
|
|
71
|
+
export type BoundingBoxCoords = [number, number, number, number];
|
|
72
|
+
export interface FrameBoundingBox {
|
|
73
|
+
[frameId: string]: BoundingBoxCoords;
|
|
74
|
+
}
|
|
75
|
+
export interface IdentityBoundingBox {
|
|
76
|
+
boundingBox: FrameBoundingBox;
|
|
77
|
+
class: 'real' | 'fake';
|
|
78
|
+
confidence: number;
|
|
79
|
+
}
|
|
80
|
+
export interface BoundingBoxesMap {
|
|
81
|
+
[identityId: string]: IdentityBoundingBox;
|
|
82
|
+
}
|
|
83
|
+
export interface ProcessedMedia extends MediaRecord {
|
|
84
|
+
result?: DetectionResult;
|
|
85
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.safeJsonParse = exports.isVideo = exports.isImage = exports.getMimeType = void 0;
|
|
4
|
+
const getMimeType = (path) => {
|
|
5
|
+
const mimeTypes = {
|
|
6
|
+
// Images
|
|
7
|
+
'.jpg': 'image/jpeg',
|
|
8
|
+
'.jpeg': 'image/jpeg',
|
|
9
|
+
'.png': 'image/png',
|
|
10
|
+
'.gif': 'image/gif',
|
|
11
|
+
'.webp': 'image/webp',
|
|
12
|
+
'.bmp': 'image/bmp',
|
|
13
|
+
'.svg': 'image/svg+xml',
|
|
14
|
+
// Videos
|
|
15
|
+
'.mp4': 'video/mp4',
|
|
16
|
+
'.mov': 'video/quicktime',
|
|
17
|
+
'.avi': 'video/x-msvideo',
|
|
18
|
+
'.mkv': 'video/x-matroska',
|
|
19
|
+
'.webm': 'video/webm',
|
|
20
|
+
'.flv': 'video/x-flv',
|
|
21
|
+
'.wmv': 'video/x-ms-wmv',
|
|
22
|
+
'.m4v': 'video/x-m4v',
|
|
23
|
+
'.3gp': 'video/3gpp',
|
|
24
|
+
// Audio
|
|
25
|
+
'.mp3': 'audio/mpeg',
|
|
26
|
+
'.wav': 'audio/wav',
|
|
27
|
+
'.m4a': 'audio/mp4',
|
|
28
|
+
'.aac': 'audio/aac',
|
|
29
|
+
'.flac': 'audio/flac',
|
|
30
|
+
'.ogg': 'audio/ogg',
|
|
31
|
+
};
|
|
32
|
+
const extension = path.substring(path.lastIndexOf('.')).toLowerCase();
|
|
33
|
+
return mimeTypes[extension] || 'application/octet-stream';
|
|
34
|
+
};
|
|
35
|
+
exports.getMimeType = getMimeType;
|
|
36
|
+
const isImage = (mimeType) => {
|
|
37
|
+
return mimeType.startsWith('image/');
|
|
38
|
+
};
|
|
39
|
+
exports.isImage = isImage;
|
|
40
|
+
const isVideo = (mimeType) => {
|
|
41
|
+
return mimeType.startsWith('video/');
|
|
42
|
+
};
|
|
43
|
+
exports.isVideo = isVideo;
|
|
44
|
+
const safeJsonParse = (jsonString) => {
|
|
45
|
+
try {
|
|
46
|
+
return JSON.parse(jsonString);
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
console.error('JSON parsing error:', error);
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
exports.safeJsonParse = safeJsonParse;
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@authenta/core",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Authenta API client — pure TypeScript, works in Node.js and React Native",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"test": "jest --passWithNoTests",
|
|
13
|
+
"prepublishOnly": "npm run build"
|
|
14
|
+
},
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "https://github.com/phospheneai/authenta-reactnative-sdk.git"
|
|
18
|
+
},
|
|
19
|
+
"keywords": ["authenta", "ekyc", "face", "liveness", "typescript"],
|
|
20
|
+
"author": "Authenta",
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@types/jest": "^29.0.0",
|
|
24
|
+
"@types/node": "^20.0.0",
|
|
25
|
+
"jest": "^29.0.0",
|
|
26
|
+
"ts-jest": "^29.0.0",
|
|
27
|
+
"typescript": "^4.0.0"
|
|
28
|
+
}
|
|
29
|
+
}
|