vesant-sdk 1.2.0 → 1.3.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/dist/{client-BWp5FI3x.d.ts → client-B6fUFAUM.d.mts} +2 -1
- package/dist/{client-BIfLMfuC.d.mts → client-DoczGA6L.d.ts} +2 -1
- package/dist/client-DzElM7u-.d.mts +238 -0
- package/dist/client-DzElM7u-.d.ts +238 -0
- package/dist/compliance/index.d.mts +5 -4
- package/dist/compliance/index.d.ts +5 -4
- package/dist/compliance/index.js +306 -98
- package/dist/compliance/index.js.map +1 -1
- package/dist/compliance/index.mjs +306 -98
- package/dist/compliance/index.mjs.map +1 -1
- package/dist/decisions/index.d.mts +100 -0
- package/dist/decisions/index.d.ts +100 -0
- package/dist/decisions/index.js +607 -0
- package/dist/decisions/index.js.map +1 -0
- package/dist/decisions/index.mjs +605 -0
- package/dist/decisions/index.mjs.map +1 -0
- package/dist/geolocation/index.d.mts +4 -3
- package/dist/geolocation/index.d.ts +4 -3
- package/dist/geolocation/index.js +306 -98
- package/dist/geolocation/index.js.map +1 -1
- package/dist/geolocation/index.mjs +306 -98
- package/dist/geolocation/index.mjs.map +1 -1
- package/dist/index.d.mts +14 -6
- package/dist/index.d.ts +14 -6
- package/dist/index.js +641 -90
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +632 -91
- package/dist/index.mjs.map +1 -1
- package/dist/kyc/core.d.mts +3 -2
- package/dist/kyc/core.d.ts +3 -2
- package/dist/kyc/core.js +249 -29
- package/dist/kyc/core.js.map +1 -1
- package/dist/kyc/core.mjs +249 -29
- package/dist/kyc/core.mjs.map +1 -1
- package/dist/kyc/index.d.mts +3 -2
- package/dist/kyc/index.d.ts +3 -2
- package/dist/kyc/index.js +249 -29
- package/dist/kyc/index.js.map +1 -1
- package/dist/kyc/index.mjs +249 -29
- package/dist/kyc/index.mjs.map +1 -1
- package/dist/react.d.mts +4 -3
- package/dist/react.d.ts +4 -3
- package/dist/react.js +1 -1
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +1 -1
- package/dist/react.mjs.map +1 -1
- package/dist/risk-profile/index.d.mts +4 -4
- package/dist/risk-profile/index.d.ts +4 -4
- package/dist/risk-profile/index.js +249 -29
- package/dist/risk-profile/index.js.map +1 -1
- package/dist/risk-profile/index.mjs +249 -29
- package/dist/risk-profile/index.mjs.map +1 -1
- package/dist/scores/index.d.mts +96 -0
- package/dist/scores/index.d.ts +96 -0
- package/dist/scores/index.js +594 -0
- package/dist/scores/index.js.map +1 -0
- package/dist/scores/index.mjs +591 -0
- package/dist/scores/index.mjs.map +1 -0
- package/dist/{types-DfHLp_tz.d.ts → types-DLC7Sfy5.d.ts} +1 -1
- package/dist/types-DZHongaK.d.mts +61 -0
- package/dist/types-DZHongaK.d.ts +61 -0
- package/dist/{types-DKCQN4C5.d.mts → types-jaLuzruy.d.mts} +1 -1
- package/dist/webhooks/index.d.mts +176 -0
- package/dist/webhooks/index.d.ts +176 -0
- package/dist/webhooks/index.js +193 -0
- package/dist/webhooks/index.js.map +1 -0
- package/dist/webhooks/index.mjs +188 -0
- package/dist/webhooks/index.mjs.map +1 -0
- package/package.json +16 -1
- package/dist/types-BpKxSXGF.d.mts +0 -177
- package/dist/types-BpKxSXGF.d.ts +0 -177
package/dist/index.mjs
CHANGED
|
@@ -69,6 +69,146 @@ var ComplianceError = class _ComplianceError extends CGSError {
|
|
|
69
69
|
Object.setPrototypeOf(this, _ComplianceError.prototype);
|
|
70
70
|
}
|
|
71
71
|
};
|
|
72
|
+
var CircuitBreakerOpenError = class _CircuitBreakerOpenError extends CGSError {
|
|
73
|
+
constructor() {
|
|
74
|
+
super("Circuit breaker is open \u2014 requests are temporarily blocked", "CIRCUIT_BREAKER_OPEN", 503);
|
|
75
|
+
this.name = "CircuitBreakerOpenError";
|
|
76
|
+
Object.setPrototypeOf(this, _CircuitBreakerOpenError.prototype);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// src/core/circuit-breaker.ts
|
|
81
|
+
var CircuitBreaker = class {
|
|
82
|
+
constructor(config = {}) {
|
|
83
|
+
this.state = "closed";
|
|
84
|
+
this.failures = 0;
|
|
85
|
+
this.successes = 0;
|
|
86
|
+
this.lastFailureTime = null;
|
|
87
|
+
this.failureThreshold = config.failureThreshold ?? 5;
|
|
88
|
+
this.resetTimeout = config.resetTimeout ?? 3e4;
|
|
89
|
+
this.successThreshold = config.successThreshold ?? 1;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Check if a request can proceed through the circuit breaker.
|
|
93
|
+
*/
|
|
94
|
+
canExecute() {
|
|
95
|
+
if (this.state === "closed") {
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
if (this.state === "open") {
|
|
99
|
+
const now = Date.now();
|
|
100
|
+
if (this.lastFailureTime && now - this.lastFailureTime >= this.resetTimeout) {
|
|
101
|
+
this.state = "half-open";
|
|
102
|
+
this.successes = 0;
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Record a successful request.
|
|
111
|
+
*/
|
|
112
|
+
onSuccess() {
|
|
113
|
+
if (this.state === "half-open") {
|
|
114
|
+
this.successes++;
|
|
115
|
+
if (this.successes >= this.successThreshold) {
|
|
116
|
+
this.state = "closed";
|
|
117
|
+
this.failures = 0;
|
|
118
|
+
this.successes = 0;
|
|
119
|
+
this.lastFailureTime = null;
|
|
120
|
+
}
|
|
121
|
+
} else if (this.state === "closed") {
|
|
122
|
+
this.failures = 0;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Record a failed request.
|
|
127
|
+
*/
|
|
128
|
+
onFailure() {
|
|
129
|
+
this.failures++;
|
|
130
|
+
this.lastFailureTime = Date.now();
|
|
131
|
+
if (this.state === "half-open") {
|
|
132
|
+
this.state = "open";
|
|
133
|
+
this.successes = 0;
|
|
134
|
+
} else if (this.state === "closed" && this.failures >= this.failureThreshold) {
|
|
135
|
+
this.state = "open";
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Get current circuit breaker status.
|
|
140
|
+
*/
|
|
141
|
+
getStatus() {
|
|
142
|
+
return {
|
|
143
|
+
state: this.state,
|
|
144
|
+
failures: this.failures,
|
|
145
|
+
successes: this.successes,
|
|
146
|
+
lastFailureTime: this.lastFailureTime,
|
|
147
|
+
nextRetryTime: this.state === "open" && this.lastFailureTime ? this.lastFailureTime + this.resetTimeout : null
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Reset the circuit breaker to its initial closed state.
|
|
152
|
+
*/
|
|
153
|
+
reset() {
|
|
154
|
+
this.state = "closed";
|
|
155
|
+
this.failures = 0;
|
|
156
|
+
this.successes = 0;
|
|
157
|
+
this.lastFailureTime = null;
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
// src/core/rate-limiter.ts
|
|
162
|
+
var RateLimitTracker = class {
|
|
163
|
+
constructor() {
|
|
164
|
+
this.limit = null;
|
|
165
|
+
this.remaining = null;
|
|
166
|
+
this.reset = null;
|
|
167
|
+
this.retryAfter = null;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Extract rate limit information from response headers.
|
|
171
|
+
*/
|
|
172
|
+
updateFromHeaders(headers) {
|
|
173
|
+
const limit = headers.get("x-ratelimit-limit");
|
|
174
|
+
const remaining = headers.get("x-ratelimit-remaining");
|
|
175
|
+
const reset = headers.get("x-ratelimit-reset");
|
|
176
|
+
const retryAfter = headers.get("retry-after");
|
|
177
|
+
if (limit !== null) this.limit = parseInt(limit, 10);
|
|
178
|
+
if (remaining !== null) this.remaining = parseInt(remaining, 10);
|
|
179
|
+
if (reset !== null) this.reset = parseInt(reset, 10);
|
|
180
|
+
if (retryAfter !== null) this.retryAfter = parseInt(retryAfter, 10);
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Check if the rate limit has been exceeded based on tracked headers.
|
|
184
|
+
*/
|
|
185
|
+
isLimitExceeded() {
|
|
186
|
+
if (this.remaining !== null && this.remaining <= 0) {
|
|
187
|
+
if (this.reset !== null) {
|
|
188
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
189
|
+
if (now >= this.reset) {
|
|
190
|
+
this.remaining = null;
|
|
191
|
+
this.reset = null;
|
|
192
|
+
this.retryAfter = null;
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Get current rate limit status.
|
|
202
|
+
*/
|
|
203
|
+
getStatus() {
|
|
204
|
+
return {
|
|
205
|
+
limit: this.limit,
|
|
206
|
+
remaining: this.remaining,
|
|
207
|
+
reset: this.reset,
|
|
208
|
+
retryAfter: this.retryAfter
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
};
|
|
72
212
|
|
|
73
213
|
// src/core/logger.ts
|
|
74
214
|
function createConsoleLogger() {
|
|
@@ -99,11 +239,82 @@ var noopLogger = {
|
|
|
99
239
|
};
|
|
100
240
|
|
|
101
241
|
// src/core/version.ts
|
|
102
|
-
var SDK_VERSION = "1.
|
|
242
|
+
var SDK_VERSION = "1.3.0";
|
|
243
|
+
|
|
244
|
+
// src/shared/browser-utils.ts
|
|
245
|
+
function generateUUID() {
|
|
246
|
+
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
247
|
+
return crypto.randomUUID();
|
|
248
|
+
}
|
|
249
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
250
|
+
const r = Math.random() * 16 | 0;
|
|
251
|
+
const v = c === "x" ? r : r & 3 | 8;
|
|
252
|
+
return v.toString(16);
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
function generateDeviceId() {
|
|
256
|
+
if (typeof window === "undefined" || typeof localStorage === "undefined") {
|
|
257
|
+
return generateUUID();
|
|
258
|
+
}
|
|
259
|
+
const storageKey = "cgs_device_id";
|
|
260
|
+
let deviceId = localStorage.getItem(storageKey);
|
|
261
|
+
if (!deviceId) {
|
|
262
|
+
deviceId = generateUUID();
|
|
263
|
+
localStorage.setItem(storageKey, deviceId);
|
|
264
|
+
}
|
|
265
|
+
return deviceId;
|
|
266
|
+
}
|
|
267
|
+
function getBrowserInfo() {
|
|
268
|
+
if (typeof navigator === "undefined") {
|
|
269
|
+
return { browser: "unknown", browser_version: "", os: "unknown", os_version: "" };
|
|
270
|
+
}
|
|
271
|
+
const ua = navigator.userAgent;
|
|
272
|
+
let browser = "unknown";
|
|
273
|
+
let browserVersion = "";
|
|
274
|
+
let os = "unknown";
|
|
275
|
+
let osVersion = "";
|
|
276
|
+
if (ua.includes("Firefox/")) {
|
|
277
|
+
browser = "Firefox";
|
|
278
|
+
browserVersion = ua.match(/Firefox\/([\d.]+)/)?.[1] || "";
|
|
279
|
+
} else if (ua.includes("Edg/")) {
|
|
280
|
+
browser = "Edge";
|
|
281
|
+
browserVersion = ua.match(/Edg\/([\d.]+)/)?.[1] || "";
|
|
282
|
+
} else if (ua.includes("Chrome/")) {
|
|
283
|
+
browser = "Chrome";
|
|
284
|
+
browserVersion = ua.match(/Chrome\/([\d.]+)/)?.[1] || "";
|
|
285
|
+
} else if (ua.includes("Safari/") && !ua.includes("Chrome")) {
|
|
286
|
+
browser = "Safari";
|
|
287
|
+
browserVersion = ua.match(/Version\/([\d.]+)/)?.[1] || "";
|
|
288
|
+
} else if (ua.includes("Opera") || ua.includes("OPR/")) {
|
|
289
|
+
browser = "Opera";
|
|
290
|
+
browserVersion = ua.match(/(?:Opera|OPR)\/([\d.]+)/)?.[1] || "";
|
|
291
|
+
}
|
|
292
|
+
if (ua.includes("Windows")) {
|
|
293
|
+
os = "Windows";
|
|
294
|
+
if (ua.includes("Windows NT 10.0")) osVersion = "10";
|
|
295
|
+
else if (ua.includes("Windows NT 6.3")) osVersion = "8.1";
|
|
296
|
+
else if (ua.includes("Windows NT 6.2")) osVersion = "8";
|
|
297
|
+
else if (ua.includes("Windows NT 6.1")) osVersion = "7";
|
|
298
|
+
} else if (ua.includes("Mac OS X")) {
|
|
299
|
+
os = "macOS";
|
|
300
|
+
osVersion = ua.match(/Mac OS X ([\d_]+)/)?.[1]?.replace(/_/g, ".") || "";
|
|
301
|
+
} else if (ua.includes("Linux")) {
|
|
302
|
+
os = "Linux";
|
|
303
|
+
} else if (ua.includes("Android")) {
|
|
304
|
+
os = "Android";
|
|
305
|
+
osVersion = ua.match(/Android ([\d.]+)/)?.[1] || "";
|
|
306
|
+
} else if (ua.includes("iOS") || ua.includes("iPhone") || ua.includes("iPad")) {
|
|
307
|
+
os = "iOS";
|
|
308
|
+
osVersion = ua.match(/OS ([\d_]+)/)?.[1]?.replace(/_/g, ".") || "";
|
|
309
|
+
}
|
|
310
|
+
return { browser, browser_version: browserVersion, os, os_version: osVersion };
|
|
311
|
+
}
|
|
103
312
|
|
|
104
313
|
// src/core/client.ts
|
|
105
314
|
var BaseClient = class {
|
|
106
315
|
constructor(config) {
|
|
316
|
+
this.circuitBreaker = null;
|
|
317
|
+
this.rateLimitTracker = null;
|
|
107
318
|
if (!config.baseURL?.trim()) {
|
|
108
319
|
throw new ValidationError("baseURL is required and must be a non-empty string", ["baseURL"]);
|
|
109
320
|
}
|
|
@@ -113,8 +324,7 @@ var BaseClient = class {
|
|
|
113
324
|
this.interceptors = config.interceptors || [];
|
|
114
325
|
this.logger = config.logger || createConsoleLogger();
|
|
115
326
|
this.config = {
|
|
116
|
-
|
|
117
|
-
tenantId: config.tenantId,
|
|
327
|
+
...config,
|
|
118
328
|
apiKey: config.apiKey || "",
|
|
119
329
|
headers: config.headers || {},
|
|
120
330
|
timeout: config.timeout || 1e4,
|
|
@@ -123,22 +333,49 @@ var BaseClient = class {
|
|
|
123
333
|
interceptors: this.interceptors,
|
|
124
334
|
logger: this.logger
|
|
125
335
|
};
|
|
336
|
+
if (config.circuitBreaker) {
|
|
337
|
+
this.circuitBreaker = new CircuitBreaker(config.circuitBreaker);
|
|
338
|
+
}
|
|
339
|
+
if (config.enableRateLimitTracking) {
|
|
340
|
+
this.rateLimitTracker = new RateLimitTracker();
|
|
341
|
+
}
|
|
126
342
|
}
|
|
127
343
|
/**
|
|
128
344
|
* Make an HTTP request with timeout and error handling
|
|
129
345
|
*/
|
|
130
346
|
async request(endpoint, options = {}, serviceURL, requestOptions) {
|
|
131
|
-
const
|
|
347
|
+
const requestId = generateUUID();
|
|
348
|
+
if (this.circuitBreaker && !this.circuitBreaker.canExecute()) {
|
|
349
|
+
const error = new CircuitBreakerOpenError();
|
|
350
|
+
error.requestId = requestId;
|
|
351
|
+
throw error;
|
|
352
|
+
}
|
|
353
|
+
if (this.rateLimitTracker && this.rateLimitTracker.isLimitExceeded()) {
|
|
354
|
+
const status = this.rateLimitTracker.getStatus();
|
|
355
|
+
const error = new RateLimitError(status.retryAfter ?? void 0);
|
|
356
|
+
error.requestId = requestId;
|
|
357
|
+
throw error;
|
|
358
|
+
}
|
|
359
|
+
const baseURL = this.config.environment === "sandbox" && this.config.sandboxBaseURL ? this.config.sandboxBaseURL : serviceURL || this.config.baseURL;
|
|
360
|
+
const url = `${serviceURL || baseURL}${endpoint}`;
|
|
132
361
|
const headers = {
|
|
133
362
|
"Content-Type": "application/json",
|
|
134
363
|
"X-Tenant-ID": this.config.tenantId,
|
|
135
364
|
"X-SDK-Version": `vesant-sdk-ts/${SDK_VERSION}`,
|
|
365
|
+
"X-Request-ID": requestId,
|
|
136
366
|
...this.config.headers,
|
|
137
367
|
...options.headers || {}
|
|
138
368
|
};
|
|
139
369
|
if (this.config.apiKey) {
|
|
140
370
|
headers["Authorization"] = `Bearer ${this.config.apiKey}`;
|
|
141
371
|
}
|
|
372
|
+
if (this.config.environment === "sandbox") {
|
|
373
|
+
headers["X-Sandbox"] = "true";
|
|
374
|
+
}
|
|
375
|
+
const method = (options.method || "GET").toUpperCase();
|
|
376
|
+
if (["POST", "PUT", "PATCH"].includes(method)) {
|
|
377
|
+
headers["Idempotency-Key"] = requestOptions?.idempotencyKey || generateUUID();
|
|
378
|
+
}
|
|
142
379
|
const controller = new AbortController();
|
|
143
380
|
const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
|
|
144
381
|
if (requestOptions?.signal) {
|
|
@@ -166,6 +403,9 @@ var BaseClient = class {
|
|
|
166
403
|
signal: controller.signal
|
|
167
404
|
});
|
|
168
405
|
clearTimeout(timeoutId);
|
|
406
|
+
if (this.rateLimitTracker) {
|
|
407
|
+
this.rateLimitTracker.updateFromHeaders(response.headers);
|
|
408
|
+
}
|
|
169
409
|
let data;
|
|
170
410
|
try {
|
|
171
411
|
data = await response.json();
|
|
@@ -174,13 +414,15 @@ var BaseClient = class {
|
|
|
174
414
|
this.handleErrorResponse(response.status, {
|
|
175
415
|
error: `HTTP ${response.status}`,
|
|
176
416
|
message: response.statusText
|
|
177
|
-
});
|
|
417
|
+
}, requestId);
|
|
178
418
|
}
|
|
419
|
+
this.circuitBreaker?.onSuccess();
|
|
179
420
|
return void 0;
|
|
180
421
|
}
|
|
181
422
|
if (!response.ok) {
|
|
182
|
-
this.handleErrorResponse(response.status, data || {});
|
|
423
|
+
this.handleErrorResponse(response.status, data || {}, requestId);
|
|
183
424
|
}
|
|
425
|
+
this.circuitBreaker?.onSuccess();
|
|
184
426
|
let result = data;
|
|
185
427
|
for (const interceptor of this.interceptors) {
|
|
186
428
|
if (interceptor.onResponse) {
|
|
@@ -193,6 +435,14 @@ var BaseClient = class {
|
|
|
193
435
|
return result;
|
|
194
436
|
} catch (error) {
|
|
195
437
|
clearTimeout(timeoutId);
|
|
438
|
+
if (error instanceof CGSError && error.statusCode && error.statusCode >= 500) {
|
|
439
|
+
this.circuitBreaker?.onFailure();
|
|
440
|
+
} else if (error instanceof NetworkError || error instanceof TimeoutError) {
|
|
441
|
+
this.circuitBreaker?.onFailure();
|
|
442
|
+
}
|
|
443
|
+
if (error instanceof CGSError && !error.requestId) {
|
|
444
|
+
error.requestId = requestId;
|
|
445
|
+
}
|
|
196
446
|
if (error instanceof Error) {
|
|
197
447
|
for (const interceptor of this.interceptors) {
|
|
198
448
|
if (interceptor.onError) {
|
|
@@ -203,15 +453,23 @@ var BaseClient = class {
|
|
|
203
453
|
if (error instanceof Error) {
|
|
204
454
|
if (error.name === "AbortError") {
|
|
205
455
|
if (requestOptions?.signal?.aborted) {
|
|
206
|
-
|
|
456
|
+
const abortError = new CGSError("Request aborted", "REQUEST_ABORTED");
|
|
457
|
+
abortError.requestId = requestId;
|
|
458
|
+
throw abortError;
|
|
207
459
|
}
|
|
208
|
-
|
|
460
|
+
this.circuitBreaker?.onFailure();
|
|
461
|
+
const timeoutError = new TimeoutError(this.config.timeout);
|
|
462
|
+
timeoutError.requestId = requestId;
|
|
463
|
+
throw timeoutError;
|
|
209
464
|
}
|
|
210
465
|
if (error instanceof CGSError) {
|
|
211
466
|
throw error;
|
|
212
467
|
}
|
|
213
468
|
}
|
|
214
|
-
|
|
469
|
+
const networkError = new NetworkError("Network request failed", error);
|
|
470
|
+
networkError.requestId = requestId;
|
|
471
|
+
this.circuitBreaker?.onFailure();
|
|
472
|
+
throw networkError;
|
|
215
473
|
}
|
|
216
474
|
}
|
|
217
475
|
/**
|
|
@@ -250,29 +508,36 @@ var BaseClient = class {
|
|
|
250
508
|
/**
|
|
251
509
|
* Handle error responses from API
|
|
252
510
|
*/
|
|
253
|
-
handleErrorResponse(status, data) {
|
|
511
|
+
handleErrorResponse(status, data, requestId) {
|
|
254
512
|
const message = data.error || data.message || `HTTP ${status}`;
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
513
|
+
const createError = () => {
|
|
514
|
+
switch (status) {
|
|
515
|
+
case 400:
|
|
516
|
+
return new CGSError(message, "BAD_REQUEST", 400, data);
|
|
517
|
+
case 401:
|
|
518
|
+
return new AuthenticationError(message);
|
|
519
|
+
case 403:
|
|
520
|
+
return new CGSError(message, "FORBIDDEN", 403, data);
|
|
521
|
+
case 404:
|
|
522
|
+
return new CGSError(message, "NOT_FOUND", 404, data);
|
|
523
|
+
case 429: {
|
|
524
|
+
const retryAfter = data.retry_after || data.retryAfter;
|
|
525
|
+
return new RateLimitError(retryAfter);
|
|
526
|
+
}
|
|
527
|
+
case 500:
|
|
528
|
+
case 502:
|
|
529
|
+
case 503:
|
|
530
|
+
case 504:
|
|
531
|
+
return new ServiceUnavailableError(message);
|
|
532
|
+
default:
|
|
533
|
+
return new CGSError(message, "UNKNOWN_ERROR", status, data);
|
|
267
534
|
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
throw new ServiceUnavailableError(message);
|
|
273
|
-
default:
|
|
274
|
-
throw new CGSError(message, "UNKNOWN_ERROR", status, data);
|
|
535
|
+
};
|
|
536
|
+
const error = createError();
|
|
537
|
+
if (requestId) {
|
|
538
|
+
error.requestId = requestId;
|
|
275
539
|
}
|
|
540
|
+
throw error;
|
|
276
541
|
}
|
|
277
542
|
/**
|
|
278
543
|
* Build query string from parameters
|
|
@@ -316,6 +581,18 @@ var BaseClient = class {
|
|
|
316
581
|
getConfig() {
|
|
317
582
|
return { ...this.config };
|
|
318
583
|
}
|
|
584
|
+
/**
|
|
585
|
+
* Get rate limit status from tracked response headers.
|
|
586
|
+
*/
|
|
587
|
+
getRateLimitStatus() {
|
|
588
|
+
return this.rateLimitTracker?.getStatus() ?? null;
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* Get circuit breaker status.
|
|
592
|
+
*/
|
|
593
|
+
getCircuitBreakerStatus() {
|
|
594
|
+
return this.circuitBreaker?.getStatus() ?? null;
|
|
595
|
+
}
|
|
319
596
|
/**
|
|
320
597
|
* Health check endpoint
|
|
321
598
|
*/
|
|
@@ -324,73 +601,46 @@ var BaseClient = class {
|
|
|
324
601
|
}
|
|
325
602
|
};
|
|
326
603
|
|
|
327
|
-
// src/
|
|
328
|
-
function
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
return v.toString(16);
|
|
336
|
-
});
|
|
604
|
+
// src/core/webhook-utils.ts
|
|
605
|
+
async function verifyWebhookSignature(payload, signature, secret) {
|
|
606
|
+
const hexDigest = await computeHmacSha256(payload, secret);
|
|
607
|
+
const expectedPrefixed = `sha256=${hexDigest}`;
|
|
608
|
+
if (signature.startsWith("sha256=")) {
|
|
609
|
+
return constantTimeEqual(signature, expectedPrefixed);
|
|
610
|
+
}
|
|
611
|
+
return constantTimeEqual(signature, hexDigest);
|
|
337
612
|
}
|
|
338
|
-
function
|
|
339
|
-
if (typeof
|
|
340
|
-
|
|
613
|
+
async function computeHmacSha256(message, secret) {
|
|
614
|
+
if (typeof globalThis.crypto !== "undefined" && globalThis.crypto.subtle) {
|
|
615
|
+
const encoder = new TextEncoder();
|
|
616
|
+
const key = await globalThis.crypto.subtle.importKey(
|
|
617
|
+
"raw",
|
|
618
|
+
encoder.encode(secret),
|
|
619
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
620
|
+
false,
|
|
621
|
+
["sign"]
|
|
622
|
+
);
|
|
623
|
+
const sig = await globalThis.crypto.subtle.sign("HMAC", key, encoder.encode(message));
|
|
624
|
+
return Array.from(new Uint8Array(sig)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
341
625
|
}
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
626
|
+
try {
|
|
627
|
+
const { createHmac } = await import('crypto');
|
|
628
|
+
return createHmac("sha256", secret).update(message).digest("hex");
|
|
629
|
+
} catch {
|
|
630
|
+
throw new Error(
|
|
631
|
+
"No crypto implementation available. Requires Web Crypto API or Node.js crypto module."
|
|
632
|
+
);
|
|
347
633
|
}
|
|
348
|
-
return deviceId;
|
|
349
634
|
}
|
|
350
|
-
function
|
|
351
|
-
if (
|
|
352
|
-
return
|
|
353
|
-
}
|
|
354
|
-
const ua = navigator.userAgent;
|
|
355
|
-
let browser = "unknown";
|
|
356
|
-
let browserVersion = "";
|
|
357
|
-
let os = "unknown";
|
|
358
|
-
let osVersion = "";
|
|
359
|
-
if (ua.includes("Firefox/")) {
|
|
360
|
-
browser = "Firefox";
|
|
361
|
-
browserVersion = ua.match(/Firefox\/([\d.]+)/)?.[1] || "";
|
|
362
|
-
} else if (ua.includes("Edg/")) {
|
|
363
|
-
browser = "Edge";
|
|
364
|
-
browserVersion = ua.match(/Edg\/([\d.]+)/)?.[1] || "";
|
|
365
|
-
} else if (ua.includes("Chrome/")) {
|
|
366
|
-
browser = "Chrome";
|
|
367
|
-
browserVersion = ua.match(/Chrome\/([\d.]+)/)?.[1] || "";
|
|
368
|
-
} else if (ua.includes("Safari/") && !ua.includes("Chrome")) {
|
|
369
|
-
browser = "Safari";
|
|
370
|
-
browserVersion = ua.match(/Version\/([\d.]+)/)?.[1] || "";
|
|
371
|
-
} else if (ua.includes("Opera") || ua.includes("OPR/")) {
|
|
372
|
-
browser = "Opera";
|
|
373
|
-
browserVersion = ua.match(/(?:Opera|OPR)\/([\d.]+)/)?.[1] || "";
|
|
635
|
+
function constantTimeEqual(a, b) {
|
|
636
|
+
if (a.length !== b.length) {
|
|
637
|
+
return false;
|
|
374
638
|
}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
else if (ua.includes("Windows NT 6.3")) osVersion = "8.1";
|
|
379
|
-
else if (ua.includes("Windows NT 6.2")) osVersion = "8";
|
|
380
|
-
else if (ua.includes("Windows NT 6.1")) osVersion = "7";
|
|
381
|
-
} else if (ua.includes("Mac OS X")) {
|
|
382
|
-
os = "macOS";
|
|
383
|
-
osVersion = ua.match(/Mac OS X ([\d_]+)/)?.[1]?.replace(/_/g, ".") || "";
|
|
384
|
-
} else if (ua.includes("Linux")) {
|
|
385
|
-
os = "Linux";
|
|
386
|
-
} else if (ua.includes("Android")) {
|
|
387
|
-
os = "Android";
|
|
388
|
-
osVersion = ua.match(/Android ([\d.]+)/)?.[1] || "";
|
|
389
|
-
} else if (ua.includes("iOS") || ua.includes("iPhone") || ua.includes("iPad")) {
|
|
390
|
-
os = "iOS";
|
|
391
|
-
osVersion = ua.match(/OS ([\d_]+)/)?.[1]?.replace(/_/g, ".") || "";
|
|
639
|
+
let result = 0;
|
|
640
|
+
for (let i = 0; i < a.length; i++) {
|
|
641
|
+
result |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
392
642
|
}
|
|
393
|
-
return
|
|
643
|
+
return result === 0;
|
|
394
644
|
}
|
|
395
645
|
|
|
396
646
|
// src/geolocation/ciphertext.ts
|
|
@@ -2514,6 +2764,297 @@ var KycClient = class extends BaseClient {
|
|
|
2514
2764
|
// ============================================================================
|
|
2515
2765
|
};
|
|
2516
2766
|
|
|
2517
|
-
|
|
2767
|
+
// src/decisions/client.ts
|
|
2768
|
+
var DecisionsClient = class extends BaseClient {
|
|
2769
|
+
/**
|
|
2770
|
+
* Record a new decision for a customer.
|
|
2771
|
+
*/
|
|
2772
|
+
async recordDecision(request, requestOptions) {
|
|
2773
|
+
return this.requestWithRetry(
|
|
2774
|
+
"/api/v1/decisions",
|
|
2775
|
+
{ method: "POST", body: JSON.stringify(request) },
|
|
2776
|
+
void 0,
|
|
2777
|
+
void 0,
|
|
2778
|
+
requestOptions
|
|
2779
|
+
);
|
|
2780
|
+
}
|
|
2781
|
+
/**
|
|
2782
|
+
* Get decisions for a customer.
|
|
2783
|
+
*/
|
|
2784
|
+
async getDecisions(customerId, filters, requestOptions) {
|
|
2785
|
+
const queryString = this.buildQueryString({
|
|
2786
|
+
customer_id: customerId,
|
|
2787
|
+
...filters
|
|
2788
|
+
});
|
|
2789
|
+
return this.requestWithRetry(
|
|
2790
|
+
`/api/v1/decisions${queryString}`,
|
|
2791
|
+
{ method: "GET" },
|
|
2792
|
+
void 0,
|
|
2793
|
+
void 0,
|
|
2794
|
+
requestOptions
|
|
2795
|
+
);
|
|
2796
|
+
}
|
|
2797
|
+
/**
|
|
2798
|
+
* Get a specific decision by ID.
|
|
2799
|
+
*/
|
|
2800
|
+
async getDecision(decisionId, requestOptions) {
|
|
2801
|
+
return this.requestWithRetry(
|
|
2802
|
+
`/api/v1/decisions/${encodeURIComponent(decisionId)}`,
|
|
2803
|
+
{ method: "GET" },
|
|
2804
|
+
void 0,
|
|
2805
|
+
void 0,
|
|
2806
|
+
requestOptions
|
|
2807
|
+
);
|
|
2808
|
+
}
|
|
2809
|
+
/**
|
|
2810
|
+
* Apply a label to a customer.
|
|
2811
|
+
*/
|
|
2812
|
+
async applyLabel(request, requestOptions) {
|
|
2813
|
+
return this.requestWithRetry(
|
|
2814
|
+
"/api/v1/labels",
|
|
2815
|
+
{ method: "POST", body: JSON.stringify(request) },
|
|
2816
|
+
void 0,
|
|
2817
|
+
void 0,
|
|
2818
|
+
requestOptions
|
|
2819
|
+
);
|
|
2820
|
+
}
|
|
2821
|
+
/**
|
|
2822
|
+
* Remove a label from a customer.
|
|
2823
|
+
*/
|
|
2824
|
+
async removeLabel(customerId, requestOptions) {
|
|
2825
|
+
return this.requestWithRetry(
|
|
2826
|
+
`/api/v1/labels/${encodeURIComponent(customerId)}`,
|
|
2827
|
+
{ method: "DELETE" },
|
|
2828
|
+
void 0,
|
|
2829
|
+
void 0,
|
|
2830
|
+
requestOptions
|
|
2831
|
+
);
|
|
2832
|
+
}
|
|
2833
|
+
/**
|
|
2834
|
+
* Get labels for a customer.
|
|
2835
|
+
*/
|
|
2836
|
+
async getLabels(customerId, requestOptions) {
|
|
2837
|
+
const queryString = this.buildQueryString({ customer_id: customerId });
|
|
2838
|
+
return this.requestWithRetry(
|
|
2839
|
+
`/api/v1/labels${queryString}`,
|
|
2840
|
+
{ method: "GET" },
|
|
2841
|
+
void 0,
|
|
2842
|
+
void 0,
|
|
2843
|
+
requestOptions
|
|
2844
|
+
);
|
|
2845
|
+
}
|
|
2846
|
+
};
|
|
2847
|
+
|
|
2848
|
+
// src/scores/client.ts
|
|
2849
|
+
var ScoresClient = class extends BaseClient {
|
|
2850
|
+
/**
|
|
2851
|
+
* Get the current risk score for a customer.
|
|
2852
|
+
*/
|
|
2853
|
+
async getScore(customerId, requestOptions) {
|
|
2854
|
+
return this.requestWithRetry(
|
|
2855
|
+
`/api/v1/scores/${encodeURIComponent(customerId)}`,
|
|
2856
|
+
{ method: "GET" },
|
|
2857
|
+
void 0,
|
|
2858
|
+
void 0,
|
|
2859
|
+
requestOptions
|
|
2860
|
+
);
|
|
2861
|
+
}
|
|
2862
|
+
/**
|
|
2863
|
+
* Get a detailed score breakdown for a customer.
|
|
2864
|
+
*/
|
|
2865
|
+
async getScoreBreakdown(customerId, requestOptions) {
|
|
2866
|
+
return this.requestWithRetry(
|
|
2867
|
+
`/api/v1/scores/${encodeURIComponent(customerId)}/breakdown`,
|
|
2868
|
+
{ method: "GET" },
|
|
2869
|
+
void 0,
|
|
2870
|
+
void 0,
|
|
2871
|
+
requestOptions
|
|
2872
|
+
);
|
|
2873
|
+
}
|
|
2874
|
+
};
|
|
2875
|
+
var WorkflowClient = class extends BaseClient {
|
|
2876
|
+
/**
|
|
2877
|
+
* Get the current workflow status for a customer.
|
|
2878
|
+
*/
|
|
2879
|
+
async getWorkflowStatus(customerId, requestOptions) {
|
|
2880
|
+
return this.requestWithRetry(
|
|
2881
|
+
`/api/v1/workflows/${encodeURIComponent(customerId)}/status`,
|
|
2882
|
+
{ method: "GET" },
|
|
2883
|
+
void 0,
|
|
2884
|
+
void 0,
|
|
2885
|
+
requestOptions
|
|
2886
|
+
);
|
|
2887
|
+
}
|
|
2888
|
+
/**
|
|
2889
|
+
* List workflows with optional filters.
|
|
2890
|
+
*/
|
|
2891
|
+
async listWorkflows(filters, requestOptions) {
|
|
2892
|
+
const queryString = this.buildQueryString(filters || {});
|
|
2893
|
+
return this.requestWithRetry(
|
|
2894
|
+
`/api/v1/workflows${queryString}`,
|
|
2895
|
+
{ method: "GET" },
|
|
2896
|
+
void 0,
|
|
2897
|
+
void 0,
|
|
2898
|
+
requestOptions
|
|
2899
|
+
);
|
|
2900
|
+
}
|
|
2901
|
+
/**
|
|
2902
|
+
* Get a specific workflow by ID.
|
|
2903
|
+
*/
|
|
2904
|
+
async getWorkflow(workflowId, requestOptions) {
|
|
2905
|
+
return this.requestWithRetry(
|
|
2906
|
+
`/api/v1/workflows/${encodeURIComponent(workflowId)}`,
|
|
2907
|
+
{ method: "GET" },
|
|
2908
|
+
void 0,
|
|
2909
|
+
void 0,
|
|
2910
|
+
requestOptions
|
|
2911
|
+
);
|
|
2912
|
+
}
|
|
2913
|
+
};
|
|
2914
|
+
|
|
2915
|
+
// src/webhooks/handler.ts
|
|
2916
|
+
var WebhookHandler = class {
|
|
2917
|
+
constructor(config) {
|
|
2918
|
+
this.handlers = /* @__PURE__ */ new Map();
|
|
2919
|
+
this.anyHandlers = [];
|
|
2920
|
+
this.secret = config.secret;
|
|
2921
|
+
this.tolerance = config.tolerance ?? 3e5;
|
|
2922
|
+
}
|
|
2923
|
+
/**
|
|
2924
|
+
* Register a handler for a specific event type.
|
|
2925
|
+
*/
|
|
2926
|
+
on(eventType, handler) {
|
|
2927
|
+
const existing = this.handlers.get(eventType) || [];
|
|
2928
|
+
existing.push(handler);
|
|
2929
|
+
this.handlers.set(eventType, existing);
|
|
2930
|
+
return this;
|
|
2931
|
+
}
|
|
2932
|
+
/**
|
|
2933
|
+
* Register a catch-all handler for all event types.
|
|
2934
|
+
*/
|
|
2935
|
+
onAny(handler) {
|
|
2936
|
+
this.anyHandlers.push(handler);
|
|
2937
|
+
return this;
|
|
2938
|
+
}
|
|
2939
|
+
/**
|
|
2940
|
+
* Verify signature and parse the webhook body.
|
|
2941
|
+
*/
|
|
2942
|
+
async verifyAndParse(body, signature) {
|
|
2943
|
+
const isValid = await verifyWebhookSignature(body, signature, this.secret);
|
|
2944
|
+
if (!isValid) {
|
|
2945
|
+
throw new Error("Invalid webhook signature");
|
|
2946
|
+
}
|
|
2947
|
+
return this.parseAndValidate(body);
|
|
2948
|
+
}
|
|
2949
|
+
/**
|
|
2950
|
+
* Verify signature, parse, and dispatch to registered handlers.
|
|
2951
|
+
*/
|
|
2952
|
+
async handle(body, signature) {
|
|
2953
|
+
const event = await this.verifyAndParse(body, signature);
|
|
2954
|
+
await this.dispatch(event);
|
|
2955
|
+
}
|
|
2956
|
+
/**
|
|
2957
|
+
* Parse an event without signature verification (for testing).
|
|
2958
|
+
*/
|
|
2959
|
+
parseEvent(body) {
|
|
2960
|
+
return this.parseAndValidate(body);
|
|
2961
|
+
}
|
|
2962
|
+
parseAndValidate(body) {
|
|
2963
|
+
const event = JSON.parse(body);
|
|
2964
|
+
if (!event.type || !event.id || !event.timestamp) {
|
|
2965
|
+
throw new Error("Invalid webhook event: missing required fields (type, id, timestamp)");
|
|
2966
|
+
}
|
|
2967
|
+
if (this.tolerance > 0) {
|
|
2968
|
+
const eventTime = new Date(event.timestamp).getTime();
|
|
2969
|
+
const now = Date.now();
|
|
2970
|
+
if (Math.abs(now - eventTime) > this.tolerance) {
|
|
2971
|
+
throw new Error(
|
|
2972
|
+
`Webhook event timestamp is outside tolerance window (${this.tolerance}ms)`
|
|
2973
|
+
);
|
|
2974
|
+
}
|
|
2975
|
+
}
|
|
2976
|
+
return event;
|
|
2977
|
+
}
|
|
2978
|
+
async dispatch(event) {
|
|
2979
|
+
const typeHandlers = this.handlers.get(event.type) || [];
|
|
2980
|
+
const allHandlers = [...typeHandlers, ...this.anyHandlers];
|
|
2981
|
+
for (const handler of allHandlers) {
|
|
2982
|
+
await handler(event);
|
|
2983
|
+
}
|
|
2984
|
+
}
|
|
2985
|
+
};
|
|
2986
|
+
|
|
2987
|
+
// src/webhooks/middleware.ts
|
|
2988
|
+
function createWebhookMiddleware(options) {
|
|
2989
|
+
const handler = buildHandler(options);
|
|
2990
|
+
const signatureHeader = options.signatureHeader || "x-webhook-signature";
|
|
2991
|
+
return async (req, res, next) => {
|
|
2992
|
+
try {
|
|
2993
|
+
const body = typeof req.body === "string" ? req.body : req.body.toString("utf-8");
|
|
2994
|
+
const signature = req.headers[signatureHeader];
|
|
2995
|
+
if (!signature || typeof signature !== "string") {
|
|
2996
|
+
res.status(401).json({ error: "Missing webhook signature" });
|
|
2997
|
+
return;
|
|
2998
|
+
}
|
|
2999
|
+
await handler.handle(body, signature);
|
|
3000
|
+
res.status(200).json({ received: true });
|
|
3001
|
+
} catch (error) {
|
|
3002
|
+
const message = error instanceof Error ? error.message : "Webhook processing failed";
|
|
3003
|
+
if (message.includes("signature")) {
|
|
3004
|
+
res.status(401).json({ error: message });
|
|
3005
|
+
} else if (message.includes("tolerance") || message.includes("timestamp")) {
|
|
3006
|
+
res.status(400).json({ error: message });
|
|
3007
|
+
} else if (next) {
|
|
3008
|
+
next(error);
|
|
3009
|
+
} else {
|
|
3010
|
+
res.status(500).json({ error: message });
|
|
3011
|
+
}
|
|
3012
|
+
}
|
|
3013
|
+
};
|
|
3014
|
+
}
|
|
3015
|
+
function createNextWebhookHandler(options) {
|
|
3016
|
+
const handler = buildHandler(options);
|
|
3017
|
+
const signatureHeader = options.signatureHeader || "x-webhook-signature";
|
|
3018
|
+
return async (request) => {
|
|
3019
|
+
try {
|
|
3020
|
+
const body = await request.text();
|
|
3021
|
+
const signature = request.headers.get(signatureHeader);
|
|
3022
|
+
if (!signature) {
|
|
3023
|
+
return new Response(JSON.stringify({ error: "Missing webhook signature" }), {
|
|
3024
|
+
status: 401,
|
|
3025
|
+
headers: { "Content-Type": "application/json" }
|
|
3026
|
+
});
|
|
3027
|
+
}
|
|
3028
|
+
await handler.handle(body, signature);
|
|
3029
|
+
return new Response(JSON.stringify({ received: true }), {
|
|
3030
|
+
status: 200,
|
|
3031
|
+
headers: { "Content-Type": "application/json" }
|
|
3032
|
+
});
|
|
3033
|
+
} catch (error) {
|
|
3034
|
+
const message = error instanceof Error ? error.message : "Webhook processing failed";
|
|
3035
|
+
const status = message.includes("signature") ? 401 : message.includes("tolerance") || message.includes("timestamp") ? 400 : 500;
|
|
3036
|
+
return new Response(JSON.stringify({ error: message }), {
|
|
3037
|
+
status,
|
|
3038
|
+
headers: { "Content-Type": "application/json" }
|
|
3039
|
+
});
|
|
3040
|
+
}
|
|
3041
|
+
};
|
|
3042
|
+
}
|
|
3043
|
+
function buildHandler(options) {
|
|
3044
|
+
const handler = new WebhookHandler(options);
|
|
3045
|
+
if (options.handlers) {
|
|
3046
|
+
for (const [eventType, eventHandler] of Object.entries(options.handlers)) {
|
|
3047
|
+
if (eventHandler) {
|
|
3048
|
+
handler.on(eventType, eventHandler);
|
|
3049
|
+
}
|
|
3050
|
+
}
|
|
3051
|
+
}
|
|
3052
|
+
if (options.onAny) {
|
|
3053
|
+
handler.onAny(options.onAny);
|
|
3054
|
+
}
|
|
3055
|
+
return handler;
|
|
3056
|
+
}
|
|
3057
|
+
|
|
3058
|
+
export { AuthenticationError, BaseClient, CGSError, CircuitBreaker, CircuitBreakerOpenError, ComplianceBlockedError, ComplianceClient, ComplianceError, DEFAULT_CURRENCY_RATES, DecisionsClient, GeolocationClient, KycClient, NetworkError, RateLimitError, RateLimitTracker, RiskProfileClient, SDK_VERSION, ScoresClient, ServiceUnavailableError, TimeoutError, ValidationError, WebhookHandler, WorkflowClient, createConsoleLogger, createNextWebhookHandler, createWebhookMiddleware, decodeCipherText, generateCipherText, isCipherTextExpired, noopLogger, verifyWebhookSignature };
|
|
2518
3059
|
//# sourceMappingURL=index.mjs.map
|
|
2519
3060
|
//# sourceMappingURL=index.mjs.map
|