@flonkid/kyc 1.4.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.
@@ -0,0 +1,127 @@
1
+ type SessionStatus = 'pending' | 'connected' | 'processing' | 'completed' | 'failed' | 'expired';
2
+ type DocumentType = 'passport' | 'id_card' | 'driver_license';
3
+ type WidgetLanguage = 'en' | 'de' | 'uk';
4
+ interface FlonkKYCServerOptions {
5
+ secretKey: string;
6
+ apiBase?: string;
7
+ timeout?: number;
8
+ }
9
+ interface CreateSessionParams {
10
+ clientMetadata?: Record<string, unknown>;
11
+ /** 1–60, default: 5 */
12
+ expiryMinutes?: number;
13
+ language?: WidgetLanguage;
14
+ serverMetadata?: Record<string, unknown>;
15
+ }
16
+ interface Session {
17
+ id: string;
18
+ status: SessionStatus;
19
+ expiresAt: string;
20
+ clientMetadata?: Record<string, unknown>;
21
+ widgetUrl: string;
22
+ qrCodeUrl: string;
23
+ createdAt: string;
24
+ projectId: string;
25
+ allowManualUpload: boolean;
26
+ embedToken?: string;
27
+ reused?: boolean;
28
+ testMode?: boolean;
29
+ }
30
+ interface SessionDetails extends Session {
31
+ lastActivity?: string;
32
+ serverMetadata?: Record<string, unknown>;
33
+ updatedAt: string;
34
+ }
35
+ interface UpdateSessionParams {
36
+ clientMetadata?: Record<string, unknown>;
37
+ serverMetadata?: Record<string, unknown>;
38
+ }
39
+ interface WebhookEvent {
40
+ id: string;
41
+ type: 'verification.completed' | 'verification.status_changed' | 'verification.updated';
42
+ created: number;
43
+ livemode: boolean;
44
+ data: {
45
+ object: {
46
+ id: string;
47
+ client_id?: string;
48
+ client_metadata?: Record<string, unknown>;
49
+ status: string;
50
+ confidence?: number;
51
+ document_type: string;
52
+ extracted_data: {
53
+ full_name?: string;
54
+ first_name?: string;
55
+ last_name?: string;
56
+ date_of_birth?: string;
57
+ nationality?: string;
58
+ sex?: string;
59
+ [key: string]: unknown;
60
+ };
61
+ created_at: string;
62
+ updated_at?: string;
63
+ updated_by?: string;
64
+ test_mode?: boolean;
65
+ poa_verification?: {
66
+ status: string;
67
+ confidence_score: number | null;
68
+ };
69
+ previous_status?: string;
70
+ rejection_reason?: string;
71
+ reviewed_by?: string;
72
+ };
73
+ };
74
+ }
75
+ interface WebhookVerifyOptions {
76
+ /** Max clock skew in seconds. Default: 300 */
77
+ maxSkewSeconds?: number;
78
+ }
79
+
80
+ declare class Webhooks {
81
+ /**
82
+ * Verify X-Signature-256 header: "sha256=<hex>"
83
+ */
84
+ verifySignature(payload: string, signature: string, secret: string): WebhookEvent;
85
+ /**
86
+ * Verify Stripe-like header: "t=<unix>, v1=<hex>"
87
+ */
88
+ verifyTimestampSignature(payload: string, header: string, secret: string, options?: WebhookVerifyOptions): WebhookEvent;
89
+ /**
90
+ * Auto-detect signature format and verify.
91
+ */
92
+ constructEvent(payload: string, signature: string, secret: string): WebhookEvent;
93
+ private parsePayload;
94
+ private parseHeader;
95
+ private safeEqual;
96
+ }
97
+
98
+ declare class FlonkKYCServer {
99
+ static readonly version = "1.4.0";
100
+ private readonly http;
101
+ readonly webhooks: Webhooks;
102
+ constructor(options: FlonkKYCServerOptions);
103
+ createSession(params?: CreateSessionParams): Promise<Session>;
104
+ getSession(sessionId: string): Promise<SessionDetails>;
105
+ updateSession(sessionId: string, params: UpdateSessionParams): Promise<SessionDetails>;
106
+ }
107
+
108
+ declare class FlonkError extends Error {
109
+ readonly code: string;
110
+ readonly statusCode?: number | undefined;
111
+ constructor(message: string, code: string, statusCode?: number | undefined);
112
+ }
113
+ declare class FlonkAPIError extends FlonkError {
114
+ readonly body?: unknown | undefined;
115
+ constructor(message: string, statusCode: number, body?: unknown | undefined);
116
+ }
117
+ declare class FlonkAuthenticationError extends FlonkError {
118
+ constructor(message?: string);
119
+ }
120
+ declare class FlonkValidationError extends FlonkError {
121
+ constructor(message: string);
122
+ }
123
+ declare class FlonkWebhookSignatureError extends FlonkError {
124
+ constructor(message?: string);
125
+ }
126
+
127
+ export { type CreateSessionParams, type DocumentType, FlonkAPIError, FlonkAuthenticationError, FlonkError, FlonkKYCServer, type FlonkKYCServerOptions, FlonkValidationError, FlonkWebhookSignatureError, type Session, type SessionDetails, type SessionStatus, type UpdateSessionParams, type WebhookEvent, type WebhookVerifyOptions, Webhooks, type WidgetLanguage };
package/dist/server.js ADDED
@@ -0,0 +1,184 @@
1
+ import * as crypto from 'crypto';
2
+
3
+ // src/shared/constants.ts
4
+ var SDK_VERSION = "1.4.0";
5
+ var DEFAULT_API_BASE = "https://api.flonk.id/v1";
6
+
7
+ // src/shared/errors.ts
8
+ var FlonkError = class extends Error {
9
+ constructor(message, code, statusCode) {
10
+ super(message);
11
+ this.code = code;
12
+ this.statusCode = statusCode;
13
+ this.name = "FlonkError";
14
+ }
15
+ };
16
+ var FlonkAPIError = class extends FlonkError {
17
+ constructor(message, statusCode, body) {
18
+ super(message, "api_error", statusCode);
19
+ this.body = body;
20
+ this.name = "FlonkAPIError";
21
+ }
22
+ };
23
+ var FlonkAuthenticationError = class extends FlonkError {
24
+ constructor(message = "Invalid or missing secret key") {
25
+ super(message, "authentication_error", 401);
26
+ this.name = "FlonkAuthenticationError";
27
+ }
28
+ };
29
+ var FlonkValidationError = class extends FlonkError {
30
+ constructor(message) {
31
+ super(message, "validation_error", 400);
32
+ this.name = "FlonkValidationError";
33
+ }
34
+ };
35
+ var FlonkWebhookSignatureError = class extends FlonkError {
36
+ constructor(message = "Invalid webhook signature") {
37
+ super(message, "webhook_signature_error");
38
+ this.name = "FlonkWebhookSignatureError";
39
+ }
40
+ };
41
+
42
+ // src/server/http-client.ts
43
+ var HttpClient = class {
44
+ constructor(baseUrl, secretKey, timeout = 3e4) {
45
+ this.baseUrl = baseUrl;
46
+ this.secretKey = secretKey;
47
+ this.timeout = timeout;
48
+ }
49
+ async get(path) {
50
+ return this.request("GET", path);
51
+ }
52
+ async post(path, body) {
53
+ return this.request("POST", path, body);
54
+ }
55
+ async patch(path, body) {
56
+ return this.request("PATCH", path, body);
57
+ }
58
+ async request(method, path, body) {
59
+ const controller = new AbortController();
60
+ const timer = setTimeout(() => controller.abort(), this.timeout);
61
+ try {
62
+ const res = await fetch(`${this.baseUrl}${path}`, {
63
+ method,
64
+ headers: {
65
+ "Authorization": `Bearer ${this.secretKey}`,
66
+ "Content-Type": "application/json",
67
+ "User-Agent": `flonk-kyc-node/${SDK_VERSION}`
68
+ },
69
+ body: body ? JSON.stringify(body) : void 0,
70
+ signal: controller.signal
71
+ });
72
+ const json = await res.json().catch(() => null);
73
+ if (!res.ok) {
74
+ if (res.status === 401) throw new FlonkAuthenticationError();
75
+ throw new FlonkAPIError(
76
+ json?.message || `Request failed: ${res.status}`,
77
+ res.status,
78
+ json
79
+ );
80
+ }
81
+ return json;
82
+ } catch (err) {
83
+ if (err instanceof FlonkAPIError || err instanceof FlonkAuthenticationError) throw err;
84
+ throw new FlonkAPIError(err.message, 0);
85
+ } finally {
86
+ clearTimeout(timer);
87
+ }
88
+ }
89
+ };
90
+ var Webhooks = class {
91
+ /**
92
+ * Verify X-Signature-256 header: "sha256=<hex>"
93
+ */
94
+ verifySignature(payload, signature, secret) {
95
+ const expected = "sha256=" + crypto.createHmac("sha256", secret).update(payload).digest("hex");
96
+ if (!this.safeEqual(signature, expected)) {
97
+ throw new FlonkWebhookSignatureError();
98
+ }
99
+ return this.parsePayload(payload);
100
+ }
101
+ /**
102
+ * Verify Stripe-like header: "t=<unix>, v1=<hex>"
103
+ */
104
+ verifyTimestampSignature(payload, header, secret, options) {
105
+ const maxSkew = options?.maxSkewSeconds ?? 300;
106
+ const parts = this.parseHeader(header);
107
+ const ts = Number(parts["t"]);
108
+ const v1 = parts["v1"];
109
+ if (!ts || !v1) {
110
+ throw new FlonkWebhookSignatureError("Missing timestamp or signature");
111
+ }
112
+ if (Math.abs(Math.floor(Date.now() / 1e3) - ts) > maxSkew) {
113
+ throw new FlonkWebhookSignatureError(`Timestamp skew exceeds ${maxSkew}s`);
114
+ }
115
+ const expected = crypto.createHmac("sha256", secret).update(`${ts}.${payload}`).digest("hex");
116
+ if (!this.safeEqual(v1, expected)) {
117
+ throw new FlonkWebhookSignatureError();
118
+ }
119
+ return this.parsePayload(payload);
120
+ }
121
+ /**
122
+ * Auto-detect signature format and verify.
123
+ */
124
+ constructEvent(payload, signature, secret) {
125
+ if (signature.startsWith("sha256=")) {
126
+ return this.verifySignature(payload, signature, secret);
127
+ }
128
+ if (signature.includes("t=") && signature.includes("v1=")) {
129
+ return this.verifyTimestampSignature(payload, signature, secret);
130
+ }
131
+ throw new FlonkWebhookSignatureError("Unrecognized signature format");
132
+ }
133
+ parsePayload(payload) {
134
+ try {
135
+ return JSON.parse(payload);
136
+ } catch {
137
+ throw new FlonkWebhookSignatureError("Malformed JSON payload");
138
+ }
139
+ }
140
+ parseHeader(header) {
141
+ const result = {};
142
+ for (const part of header.split(",")) {
143
+ const [k, v] = part.trim().split("=");
144
+ if (k && v) result[k] = v;
145
+ }
146
+ return result;
147
+ }
148
+ safeEqual(a, b) {
149
+ if (a.length !== b.length) return false;
150
+ return crypto.timingSafeEqual(Buffer.from(a), Buffer.from(b));
151
+ }
152
+ };
153
+
154
+ // src/server/index.ts
155
+ var SK_PATTERN = /^sk_(live|sandbox|test_sandbox|test_live)_[A-Za-z0-9]{16,}$/;
156
+ var FlonkKYCServer = class {
157
+ constructor(options) {
158
+ this.webhooks = new Webhooks();
159
+ if (!options.secretKey) throw new FlonkAuthenticationError("secretKey is required");
160
+ if (!SK_PATTERN.test(options.secretKey)) {
161
+ throw new FlonkAuthenticationError("Invalid secret key format. Expected: sk_live_*, sk_sandbox_*, sk_test_sandbox_*, sk_test_live_*");
162
+ }
163
+ const base = (options.apiBase || DEFAULT_API_BASE).replace(/\/$/, "");
164
+ this.http = new HttpClient(base, options.secretKey, options.timeout);
165
+ }
166
+ static {
167
+ this.version = SDK_VERSION;
168
+ }
169
+ async createSession(params) {
170
+ return this.http.post("/sessions", params ?? {});
171
+ }
172
+ async getSession(sessionId) {
173
+ if (!sessionId) throw new FlonkValidationError("sessionId is required");
174
+ return this.http.get(`/sessions/${sessionId}`);
175
+ }
176
+ async updateSession(sessionId, params) {
177
+ if (!sessionId) throw new FlonkValidationError("sessionId is required");
178
+ return this.http.patch(`/sessions/${sessionId}`, params);
179
+ }
180
+ };
181
+
182
+ export { FlonkAPIError, FlonkAuthenticationError, FlonkError, FlonkKYCServer, FlonkValidationError, FlonkWebhookSignatureError, Webhooks };
183
+ //# sourceMappingURL=server.js.map
184
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/shared/constants.ts","../src/shared/errors.ts","../src/server/http-client.ts","../src/server/webhooks.ts","../src/server/index.ts"],"names":[],"mappings":"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","file":"server.js","sourcesContent":["export const SDK_VERSION = '1.4.0';\r\nexport const DEFAULT_WIDGET_URL = 'https://widget.flonk.id';\r\nexport const DEFAULT_API_BASE = 'https://api.flonk.id/v1';\r\n\r\nexport const WIDGET_EVENTS = {\r\n READY: 'KYC_WIDGET_READY',\r\n COMPLETE: 'KYC_COMPLETE',\r\n CANCEL: 'KYC_CANCEL',\r\n ERROR: 'KYC_ERROR',\r\n} as const;\r\n","export class FlonkError extends Error {\r\n constructor(\r\n message: string,\r\n public readonly code: string,\r\n public readonly statusCode?: number,\r\n ) {\r\n super(message);\r\n this.name = 'FlonkError';\r\n }\r\n}\r\n\r\nexport class FlonkAPIError extends FlonkError {\r\n constructor(\r\n message: string,\r\n statusCode: number,\r\n public readonly body?: unknown,\r\n ) {\r\n super(message, 'api_error', statusCode);\r\n this.name = 'FlonkAPIError';\r\n }\r\n}\r\n\r\nexport class FlonkAuthenticationError extends FlonkError {\r\n constructor(message = 'Invalid or missing secret key') {\r\n super(message, 'authentication_error', 401);\r\n this.name = 'FlonkAuthenticationError';\r\n }\r\n}\r\n\r\nexport class FlonkValidationError extends FlonkError {\r\n constructor(message: string) {\r\n super(message, 'validation_error', 400);\r\n this.name = 'FlonkValidationError';\r\n }\r\n}\r\n\r\nexport class FlonkWebhookSignatureError extends FlonkError {\r\n constructor(message = 'Invalid webhook signature') {\r\n super(message, 'webhook_signature_error');\r\n this.name = 'FlonkWebhookSignatureError';\r\n }\r\n}\r\n","import { SDK_VERSION } from '../shared/constants';\r\nimport { FlonkAPIError, FlonkAuthenticationError } from '../shared/errors';\r\n\r\nexport class HttpClient {\r\n constructor(\r\n private readonly baseUrl: string,\r\n private readonly secretKey: string,\r\n private readonly timeout = 30_000,\r\n ) {}\r\n\r\n async get<T>(path: string): Promise<T> {\r\n return this.request<T>('GET', path);\r\n }\r\n\r\n async post<T>(path: string, body?: unknown): Promise<T> {\r\n return this.request<T>('POST', path, body);\r\n }\r\n\r\n async patch<T>(path: string, body?: unknown): Promise<T> {\r\n return this.request<T>('PATCH', path, body);\r\n }\r\n\r\n private async request<T>(method: string, path: string, body?: unknown): Promise<T> {\r\n const controller = new AbortController();\r\n const timer = setTimeout(() => controller.abort(), this.timeout);\r\n\r\n try {\r\n const res = await fetch(`${this.baseUrl}${path}`, {\r\n method,\r\n headers: {\r\n 'Authorization': `Bearer ${this.secretKey}`,\r\n 'Content-Type': 'application/json',\r\n 'User-Agent': `flonk-kyc-node/${SDK_VERSION}`,\r\n },\r\n body: body ? JSON.stringify(body) : undefined,\r\n signal: controller.signal,\r\n });\r\n\r\n const json = await res.json().catch(() => null);\r\n\r\n if (!res.ok) {\r\n if (res.status === 401) throw new FlonkAuthenticationError();\r\n throw new FlonkAPIError(\r\n json?.message || `Request failed: ${res.status}`,\r\n res.status,\r\n json,\r\n );\r\n }\r\n\r\n return json as T;\r\n } catch (err) {\r\n if (err instanceof FlonkAPIError || err instanceof FlonkAuthenticationError) throw err;\r\n throw new FlonkAPIError((err as Error).message, 0);\r\n } finally {\r\n clearTimeout(timer);\r\n }\r\n }\r\n}\r\n","import * as crypto from 'crypto';\r\nimport { FlonkWebhookSignatureError } from '../shared/errors';\r\nimport type { WebhookEvent, WebhookVerifyOptions } from '../shared/types';\r\n\r\nexport class Webhooks {\r\n /**\r\n * Verify X-Signature-256 header: \"sha256=<hex>\"\r\n */\r\n verifySignature(payload: string, signature: string, secret: string): WebhookEvent {\r\n const expected =\r\n 'sha256=' + crypto.createHmac('sha256', secret).update(payload).digest('hex');\r\n\r\n if (!this.safeEqual(signature, expected)) {\r\n throw new FlonkWebhookSignatureError();\r\n }\r\n\r\n return this.parsePayload(payload);\r\n }\r\n\r\n /**\r\n * Verify Stripe-like header: \"t=<unix>, v1=<hex>\"\r\n */\r\n verifyTimestampSignature(\r\n payload: string,\r\n header: string,\r\n secret: string,\r\n options?: WebhookVerifyOptions,\r\n ): WebhookEvent {\r\n const maxSkew = options?.maxSkewSeconds ?? 300;\r\n const parts = this.parseHeader(header);\r\n const ts = Number(parts['t']);\r\n const v1 = parts['v1'];\r\n\r\n if (!ts || !v1) {\r\n throw new FlonkWebhookSignatureError('Missing timestamp or signature');\r\n }\r\n\r\n if (Math.abs(Math.floor(Date.now() / 1000) - ts) > maxSkew) {\r\n throw new FlonkWebhookSignatureError(`Timestamp skew exceeds ${maxSkew}s`);\r\n }\r\n\r\n const expected = crypto\r\n .createHmac('sha256', secret)\r\n .update(`${ts}.${payload}`)\r\n .digest('hex');\r\n\r\n if (!this.safeEqual(v1, expected)) {\r\n throw new FlonkWebhookSignatureError();\r\n }\r\n\r\n return this.parsePayload(payload);\r\n }\r\n\r\n /**\r\n * Auto-detect signature format and verify.\r\n */\r\n constructEvent(payload: string, signature: string, secret: string): WebhookEvent {\r\n if (signature.startsWith('sha256=')) {\r\n return this.verifySignature(payload, signature, secret);\r\n }\r\n if (signature.includes('t=') && signature.includes('v1=')) {\r\n return this.verifyTimestampSignature(payload, signature, secret);\r\n }\r\n throw new FlonkWebhookSignatureError('Unrecognized signature format');\r\n }\r\n\r\n private parsePayload(payload: string): WebhookEvent {\r\n try {\r\n return JSON.parse(payload) as WebhookEvent;\r\n } catch {\r\n throw new FlonkWebhookSignatureError('Malformed JSON payload');\r\n }\r\n }\r\n\r\n private parseHeader(header: string): Record<string, string> {\r\n const result: Record<string, string> = {};\r\n for (const part of header.split(',')) {\r\n const [k, v] = part.trim().split('=');\r\n if (k && v) result[k] = v;\r\n }\r\n return result;\r\n }\r\n\r\n private safeEqual(a: string, b: string): boolean {\r\n if (a.length !== b.length) return false;\r\n return crypto.timingSafeEqual(Buffer.from(a), Buffer.from(b));\r\n }\r\n}\r\n","import { SDK_VERSION, DEFAULT_API_BASE } from '../shared/constants';\r\nimport { FlonkAuthenticationError, FlonkValidationError } from '../shared/errors';\r\nimport type {\r\n FlonkKYCServerOptions,\r\n CreateSessionParams,\r\n Session,\r\n SessionDetails,\r\n UpdateSessionParams,\r\n} from '../shared/types';\r\nimport { HttpClient } from './http-client';\r\nimport { Webhooks } from './webhooks';\r\n\r\nconst SK_PATTERN = /^sk_(live|sandbox|test_sandbox|test_live)_[A-Za-z0-9]{16,}$/;\r\n\r\nexport class FlonkKYCServer {\r\n static readonly version = SDK_VERSION;\r\n\r\n private readonly http: HttpClient;\r\n public readonly webhooks = new Webhooks();\r\n\r\n constructor(options: FlonkKYCServerOptions) {\r\n if (!options.secretKey) throw new FlonkAuthenticationError('secretKey is required');\r\n if (!SK_PATTERN.test(options.secretKey)) {\r\n throw new FlonkAuthenticationError('Invalid secret key format. Expected: sk_live_*, sk_sandbox_*, sk_test_sandbox_*, sk_test_live_*');\r\n }\r\n\r\n const base = (options.apiBase || DEFAULT_API_BASE).replace(/\\/$/, '');\r\n this.http = new HttpClient(base, options.secretKey, options.timeout);\r\n }\r\n\r\n async createSession(params?: CreateSessionParams): Promise<Session> {\r\n return this.http.post<Session>('/sessions', params ?? {});\r\n }\r\n\r\n async getSession(sessionId: string): Promise<SessionDetails> {\r\n if (!sessionId) throw new FlonkValidationError('sessionId is required');\r\n return this.http.get<SessionDetails>(`/sessions/${sessionId}`);\r\n }\r\n\r\n async updateSession(sessionId: string, params: UpdateSessionParams): Promise<SessionDetails> {\r\n if (!sessionId) throw new FlonkValidationError('sessionId is required');\r\n return this.http.patch<SessionDetails>(`/sessions/${sessionId}`, params);\r\n }\r\n}\r\n"]}
@@ -0,0 +1,153 @@
1
+ type SessionStatus = 'pending' | 'connected' | 'processing' | 'completed' | 'failed' | 'expired';
2
+ type DocumentType = 'passport' | 'id_card' | 'driver_license';
3
+ type WidgetLanguage = 'en' | 'de' | 'uk';
4
+ interface FlonkKYCOptions {
5
+ widgetUrl?: string;
6
+ apiBase?: string;
7
+ }
8
+ interface WidgetInitConfig {
9
+ serverUrl?: string;
10
+ publishableKey?: string;
11
+ sessionId?: string;
12
+ embedToken?: string;
13
+ clientMetadata?: Record<string, unknown>;
14
+ lang?: WidgetLanguage;
15
+ overlayColor?: string;
16
+ allowManualUpload?: boolean;
17
+ mount?: HTMLElement;
18
+ onSuccess?: (result: VerificationResult) => void;
19
+ onError?: (error: string) => void;
20
+ onCancel?: () => void;
21
+ onReady?: () => void;
22
+ }
23
+ interface WidgetPreviewConfig {
24
+ colors?: PreviewColors;
25
+ documentType?: DocumentType;
26
+ lang?: WidgetLanguage;
27
+ overlayColor?: string;
28
+ onSuccess?: (result: VerificationResult) => void;
29
+ onError?: (error: string) => void;
30
+ onCancel?: () => void;
31
+ }
32
+ interface WidgetEmbedConfig {
33
+ container: string | HTMLElement;
34
+ colors?: PreviewColors;
35
+ device?: 'mobile' | 'desktop';
36
+ scale?: number;
37
+ documentType?: DocumentType;
38
+ lang?: WidgetLanguage;
39
+ }
40
+ interface WidgetInstance {
41
+ iframe: HTMLIFrameElement;
42
+ destroy: () => void;
43
+ }
44
+ interface EmbedInstance extends WidgetInstance {
45
+ setColors: (colors: Partial<PreviewColors>) => void;
46
+ setDevice: (device: 'mobile' | 'desktop') => void;
47
+ getColors: () => PreviewColors;
48
+ }
49
+ interface PreviewColors {
50
+ primaryColor?: string;
51
+ secondaryColor?: string;
52
+ }
53
+ interface VerificationResult {
54
+ sessionId?: string;
55
+ status?: string;
56
+ [key: string]: unknown;
57
+ }
58
+ interface FlonkKYCServerOptions {
59
+ secretKey: string;
60
+ apiBase?: string;
61
+ timeout?: number;
62
+ }
63
+ interface CreateSessionParams {
64
+ clientMetadata?: Record<string, unknown>;
65
+ /** 1–60, default: 5 */
66
+ expiryMinutes?: number;
67
+ language?: WidgetLanguage;
68
+ serverMetadata?: Record<string, unknown>;
69
+ }
70
+ interface Session {
71
+ id: string;
72
+ status: SessionStatus;
73
+ expiresAt: string;
74
+ clientMetadata?: Record<string, unknown>;
75
+ widgetUrl: string;
76
+ qrCodeUrl: string;
77
+ createdAt: string;
78
+ projectId: string;
79
+ allowManualUpload: boolean;
80
+ embedToken?: string;
81
+ reused?: boolean;
82
+ testMode?: boolean;
83
+ }
84
+ interface SessionDetails extends Session {
85
+ lastActivity?: string;
86
+ serverMetadata?: Record<string, unknown>;
87
+ updatedAt: string;
88
+ }
89
+ interface UpdateSessionParams {
90
+ clientMetadata?: Record<string, unknown>;
91
+ serverMetadata?: Record<string, unknown>;
92
+ }
93
+ interface WebhookEvent {
94
+ id: string;
95
+ type: 'verification.completed' | 'verification.status_changed' | 'verification.updated';
96
+ created: number;
97
+ livemode: boolean;
98
+ data: {
99
+ object: {
100
+ id: string;
101
+ client_id?: string;
102
+ client_metadata?: Record<string, unknown>;
103
+ status: string;
104
+ confidence?: number;
105
+ document_type: string;
106
+ extracted_data: {
107
+ full_name?: string;
108
+ first_name?: string;
109
+ last_name?: string;
110
+ date_of_birth?: string;
111
+ nationality?: string;
112
+ sex?: string;
113
+ [key: string]: unknown;
114
+ };
115
+ created_at: string;
116
+ updated_at?: string;
117
+ updated_by?: string;
118
+ test_mode?: boolean;
119
+ poa_verification?: {
120
+ status: string;
121
+ confidence_score: number | null;
122
+ };
123
+ previous_status?: string;
124
+ rejection_reason?: string;
125
+ reviewed_by?: string;
126
+ };
127
+ };
128
+ }
129
+ interface WebhookVerifyOptions {
130
+ /** Max clock skew in seconds. Default: 300 */
131
+ maxSkewSeconds?: number;
132
+ }
133
+
134
+ declare class FlonkError extends Error {
135
+ readonly code: string;
136
+ readonly statusCode?: number | undefined;
137
+ constructor(message: string, code: string, statusCode?: number | undefined);
138
+ }
139
+ declare class FlonkAPIError extends FlonkError {
140
+ readonly body?: unknown | undefined;
141
+ constructor(message: string, statusCode: number, body?: unknown | undefined);
142
+ }
143
+ declare class FlonkAuthenticationError extends FlonkError {
144
+ constructor(message?: string);
145
+ }
146
+ declare class FlonkValidationError extends FlonkError {
147
+ constructor(message: string);
148
+ }
149
+ declare class FlonkWebhookSignatureError extends FlonkError {
150
+ constructor(message?: string);
151
+ }
152
+
153
+ export { type CreateSessionParams, type DocumentType, type EmbedInstance, FlonkAPIError, FlonkAuthenticationError, FlonkError, type FlonkKYCOptions, type FlonkKYCServerOptions, FlonkValidationError, FlonkWebhookSignatureError, type PreviewColors, type Session, type SessionDetails, type SessionStatus, type UpdateSessionParams, type VerificationResult, type WebhookEvent, type WebhookVerifyOptions, type WidgetEmbedConfig, type WidgetInitConfig, type WidgetInstance, type WidgetLanguage, type WidgetPreviewConfig };
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@flonkid/kyc",
3
+ "version": "1.4.0",
4
+ "description": "Official Flonk KYC SDK — identity verification for any application",
5
+ "license": "SEE LICENSE IN LICENSE",
6
+ "author": "Flonk.id — Dmytrii Popovych (dimon1936)",
7
+ "type": "module",
8
+ "main": "./dist/index.cjs",
9
+ "module": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "import": { "types": "./dist/index.d.ts", "default": "./dist/index.js" },
14
+ "require": { "types": "./dist/index.d.ts", "default": "./dist/index.cjs" }
15
+ },
16
+ "./server": {
17
+ "import": { "types": "./dist/server.d.ts", "default": "./dist/server.js" },
18
+ "require": { "types": "./dist/server.d.ts", "default": "./dist/server.cjs" }
19
+ },
20
+ "./types": {
21
+ "import": { "types": "./dist/types.d.ts" },
22
+ "require": { "types": "./dist/types.d.ts" }
23
+ }
24
+ },
25
+ "files": ["dist"],
26
+ "sideEffects": false,
27
+ "scripts": {
28
+ "build": "tsup",
29
+ "dev": "tsup --watch",
30
+ "type-check": "tsc --noEmit",
31
+ "prepublishOnly": "npm run build"
32
+ },
33
+ "dependencies": {},
34
+ "peerDependencies": {
35
+ "react": ">=18.0.0"
36
+ },
37
+ "peerDependenciesMeta": {
38
+ "react": { "optional": true }
39
+ },
40
+ "devDependencies": {
41
+ "tsup": "^8.0.0",
42
+ "typescript": "^5.7.2",
43
+ "@types/node": "^22.10.1",
44
+ "@types/react": "^19.0.0",
45
+ "react": "^19.0.0"
46
+ },
47
+ "engines": { "node": ">=18.0.0" },
48
+ "keywords": ["kyc", "identity-verification", "flonk", "sdk", "widget"]
49
+ }