@keverdjs/fraud-sdk-react 1.1.2 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  'use strict';
2
2
 
3
+ var fraudSdk = require('@keverdjs/fraud-sdk');
3
4
  var react = require('react');
4
5
  var jsxRuntime = require('react/jsx-runtime');
5
6
 
@@ -633,8 +634,6 @@ var KeverdBehavioralCollector = class {
633
634
  }, 0);
634
635
  }
635
636
  };
636
-
637
- // src/core/keverd-sdk.ts
638
637
  var KeverdSDK = class {
639
638
  constructor() {
640
639
  this.config = null;
@@ -653,9 +652,6 @@ var KeverdSDK = class {
653
652
  }
654
653
  return;
655
654
  }
656
- if (!config.apiKey) {
657
- throw new Error("Keverd SDK: apiKey is required");
658
- }
659
655
  this.config = {
660
656
  debug: false,
661
657
  ...config
@@ -666,8 +662,6 @@ var KeverdSDK = class {
666
662
  if (this.config.debug) {
667
663
  console.log("[Keverd SDK] Initialized successfully");
668
664
  }
669
- this.startSession().catch(() => {
670
- });
671
665
  }
672
666
  /**
673
667
  * Get visitor data (fingerprint and risk assessment)
@@ -689,7 +683,8 @@ var KeverdSDK = class {
689
683
  session: sessionInfo,
690
684
  behavioral: behavioralData
691
685
  };
692
- const response = await this.sendFingerprintRequest(request, options);
686
+ const privacySignals = this.collectPrivacySignals();
687
+ const response = await this.sendFingerprintRequest(request, options, privacySignals);
693
688
  return this.transformResponse(response);
694
689
  } catch (error) {
695
690
  const keverdError = {
@@ -702,6 +697,27 @@ var KeverdSDK = class {
702
697
  throw keverdError;
703
698
  }
704
699
  }
700
+ /**
701
+ * Verify user identity during login attempts.
702
+ *
703
+ * This is a thin wrapper around the core web SDK `verifyLogin`, so it sends
704
+ * the same enhanced signals and structured login context as the vanilla SDK.
705
+ */
706
+ async verifyLogin(options) {
707
+ if (!this.isInitialized || !this.config) {
708
+ throw new Error("Keverd SDK not initialized. Call init() first.");
709
+ }
710
+ if (!this.config.apiKey) {
711
+ throw new Error("Keverd SDK: apiKey is required for verifyLogin");
712
+ }
713
+ fraudSdk.Keverd.init({
714
+ apiKey: this.config.apiKey,
715
+ userId: this.config.userId,
716
+ endpoint: this.getDefaultEndpoint(),
717
+ debug: this.config.debug || false
718
+ });
719
+ return fraudSdk.Keverd.verifyLogin(options);
720
+ }
705
721
  /**
706
722
  * Extract origin and referrer from browser
707
723
  */
@@ -716,11 +732,11 @@ var KeverdSDK = class {
716
732
  /**
717
733
  * Send fingerprint request to backend
718
734
  */
719
- async sendFingerprintRequest(request, options) {
735
+ async sendFingerprintRequest(request, options, privacySignals) {
720
736
  if (!this.config) {
721
737
  throw new Error("SDK not initialized");
722
738
  }
723
- const url = `${this.getDefaultEndpoint()}/fingerprint/score`;
739
+ const url = `${this.getDefaultEndpoint()}/v2/fingerprint`;
724
740
  const headers = {
725
741
  "Content-Type": "application/json",
726
742
  "X-SDK-Source": "react"
@@ -739,20 +755,11 @@ var KeverdSDK = class {
739
755
  headers["X-API-KEY"] = apiKey;
740
756
  headers["Authorization"] = `Bearer ${apiKey}`;
741
757
  }
742
- if (options?.tag === "sandbox") {
743
- headers["X-Sandbox"] = "true";
744
- }
745
- const requestBody = {
746
- ...request,
747
- session: {
748
- ...request.session || {},
749
- sessionId: this.sessionId || request.session?.sessionId
750
- }
751
- };
758
+ const signalsPayload = this.buildV2SignalsPayload(request, privacySignals);
752
759
  const response = await fetch(url, {
753
760
  method: "POST",
754
761
  headers,
755
- body: JSON.stringify(requestBody)
762
+ body: JSON.stringify({ signals: signalsPayload })
756
763
  });
757
764
  if (!response.ok) {
758
765
  const errorText = await response.text().catch(() => "Unknown error");
@@ -773,25 +780,345 @@ var KeverdSDK = class {
773
780
  * Transform API response to visitor data format
774
781
  */
775
782
  transformResponse(response) {
783
+ const fingerprintId = response.device_id || response.fingerprint;
784
+ if (fingerprintId) {
785
+ const similarity = typeof response.similarity === "number" ? response.similarity : 0;
786
+ const score = Number((1 - similarity).toFixed(3));
787
+ const riskScore = Math.round(score * 100);
788
+ const reasons = [];
789
+ if (response.is_new) reasons.push("new_device");
790
+ if (response.is_drifted) reasons.push("device_drifted");
791
+ if (reasons.length === 0) reasons.push("known_device_match");
792
+ const requestId = response.requestId || response.event_id || fingerprintId;
793
+ const historyCount = response.history_count ?? response.times_seen ?? 1;
794
+ return {
795
+ visitorId: fingerprintId,
796
+ riskScore,
797
+ score,
798
+ action: this.actionFromRisk(riskScore),
799
+ reasons,
800
+ sessionId: this.sessionId || fingerprintId,
801
+ requestId,
802
+ confidence: similarity,
803
+ extendedResult: {
804
+ fingerprint: fingerprintId,
805
+ eventId: requestId,
806
+ similarity,
807
+ isNew: response.is_new ?? false,
808
+ isDrifted: response.is_drifted ?? false,
809
+ visitCount: historyCount,
810
+ historyCount,
811
+ firstSeen: response.first_seen,
812
+ lastSeen: response.last_seen || (/* @__PURE__ */ new Date()).toISOString(),
813
+ browser: {
814
+ name: this._detectBrowser(),
815
+ version: void 0
816
+ },
817
+ network: {
818
+ anonymizer: "Unknown"
819
+ }
820
+ }
821
+ };
822
+ }
776
823
  return {
777
- visitorId: response.requestId,
778
- riskScore: response.risk_score,
779
- score: response.score,
780
- action: response.action,
824
+ visitorId: response.requestId || this.sessionId || this.generateSessionId(),
825
+ riskScore: response.risk_score ?? 0,
826
+ score: response.score ?? 0,
827
+ action: response.action ?? "allow",
781
828
  reasons: response.reason || [],
782
- sessionId: response.session_id,
783
- requestId: response.requestId,
829
+ sessionId: response.session_id || this.sessionId || this.generateSessionId(),
830
+ requestId: response.requestId || this.sessionId || this.generateSessionId(),
784
831
  simSwapEngine: response.sim_swap_engine,
785
- confidence: 1 - response.score
786
- // Inverse of risk score as confidence
832
+ confidence: response.confidence ?? 1 - (response.score ?? 0),
833
+ // Prefer backend confidence when present
834
+ deviceMatch: response.device_match,
835
+ fraudProbability: response.fraud_probability,
836
+ recommendedAction: response.recommended_action,
837
+ adaptiveResponse: response.adaptive_response ? {
838
+ recommendedAction: response.adaptive_response.recommended_action,
839
+ challenges: Array.isArray(response.adaptive_response.challenges) ? response.adaptive_response.challenges : void 0,
840
+ reason: response.adaptive_response.reason,
841
+ confidence: response.adaptive_response.confidence
842
+ } : void 0
843
+ };
844
+ }
845
+ buildV2SignalsPayload(request, privacySignals) {
846
+ const behavioral = request.behavioral || {};
847
+ const device = request.device;
848
+ const session = request.session || {};
849
+ const webgl = this.getWebGLInfo();
850
+ const browserInfo = this.parseBrowserInfo();
851
+ const osInfo = this.parseOsInfo();
852
+ const audioHash = this.computeSimpleHash(
853
+ `${navigator.userAgent}|${navigator.language}|${device.fingerprint}`
854
+ );
855
+ const detectedFonts = this.detectBasicFonts();
856
+ const screenWidth = Number(device.screenWidth ?? window.screen.width);
857
+ const screenHeight = Number(device.screenHeight ?? window.screen.height);
858
+ const maxTouchPoints = Number(navigator.maxTouchPoints || 0);
859
+ const touchSupport = maxTouchPoints > 0 || "ontouchstart" in window;
860
+ const memory = Number(navigator.deviceMemory || 0);
861
+ const concurrency = Number(navigator.hardwareConcurrency || 0);
862
+ const plugins = Array.from(navigator.plugins || []).map((plugin) => plugin.name).filter(Boolean).slice(0, 20);
863
+ const storageFlags = this.getStorageFlags();
864
+ const isIncognitoByStorage = !storageFlags.local_storage && !storageFlags.session_storage && !storageFlags.cookies;
865
+ const isIncognito = (privacySignals?.isIncognito ?? false) || isIncognitoByStorage;
866
+ const payload = {
867
+ hardware: {
868
+ ...memory > 0 ? { memory, device_memory: memory } : {},
869
+ concurrency,
870
+ screen_width: Number.isFinite(screenWidth) ? screenWidth : window.screen.width,
871
+ screen_height: Number.isFinite(screenHeight) ? screenHeight : window.screen.height,
872
+ color_depth: window.screen.colorDepth,
873
+ device_pixel_ratio: window.devicePixelRatio || 1,
874
+ touch_support: touchSupport,
875
+ max_touch_points: maxTouchPoints,
876
+ platform: navigator.platform || "unknown",
877
+ architecture: navigator.platform || "unknown",
878
+ device: device.device,
879
+ timezone: device.timezone,
880
+ languages: Array.isArray(navigator.languages) ? navigator.languages : [navigator.language],
881
+ gpu: {
882
+ renderer: webgl.renderer !== "unknown" ? webgl.renderer : "extraction-failed",
883
+ vendor: webgl.vendor !== "unknown" ? webgl.vendor : "extraction-failed",
884
+ version: webgl.version || void 0,
885
+ shading_language_version: webgl.shadingLanguageVersion || void 0
886
+ }
887
+ },
888
+ graphics: {
889
+ vendor: webgl.vendor,
890
+ renderer: webgl.renderer,
891
+ canvas_hash: device.fingerprint,
892
+ canvas: {
893
+ hash_webgl: this.computeSimpleHash(`${webgl.vendor}|${webgl.renderer}|${webgl.version || ""}`),
894
+ hash_winding: this.computeSimpleHash(`${device.fingerprint}|winding`),
895
+ hash_2d: this.computeSimpleHash(`${device.fingerprint}|2d`),
896
+ hash_text: this.computeSimpleHash(`${device.fingerprint}|text`)
897
+ },
898
+ webgl: {
899
+ vendor: webgl.vendor,
900
+ renderer: webgl.renderer,
901
+ version: webgl.version || void 0,
902
+ shading_language_version: webgl.shadingLanguageVersion || void 0
903
+ }
904
+ },
905
+ audio: {
906
+ fingerprint: audioHash,
907
+ audio_hash: audioHash,
908
+ available: true,
909
+ sample_rate: 44100,
910
+ codecs: this.getAudioCodecs()
911
+ },
912
+ behavioral: {
913
+ mouse_speed: behavioral.swipe_velocity ?? 0,
914
+ languages: Array.isArray(navigator.languages) ? navigator.languages : [navigator.language],
915
+ timezone: device.timezone
916
+ },
917
+ software: {
918
+ os: osInfo.os,
919
+ browser: browserInfo.browser,
920
+ version: browserInfo.version,
921
+ platform: navigator.platform,
922
+ ua: navigator.userAgent,
923
+ fonts: detectedFonts.slice(0, 20),
924
+ plugins,
925
+ storage_flags: storageFlags,
926
+ do_not_track: navigator.doNotTrack ?? null
927
+ },
928
+ network: {
929
+ ip_isp: "unknown",
930
+ vpn: privacySignals?.isVPN ?? false,
931
+ vpn_detected: privacySignals?.isVPN ?? false,
932
+ proxy: false,
933
+ tor: false
934
+ },
935
+ session: {
936
+ incognito: isIncognito,
937
+ session_id: this.sessionId || session.sessionId || request.userId || this.generateSessionId(),
938
+ bot: privacySignals?.isAutomated ?? false,
939
+ automation: privacySignals?.isAutomated ?? false,
940
+ hasAdBlocker: privacySignals?.hasAdBlocker ?? false,
941
+ has_ad_blocker: privacySignals?.hasAdBlocker ?? false
942
+ },
943
+ confidence: 0.9
944
+ };
945
+ const persistenceScore = this.calculatePersistenceScore(payload);
946
+ const fallback = persistenceScore < 0.7;
947
+ payload._metadata = {
948
+ fallback,
949
+ fallback_reason: fallback ? "incognito_restrictions" : void 0,
950
+ sdk_version: "2.0.0",
951
+ persistence_score: persistenceScore,
952
+ collected_at: Date.now()
953
+ };
954
+ if (fallback) {
955
+ return {
956
+ hardware: payload.hardware,
957
+ graphics: {
958
+ canvas: {
959
+ hash_webgl: payload.graphics?.canvas?.hash_webgl,
960
+ hash_winding: payload.graphics?.canvas?.hash_winding
961
+ },
962
+ webgl: {
963
+ vendor: payload.graphics?.webgl?.vendor,
964
+ renderer: payload.graphics?.webgl?.renderer
965
+ }
966
+ },
967
+ session: payload.session,
968
+ _metadata: payload._metadata
969
+ };
970
+ }
971
+ return payload;
972
+ }
973
+ getWebGLInfo() {
974
+ try {
975
+ const canvas = document.createElement("canvas");
976
+ const gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
977
+ if (!gl) {
978
+ return { vendor: "unknown", renderer: "unknown" };
979
+ }
980
+ const debugInfo = gl.getExtension("WEBGL_debug_renderer_info");
981
+ if (debugInfo) {
982
+ return {
983
+ vendor: String(gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL) || "unknown"),
984
+ renderer: String(gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL) || "unknown"),
985
+ version: String(gl.getParameter(gl.VERSION) || "unknown"),
986
+ shadingLanguageVersion: String(gl.getParameter(gl.SHADING_LANGUAGE_VERSION) || "unknown")
987
+ };
988
+ }
989
+ return {
990
+ vendor: String(gl.getParameter(gl.VENDOR) || "unknown"),
991
+ renderer: String(gl.getParameter(gl.RENDERER) || "unknown"),
992
+ version: String(gl.getParameter(gl.VERSION) || "unknown"),
993
+ shadingLanguageVersion: String(gl.getParameter(gl.SHADING_LANGUAGE_VERSION) || "unknown")
994
+ };
995
+ } catch {
996
+ return { vendor: "unknown", renderer: "unknown" };
997
+ }
998
+ }
999
+ parseBrowserInfo() {
1000
+ const ua = navigator.userAgent;
1001
+ const browser = this._detectBrowser();
1002
+ const versionMatch = ua.match(/Chrome\/([\d.]+)/) || ua.match(/Firefox\/([\d.]+)/) || ua.match(/Version\/([\d.]+)/) || ua.match(/Edg\/([\d.]+)/);
1003
+ return { browser, version: versionMatch?.[1] || "unknown" };
1004
+ }
1005
+ parseOsInfo() {
1006
+ const ua = navigator.userAgent;
1007
+ if (ua.includes("Mac OS X")) return { os: "Mac OS X" };
1008
+ if (ua.includes("Windows")) return { os: "Windows" };
1009
+ if (ua.includes("Android")) return { os: "Android" };
1010
+ if (ua.includes("iPhone") || ua.includes("iPad")) return { os: "iOS" };
1011
+ if (ua.includes("Linux")) return { os: "Linux" };
1012
+ return { os: "unknown" };
1013
+ }
1014
+ detectBasicFonts() {
1015
+ const commonFonts = ["Arial", "Helvetica", "Verdana", "Times New Roman", "Courier New"];
1016
+ return commonFonts.slice(0, 3);
1017
+ }
1018
+ calculatePersistenceScore(payload) {
1019
+ let score = 0;
1020
+ if (payload.hardware?.gpu?.renderer && payload.hardware.gpu.renderer !== "extraction-failed") score += 0.25;
1021
+ if (payload.hardware?.screen_width && payload.hardware?.screen_height) score += 0.15;
1022
+ if ((payload.hardware?.concurrency || 0) > 0) score += 0.1;
1023
+ if (payload.graphics?.webgl?.renderer || payload.graphics?.webgl?.vendor) score += 0.2;
1024
+ if (payload.graphics?.canvas?.hash_webgl) score += 0.15;
1025
+ if (payload.audio?.fingerprint) score += 0.1;
1026
+ if ((payload.software?.fonts || []).length > 0) score += 0.03;
1027
+ if ((payload.software?.plugins || []).length > 0) score += 0.02;
1028
+ return Number(Math.min(1, score).toFixed(2));
1029
+ }
1030
+ getStorageFlags() {
1031
+ return {
1032
+ local_storage: this.isApiAvailable(() => window.localStorage),
1033
+ session_storage: this.isApiAvailable(() => window.sessionStorage),
1034
+ indexed_db: this.isApiAvailable(() => window.indexedDB),
1035
+ cookies: navigator.cookieEnabled === true
787
1036
  };
788
1037
  }
1038
+ isApiAvailable(accessor) {
1039
+ try {
1040
+ return typeof accessor() !== "undefined";
1041
+ } catch {
1042
+ return false;
1043
+ }
1044
+ }
1045
+ getAudioCodecs() {
1046
+ const audio = document.createElement("audio");
1047
+ return {
1048
+ mp3: audio.canPlayType("audio/mpeg"),
1049
+ ogg: audio.canPlayType("audio/ogg"),
1050
+ aac: audio.canPlayType("audio/aac"),
1051
+ opus: audio.canPlayType("audio/ogg; codecs=opus")
1052
+ };
1053
+ }
1054
+ collectPrivacySignals() {
1055
+ const isIncognito = this.isStorageUnavailable();
1056
+ return {
1057
+ isIncognito,
1058
+ isVPN: false,
1059
+ isAutomated: navigator.webdriver === true,
1060
+ hasAdBlocker: this.detectAdBlocker()
1061
+ };
1062
+ }
1063
+ isStorageUnavailable() {
1064
+ try {
1065
+ const hasLocal = typeof window.localStorage !== "undefined";
1066
+ const hasSession = typeof window.sessionStorage !== "undefined";
1067
+ const cookies = navigator.cookieEnabled === true;
1068
+ return !hasLocal && !hasSession && !cookies;
1069
+ } catch {
1070
+ return true;
1071
+ }
1072
+ }
1073
+ detectAdBlocker() {
1074
+ try {
1075
+ const bait = document.createElement("div");
1076
+ bait.className = "adsbox";
1077
+ bait.style.position = "absolute";
1078
+ bait.style.left = "-9999px";
1079
+ document.body.appendChild(bait);
1080
+ const blocked = bait.offsetHeight === 0 || bait.offsetWidth === 0;
1081
+ document.body.removeChild(bait);
1082
+ return blocked;
1083
+ } catch {
1084
+ return false;
1085
+ }
1086
+ }
1087
+ computeSimpleHash(value) {
1088
+ let hash = 0;
1089
+ for (let i = 0; i < value.length; i++) {
1090
+ hash = (hash << 5) - hash + value.charCodeAt(i) | 0;
1091
+ }
1092
+ return Math.abs(hash).toString(16);
1093
+ }
1094
+ actionFromRisk(riskScore) {
1095
+ if (riskScore >= 85) return "block";
1096
+ if (riskScore >= 65) return "hard_challenge";
1097
+ if (riskScore >= 40) return "soft_challenge";
1098
+ return "allow";
1099
+ }
789
1100
  /**
790
- * Get default endpoint
1101
+ * Resolve endpoint: config > env > production default.
791
1102
  */
792
1103
  getDefaultEndpoint() {
1104
+ const configuredEndpoint = this.normalizeEndpoint(this.config?.endpoint);
1105
+ if (configuredEndpoint) {
1106
+ return configuredEndpoint;
1107
+ }
1108
+ const envEndpoint = this.normalizeEndpoint(
1109
+ typeof globalThis !== "undefined" ? globalThis?.process?.env?.NEXT_PUBLIC_API_URL : void 0
1110
+ );
1111
+ if (envEndpoint) {
1112
+ return envEndpoint;
1113
+ }
793
1114
  return "https://api.keverd.com";
794
1115
  }
1116
+ normalizeEndpoint(value) {
1117
+ if (!value) return void 0;
1118
+ const trimmed = value.trim();
1119
+ if (!trimmed) return void 0;
1120
+ return trimmed.replace(/\/+$/, "");
1121
+ }
795
1122
  /**
796
1123
  * Generate a session ID
797
1124
  */
@@ -993,9 +1320,6 @@ var KeverdSDK = class {
993
1320
  * Destroy the SDK instance
994
1321
  */
995
1322
  async destroy() {
996
- if (this.sessionId) {
997
- await this.endSession();
998
- }
999
1323
  const wasDebug = this.config?.debug;
1000
1324
  this.behavioralCollector.stop();
1001
1325
  this.isInitialized = false;
@@ -1019,15 +1343,19 @@ var KeverdSDK = class {
1019
1343
  }
1020
1344
  };
1021
1345
  var KeverdContext = react.createContext(null);
1022
- function KeverdProvider({ loadOptions, children }) {
1346
+ function KeverdProvider({ apiKey, endpoint, debug, loadOptions, children }) {
1023
1347
  const sdkRef = react.useRef(new KeverdSDK());
1024
1348
  const [isReady, setIsReady] = react.useState(false);
1025
1349
  react.useEffect(() => {
1026
1350
  const sdk = sdkRef.current;
1027
1351
  if (!sdk.isReady()) {
1352
+ const resolvedApiKey = apiKey ?? loadOptions?.apiKey;
1353
+ const resolvedEndpoint = endpoint ?? loadOptions?.endpoint;
1354
+ const resolvedDebug = debug ?? loadOptions?.debug ?? false;
1028
1355
  sdk.init({
1029
- apiKey: loadOptions.apiKey,
1030
- debug: loadOptions.debug || false
1356
+ apiKey: resolvedApiKey,
1357
+ endpoint: resolvedEndpoint,
1358
+ debug: resolvedDebug
1031
1359
  });
1032
1360
  setIsReady(true);
1033
1361
  }
@@ -1038,7 +1366,7 @@ function KeverdProvider({ loadOptions, children }) {
1038
1366
  setIsReady(false);
1039
1367
  }
1040
1368
  };
1041
- }, []);
1369
+ }, [apiKey, endpoint, debug, loadOptions]);
1042
1370
  const contextValue = {
1043
1371
  sdk: sdkRef.current,
1044
1372
  isReady
@@ -1105,11 +1433,112 @@ function useKeverdVisitorData(options) {
1105
1433
  getData
1106
1434
  };
1107
1435
  }
1436
+ function useKeverd() {
1437
+ const { isLoading, error, data, getData } = useKeverdVisitorData({ immediate: true });
1438
+ react.useEffect(() => {
1439
+ if (typeof window === "undefined") return;
1440
+ if (data?.visitorId) {
1441
+ window.__KEVERD_DEVICE_ID__ = data.visitorId;
1442
+ }
1443
+ }, [data?.visitorId]);
1444
+ const refresh = async () => {
1445
+ await getData({ ignoreCache: true });
1446
+ };
1447
+ return react.useMemo(
1448
+ () => ({
1449
+ deviceId: data?.visitorId || null,
1450
+ riskScore: typeof data?.riskScore === "number" ? data.riskScore : null,
1451
+ isLoading,
1452
+ error,
1453
+ refresh
1454
+ }),
1455
+ [data?.visitorId, data?.riskScore, isLoading, error]
1456
+ );
1457
+ }
1458
+
1459
+ // src/utils/identifiers.ts
1460
+ function bufferToHex(buffer) {
1461
+ const bytes = new Uint8Array(buffer);
1462
+ let hex = "";
1463
+ for (let i = 0; i < bytes.length; i++) {
1464
+ hex += bytes[i].toString(16).padStart(2, "0");
1465
+ }
1466
+ return hex;
1467
+ }
1468
+ function fallbackHash(input) {
1469
+ let hash = 0;
1470
+ for (let i = 0; i < input.length; i++) {
1471
+ hash = (hash << 5) - hash + input.charCodeAt(i);
1472
+ hash |= 0;
1473
+ }
1474
+ return Math.abs(hash).toString(16);
1475
+ }
1476
+ async function hashLoginIdentifier(identifier, options) {
1477
+ if (!identifier) {
1478
+ throw new Error("[Keverd SDK] identifier is required for hashing");
1479
+ }
1480
+ const salt = options?.salt ?? "";
1481
+ const normalized = (salt + identifier).toLowerCase();
1482
+ const prefix = options?.prefix ?? "sha256";
1483
+ try {
1484
+ if (typeof window !== "undefined" && window.crypto?.subtle) {
1485
+ const encoder = new TextEncoder();
1486
+ const digest = await window.crypto.subtle.digest("SHA-256", encoder.encode(normalized));
1487
+ return `${prefix}:${bufferToHex(digest)}`;
1488
+ }
1489
+ } catch {
1490
+ }
1491
+ return `${prefix}:${fallbackHash(normalized)}`;
1492
+ }
1493
+ async function buildLoginContextFromIdentifier(options) {
1494
+ const identifierHash = await hashLoginIdentifier(options.identifier, {
1495
+ salt: options.salt
1496
+ });
1497
+ return {
1498
+ identifierHash,
1499
+ result: options.result,
1500
+ failureReason: options.failureReason,
1501
+ authMethod: options.authMethod,
1502
+ mfaUsed: options.mfaUsed,
1503
+ attemptId: options.attemptId
1504
+ };
1505
+ }
1506
+
1507
+ // src/utils/adaptive.ts
1508
+ function handleAdaptiveResponse(response, handlers) {
1509
+ const anyResp = response;
1510
+ const adaptive = anyResp.adaptive_response || {};
1511
+ const recommendedAction = anyResp.recommended_action || adaptive.recommended_action || response.action;
1512
+ const challenges = Array.isArray(adaptive.challenges) ? adaptive.challenges : [];
1513
+ switch (recommendedAction) {
1514
+ case "allow":
1515
+ handlers.onAllow?.(response);
1516
+ break;
1517
+ case "soft_challenge":
1518
+ handlers.onSoftChallenge?.(response);
1519
+ break;
1520
+ case "hard_challenge":
1521
+ handlers.onHardChallenge?.(response);
1522
+ break;
1523
+ case "block":
1524
+ handlers.onBlock?.(response);
1525
+ break;
1526
+ default:
1527
+ handlers.onUnknown?.(response);
1528
+ }
1529
+ if (challenges.length) {
1530
+ handlers.onChallenges?.(response, challenges);
1531
+ }
1532
+ }
1108
1533
 
1109
1534
  exports.KeverdBehavioralCollector = KeverdBehavioralCollector;
1110
1535
  exports.KeverdDeviceCollector = KeverdDeviceCollector;
1111
1536
  exports.KeverdProvider = KeverdProvider;
1112
1537
  exports.KeverdSDK = KeverdSDK;
1538
+ exports.buildLoginContextFromIdentifier = buildLoginContextFromIdentifier;
1539
+ exports.handleAdaptiveResponse = handleAdaptiveResponse;
1540
+ exports.hashLoginIdentifier = hashLoginIdentifier;
1541
+ exports.useKeverd = useKeverd;
1113
1542
  exports.useKeverdContext = useKeverdContext;
1114
1543
  exports.useKeverdVisitorData = useKeverdVisitorData;
1115
1544
  //# sourceMappingURL=index.js.map