vesant-sdk 1.1.1 → 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-i5QtnFIa.d.ts → client-B6fUFAUM.d.mts} +26 -1
- package/dist/{client-BfeDYmrZ.d.mts → client-DoczGA6L.d.ts} +26 -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 +522 -30
- package/dist/compliance/index.js.map +1 -1
- package/dist/compliance/index.mjs +522 -30
- 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 +546 -278
- package/dist/geolocation/index.js.map +1 -1
- package/dist/geolocation/index.mjs +546 -278
- 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 +946 -335
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +937 -336
- 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 +29 -3
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +29 -3
- 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,13 +581,273 @@ 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
|
*/
|
|
322
599
|
async healthCheck() {
|
|
323
600
|
return this.request("/api/v1/health");
|
|
324
601
|
}
|
|
325
|
-
};
|
|
602
|
+
};
|
|
603
|
+
|
|
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);
|
|
612
|
+
}
|
|
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("");
|
|
625
|
+
}
|
|
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
|
+
);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
function constantTimeEqual(a, b) {
|
|
636
|
+
if (a.length !== b.length) {
|
|
637
|
+
return false;
|
|
638
|
+
}
|
|
639
|
+
let result = 0;
|
|
640
|
+
for (let i = 0; i < a.length; i++) {
|
|
641
|
+
result |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
642
|
+
}
|
|
643
|
+
return result === 0;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// src/geolocation/ciphertext.ts
|
|
647
|
+
var CIPHER_TEXT_EXPIRY_MINUTES = 5;
|
|
648
|
+
async function computeHMAC(key, message) {
|
|
649
|
+
if (typeof globalThis.crypto !== "undefined" && globalThis.crypto.subtle) {
|
|
650
|
+
const encoder = new TextEncoder();
|
|
651
|
+
const keyData = encoder.encode(key);
|
|
652
|
+
const msgData = encoder.encode(message);
|
|
653
|
+
const cryptoKey = await globalThis.crypto.subtle.importKey(
|
|
654
|
+
"raw",
|
|
655
|
+
keyData,
|
|
656
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
657
|
+
false,
|
|
658
|
+
["sign"]
|
|
659
|
+
);
|
|
660
|
+
const signature = await globalThis.crypto.subtle.sign("HMAC", cryptoKey, msgData);
|
|
661
|
+
const bytes = new Uint8Array(signature);
|
|
662
|
+
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
663
|
+
}
|
|
664
|
+
const { createHmac } = await import('crypto');
|
|
665
|
+
return createHmac("sha256", key).update(message).digest("hex");
|
|
666
|
+
}
|
|
667
|
+
function getWebGLInfo() {
|
|
668
|
+
if (typeof document === "undefined") return null;
|
|
669
|
+
try {
|
|
670
|
+
const canvas = document.createElement("canvas");
|
|
671
|
+
const gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
|
|
672
|
+
if (!gl) return null;
|
|
673
|
+
const debugInfo = gl.getExtension("WEBGL_debug_renderer_info");
|
|
674
|
+
if (!debugInfo) return null;
|
|
675
|
+
return {
|
|
676
|
+
vendor: gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL) || "",
|
|
677
|
+
renderer: gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL) || ""
|
|
678
|
+
};
|
|
679
|
+
} catch {
|
|
680
|
+
return null;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
function getNetworkInfo() {
|
|
684
|
+
if (typeof navigator === "undefined") return void 0;
|
|
685
|
+
const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
|
|
686
|
+
if (!connection) return void 0;
|
|
687
|
+
return {
|
|
688
|
+
effective_type: connection.effectiveType,
|
|
689
|
+
downlink: connection.downlink,
|
|
690
|
+
rtt: connection.rtt,
|
|
691
|
+
save_data: connection.saveData
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
async function requestGPSLocation(timeout = 1e4, highAccuracy = true) {
|
|
695
|
+
if (typeof navigator === "undefined" || !navigator.geolocation) {
|
|
696
|
+
return null;
|
|
697
|
+
}
|
|
698
|
+
return new Promise((resolve) => {
|
|
699
|
+
navigator.geolocation.getCurrentPosition(
|
|
700
|
+
(position) => {
|
|
701
|
+
const { latitude, longitude, accuracy } = position.coords;
|
|
702
|
+
if (latitude < -90 || latitude > 90) {
|
|
703
|
+
resolve(null);
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
if (longitude < -180 || longitude > 180) {
|
|
707
|
+
resolve(null);
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
if (accuracy !== null && accuracy !== void 0 && accuracy <= 0) {
|
|
711
|
+
resolve(null);
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
resolve({
|
|
715
|
+
latitude,
|
|
716
|
+
longitude,
|
|
717
|
+
accuracy,
|
|
718
|
+
altitude: position.coords.altitude ?? void 0,
|
|
719
|
+
altitude_accuracy: position.coords.altitudeAccuracy ?? void 0,
|
|
720
|
+
heading: position.coords.heading ?? void 0,
|
|
721
|
+
speed: position.coords.speed ?? void 0,
|
|
722
|
+
timestamp: position.timestamp
|
|
723
|
+
});
|
|
724
|
+
},
|
|
725
|
+
() => {
|
|
726
|
+
resolve(null);
|
|
727
|
+
},
|
|
728
|
+
{
|
|
729
|
+
enableHighAccuracy: highAccuracy,
|
|
730
|
+
timeout,
|
|
731
|
+
maximumAge: 0
|
|
732
|
+
}
|
|
733
|
+
);
|
|
734
|
+
});
|
|
735
|
+
}
|
|
736
|
+
function collectDeviceData(includeWebGL) {
|
|
737
|
+
const browserInfo = getBrowserInfo();
|
|
738
|
+
const webglInfo = includeWebGL ? getWebGLInfo() : null;
|
|
739
|
+
return {
|
|
740
|
+
device_id: generateDeviceId(),
|
|
741
|
+
user_agent: typeof navigator !== "undefined" ? navigator.userAgent : "",
|
|
742
|
+
platform: typeof navigator !== "undefined" ? navigator.platform : "",
|
|
743
|
+
browser: browserInfo.browser,
|
|
744
|
+
browser_version: browserInfo.browser_version,
|
|
745
|
+
os: browserInfo.os,
|
|
746
|
+
os_version: browserInfo.os_version,
|
|
747
|
+
screen_resolution: typeof screen !== "undefined" ? `${screen.width}x${screen.height}` : void 0,
|
|
748
|
+
language: typeof navigator !== "undefined" ? navigator.language : void 0,
|
|
749
|
+
timezone: Intl?.DateTimeFormat?.()?.resolvedOptions?.()?.timeZone,
|
|
750
|
+
color_depth: typeof screen !== "undefined" ? screen.colorDepth : void 0,
|
|
751
|
+
hardware_concurrency: typeof navigator !== "undefined" ? navigator.hardwareConcurrency : void 0,
|
|
752
|
+
device_memory: typeof navigator !== "undefined" ? navigator.deviceMemory : void 0,
|
|
753
|
+
touch_support: typeof navigator !== "undefined" ? navigator.maxTouchPoints > 0 : void 0,
|
|
754
|
+
webgl_vendor: webglInfo?.vendor,
|
|
755
|
+
webgl_renderer: webglInfo?.renderer
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
function encodePayload(payload) {
|
|
759
|
+
const json = JSON.stringify(payload);
|
|
760
|
+
if (typeof btoa !== "undefined") {
|
|
761
|
+
return btoa(unescape(encodeURIComponent(json)));
|
|
762
|
+
} else if (typeof Buffer !== "undefined") {
|
|
763
|
+
return Buffer.from(json, "utf-8").toString("base64");
|
|
764
|
+
}
|
|
765
|
+
throw new Error("No base64 encoding method available");
|
|
766
|
+
}
|
|
767
|
+
async function generateCipherText(options, config) {
|
|
768
|
+
const warnings = [];
|
|
769
|
+
let requestLocation = options.requestLocation ?? false;
|
|
770
|
+
if (config?.require_gps) {
|
|
771
|
+
const reason = options.reason;
|
|
772
|
+
if (reason === "login" && config.require_gps.login || reason === "registration" && config.require_gps.registration || reason === "transaction" && config.require_gps.transaction) {
|
|
773
|
+
requestLocation = true;
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
const deviceData = collectDeviceData(options.includeWebGL !== false);
|
|
777
|
+
let locationData;
|
|
778
|
+
if (requestLocation) {
|
|
779
|
+
const location = await requestGPSLocation(
|
|
780
|
+
options.locationTimeout || 1e4,
|
|
781
|
+
options.highAccuracy !== false
|
|
782
|
+
);
|
|
783
|
+
if (location) {
|
|
784
|
+
locationData = location;
|
|
785
|
+
} else {
|
|
786
|
+
warnings.push("GPS location not available or permission denied");
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
let networkData;
|
|
790
|
+
if (options.includeNetworkInfo !== false) {
|
|
791
|
+
networkData = getNetworkInfo();
|
|
792
|
+
}
|
|
793
|
+
const now = /* @__PURE__ */ new Date();
|
|
794
|
+
const expiry = new Date(now.getTime() + CIPHER_TEXT_EXPIRY_MINUTES * 60 * 1e3);
|
|
795
|
+
const payload = {
|
|
796
|
+
device: deviceData,
|
|
797
|
+
location: locationData,
|
|
798
|
+
network: networkData,
|
|
799
|
+
metadata: {
|
|
800
|
+
collected_at: now.toISOString(),
|
|
801
|
+
sdk_version: SDK_VERSION,
|
|
802
|
+
collection_reason: options.reason,
|
|
803
|
+
page_url: typeof window !== "undefined" ? window.location.href : void 0,
|
|
804
|
+
referrer: typeof document !== "undefined" ? document.referrer || void 0 : void 0
|
|
805
|
+
}
|
|
806
|
+
};
|
|
807
|
+
const encoded = encodePayload(payload);
|
|
808
|
+
const timestamp = now.getTime().toString(36);
|
|
809
|
+
let cipherText;
|
|
810
|
+
const hmacKey = options.signingKey || options.apiKey;
|
|
811
|
+
if (hmacKey) {
|
|
812
|
+
const message = `02.${timestamp}.${encoded}`;
|
|
813
|
+
const hmac = await computeHMAC(hmacKey, message);
|
|
814
|
+
cipherText = `${message}.${hmac}`;
|
|
815
|
+
} else {
|
|
816
|
+
cipherText = `01.${timestamp}.${encoded}`;
|
|
817
|
+
}
|
|
818
|
+
return {
|
|
819
|
+
cipherText,
|
|
820
|
+
locationCaptured: !!locationData,
|
|
821
|
+
warnings: warnings.length > 0 ? warnings : void 0,
|
|
822
|
+
generatedAt: now.toISOString(),
|
|
823
|
+
expiresAt: expiry.toISOString()
|
|
824
|
+
};
|
|
825
|
+
}
|
|
826
|
+
function decodeCipherText(cipherText) {
|
|
827
|
+
try {
|
|
828
|
+
const parts = cipherText.split(".");
|
|
829
|
+
if (parts.length !== 3 && parts.length !== 4) return null;
|
|
830
|
+
const [version, , encodedB64] = parts;
|
|
831
|
+
if (version !== "01" && version !== "02") return null;
|
|
832
|
+
const json = decodeURIComponent(escape(atob(encodedB64)));
|
|
833
|
+
return JSON.parse(json);
|
|
834
|
+
} catch {
|
|
835
|
+
return null;
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
function isCipherTextExpired(cipherText) {
|
|
839
|
+
try {
|
|
840
|
+
const parts = cipherText.split(".");
|
|
841
|
+
if (parts.length !== 3 && parts.length !== 4) return true;
|
|
842
|
+
const timestampB36 = parts[1];
|
|
843
|
+
const timestamp = parseInt(timestampB36, 36);
|
|
844
|
+
const now = Date.now();
|
|
845
|
+
const expiryMs = CIPHER_TEXT_EXPIRY_MINUTES * 60 * 1e3;
|
|
846
|
+
return now - timestamp > expiryMs;
|
|
847
|
+
} catch {
|
|
848
|
+
return true;
|
|
849
|
+
}
|
|
850
|
+
}
|
|
326
851
|
|
|
327
852
|
// src/geolocation/client.ts
|
|
328
853
|
var GeolocationClient = class extends BaseClient {
|
|
@@ -424,7 +949,11 @@ var GeolocationClient = class extends BaseClient {
|
|
|
424
949
|
* ```
|
|
425
950
|
*/
|
|
426
951
|
async getGPSConfig(requestOptions) {
|
|
427
|
-
|
|
952
|
+
const config = await this.requestWithRetry("/api/v1/geo/config", void 0, void 0, void 0, requestOptions);
|
|
953
|
+
if (config.signing_key) {
|
|
954
|
+
this.cachedSigningKey = config.signing_key;
|
|
955
|
+
}
|
|
956
|
+
return config;
|
|
428
957
|
}
|
|
429
958
|
// ============================================================================
|
|
430
959
|
// CipherText Validation
|
|
@@ -628,314 +1157,95 @@ var GeolocationClient = class extends BaseClient {
|
|
|
628
1157
|
}
|
|
629
1158
|
/**
|
|
630
1159
|
* Resend notification for a location request
|
|
631
|
-
*
|
|
632
|
-
* @param requestId - Location request ID
|
|
633
|
-
* @param contact - Email or phone to send to
|
|
634
|
-
*/
|
|
635
|
-
async resendLocationRequest(requestId, contact, requestOptions) {
|
|
636
|
-
await this.request(`/api/v1/location-requests/${requestId}/resend`, {
|
|
637
|
-
method: "POST",
|
|
638
|
-
body: JSON.stringify(contact)
|
|
639
|
-
}, void 0, requestOptions);
|
|
640
|
-
}
|
|
641
|
-
// ============================================================================
|
|
642
|
-
// Location Share (Customer-facing - for SDK integration in customer apps)
|
|
643
|
-
// ============================================================================
|
|
644
|
-
/**
|
|
645
|
-
* Get location share info (customer-facing)
|
|
646
|
-
* This is called from the customer's device to get request details
|
|
647
|
-
*
|
|
648
|
-
* @param token - Secure token from share link
|
|
649
|
-
* @returns Location share info
|
|
650
|
-
*/
|
|
651
|
-
async getLocationShareInfo(token, requestOptions) {
|
|
652
|
-
return this.request(`/api/v1/location/share/${token}`, void 0, void 0, requestOptions);
|
|
653
|
-
}
|
|
654
|
-
/**
|
|
655
|
-
* Submit location capture (customer-facing)
|
|
656
|
-
* This is called from the customer's device to submit their location
|
|
657
|
-
*
|
|
658
|
-
* @param token - Secure token from share link
|
|
659
|
-
* @param capture - Location capture data (GPS or WiFi)
|
|
660
|
-
* @returns Capture response
|
|
661
|
-
*
|
|
662
|
-
* @example
|
|
663
|
-
* ```typescript
|
|
664
|
-
* // Using GPS (preferred)
|
|
665
|
-
* const result = await client.captureLocation(token, {
|
|
666
|
-
* latitude: 37.7749,
|
|
667
|
-
* longitude: -122.4194,
|
|
668
|
-
* accuracy: 10
|
|
669
|
-
* });
|
|
670
|
-
*
|
|
671
|
-
* // Using WiFi positioning (fallback)
|
|
672
|
-
* const result = await client.captureLocation(token, {
|
|
673
|
-
* wifi_networks: [
|
|
674
|
-
* { macAddress: "00:11:22:33:44:55", signalStrength: -50 },
|
|
675
|
-
* { macAddress: "AA:BB:CC:DD:EE:FF", signalStrength: -70 }
|
|
676
|
-
* ]
|
|
677
|
-
* });
|
|
678
|
-
* ```
|
|
679
|
-
*/
|
|
680
|
-
async captureLocation(token, capture, requestOptions) {
|
|
681
|
-
return this.request(`/api/v1/location/share/${token}`, {
|
|
682
|
-
method: "POST",
|
|
683
|
-
body: JSON.stringify(capture)
|
|
684
|
-
}, void 0, requestOptions);
|
|
685
|
-
}
|
|
686
|
-
// ============================================================================
|
|
687
|
-
// Utility Methods (inherited from BaseClient: healthCheck, updateConfig, getConfig, buildQueryString)
|
|
688
|
-
// ============================================================================
|
|
689
|
-
};
|
|
690
|
-
|
|
691
|
-
// src/shared/browser-utils.ts
|
|
692
|
-
function generateUUID() {
|
|
693
|
-
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
694
|
-
return crypto.randomUUID();
|
|
695
|
-
}
|
|
696
|
-
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
697
|
-
const r = Math.random() * 16 | 0;
|
|
698
|
-
const v = c === "x" ? r : r & 3 | 8;
|
|
699
|
-
return v.toString(16);
|
|
700
|
-
});
|
|
701
|
-
}
|
|
702
|
-
function generateDeviceId() {
|
|
703
|
-
if (typeof window === "undefined" || typeof localStorage === "undefined") {
|
|
704
|
-
return generateUUID();
|
|
705
|
-
}
|
|
706
|
-
const storageKey = "cgs_device_id";
|
|
707
|
-
let deviceId = localStorage.getItem(storageKey);
|
|
708
|
-
if (!deviceId) {
|
|
709
|
-
deviceId = generateUUID();
|
|
710
|
-
localStorage.setItem(storageKey, deviceId);
|
|
711
|
-
}
|
|
712
|
-
return deviceId;
|
|
713
|
-
}
|
|
714
|
-
function getBrowserInfo() {
|
|
715
|
-
if (typeof navigator === "undefined") {
|
|
716
|
-
return { browser: "unknown", browser_version: "", os: "unknown", os_version: "" };
|
|
717
|
-
}
|
|
718
|
-
const ua = navigator.userAgent;
|
|
719
|
-
let browser = "unknown";
|
|
720
|
-
let browserVersion = "";
|
|
721
|
-
let os = "unknown";
|
|
722
|
-
let osVersion = "";
|
|
723
|
-
if (ua.includes("Firefox/")) {
|
|
724
|
-
browser = "Firefox";
|
|
725
|
-
browserVersion = ua.match(/Firefox\/([\d.]+)/)?.[1] || "";
|
|
726
|
-
} else if (ua.includes("Edg/")) {
|
|
727
|
-
browser = "Edge";
|
|
728
|
-
browserVersion = ua.match(/Edg\/([\d.]+)/)?.[1] || "";
|
|
729
|
-
} else if (ua.includes("Chrome/")) {
|
|
730
|
-
browser = "Chrome";
|
|
731
|
-
browserVersion = ua.match(/Chrome\/([\d.]+)/)?.[1] || "";
|
|
732
|
-
} else if (ua.includes("Safari/") && !ua.includes("Chrome")) {
|
|
733
|
-
browser = "Safari";
|
|
734
|
-
browserVersion = ua.match(/Version\/([\d.]+)/)?.[1] || "";
|
|
735
|
-
} else if (ua.includes("Opera") || ua.includes("OPR/")) {
|
|
736
|
-
browser = "Opera";
|
|
737
|
-
browserVersion = ua.match(/(?:Opera|OPR)\/([\d.]+)/)?.[1] || "";
|
|
738
|
-
}
|
|
739
|
-
if (ua.includes("Windows")) {
|
|
740
|
-
os = "Windows";
|
|
741
|
-
if (ua.includes("Windows NT 10.0")) osVersion = "10";
|
|
742
|
-
else if (ua.includes("Windows NT 6.3")) osVersion = "8.1";
|
|
743
|
-
else if (ua.includes("Windows NT 6.2")) osVersion = "8";
|
|
744
|
-
else if (ua.includes("Windows NT 6.1")) osVersion = "7";
|
|
745
|
-
} else if (ua.includes("Mac OS X")) {
|
|
746
|
-
os = "macOS";
|
|
747
|
-
osVersion = ua.match(/Mac OS X ([\d_]+)/)?.[1]?.replace(/_/g, ".") || "";
|
|
748
|
-
} else if (ua.includes("Linux")) {
|
|
749
|
-
os = "Linux";
|
|
750
|
-
} else if (ua.includes("Android")) {
|
|
751
|
-
os = "Android";
|
|
752
|
-
osVersion = ua.match(/Android ([\d.]+)/)?.[1] || "";
|
|
753
|
-
} else if (ua.includes("iOS") || ua.includes("iPhone") || ua.includes("iPad")) {
|
|
754
|
-
os = "iOS";
|
|
755
|
-
osVersion = ua.match(/OS ([\d_]+)/)?.[1]?.replace(/_/g, ".") || "";
|
|
756
|
-
}
|
|
757
|
-
return { browser, browser_version: browserVersion, os, os_version: osVersion };
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
// src/geolocation/ciphertext.ts
|
|
761
|
-
var CIPHER_TEXT_EXPIRY_MINUTES = 5;
|
|
762
|
-
function getWebGLInfo() {
|
|
763
|
-
if (typeof document === "undefined") return null;
|
|
764
|
-
try {
|
|
765
|
-
const canvas = document.createElement("canvas");
|
|
766
|
-
const gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
|
|
767
|
-
if (!gl) return null;
|
|
768
|
-
const debugInfo = gl.getExtension("WEBGL_debug_renderer_info");
|
|
769
|
-
if (!debugInfo) return null;
|
|
770
|
-
return {
|
|
771
|
-
vendor: gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL) || "",
|
|
772
|
-
renderer: gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL) || ""
|
|
773
|
-
};
|
|
774
|
-
} catch {
|
|
775
|
-
return null;
|
|
776
|
-
}
|
|
777
|
-
}
|
|
778
|
-
function getNetworkInfo() {
|
|
779
|
-
if (typeof navigator === "undefined") return void 0;
|
|
780
|
-
const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
|
|
781
|
-
if (!connection) return void 0;
|
|
782
|
-
return {
|
|
783
|
-
effective_type: connection.effectiveType,
|
|
784
|
-
downlink: connection.downlink,
|
|
785
|
-
rtt: connection.rtt,
|
|
786
|
-
save_data: connection.saveData
|
|
787
|
-
};
|
|
788
|
-
}
|
|
789
|
-
async function requestGPSLocation(timeout = 1e4, highAccuracy = true) {
|
|
790
|
-
if (typeof navigator === "undefined" || !navigator.geolocation) {
|
|
791
|
-
return null;
|
|
792
|
-
}
|
|
793
|
-
return new Promise((resolve) => {
|
|
794
|
-
navigator.geolocation.getCurrentPosition(
|
|
795
|
-
(position) => {
|
|
796
|
-
const { latitude, longitude, accuracy } = position.coords;
|
|
797
|
-
if (latitude < -90 || latitude > 90) {
|
|
798
|
-
resolve(null);
|
|
799
|
-
return;
|
|
800
|
-
}
|
|
801
|
-
if (longitude < -180 || longitude > 180) {
|
|
802
|
-
resolve(null);
|
|
803
|
-
return;
|
|
804
|
-
}
|
|
805
|
-
if (accuracy !== null && accuracy !== void 0 && accuracy <= 0) {
|
|
806
|
-
resolve(null);
|
|
807
|
-
return;
|
|
808
|
-
}
|
|
809
|
-
resolve({
|
|
810
|
-
latitude,
|
|
811
|
-
longitude,
|
|
812
|
-
accuracy,
|
|
813
|
-
altitude: position.coords.altitude ?? void 0,
|
|
814
|
-
altitude_accuracy: position.coords.altitudeAccuracy ?? void 0,
|
|
815
|
-
heading: position.coords.heading ?? void 0,
|
|
816
|
-
speed: position.coords.speed ?? void 0,
|
|
817
|
-
timestamp: position.timestamp
|
|
818
|
-
});
|
|
819
|
-
},
|
|
820
|
-
() => {
|
|
821
|
-
resolve(null);
|
|
822
|
-
},
|
|
823
|
-
{
|
|
824
|
-
enableHighAccuracy: highAccuracy,
|
|
825
|
-
timeout,
|
|
826
|
-
maximumAge: 0
|
|
827
|
-
}
|
|
828
|
-
);
|
|
829
|
-
});
|
|
830
|
-
}
|
|
831
|
-
function collectDeviceData(includeWebGL) {
|
|
832
|
-
const browserInfo = getBrowserInfo();
|
|
833
|
-
const webglInfo = includeWebGL ? getWebGLInfo() : null;
|
|
834
|
-
return {
|
|
835
|
-
device_id: generateDeviceId(),
|
|
836
|
-
user_agent: typeof navigator !== "undefined" ? navigator.userAgent : "",
|
|
837
|
-
platform: typeof navigator !== "undefined" ? navigator.platform : "",
|
|
838
|
-
browser: browserInfo.browser,
|
|
839
|
-
browser_version: browserInfo.browser_version,
|
|
840
|
-
os: browserInfo.os,
|
|
841
|
-
os_version: browserInfo.os_version,
|
|
842
|
-
screen_resolution: typeof screen !== "undefined" ? `${screen.width}x${screen.height}` : void 0,
|
|
843
|
-
language: typeof navigator !== "undefined" ? navigator.language : void 0,
|
|
844
|
-
timezone: Intl?.DateTimeFormat?.()?.resolvedOptions?.()?.timeZone,
|
|
845
|
-
color_depth: typeof screen !== "undefined" ? screen.colorDepth : void 0,
|
|
846
|
-
hardware_concurrency: typeof navigator !== "undefined" ? navigator.hardwareConcurrency : void 0,
|
|
847
|
-
device_memory: typeof navigator !== "undefined" ? navigator.deviceMemory : void 0,
|
|
848
|
-
touch_support: typeof navigator !== "undefined" ? navigator.maxTouchPoints > 0 : void 0,
|
|
849
|
-
webgl_vendor: webglInfo?.vendor,
|
|
850
|
-
webgl_renderer: webglInfo?.renderer
|
|
851
|
-
};
|
|
852
|
-
}
|
|
853
|
-
function encodePayload(payload) {
|
|
854
|
-
const json = JSON.stringify(payload);
|
|
855
|
-
if (typeof btoa !== "undefined") {
|
|
856
|
-
return btoa(unescape(encodeURIComponent(json)));
|
|
857
|
-
} else if (typeof Buffer !== "undefined") {
|
|
858
|
-
return Buffer.from(json, "utf-8").toString("base64");
|
|
859
|
-
}
|
|
860
|
-
throw new Error("No base64 encoding method available");
|
|
861
|
-
}
|
|
862
|
-
async function generateCipherText(options, config) {
|
|
863
|
-
const warnings = [];
|
|
864
|
-
let requestLocation = options.requestLocation ?? false;
|
|
865
|
-
if (config?.require_gps) {
|
|
866
|
-
const reason = options.reason;
|
|
867
|
-
if (reason === "login" && config.require_gps.login || reason === "registration" && config.require_gps.registration || reason === "transaction" && config.require_gps.transaction) {
|
|
868
|
-
requestLocation = true;
|
|
869
|
-
}
|
|
870
|
-
}
|
|
871
|
-
const deviceData = collectDeviceData(options.includeWebGL !== false);
|
|
872
|
-
let locationData;
|
|
873
|
-
if (requestLocation) {
|
|
874
|
-
const location = await requestGPSLocation(
|
|
875
|
-
options.locationTimeout || 1e4,
|
|
876
|
-
options.highAccuracy !== false
|
|
877
|
-
);
|
|
878
|
-
if (location) {
|
|
879
|
-
locationData = location;
|
|
880
|
-
} else {
|
|
881
|
-
warnings.push("GPS location not available or permission denied");
|
|
882
|
-
}
|
|
1160
|
+
*
|
|
1161
|
+
* @param requestId - Location request ID
|
|
1162
|
+
* @param contact - Email or phone to send to
|
|
1163
|
+
*/
|
|
1164
|
+
async resendLocationRequest(requestId, contact, requestOptions) {
|
|
1165
|
+
await this.request(`/api/v1/location-requests/${requestId}/resend`, {
|
|
1166
|
+
method: "POST",
|
|
1167
|
+
body: JSON.stringify(contact)
|
|
1168
|
+
}, void 0, requestOptions);
|
|
883
1169
|
}
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
1170
|
+
// ============================================================================
|
|
1171
|
+
// Location Share (Customer-facing - for SDK integration in customer apps)
|
|
1172
|
+
// ============================================================================
|
|
1173
|
+
/**
|
|
1174
|
+
* Get location share info (customer-facing)
|
|
1175
|
+
* This is called from the customer's device to get request details
|
|
1176
|
+
*
|
|
1177
|
+
* @param token - Secure token from share link
|
|
1178
|
+
* @returns Location share info
|
|
1179
|
+
*/
|
|
1180
|
+
async getLocationShareInfo(token, requestOptions) {
|
|
1181
|
+
return this.request(`/api/v1/location/share/${token}`, void 0, void 0, requestOptions);
|
|
887
1182
|
}
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
if (version !== "01") return null;
|
|
920
|
-
const json = decodeURIComponent(escape(atob(encodedB64)));
|
|
921
|
-
return JSON.parse(json);
|
|
922
|
-
} catch {
|
|
923
|
-
return null;
|
|
1183
|
+
/**
|
|
1184
|
+
* Submit location capture (customer-facing)
|
|
1185
|
+
* This is called from the customer's device to submit their location
|
|
1186
|
+
*
|
|
1187
|
+
* @param token - Secure token from share link
|
|
1188
|
+
* @param capture - Location capture data (GPS or WiFi)
|
|
1189
|
+
* @returns Capture response
|
|
1190
|
+
*
|
|
1191
|
+
* @example
|
|
1192
|
+
* ```typescript
|
|
1193
|
+
* // Using GPS (preferred)
|
|
1194
|
+
* const result = await client.captureLocation(token, {
|
|
1195
|
+
* latitude: 37.7749,
|
|
1196
|
+
* longitude: -122.4194,
|
|
1197
|
+
* accuracy: 10
|
|
1198
|
+
* });
|
|
1199
|
+
*
|
|
1200
|
+
* // Using WiFi positioning (fallback)
|
|
1201
|
+
* const result = await client.captureLocation(token, {
|
|
1202
|
+
* wifi_networks: [
|
|
1203
|
+
* { macAddress: "00:11:22:33:44:55", signalStrength: -50 },
|
|
1204
|
+
* { macAddress: "AA:BB:CC:DD:EE:FF", signalStrength: -70 }
|
|
1205
|
+
* ]
|
|
1206
|
+
* });
|
|
1207
|
+
* ```
|
|
1208
|
+
*/
|
|
1209
|
+
async captureLocation(token, capture, requestOptions) {
|
|
1210
|
+
return this.request(`/api/v1/location/share/${token}`, {
|
|
1211
|
+
method: "POST",
|
|
1212
|
+
body: JSON.stringify(capture)
|
|
1213
|
+
}, void 0, requestOptions);
|
|
924
1214
|
}
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
1215
|
+
// ============================================================================
|
|
1216
|
+
// CipherText Generation
|
|
1217
|
+
// ============================================================================
|
|
1218
|
+
/**
|
|
1219
|
+
* Generate a signed cipherText containing device and location data.
|
|
1220
|
+
*
|
|
1221
|
+
* Automatically passes the client's API key for HMAC signing (v02 format).
|
|
1222
|
+
* If no API key is configured, falls back to unsigned v01 format.
|
|
1223
|
+
*
|
|
1224
|
+
* @param options - Options for cipherText generation
|
|
1225
|
+
* @param gpsConfig - Optional GPS config (from getGPSConfig)
|
|
1226
|
+
* @returns CipherText result with the signed string
|
|
1227
|
+
*
|
|
1228
|
+
* @example
|
|
1229
|
+
* ```typescript
|
|
1230
|
+
* const result = await client.generateCipherText({ reason: 'login' });
|
|
1231
|
+
* console.log(result.cipherText); // v02 signed cipherText
|
|
1232
|
+
* ```
|
|
1233
|
+
*/
|
|
1234
|
+
async generateCipherText(options, gpsConfig) {
|
|
1235
|
+
let signingKey = this.cachedSigningKey;
|
|
1236
|
+
if (!signingKey) {
|
|
1237
|
+
const config = await this.getGPSConfig();
|
|
1238
|
+
signingKey = config.signing_key;
|
|
1239
|
+
}
|
|
1240
|
+
return generateCipherText(
|
|
1241
|
+
{ ...options, signingKey: signingKey || void 0 },
|
|
1242
|
+
gpsConfig
|
|
1243
|
+
);
|
|
937
1244
|
}
|
|
938
|
-
|
|
1245
|
+
// ============================================================================
|
|
1246
|
+
// Utility Methods (inherited from BaseClient: healthCheck, updateConfig, getConfig, buildQueryString)
|
|
1247
|
+
// ============================================================================
|
|
1248
|
+
};
|
|
939
1249
|
|
|
940
1250
|
// src/risk-profile/client.ts
|
|
941
1251
|
var RiskProfileClient = class extends BaseClient {
|
|
@@ -2454,6 +2764,297 @@ var KycClient = class extends BaseClient {
|
|
|
2454
2764
|
// ============================================================================
|
|
2455
2765
|
};
|
|
2456
2766
|
|
|
2457
|
-
|
|
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 };
|
|
2458
3059
|
//# sourceMappingURL=index.mjs.map
|
|
2459
3060
|
//# sourceMappingURL=index.mjs.map
|