@truesift/express 0.1.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/index.js ADDED
@@ -0,0 +1,921 @@
1
+ // src/http/buildBotGuardUrl.ts
2
+ var BOT_GUARD_ENDPOINT_PATHS = {
3
+ challenge: "/challenge",
4
+ verify: "/verify"
5
+ };
6
+ function removeTrailingSlashes(value) {
7
+ return value.replace(/\/+$/, "");
8
+ }
9
+ function buildBotGuardUrl(apiBaseUrl, endpoint) {
10
+ const normalizedApiBaseUrl = removeTrailingSlashes(apiBaseUrl);
11
+ const endpointPath = BOT_GUARD_ENDPOINT_PATHS[endpoint];
12
+ return `${normalizedApiBaseUrl}${endpointPath}`;
13
+ }
14
+
15
+ // src/errors/BotGuardError.ts
16
+ var BotGuardError = class extends Error {
17
+ code;
18
+ statusCode;
19
+ requestId;
20
+ context;
21
+ constructor(options) {
22
+ const errorOptions = options.cause === void 0 ? void 0 : { cause: options.cause };
23
+ super(options.message, errorOptions);
24
+ this.name = new.target.name;
25
+ this.code = options.code;
26
+ if (options.statusCode !== void 0) {
27
+ this.statusCode = options.statusCode;
28
+ }
29
+ if (options.requestId !== void 0) {
30
+ this.requestId = options.requestId;
31
+ }
32
+ if (options.context !== void 0) {
33
+ this.context = options.context;
34
+ }
35
+ Object.setPrototypeOf(this, new.target.prototype);
36
+ }
37
+ toJSON() {
38
+ return {
39
+ name: this.name,
40
+ code: this.code,
41
+ message: this.message,
42
+ ...this.statusCode !== void 0 ? { statusCode: this.statusCode } : {},
43
+ ...this.requestId !== void 0 ? { requestId: this.requestId } : {},
44
+ ...this.context !== void 0 ? { context: this.context } : {}
45
+ };
46
+ }
47
+ };
48
+
49
+ // src/errors/BotGuardRequestError.ts
50
+ var DEFAULT_REQUEST_ERROR_MESSAGE = "TrueSift API request failed.";
51
+ var BotGuardRequestError = class extends BotGuardError {
52
+ constructor(options = {}) {
53
+ const errorOptions = {
54
+ code: "TRUESIFT_REQUEST_ERROR",
55
+ message: options.message ?? DEFAULT_REQUEST_ERROR_MESSAGE,
56
+ ...options.statusCode !== void 0 ? { statusCode: options.statusCode } : {},
57
+ ...options.requestId !== void 0 ? { requestId: options.requestId } : {},
58
+ ...options.context !== void 0 ? { context: options.context } : {},
59
+ ...options.cause !== void 0 ? { cause: options.cause } : {}
60
+ };
61
+ super(errorOptions);
62
+ }
63
+ };
64
+
65
+ // src/errors/BotGuardTimeoutError.ts
66
+ var DEFAULT_TIMEOUT_ERROR_MESSAGE = "TrueSift API request timed out.";
67
+ var BotGuardTimeoutError = class extends BotGuardError {
68
+ timeoutMs;
69
+ constructor(options = {}) {
70
+ const context = {
71
+ ...options.context ?? {},
72
+ ...options.timeoutMs !== void 0 ? { timeoutMs: options.timeoutMs } : {}
73
+ };
74
+ const errorOptions = {
75
+ code: "TRUESIFT_TIMEOUT_ERROR",
76
+ message: options.message ?? DEFAULT_TIMEOUT_ERROR_MESSAGE,
77
+ ...options.requestId !== void 0 ? { requestId: options.requestId } : {},
78
+ ...Object.keys(context).length > 0 ? { context } : {},
79
+ ...options.cause !== void 0 ? { cause: options.cause } : {}
80
+ };
81
+ super(errorOptions);
82
+ if (options.timeoutMs !== void 0) {
83
+ this.timeoutMs = options.timeoutMs;
84
+ }
85
+ }
86
+ toJSON() {
87
+ return {
88
+ ...super.toJSON(),
89
+ code: "TRUESIFT_TIMEOUT_ERROR",
90
+ ...this.timeoutMs !== void 0 ? { timeoutMs: this.timeoutMs } : {}
91
+ };
92
+ }
93
+ };
94
+
95
+ // src/errors/BotGuardApiError.ts
96
+ var DEFAULT_API_ERROR_MESSAGE = "TrueSift API returned an error response.";
97
+ var BotGuardApiError = class extends BotGuardError {
98
+ apiError;
99
+ constructor(options = {}) {
100
+ const context = {
101
+ ...options.context ?? {},
102
+ ...options.apiError !== void 0 ? { apiError: options.apiError } : {}
103
+ };
104
+ const errorOptions = {
105
+ code: "TRUESIFT_API_ERROR",
106
+ message: options.message ?? DEFAULT_API_ERROR_MESSAGE,
107
+ ...options.statusCode !== void 0 ? { statusCode: options.statusCode } : {},
108
+ ...options.requestId !== void 0 ? { requestId: options.requestId } : {},
109
+ ...Object.keys(context).length > 0 ? { context } : {},
110
+ ...options.cause !== void 0 ? { cause: options.cause } : {}
111
+ };
112
+ super(errorOptions);
113
+ if (options.apiError !== void 0) {
114
+ this.apiError = options.apiError;
115
+ }
116
+ }
117
+ toJSON() {
118
+ return {
119
+ ...super.toJSON(),
120
+ code: "TRUESIFT_API_ERROR",
121
+ ...this.apiError !== void 0 ? { apiError: this.apiError } : {}
122
+ };
123
+ }
124
+ };
125
+
126
+ // src/errors/BotGuardAuthError.ts
127
+ var DEFAULT_AUTH_ERROR_MESSAGE = "TrueSift API authentication failed.";
128
+ var BotGuardAuthError = class extends BotGuardError {
129
+ constructor(options = {}) {
130
+ const errorOptions = {
131
+ code: "TRUESIFT_AUTH_ERROR",
132
+ message: options.message ?? DEFAULT_AUTH_ERROR_MESSAGE,
133
+ ...options.statusCode !== void 0 ? { statusCode: options.statusCode } : {},
134
+ ...options.requestId !== void 0 ? { requestId: options.requestId } : {},
135
+ ...options.context !== void 0 ? { context: options.context } : {},
136
+ ...options.cause !== void 0 ? { cause: options.cause } : {}
137
+ };
138
+ super(errorOptions);
139
+ }
140
+ };
141
+
142
+ // src/errors/BotGuardResponseError.ts
143
+ var DEFAULT_RESPONSE_ERROR_MESSAGE = "TrueSift API returned an invalid response.";
144
+ var BotGuardResponseError = class extends BotGuardError {
145
+ constructor(options = {}) {
146
+ const errorOptions = {
147
+ code: "TRUESIFT_RESPONSE_ERROR",
148
+ message: options.message ?? DEFAULT_RESPONSE_ERROR_MESSAGE,
149
+ ...options.statusCode !== void 0 ? { statusCode: options.statusCode } : {},
150
+ ...options.requestId !== void 0 ? { requestId: options.requestId } : {},
151
+ ...options.context !== void 0 ? { context: options.context } : {},
152
+ ...options.cause !== void 0 ? { cause: options.cause } : {}
153
+ };
154
+ super(errorOptions);
155
+ }
156
+ };
157
+
158
+ // src/utils/isRecord.ts
159
+ function isRecord(value) {
160
+ return typeof value === "object" && value !== null && !Array.isArray(value);
161
+ }
162
+
163
+ // src/http/parseBotGuardResponse.ts
164
+ function getRequestIdFromHeaders(headers) {
165
+ const requestId = headers.get("x-request-id") ?? headers.get("x-correlation-id") ?? headers.get("x-trace-id");
166
+ return requestId === null || requestId.trim().length === 0 ? void 0 : requestId.trim();
167
+ }
168
+ function isJsonContentType(headers) {
169
+ const contentType = headers.get("content-type");
170
+ return contentType !== null && contentType.toLowerCase().includes("json");
171
+ }
172
+ function isTrueSiftJsonErrorResponse(body) {
173
+ return isRecord(body) && body["success"] === false && (body["error"] === void 0 || isRecord(body["error"]));
174
+ }
175
+ function getApiErrorMessage(body) {
176
+ return body.error?.message ?? "TrueSift API returned an error response.";
177
+ }
178
+ function getApiErrorCode(body) {
179
+ return body.error?.code;
180
+ }
181
+ async function parseJsonBody(response) {
182
+ try {
183
+ return await response.json();
184
+ } catch (error) {
185
+ throw new BotGuardResponseError({
186
+ message: "TrueSift API returned invalid JSON.",
187
+ statusCode: response.status,
188
+ requestId: getRequestIdFromHeaders(response.headers),
189
+ context: {
190
+ statusCode: response.status
191
+ },
192
+ cause: error
193
+ });
194
+ }
195
+ }
196
+ async function parseBotGuardResponse(response) {
197
+ const requestId = getRequestIdFromHeaders(response.headers);
198
+ if (!isJsonContentType(response.headers)) {
199
+ throw new BotGuardResponseError({
200
+ message: "TrueSift API response must be JSON.",
201
+ statusCode: response.status,
202
+ ...requestId !== void 0 ? { requestId } : {},
203
+ context: {
204
+ statusCode: response.status,
205
+ contentType: response.headers.get("content-type")
206
+ }
207
+ });
208
+ }
209
+ const body = await parseJsonBody(response);
210
+ if (response.status === 401 || response.status === 403) {
211
+ throw new BotGuardAuthError({
212
+ statusCode: response.status,
213
+ ...requestId !== void 0 ? { requestId } : {},
214
+ context: {
215
+ statusCode: response.status
216
+ }
217
+ });
218
+ }
219
+ if (!response.ok) {
220
+ if (isTrueSiftJsonErrorResponse(body)) {
221
+ throw new BotGuardApiError({
222
+ message: getApiErrorMessage(body),
223
+ statusCode: response.status,
224
+ requestId: body.requestId ?? requestId,
225
+ apiError: {
226
+ ...getApiErrorCode(body) !== void 0 ? { code: getApiErrorCode(body) } : {},
227
+ message: getApiErrorMessage(body),
228
+ ...body.error?.details !== void 0 ? { details: body.error.details } : {}
229
+ },
230
+ context: {
231
+ statusCode: response.status
232
+ }
233
+ });
234
+ }
235
+ throw new BotGuardResponseError({
236
+ message: "TrueSift API returned an unexpected error response.",
237
+ statusCode: response.status,
238
+ ...requestId !== void 0 ? { requestId } : {},
239
+ context: {
240
+ statusCode: response.status
241
+ }
242
+ });
243
+ }
244
+ return {
245
+ statusCode: response.status,
246
+ ok: response.ok,
247
+ headers: response.headers,
248
+ body,
249
+ ...requestId !== void 0 ? { requestId } : {}
250
+ };
251
+ }
252
+
253
+ // src/http/botGuardFetch.ts
254
+ function isAbortError(error) {
255
+ return error instanceof DOMException || error instanceof Error && error.name === "AbortError";
256
+ }
257
+ async function botGuardFetch(config, options) {
258
+ const timeoutMs = options.timeoutMs ?? config.timeoutMs;
259
+ const controller = new AbortController();
260
+ const timeoutId = setTimeout(() => {
261
+ controller.abort();
262
+ }, timeoutMs);
263
+ try {
264
+ const response = await config.fetchImpl(options.url, {
265
+ method: "POST",
266
+ headers: {
267
+ "content-type": "application/json",
268
+ accept: "application/json"
269
+ },
270
+ body: JSON.stringify(options.body),
271
+ signal: controller.signal
272
+ });
273
+ const parsedResponse = await parseBotGuardResponse(response);
274
+ return parsedResponse.body;
275
+ } catch (error) {
276
+ if (isAbortError(error)) {
277
+ throw new BotGuardTimeoutError({
278
+ timeoutMs,
279
+ context: {
280
+ url: options.url,
281
+ timeoutMs
282
+ },
283
+ cause: error
284
+ });
285
+ }
286
+ if (error instanceof BotGuardError) {
287
+ throw error;
288
+ }
289
+ throw new BotGuardRequestError({
290
+ context: {
291
+ url: options.url
292
+ },
293
+ cause: error
294
+ });
295
+ } finally {
296
+ clearTimeout(timeoutId);
297
+ }
298
+ }
299
+
300
+ // src/utils/decisionHelpers.ts
301
+ function isAllowedDecision(decision) {
302
+ return decision === "allow";
303
+ }
304
+ function isReviewDecision(decision) {
305
+ return decision === "review";
306
+ }
307
+ function isBlockedDecision(decision) {
308
+ return decision === "block";
309
+ }
310
+ function createDecisionFlags(decision) {
311
+ return {
312
+ isAllowed: isAllowedDecision(decision),
313
+ isReview: isReviewDecision(decision),
314
+ isBlocked: isBlockedDecision(decision)
315
+ };
316
+ }
317
+ function isAllowed(result) {
318
+ return result.decision === "allow";
319
+ }
320
+ function isReview(result) {
321
+ return result.decision === "review";
322
+ }
323
+ function isBlocked(result) {
324
+ return result.decision === "block";
325
+ }
326
+ var isBotGuardAllowedDecision = isAllowedDecision;
327
+ var isBotGuardReviewDecision = isReviewDecision;
328
+ var isBotGuardBlockedDecision = isBlockedDecision;
329
+ function createBotGuardDecisionFlags(decision) {
330
+ return createDecisionFlags(decision);
331
+ }
332
+ function isBotGuardAllowed(result) {
333
+ return isAllowed(result);
334
+ }
335
+ function isBotGuardReview(result) {
336
+ return isReview(result);
337
+ }
338
+ function isBotGuardBlocked(result) {
339
+ return isBlocked(result);
340
+ }
341
+
342
+ // src/types/decision.types.ts
343
+ var TRUE_SIFT_DECISIONS = ["allow", "review", "block"];
344
+
345
+ // src/utils/normalizeDecision.ts
346
+ var TRUE_SIFT_DECISION_SET = new Set(
347
+ TRUE_SIFT_DECISIONS
348
+ );
349
+ function isTrueSiftDecision(value) {
350
+ return typeof value === "string" && TRUE_SIFT_DECISION_SET.has(value);
351
+ }
352
+ var isBotGuardDecision = isTrueSiftDecision;
353
+ function normalizeDecision(value) {
354
+ if (isTrueSiftDecision(value)) {
355
+ return value;
356
+ }
357
+ if (typeof value === "string") {
358
+ const normalizedValue = value.trim().toLowerCase();
359
+ if (isTrueSiftDecision(normalizedValue)) {
360
+ return normalizedValue;
361
+ }
362
+ }
363
+ throw new TypeError(
364
+ `Invalid TrueSift decision. Expected one of: ${TRUE_SIFT_DECISIONS.join(
365
+ ", "
366
+ )}.`
367
+ );
368
+ }
369
+ function normalizeBotGuardDecision(value) {
370
+ return normalizeDecision(value);
371
+ }
372
+
373
+ // src/validation/validateBotGuardResponse.ts
374
+ var TRUE_SIFT_VERIFICATION_MODES = ["observe", "protect"];
375
+ function createResponseError(message, context) {
376
+ return new BotGuardResponseError({
377
+ message,
378
+ context
379
+ });
380
+ }
381
+ function requireRecord(response) {
382
+ if (!isRecord(response)) {
383
+ throw createResponseError("TrueSift API response must be an object.", {
384
+ fieldName: "response",
385
+ expected: "object"
386
+ });
387
+ }
388
+ return response;
389
+ }
390
+ function requireSuccessTrue(record) {
391
+ if (record["success"] !== true) {
392
+ throw createResponseError(
393
+ "TrueSift API success response must contain success: true.",
394
+ {
395
+ fieldName: "success",
396
+ expected: "true"
397
+ }
398
+ );
399
+ }
400
+ }
401
+ function requireStringField(record, fieldName) {
402
+ const value = record[fieldName];
403
+ if (typeof value !== "string" || value.trim().length === 0) {
404
+ throw createResponseError(
405
+ `TrueSift API response field "${fieldName}" must be a non-empty string.`,
406
+ {
407
+ fieldName,
408
+ expected: "non-empty string"
409
+ }
410
+ );
411
+ }
412
+ return value.trim();
413
+ }
414
+ function optionalStringField(record, fieldName) {
415
+ const value = record[fieldName];
416
+ if (value === void 0) {
417
+ return void 0;
418
+ }
419
+ if (typeof value !== "string" || value.trim().length === 0) {
420
+ throw createResponseError(
421
+ `TrueSift API response field "${fieldName}" must be a non-empty string when present.`,
422
+ {
423
+ fieldName,
424
+ expected: "non-empty string"
425
+ }
426
+ );
427
+ }
428
+ return value.trim();
429
+ }
430
+ function requireFiniteNumberField(record, fieldName) {
431
+ const value = record[fieldName];
432
+ if (typeof value !== "number" || !Number.isFinite(value)) {
433
+ throw createResponseError(
434
+ `TrueSift API response field "${fieldName}" must be a finite number.`,
435
+ {
436
+ fieldName,
437
+ expected: "finite number"
438
+ }
439
+ );
440
+ }
441
+ return value;
442
+ }
443
+ function optionalStringArrayField(record, fieldName) {
444
+ const value = record[fieldName];
445
+ if (value === void 0) {
446
+ return [];
447
+ }
448
+ if (!Array.isArray(value)) {
449
+ throw createResponseError(
450
+ `TrueSift API response field "${fieldName}" must be an array when present.`,
451
+ {
452
+ fieldName,
453
+ expected: "string[]"
454
+ }
455
+ );
456
+ }
457
+ const reasonCodes = [];
458
+ for (const item of value) {
459
+ if (typeof item !== "string") {
460
+ throw createResponseError(
461
+ `TrueSift API response field "${fieldName}" must contain only strings.`,
462
+ {
463
+ fieldName,
464
+ expected: "string[]"
465
+ }
466
+ );
467
+ }
468
+ const trimmedItem = item.trim();
469
+ if (trimmedItem.length > 0) {
470
+ reasonCodes.push(trimmedItem);
471
+ }
472
+ }
473
+ return reasonCodes;
474
+ }
475
+ function optionalVerificationModeField(record, fieldName) {
476
+ const value = record[fieldName];
477
+ if (value === void 0) {
478
+ return void 0;
479
+ }
480
+ if (typeof value === "string" && TRUE_SIFT_VERIFICATION_MODES.includes(value)) {
481
+ return value;
482
+ }
483
+ throw createResponseError(
484
+ `TrueSift API response field "${fieldName}" must be a valid verification mode when present.`,
485
+ {
486
+ fieldName,
487
+ expected: "observe | protect"
488
+ }
489
+ );
490
+ }
491
+ function optionalDecisionField(record, fieldName) {
492
+ const value = record[fieldName];
493
+ if (value === void 0) {
494
+ return void 0;
495
+ }
496
+ return normalizeDecision(value);
497
+ }
498
+ function getDecisionMetadata(record) {
499
+ const mode = optionalVerificationModeField(record, "mode");
500
+ const calculatedDecision = optionalDecisionField(record, "calculatedDecision");
501
+ const effectiveDecision = optionalDecisionField(record, "effectiveDecision");
502
+ return {
503
+ ...mode !== void 0 ? { mode } : {},
504
+ ...calculatedDecision !== void 0 ? { calculatedDecision } : {},
505
+ ...effectiveDecision !== void 0 ? { effectiveDecision } : {}
506
+ };
507
+ }
508
+ function validateCreateChallengeResponse(response) {
509
+ const record = requireRecord(response);
510
+ requireSuccessTrue(record);
511
+ const requestId = optionalStringField(record, "requestId");
512
+ return {
513
+ success: true,
514
+ challengeId: requireStringField(record, "challengeId"),
515
+ challengeToken: requireStringField(record, "challengeToken"),
516
+ expiresAt: requireStringField(record, "expiresAt"),
517
+ ...requestId !== void 0 ? { requestId } : {},
518
+ raw: record
519
+ };
520
+ }
521
+ function validateVerifyChallengeResponse(response) {
522
+ const record = requireRecord(response);
523
+ requireSuccessTrue(record);
524
+ const decision = normalizeDecision(requireStringField(record, "decision"));
525
+ const decisionFlags = createDecisionFlags(decision);
526
+ const metadata = getDecisionMetadata(record);
527
+ const challengeId = optionalStringField(record, "challengeId");
528
+ const requestId = optionalStringField(record, "requestId");
529
+ return {
530
+ success: true,
531
+ decision,
532
+ score: requireFiniteNumberField(record, "score"),
533
+ reasonCodes: optionalStringArrayField(record, "reasonCodes"),
534
+ ...challengeId !== void 0 ? { challengeId } : {},
535
+ ...requestId !== void 0 ? { requestId } : {},
536
+ ...metadata,
537
+ ...decisionFlags,
538
+ raw: record
539
+ };
540
+ }
541
+
542
+ // src/errors/BotGuardConfigError.ts
543
+ var DEFAULT_CONFIG_ERROR_MESSAGE = "TrueSift SDK configuration is invalid.";
544
+ var BotGuardConfigError = class extends BotGuardError {
545
+ constructor(options = {}) {
546
+ const errorOptions = {
547
+ code: "TRUESIFT_CONFIG_ERROR",
548
+ message: options.message ?? DEFAULT_CONFIG_ERROR_MESSAGE,
549
+ ...options.context !== void 0 ? { context: options.context } : {},
550
+ ...options.cause !== void 0 ? { cause: options.cause } : {}
551
+ };
552
+ super(errorOptions);
553
+ }
554
+ };
555
+
556
+ // src/validation/validateChallengeInput.ts
557
+ function validateChallengeString(value, context) {
558
+ if (typeof value !== "string" || value.trim().length === 0) {
559
+ throw new BotGuardConfigError({
560
+ message: `TrueSift challenge field "${context.fieldName}" is required.`,
561
+ context
562
+ });
563
+ }
564
+ return value.trim();
565
+ }
566
+ function resolveChallengeString(inputValue, configValue, fieldName) {
567
+ if (inputValue !== void 0) {
568
+ return validateChallengeString(inputValue, {
569
+ fieldName,
570
+ source: "input"
571
+ });
572
+ }
573
+ return validateChallengeString(configValue, {
574
+ fieldName,
575
+ source: "config"
576
+ });
577
+ }
578
+ function validateChallengeInput(input, config) {
579
+ const siteKey = resolveChallengeString(
580
+ input.siteKey,
581
+ config.siteKey,
582
+ "siteKey"
583
+ );
584
+ const origin = resolveChallengeString(
585
+ input.origin,
586
+ config.defaultOrigin,
587
+ "origin"
588
+ );
589
+ const action = resolveChallengeString(
590
+ input.action,
591
+ config.defaultAction,
592
+ "action"
593
+ );
594
+ const path = validateChallengeString(input.path, {
595
+ fieldName: "path",
596
+ source: "input"
597
+ });
598
+ return {
599
+ siteKey,
600
+ origin,
601
+ action,
602
+ path,
603
+ ...input.metadata !== void 0 ? { metadata: input.metadata } : {}
604
+ };
605
+ }
606
+
607
+ // src/validation/validateVerifyInput.ts
608
+ function validateVerifyString(value, context) {
609
+ if (typeof value !== "string" || value.trim().length === 0) {
610
+ throw new BotGuardConfigError({
611
+ message: `TrueSift verify field "${context.fieldName}" is required.`,
612
+ context
613
+ });
614
+ }
615
+ return value.trim();
616
+ }
617
+ function resolveVerifyString(inputValue, configValue, fieldName) {
618
+ if (inputValue !== void 0) {
619
+ return validateVerifyString(inputValue, {
620
+ fieldName,
621
+ source: "input"
622
+ });
623
+ }
624
+ return validateVerifyString(configValue, {
625
+ fieldName,
626
+ source: "config"
627
+ });
628
+ }
629
+ function normalizeClientSignals(clientSignals) {
630
+ if (clientSignals === void 0) {
631
+ return void 0;
632
+ }
633
+ const elapsedMs = typeof clientSignals.elapsedMs === "number" && Number.isFinite(clientSignals.elapsedMs) && clientSignals.elapsedMs >= 0 ? Math.trunc(clientSignals.elapsedMs) : void 0;
634
+ const honeypotValue = typeof clientSignals.honeypotValue === "string" ? clientSignals.honeypotValue.slice(0, 500) : void 0;
635
+ const normalizedSignals = {
636
+ ...elapsedMs !== void 0 ? { elapsedMs } : {},
637
+ ...honeypotValue !== void 0 ? { honeypotValue } : {}
638
+ };
639
+ return Object.keys(normalizedSignals).length > 0 ? normalizedSignals : void 0;
640
+ }
641
+ function validateVerifyInput(input, config) {
642
+ const siteKey = resolveVerifyString(input.siteKey, config.siteKey, "siteKey");
643
+ const secretKey = resolveVerifyString(
644
+ input.secretKey,
645
+ config.secretKey,
646
+ "secretKey"
647
+ );
648
+ const challengeId = validateVerifyString(input.challengeId, {
649
+ fieldName: "challengeId",
650
+ source: "input"
651
+ });
652
+ const challengeToken = validateVerifyString(input.challengeToken, {
653
+ fieldName: "challengeToken",
654
+ source: "input"
655
+ });
656
+ const origin = resolveVerifyString(
657
+ input.origin,
658
+ config.defaultOrigin,
659
+ "origin"
660
+ );
661
+ const action = resolveVerifyString(
662
+ input.action,
663
+ config.defaultAction,
664
+ "action"
665
+ );
666
+ const path = validateVerifyString(input.path, {
667
+ fieldName: "path",
668
+ source: "input"
669
+ });
670
+ const clientSignals = normalizeClientSignals(input.clientSignals);
671
+ return {
672
+ siteKey,
673
+ secretKey,
674
+ challengeId,
675
+ challengeToken,
676
+ origin,
677
+ action,
678
+ path,
679
+ ...clientSignals !== void 0 ? { clientSignals } : {},
680
+ ...input.metadata !== void 0 ? { metadata: input.metadata } : {}
681
+ };
682
+ }
683
+
684
+ // src/client/BotGuardClient.ts
685
+ var BotGuardClient = class {
686
+ config;
687
+ constructor(config) {
688
+ this.config = config;
689
+ }
690
+ async createChallenge(input = {}) {
691
+ const payload = validateChallengeInput(input, this.config);
692
+ const url = buildBotGuardUrl(this.config.apiBaseUrl, "challenge");
693
+ const response = await botGuardFetch(this.config, {
694
+ url,
695
+ body: payload
696
+ });
697
+ return validateCreateChallengeResponse(response);
698
+ }
699
+ async verifyChallenge(input) {
700
+ const payload = validateVerifyInput(input, this.config);
701
+ const url = buildBotGuardUrl(this.config.apiBaseUrl, "verify");
702
+ const response = await botGuardFetch(this.config, {
703
+ url,
704
+ body: payload
705
+ });
706
+ return validateVerifyChallengeResponse(response);
707
+ }
708
+ };
709
+ var TrueSiftClient = class extends BotGuardClient {
710
+ constructor(config) {
711
+ super(config);
712
+ }
713
+ async createChallenge(input = {}) {
714
+ return super.createChallenge(input);
715
+ }
716
+ async verifyChallenge(input) {
717
+ return super.verifyChallenge(input);
718
+ }
719
+ };
720
+
721
+ // src/utils/redactBotGuardConfig.ts
722
+ var REDACTED_VALUE = "[REDACTED]";
723
+ var SENSITIVE_KEY_PATTERNS = [
724
+ "secret",
725
+ "token",
726
+ "authorization",
727
+ "auth",
728
+ "credential",
729
+ "password",
730
+ "cookie",
731
+ "set-cookie",
732
+ "api-key",
733
+ "apikey",
734
+ "private"
735
+ ];
736
+ function redactSensitiveValue(value) {
737
+ if (typeof value === "string" && value.length > 0) {
738
+ return REDACTED_VALUE;
739
+ }
740
+ if (typeof value === "number" || typeof value === "boolean") {
741
+ return value;
742
+ }
743
+ if (value === null || value === void 0) {
744
+ return value;
745
+ }
746
+ if (Array.isArray(value)) {
747
+ return value.map((item) => redactSensitiveValue(item));
748
+ }
749
+ if (typeof value === "object") {
750
+ return redactSensitiveRecord(value);
751
+ }
752
+ return REDACTED_VALUE;
753
+ }
754
+ function isSensitiveKey(key) {
755
+ const normalizedKey = key.trim().toLowerCase();
756
+ return SENSITIVE_KEY_PATTERNS.some(
757
+ (pattern) => normalizedKey.includes(pattern)
758
+ );
759
+ }
760
+ function redactSensitiveRecord(record) {
761
+ const redactedRecord = {};
762
+ for (const [key, value] of Object.entries(record)) {
763
+ redactedRecord[key] = isSensitiveKey(key) ? REDACTED_VALUE : redactSensitiveValue(value);
764
+ }
765
+ return redactedRecord;
766
+ }
767
+ function redactTrueSiftClientConfig(config) {
768
+ return {
769
+ ...typeof config.apiBaseUrl === "string" ? { apiBaseUrl: config.apiBaseUrl } : {},
770
+ ...typeof config.siteKey === "string" ? { siteKey: config.siteKey } : {},
771
+ ...typeof config.defaultAction === "string" ? { defaultAction: config.defaultAction } : {},
772
+ ...typeof config.defaultOrigin === "string" ? { defaultOrigin: config.defaultOrigin } : {},
773
+ ...typeof config.timeoutMs === "number" ? { timeoutMs: config.timeoutMs } : {},
774
+ hasSecretKey: typeof config.secretKey === "string" && config.secretKey.length > 0,
775
+ hasCustomFetch: typeof config.fetchImpl === "function",
776
+ hasLogger: typeof config.logger === "object" && config.logger !== null
777
+ };
778
+ }
779
+ function redactBotGuardConfig(config) {
780
+ return redactTrueSiftClientConfig(config);
781
+ }
782
+
783
+ // src/validation/validateClientConfig.ts
784
+ var DEFAULT_TIMEOUT_MS = 2500;
785
+ var MIN_TIMEOUT_MS = 100;
786
+ var MAX_TIMEOUT_MS = 3e4;
787
+ function validateRequiredString(value, fieldName, config) {
788
+ if (typeof value !== "string" || value.trim().length === 0) {
789
+ throw new BotGuardConfigError({
790
+ message: `TrueSift SDK configuration field "${fieldName}" is required.`,
791
+ context: {
792
+ fieldName,
793
+ config: redactBotGuardConfig(config)
794
+ }
795
+ });
796
+ }
797
+ return value.trim();
798
+ }
799
+ function validateOptionalString(value, fieldName, config) {
800
+ if (value === void 0) {
801
+ return void 0;
802
+ }
803
+ if (typeof value !== "string") {
804
+ throw new BotGuardConfigError({
805
+ message: `TrueSift SDK configuration field "${fieldName}" must be a string.`,
806
+ context: {
807
+ fieldName,
808
+ config: redactBotGuardConfig(config)
809
+ }
810
+ });
811
+ }
812
+ const trimmedValue = value.trim();
813
+ return trimmedValue.length > 0 ? trimmedValue : void 0;
814
+ }
815
+ function validateTimeoutMs(value, config) {
816
+ if (value === void 0) {
817
+ return DEFAULT_TIMEOUT_MS;
818
+ }
819
+ if (!Number.isFinite(value)) {
820
+ throw new BotGuardConfigError({
821
+ message: 'TrueSift SDK configuration field "timeoutMs" must be finite.',
822
+ context: {
823
+ fieldName: "timeoutMs",
824
+ config: redactBotGuardConfig(config)
825
+ }
826
+ });
827
+ }
828
+ if (!Number.isInteger(value)) {
829
+ throw new BotGuardConfigError({
830
+ message: 'TrueSift SDK configuration field "timeoutMs" must be an integer.',
831
+ context: {
832
+ fieldName: "timeoutMs",
833
+ config: redactBotGuardConfig(config)
834
+ }
835
+ });
836
+ }
837
+ if (value < MIN_TIMEOUT_MS || value > MAX_TIMEOUT_MS) {
838
+ throw new BotGuardConfigError({
839
+ message: `TrueSift SDK configuration field "timeoutMs" must be between ${MIN_TIMEOUT_MS} and ${MAX_TIMEOUT_MS}.`,
840
+ context: {
841
+ fieldName: "timeoutMs",
842
+ minTimeoutMs: MIN_TIMEOUT_MS,
843
+ maxTimeoutMs: MAX_TIMEOUT_MS,
844
+ config: redactBotGuardConfig(config)
845
+ }
846
+ });
847
+ }
848
+ return value;
849
+ }
850
+ function validateApiBaseUrl(value, config) {
851
+ try {
852
+ const url = new URL(value);
853
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
854
+ throw new BotGuardConfigError({
855
+ message: 'TrueSift SDK configuration field "apiBaseUrl" must use http or https.',
856
+ context: {
857
+ fieldName: "apiBaseUrl",
858
+ config: redactBotGuardConfig(config)
859
+ }
860
+ });
861
+ }
862
+ return url.toString().replace(/\/$/, "");
863
+ } catch (error) {
864
+ if (error instanceof BotGuardConfigError) {
865
+ throw error;
866
+ }
867
+ throw new BotGuardConfigError({
868
+ message: 'TrueSift SDK configuration field "apiBaseUrl" must be a valid URL.',
869
+ context: {
870
+ fieldName: "apiBaseUrl",
871
+ config: redactBotGuardConfig(config)
872
+ },
873
+ cause: error
874
+ });
875
+ }
876
+ }
877
+ function validateClientConfig(config) {
878
+ const rawApiBaseUrl = validateRequiredString(
879
+ config.apiBaseUrl,
880
+ "apiBaseUrl",
881
+ config
882
+ );
883
+ const apiBaseUrl = validateApiBaseUrl(rawApiBaseUrl, config);
884
+ const siteKey = validateRequiredString(config.siteKey, "siteKey", config);
885
+ const secretKey = validateRequiredString(
886
+ config.secretKey,
887
+ "secretKey",
888
+ config
889
+ );
890
+ const defaultAction = validateOptionalString(
891
+ config.defaultAction,
892
+ "defaultAction",
893
+ config
894
+ );
895
+ const defaultOrigin = validateOptionalString(
896
+ config.defaultOrigin,
897
+ "defaultOrigin",
898
+ config
899
+ );
900
+ const timeoutMs = validateTimeoutMs(config.timeoutMs, config);
901
+ return {
902
+ apiBaseUrl,
903
+ siteKey,
904
+ secretKey,
905
+ ...defaultAction !== void 0 ? { defaultAction } : {},
906
+ ...defaultOrigin !== void 0 ? { defaultOrigin } : {},
907
+ timeoutMs,
908
+ fetchImpl: config.fetchImpl ?? fetch,
909
+ ...config.logger !== void 0 ? { logger: config.logger } : {}
910
+ };
911
+ }
912
+
913
+ // src/client/createBotGuardClient.ts
914
+ function createBotGuardClient(config) {
915
+ return new BotGuardClient(validateClientConfig(config));
916
+ }
917
+ function createTrueSiftClient(config) {
918
+ return new TrueSiftClient(validateClientConfig(config));
919
+ }
920
+
921
+ export { BotGuardApiError, BotGuardAuthError, BotGuardClient, BotGuardConfigError, BotGuardError, BotGuardRequestError, BotGuardResponseError, BotGuardTimeoutError, REDACTED_VALUE, BotGuardApiError as TrueSiftApiError, BotGuardAuthError as TrueSiftAuthError, TrueSiftClient, BotGuardConfigError as TrueSiftConfigError, BotGuardError as TrueSiftError, BotGuardRequestError as TrueSiftRequestError, BotGuardResponseError as TrueSiftResponseError, BotGuardTimeoutError as TrueSiftTimeoutError, createBotGuardClient, createBotGuardDecisionFlags, createDecisionFlags, createTrueSiftClient, isAllowed, isAllowedDecision, isBlocked, isBlockedDecision, isBotGuardAllowed, isBotGuardAllowedDecision, isBotGuardBlocked, isBotGuardBlockedDecision, isBotGuardDecision, isBotGuardReview, isBotGuardReviewDecision, isReview, isReviewDecision, isSensitiveKey, isTrueSiftDecision, normalizeBotGuardDecision, normalizeDecision, redactBotGuardConfig, redactSensitiveRecord, redactSensitiveValue, redactTrueSiftClientConfig };