@keverdjs/fraud-sdk-react 2.0.0 → 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/README.md +24 -38
- package/dist/index.d.mts +132 -12
- package/dist/index.d.ts +132 -12
- package/dist/index.js +484 -37
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +482 -39
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
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
|
|
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,39 +697,69 @@ 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
|
+
}
|
|
721
|
+
/**
|
|
722
|
+
* Extract origin and referrer from browser
|
|
723
|
+
*/
|
|
724
|
+
getOriginHeaders() {
|
|
725
|
+
if (typeof window === "undefined") {
|
|
726
|
+
return {};
|
|
727
|
+
}
|
|
728
|
+
const origin = window.location.origin || void 0;
|
|
729
|
+
const referrer = document.referrer || void 0;
|
|
730
|
+
return { origin, referrer };
|
|
731
|
+
}
|
|
705
732
|
/**
|
|
706
733
|
* Send fingerprint request to backend
|
|
707
734
|
*/
|
|
708
|
-
async sendFingerprintRequest(request, options) {
|
|
735
|
+
async sendFingerprintRequest(request, options, privacySignals) {
|
|
709
736
|
if (!this.config) {
|
|
710
737
|
throw new Error("SDK not initialized");
|
|
711
738
|
}
|
|
712
|
-
const url = `${this.getDefaultEndpoint()}/fingerprint
|
|
739
|
+
const url = `${this.getDefaultEndpoint()}/v2/fingerprint`;
|
|
713
740
|
const headers = {
|
|
714
741
|
"Content-Type": "application/json",
|
|
715
742
|
"X-SDK-Source": "react"
|
|
716
743
|
// Identify SDK source for backend analytics
|
|
717
744
|
};
|
|
745
|
+
const { origin, referrer } = this.getOriginHeaders();
|
|
746
|
+
if (origin) {
|
|
747
|
+
headers["X-Origin-URL"] = origin;
|
|
748
|
+
}
|
|
749
|
+
if (referrer) {
|
|
750
|
+
headers["X-Referrer-URL"] = referrer;
|
|
751
|
+
}
|
|
718
752
|
const apiKey = this.config.apiKey;
|
|
719
753
|
if (apiKey) {
|
|
720
754
|
headers["x-keverd-key"] = apiKey;
|
|
721
755
|
headers["X-API-KEY"] = apiKey;
|
|
722
756
|
headers["Authorization"] = `Bearer ${apiKey}`;
|
|
723
757
|
}
|
|
724
|
-
|
|
725
|
-
headers["X-Sandbox"] = "true";
|
|
726
|
-
}
|
|
727
|
-
const requestBody = {
|
|
728
|
-
...request,
|
|
729
|
-
session: {
|
|
730
|
-
...request.session || {},
|
|
731
|
-
sessionId: this.sessionId || request.session?.sessionId
|
|
732
|
-
}
|
|
733
|
-
};
|
|
758
|
+
const signalsPayload = this.buildV2SignalsPayload(request, privacySignals);
|
|
734
759
|
const response = await fetch(url, {
|
|
735
760
|
method: "POST",
|
|
736
761
|
headers,
|
|
737
|
-
body: JSON.stringify(
|
|
762
|
+
body: JSON.stringify({ signals: signalsPayload })
|
|
738
763
|
});
|
|
739
764
|
if (!response.ok) {
|
|
740
765
|
const errorText = await response.text().catch(() => "Unknown error");
|
|
@@ -755,25 +780,345 @@ var KeverdSDK = class {
|
|
|
755
780
|
* Transform API response to visitor data format
|
|
756
781
|
*/
|
|
757
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
|
+
}
|
|
758
823
|
return {
|
|
759
|
-
visitorId: response.requestId,
|
|
760
|
-
riskScore: response.risk_score,
|
|
761
|
-
score: response.score,
|
|
762
|
-
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",
|
|
763
828
|
reasons: response.reason || [],
|
|
764
|
-
sessionId: response.session_id,
|
|
765
|
-
requestId: response.requestId,
|
|
829
|
+
sessionId: response.session_id || this.sessionId || this.generateSessionId(),
|
|
830
|
+
requestId: response.requestId || this.sessionId || this.generateSessionId(),
|
|
766
831
|
simSwapEngine: response.sim_swap_engine,
|
|
767
|
-
confidence: 1 - response.score
|
|
768
|
-
//
|
|
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
|
|
1036
|
+
};
|
|
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()
|
|
769
1061
|
};
|
|
770
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
|
+
}
|
|
771
1100
|
/**
|
|
772
|
-
*
|
|
1101
|
+
* Resolve endpoint: config > env > production default.
|
|
773
1102
|
*/
|
|
774
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
|
+
}
|
|
775
1114
|
return "https://api.keverd.com";
|
|
776
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
|
+
}
|
|
777
1122
|
/**
|
|
778
1123
|
* Generate a session ID
|
|
779
1124
|
*/
|
|
@@ -975,9 +1320,6 @@ var KeverdSDK = class {
|
|
|
975
1320
|
* Destroy the SDK instance
|
|
976
1321
|
*/
|
|
977
1322
|
async destroy() {
|
|
978
|
-
if (this.sessionId) {
|
|
979
|
-
await this.endSession();
|
|
980
|
-
}
|
|
981
1323
|
const wasDebug = this.config?.debug;
|
|
982
1324
|
this.behavioralCollector.stop();
|
|
983
1325
|
this.isInitialized = false;
|
|
@@ -1001,15 +1343,19 @@ var KeverdSDK = class {
|
|
|
1001
1343
|
}
|
|
1002
1344
|
};
|
|
1003
1345
|
var KeverdContext = react.createContext(null);
|
|
1004
|
-
function KeverdProvider({ loadOptions, children }) {
|
|
1346
|
+
function KeverdProvider({ apiKey, endpoint, debug, loadOptions, children }) {
|
|
1005
1347
|
const sdkRef = react.useRef(new KeverdSDK());
|
|
1006
1348
|
const [isReady, setIsReady] = react.useState(false);
|
|
1007
1349
|
react.useEffect(() => {
|
|
1008
1350
|
const sdk = sdkRef.current;
|
|
1009
1351
|
if (!sdk.isReady()) {
|
|
1352
|
+
const resolvedApiKey = apiKey ?? loadOptions?.apiKey;
|
|
1353
|
+
const resolvedEndpoint = endpoint ?? loadOptions?.endpoint;
|
|
1354
|
+
const resolvedDebug = debug ?? loadOptions?.debug ?? false;
|
|
1010
1355
|
sdk.init({
|
|
1011
|
-
apiKey:
|
|
1012
|
-
|
|
1356
|
+
apiKey: resolvedApiKey,
|
|
1357
|
+
endpoint: resolvedEndpoint,
|
|
1358
|
+
debug: resolvedDebug
|
|
1013
1359
|
});
|
|
1014
1360
|
setIsReady(true);
|
|
1015
1361
|
}
|
|
@@ -1020,7 +1366,7 @@ function KeverdProvider({ loadOptions, children }) {
|
|
|
1020
1366
|
setIsReady(false);
|
|
1021
1367
|
}
|
|
1022
1368
|
};
|
|
1023
|
-
}, []);
|
|
1369
|
+
}, [apiKey, endpoint, debug, loadOptions]);
|
|
1024
1370
|
const contextValue = {
|
|
1025
1371
|
sdk: sdkRef.current,
|
|
1026
1372
|
isReady
|
|
@@ -1087,11 +1433,112 @@ function useKeverdVisitorData(options) {
|
|
|
1087
1433
|
getData
|
|
1088
1434
|
};
|
|
1089
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
|
+
}
|
|
1090
1533
|
|
|
1091
1534
|
exports.KeverdBehavioralCollector = KeverdBehavioralCollector;
|
|
1092
1535
|
exports.KeverdDeviceCollector = KeverdDeviceCollector;
|
|
1093
1536
|
exports.KeverdProvider = KeverdProvider;
|
|
1094
1537
|
exports.KeverdSDK = KeverdSDK;
|
|
1538
|
+
exports.buildLoginContextFromIdentifier = buildLoginContextFromIdentifier;
|
|
1539
|
+
exports.handleAdaptiveResponse = handleAdaptiveResponse;
|
|
1540
|
+
exports.hashLoginIdentifier = hashLoginIdentifier;
|
|
1541
|
+
exports.useKeverd = useKeverd;
|
|
1095
1542
|
exports.useKeverdContext = useKeverdContext;
|
|
1096
1543
|
exports.useKeverdVisitorData = useKeverdVisitorData;
|
|
1097
1544
|
//# sourceMappingURL=index.js.map
|