@safepassage/sdk 3.4.8 → 3.4.10
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 +20 -11
- package/brands/safepassage/index.d.ts +12 -0
- package/brands/safepassage/urls.d.ts +3 -0
- package/core/VerificationSDK.d.ts +132 -0
- package/index.d.ts +2 -0
- package/index.js +1091 -0
- package/package.json +14 -47
- package/safepassage.min.js +3 -0
- package/sdk.min.js +3 -0
- package/{dist/types/index.d.ts → types/base.d.ts} +27 -10
- package/utils/crypto.d.ts +31 -0
- package/utils/environment.d.ts +8 -0
- package/{dist/utils → utils}/polyfills.d.ts +1 -2
- package/{dist/utils → utils}/security.d.ts +11 -11
- package/{dist/utils → utils}/validation.d.ts +9 -12
- package/dist/core/SafePassageSDK.d.ts +0 -230
- package/dist/core/SafePassageSDK.js +0 -651
- package/dist/index.d.ts +0 -21
- package/dist/index.js +0 -1095
- package/dist/safepassage.min.js +0 -3
- package/dist/types/index.js +0 -4
- package/dist/utils/__mocks__/polyfills.d.ts +0 -3
- package/dist/utils/__mocks__/polyfills.js +0 -10
- package/dist/utils/crypto.d.ts +0 -105
- package/dist/utils/crypto.js +0 -216
- package/dist/utils/environment.d.ts +0 -13
- package/dist/utils/environment.js +0 -75
- package/dist/utils/polyfills.js +0 -58
- package/dist/utils/security.js +0 -190
- package/dist/utils/validation.js +0 -158
package/dist/safepassage.min.js
DELETED
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
/* SafePassage SDK v3.4.8 */
|
|
2
|
-
"use strict";var SafePassageSDK=(()=>{var S=Object.defineProperty,oe=Object.defineProperties,ae=Object.getOwnPropertyDescriptor,ce=Object.getOwnPropertyDescriptors,le=Object.getOwnPropertyNames,v=Object.getOwnPropertySymbols;var P=Object.prototype.hasOwnProperty,C=Object.prototype.propertyIsEnumerable;var R=(t,e,n)=>e in t?S(t,e,{enumerable:!0,configurable:!0,writable:!0,value:n}):t[e]=n,h=(t,e)=>{for(var n in e||(e={}))P.call(e,n)&&R(t,n,e[n]);if(v)for(var n of v(e))C.call(e,n)&&R(t,n,e[n]);return t},y=(t,e)=>oe(t,ce(e));var D=(t,e)=>{var n={};for(var i in t)P.call(t,i)&&e.indexOf(i)<0&&(n[i]=t[i]);if(t!=null&&v)for(var i of v(t))e.indexOf(i)<0&&C.call(t,i)&&(n[i]=t[i]);return n};var w=(t,e)=>()=>(t&&(e=t(t=0)),e);var U=(t,e)=>{for(var n in e)S(t,n,{get:e[n],enumerable:!0})},pe=(t,e,n,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of le(e))!P.call(t,s)&&s!==n&&S(t,s,{get:()=>e[s],enumerable:!(i=ae(e,s))||i.enumerable});return t};var $=t=>pe(S({},"__esModule",{value:!0}),t);function de(t,e){return K[e].includes(t)}function W(t,e,n=[]){var s;let{origin:i}=t;return de(i,e)||n.length>0&&n.some(o=>{if(o.startsWith("*.")){let a=o.slice(2);return i.endsWith(`.${a}`)||i===`https://${a}`||i===`http://${a}`}return i===o})?!0:(console.warn(`SafePassage Security: Blocked PostMessage from untrusted origin: ${i}`,{environment:e,trustedOrigins:K[e],allowedCustomOrigins:n,eventType:(s=t.data)==null?void 0:s.type}),!1)}function H(t,e){let{data:n}=t;return!n||typeof n!="object"?{isValid:!1,error:"Invalid message format"}:n.type!=="safepassage:verification:complete"?{isValid:!1,error:"Invalid message type"}:!n.sessionId||n.sessionId!==e?{isValid:!1,error:"Session ID mismatch"}:!n.status||!["verified","failed"].includes(n.status)?{isValid:!1,error:"Invalid status value"}:{isValid:!0}}function j(t){t==="production"&&window.location.protocol!=="https:"&&console.warn("SafePassage Warning: HTTPS recommended for production environment",{current:window.location.href})}function T(t,e){try{let n=new URL(t);if(n.protocol!=="https:"&&!(n.hostname==="localhost"||n.hostname==="127.0.0.1"))return{isValid:!1,error:`HTTPS required for return URLs in ${e}`};let i=[/data:/i,/javascript:/i,/vbscript:/i,/file:/i,/ftp:/i];for(let s of i)if(s.test(t))return{isValid:!1,error:"Blocked suspicious URL scheme"};return{isValid:!0}}catch(n){return{isValid:!1,error:"Invalid URL format"}}}function p(t,e){console.warn(`SafePassage Security Event: ${t}`,h({timestamp:new Date().toISOString(),userAgent:navigator.userAgent,url:window.location.href},e))}var K,A,q,b=w(()=>{"use strict";K={production:["https://av.safepassageapp.com","https://portal.safepassageapp.com","https://api.safepassageapp.com"],staging:["https://av.staging.safepassageapp.com","https://portal.staging.safepassageapp.com","https://api.staging.safepassageapp.com"]};A=class{constructor(){this.attempts=new Map;this.maxAttempts=5;this.timeWindow=6e4}isAllowed(e){let n=Date.now(),s=(this.attempts.get(e)||[]).filter(r=>n-r<this.timeWindow);return s.length>=this.maxAttempts?(console.warn(`SafePassage Security: Rate limit exceeded for ${e}`),!1):(s.push(n),this.attempts.set(e,s),!0)}reset(e){this.attempts.delete(e)}},q=new A});var G={};U(G,{createSignedState:()=>ge,generateHMAC:()=>x,generateSecureToken:()=>B,getSigningSecret:()=>V,parseSignedState:()=>fe,verifyHMAC:()=>F});async function x(t,e){let n=new TextEncoder,i=n.encode(e),s=n.encode(t),r=await crypto.subtle.importKey("raw",i,{name:"HMAC",hash:"SHA-256"},!1,["sign"]),o=await crypto.subtle.sign("HMAC",r,s);return Array.from(new Uint8Array(o)).map(a=>a.toString(16).padStart(2,"0")).join("")}async function F(t,e,n){try{let i=await x(t,n);return ue(e,i)}catch(i){return!1}}function ue(t,e){if(t.length!==e.length)return!1;let n=0;for(let i=0;i<t.length;i++)n|=t.charCodeAt(i)^e.charCodeAt(i);return n===0}function B(t=32){let e=new Uint8Array(t);return crypto.getRandomValues(e),Array.from(e,n=>n.toString(16).padStart(2,"0")).join("")}function V(t){return{production:"safepassage-prod-hmac-2025",staging:"safepassage-stage-hmac-2025"}[t]}async function ge(t,e){let n=y(h({},t),{timestamp:Date.now(),nonce:B(16)}),i=JSON.stringify(n),s=V(e),r=await x(i,s);return btoa(JSON.stringify({data:n,signature:r}))}async function fe(t,e,n=X){try{let s=atob(t),r=JSON.parse(s);if(!r.data||!r.signature)return console.warn("SafePassage: Invalid signed state format"),null;let{data:o,signature:a}=r,c=JSON.stringify(o),l=V(e);if(!await F(c,a,l))return console.warn("SafePassage: State signature verification failed"),null;if(o.timestamp){let m=Date.now()-o.timestamp;if(m>n)return console.warn("SafePassage: State parameter expired",{age:m,maxAge:n}),null}let i=o,{timestamp:g,nonce:f}=i;return D(i,["timestamp","nonce"])}catch(s){return console.warn("SafePassage: Failed to parse signed state",s),null}}var J=w(()=>{"use strict";k()});function Q(t){if(!t.apiKey)throw new Error("apiKey is required");if(t.apiKey.length>Z)throw new Error(`apiKey exceeds maximum length of ${Z} characters`);if(!he.test(t.apiKey))throw new Error("Invalid apiKey format. Expected pk_xxx (public) or sk_xxx (private)");if(typeof window!="undefined"&&t.apiKey.startsWith("sk_"))throw new Error("Secret keys (sk_) should never be used in browser code for security reasons. Secret keys expose your account to unauthorized access if used client-side. Please use your public key (pk_) instead. If you need to use features that require a secret key (like custom challenge age), create the session server-side and pass the sessionId to startVerificationWithSession(). See: https://docs.safepassageapp.com/server-side-sessions");if(!t.returnUrl)throw new Error("returnUrl is required");if(t.returnUrl.length>E)throw new Error(`returnUrl exceeds maximum length of ${E} characters`);let e=me(),n=T(t.returnUrl,e);if(!n.isValid)throw new Error(`returnUrl validation failed: ${n.error}`);if(t.cancelUrl){if(t.cancelUrl.length>E)throw new Error(`cancelUrl exceeds maximum length of ${E} characters`);let i=T(t.cancelUrl,e);if(!i.isValid)throw new Error(`cancelUrl validation failed: ${i.error}`)}if(t.defaultChallengeAge!==void 0){if(t.defaultChallengeAge<Y)throw new Error(`defaultChallengeAge must be at least ${Y}`);if(t.defaultChallengeAge>z)throw new Error(`defaultChallengeAge cannot exceed ${z}`)}if(t.defaultVerificationMode&&!["L1","L2"].includes(t.defaultVerificationMode))throw new Error("defaultVerificationMode must be L1 or L2");if(t.mode&&!["redirect","new-tab"].includes(t.mode))throw new Error("mode must be redirect or new-tab")}function me(){if(typeof window=="undefined")return"production";let t=window.location.hostname;return t.includes("staging")||t.includes("stage")?"staging":"production"}async function ee(t,e){let{createSignedState:n}=await Promise.resolve().then(()=>(J(),G));return n(t,e)}var Y,z,E,Z,X,he,k=w(()=>{"use strict";b();Y=25,z=150,E=2048,Z=128,X=6e5,he=/^(pk_|sk_)[a-zA-Z0-9_]+$/});function M(t){let e=te[t]||te.production;if(!e||!e.startsWith("https://"))throw new Error(`HTTPS required for ${t} environment`);return e}function we(t){let e={production:"https://api.safepassageapp.com",staging:"https://api.staging.safepassageapp.com"},n=e[t]||e.production;if(!n||!n.startsWith("https://"))throw new Error(`HTTPS required for API URLs in ${t} environment`);return n}function ne(t){let e=window.location.protocol==="https:";switch(t){case"production":e||console.warn("SafePassage Warning: HTTPS recommended for production environment");break;case"staging":e||console.warn("SafePassage Warning: HTTPS strongly recommended in staging environment");break}try{M(t),we(t)}catch(n){let i=n instanceof Error?n.message:String(n);throw new Error(`Environment configuration validation failed: ${i}`)}}var te,ie=w(()=>{"use strict";te={production:"https://av.safepassageapp.com",staging:"https://av.staging.safepassageapp.com"}});var re={};U(re,{SafePassage:()=>u,default:()=>ve});var u,ve,L=w(()=>{"use strict";k();ie();b();u=class{constructor(e){this.popupWindow=null;this.messageListener=null;this.popupMonitorInterval=null;this.unloadListener=null;this.isVerificationInProgress=!1;this.currentSessionId=null;this.lastVerifyUrl=null;this.lastSessionToken=null;this.temporaryHandoffToken=null;Q(e);let n=e.environment||this.detectEnvironment();n!=="staging"&&n!=="production"&&(console.warn(`SafePassage SDK: Unknown environment '${n}', defaulting to 'production'`),n="production"),this.config=y(h({},e),{environment:n,mode:e.mode||"redirect"}),ne(this.config.environment),j(this.config.environment),p("SDK_INITIALIZED",{environment:this.config.environment,mode:this.config.mode,origin:window.location.origin,protocol:window.location.protocol,hostname:window.location.hostname}),this.setupAutoCleanup()}async verify(e={}){var s,r,o,a,c,l;let n=this.isPublicKey(),i;if(n)i=await this.createInternalSession(e);else throw new Error("Private API keys (sk_) should use the direct API, not the SDK. The SDK is designed for browser-based public key usage only.");if(!i)throw new Error("Failed to obtain sessionId from server");if(this.isVerificationInProgress){let d=new Error(`Verification already in progress for session ${(s=this.currentSessionId)==null?void 0:s.substring(0,8)}...`);throw p("RACE_CONDITION_PREVENTED",{currentSession:((r=this.currentSessionId)==null?void 0:r.substring(0,8))+"...",attemptedSession:"new-session-attempt",origin:window.location.origin}),(a=(o=this.config).onError)==null||a.call(o,d),d}this.isVerificationInProgress=!0,this.currentSessionId=i;try{let d=`${this.config.apiKey}:${window.location.origin}`;if(!q.isAllowed(d)){let f=new Error("Too many verification attempts. Please wait before trying again.");throw p("RATE_LIMIT_EXCEEDED",{apiKey:this.config.apiKey.substring(0,8)+"...",origin:window.location.origin,sessionId:i?i.substring(0,8)+"...":"undefined"}),(l=(c=this.config).onError)==null||l.call(c,f),f}let g=await this.buildVerificationUrl(e,i);p("VERIFICATION_INITIATED",{environment:this.config.environment,mode:this.config.mode,sessionId:i?i.substring(0,8)+"...":"undefined",origin:window.location.origin}),this.config.mode==="new-tab"?this.openNewTab(g,i):(this.unlockVerification(),this.redirect(g))}catch(d){throw this.unlockVerification(),d}}async buildVerificationUrl(e,n){let i=M(this.config.environment),s=e.challengeAge!==void 0,r=e.verificationMode!==void 0,o=s||r,a=await ee({merchantId:this.config.apiKey,sessionId:n,returnUrl:this.config.returnUrl,cancelUrl:this.config.cancelUrl,challengeAge:e.challengeAge||this.config.defaultChallengeAge,verificationMode:e.verificationMode||this.config.defaultVerificationMode,hasOverrides:o,externalUserId:e.externalUserId,timestamp:Date.now(),apiUrl:this.getPortalApiUrl(),engineUrl:this.getEngineUrl(),wsUrl:this.getWebSocketUrl(),environment:this.config.environment,features:{testMode:!1,warmupPeriodMs:500,qualityThreshold:.6},handoffToken:this.temporaryHandoffToken||void 0,sessionToken:this.lastSessionToken||void 0,verifyUrl:this.lastVerifyUrl||void 0},this.config.environment);if(this.lastVerifyUrl)try{let l=new URL(this.lastVerifyUrl);return l.searchParams.set("state",a),l.searchParams.set("mode",this.config.mode),e.skipIntro&&l.searchParams.set("skip_intro","true"),e.autoReturn&&l.searchParams.set("auto_return","true"),l.toString()}catch(l){}let c=new URLSearchParams({state:a,sessionId:n,mode:this.config.mode});return e.skipIntro&&c.set("skip_intro","true"),e.autoReturn&&c.set("auto_return","true"),`${i}/?${c.toString()}`}redirect(e){window.location.href=e}openNewTab(e,n){var i,s;if(this.cleanup(),this.popupMonitorInterval&&(clearInterval(this.popupMonitorInterval),this.popupMonitorInterval=null),this.popupWindow=window.open(e,"safepassage-verify","width=600,height=700"),!this.popupWindow){(s=(i=this.config).onError)==null||s.call(i,new Error("Failed to open verification window. Please check popup blocker settings."));return}this.messageListener=r=>{var l,d,g,f,I,m,_;let o=(l=r.data)==null?void 0:l.type;if(!o||typeof o!="string"||!o.startsWith("safepassage:"))return;if(!W(r,this.config.environment)){p("POSTMESSAGE_ORIGIN_BLOCKED",{origin:r.origin,environment:this.config.environment,expectedOrigins:`SafePassage trusted origins for ${this.config.environment}`,messageType:(d=r.data)==null?void 0:d.type});return}let a=H(r,n);if(!a.isValid){p("POSTMESSAGE_VALIDATION_FAILED",{error:a.error,origin:r.origin,sessionId:n.substring(0,8)+"...",messageType:(g=r.data)==null?void 0:g.type});return}let c={sessionId:r.data.sessionId,status:r.data.status,timestamp:r.data.timestamp,externalUserId:r.data.externalUserId};p("VERIFICATION_COMPLETED",{status:c.status,sessionId:n.substring(0,8)+"...",origin:r.origin}),this.cleanup({closePopup:!1}),this.unlockVerification(),this.popupMonitorInterval&&(clearInterval(this.popupMonitorInterval),this.popupMonitorInterval=null),c.status==="verified"?(I=(f=this.config).onComplete)==null||I.call(f,c):(_=(m=this.config).onError)==null||_.call(m,new Error(`Verification failed: ${c.status}`))},window.addEventListener("message",this.messageListener),this.popupMonitorInterval=setInterval(()=>{var r,o;this.popupWindow&&this.popupWindow.closed&&(p("POPUP_CLOSED_BY_USER",{sessionId:n.substring(0,8)+"...",environment:this.config.environment}),this.cleanup(),this.unlockVerification(),(o=(r=this.config).onCancel)==null||o.call(r))},500)}setupAutoCleanup(){if(this.unloadListener=()=>{p("SDK_AUTO_CLEANUP",{environment:this.config.environment,trigger:"page_unload"}),this.cleanup(),this.unlockVerification()},window.addEventListener("beforeunload",this.unloadListener),window.addEventListener("pagehide",this.unloadListener),window.history&&window.history.pushState){let e=window.history.pushState;window.history.pushState=(...n)=>(this.cleanup(),this.unlockVerification(),e.apply(window.history,n))}}detectEnvironment(){let e=window.location.hostname;return e.includes("staging")||e.includes("stage")?"staging":"production"}getEnvironment(){return this.config.environment}unlockVerification(){this.isVerificationInProgress=!1,this.currentSessionId=null,p("VERIFICATION_UNLOCKED",{environment:this.config.environment,origin:window.location.origin})}cleanup(e={}){let n=e.closePopup!==!1;this.popupWindow&&(n&&!this.popupWindow.closed&&this.popupWindow.close(),(n||this.popupWindow.closed)&&(this.popupWindow=null)),this.messageListener&&(window.removeEventListener("message",this.messageListener),this.messageListener=null),this.popupMonitorInterval&&(clearInterval(this.popupMonitorInterval),this.popupMonitorInterval=null)}removeAutoCleanupListeners(){this.unloadListener&&(window.removeEventListener("beforeunload",this.unloadListener),window.removeEventListener("pagehide",this.unloadListener),this.unloadListener=null)}destroy(){p("SDK_DESTROYED",{environment:this.config.environment,origin:window.location.origin}),this.cleanup(),this.unlockVerification(),this.removeAutoCleanupListeners()}getPortalApiUrl(){switch(this.config.environment){case"staging":return"https://api.staging.safepassageapp.com";case"production":return"https://api.safepassageapp.com";default:return"https://api.safepassageapp.com"}}getEngineUrl(){switch(this.config.environment){case"staging":return"https://engine.staging.safepassageapp.com";case"production":return"https://engine.safepassageapp.com";default:return"https://engine.safepassageapp.com"}}getWebSocketUrl(){switch(this.config.environment){case"staging":return"wss://engine.staging.safepassageapp.com/api/websocket/stream";case"production":return"wss://engine.safepassageapp.com/api/websocket/stream";default:return"wss://engine.safepassageapp.com/api/websocket/stream"}}isPublicKey(){return this.config.apiKey.startsWith("pk_")}async createInternalSession(e){var n,i;try{let s=this.getPortalApiUrl(),r=await fetch(`${s}/api/v1/sessions/create`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.config.apiKey}`},body:JSON.stringify({merchantId:this.config.apiKey,returnUrl:this.config.returnUrl,cancelUrl:this.config.cancelUrl,challengeAge:e.challengeAge,verificationMode:e.verificationMode,merchantName:document.title||window.location.hostname,externalUserId:e.externalUserId})});if(!r.ok){let c=await r.json().catch(()=>({}));throw new Error(`Failed to create session: ${r.status} ${r.statusText}. ${c.message||""}`)}let o=await r.json(),a=o.sessionId;if(!a)throw new Error("Server did not return a sessionId");return o.verifyUrl&&(this.lastVerifyUrl=o.verifyUrl),o.sessionToken&&(this.lastSessionToken=o.sessionToken),o.handoffToken&&(this.temporaryHandoffToken=o.handoffToken),p("INTERNAL_SESSION_CREATED",{sessionId:a.substring(0,8)+"...",environment:this.config.environment,apiKeyType:"public"}),a}catch(s){let r=s instanceof Error?s.message:String(s);throw p("INTERNAL_SESSION_FAILED",{error:r,environment:this.config.environment,apiKeyType:"public"}),(i=(n=this.config).onError)==null||i.call(n,s),new Error(`Failed to create verification session: ${r}`)}}},ve=u});var Se={};U(Se,{SafePassage:()=>u,VERSION:()=>se,default:()=>u});function O(){crypto.randomUUID||(crypto.randomUUID=function(){let t=new Uint8Array(16);crypto.getRandomValues(t),t[6]=t[6]&15|64,t[8]=t[8]&63|128;let e=Array.from(t).map(n=>n.toString(16).padStart(2,"0")).join("");return[e.slice(0,8),e.slice(8,12),e.slice(12,16),e.slice(16,20),e.slice(20,32)].join("-")})}function N(){let t=[];if(!window.crypto||!window.crypto.getRandomValues)throw new Error("SafePassage SDK requires Web Crypto API support");if(!window.crypto.subtle)throw new Error("SafePassage SDK requires Web Crypto subtle API for HMAC operations");crypto.randomUUID||t.push("crypto.randomUUID not supported, using polyfill"),window.URLSearchParams||t.push("URLSearchParams not supported, consider adding a polyfill for IE 11 support"),t.length>0&&console.warn("SafePassage SDK Browser Compatibility:",t.join("; "))}L();typeof window!="undefined"&&(O(),N());var se="3.4.8";if(typeof window!="undefined"&&window){let{SafePassage:t}=(L(),$(re)),e=window;e.SafePassage=t,e.SafePassage&&(e.SafePassage.VERSION=se)}return $(Se);})();
|
|
3
|
-
if(typeof SafePassageSDK !== "undefined" && SafePassageSDK.SafePassage) { window.SafePassage = SafePassageSDK.SafePassage; window.SafePassage.VERSION = SafePassageSDK.VERSION; }
|
package/dist/types/index.js
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
// Mock polyfills to avoid browser API checks in tests
|
|
2
|
-
export function setupPolyfills() {
|
|
3
|
-
// Do nothing in tests
|
|
4
|
-
}
|
|
5
|
-
export function checkBrowserCompatibility() {
|
|
6
|
-
// Do nothing in tests
|
|
7
|
-
}
|
|
8
|
-
export function polyfillCryptoRandomUUID() {
|
|
9
|
-
// Already mocked in test setup
|
|
10
|
-
}
|
package/dist/utils/crypto.d.ts
DELETED
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Crypto utilities for SafePassage SDK - Browser compatible
|
|
3
|
-
*
|
|
4
|
-
* Provides cryptographic functions for the SafePassage SDK using Web Crypto API.
|
|
5
|
-
* These utilities handle HMAC signing, state parameter protection, and secure
|
|
6
|
-
* token generation for client-side security measures.
|
|
7
|
-
*
|
|
8
|
-
* IMPORTANT SECURITY NOTE:
|
|
9
|
-
* Client-side cryptography provides defense-in-depth but cannot be considered
|
|
10
|
-
* secure against determined attackers. True security comes from server-side
|
|
11
|
-
* validation. These functions are primarily for tamper resistance and
|
|
12
|
-
* integrity checking.
|
|
13
|
-
*
|
|
14
|
-
* Features:
|
|
15
|
-
* - HMAC-SHA256 signing and verification using Web Crypto API
|
|
16
|
-
* - Constant-time string comparison to prevent timing attacks
|
|
17
|
-
* - Secure random token generation
|
|
18
|
-
* - Signed state parameter creation and parsing
|
|
19
|
-
* - Environment-specific secret derivation
|
|
20
|
-
* - Timestamp and nonce-based replay protection
|
|
21
|
-
*
|
|
22
|
-
* @author SafePassage Engineering
|
|
23
|
-
* @version 1.0.0
|
|
24
|
-
*/
|
|
25
|
-
/**
|
|
26
|
-
* Generate HMAC-SHA256 signature using Web Crypto API
|
|
27
|
-
*
|
|
28
|
-
* Creates an HMAC-SHA256 signature for the given data using the provided secret.
|
|
29
|
-
* Uses the browser's Web Crypto API for cryptographic operations.
|
|
30
|
-
*
|
|
31
|
-
* SECURITY NOTE: This is client-side HMAC where the secret is known to the client.
|
|
32
|
-
* It provides tamper resistance but not true security. Server-side verification
|
|
33
|
-
* is required for actual security.
|
|
34
|
-
*
|
|
35
|
-
* @param {string} data - Data to sign
|
|
36
|
-
* @param {string} secret - HMAC secret key
|
|
37
|
-
* @returns {Promise<string>} Promise resolving to hex-encoded HMAC signature
|
|
38
|
-
* @export
|
|
39
|
-
*/
|
|
40
|
-
export declare function generateHMAC(data: string, secret: string): Promise<string>;
|
|
41
|
-
/**
|
|
42
|
-
* Verify HMAC-SHA256 signature using Web Crypto API
|
|
43
|
-
*
|
|
44
|
-
* Verifies an HMAC-SHA256 signature against the provided data and secret.
|
|
45
|
-
* Uses constant-time comparison to prevent timing attacks.
|
|
46
|
-
*
|
|
47
|
-
* @param {string} data - Original data that was signed
|
|
48
|
-
* @param {string} signature - HMAC signature to verify
|
|
49
|
-
* @param {string} secret - HMAC secret key
|
|
50
|
-
* @returns {Promise<boolean>} Promise resolving to true if signature is valid
|
|
51
|
-
* @export
|
|
52
|
-
*/
|
|
53
|
-
export declare function verifyHMAC(data: string, signature: string, secret: string): Promise<boolean>;
|
|
54
|
-
/**
|
|
55
|
-
* Generate a secure random token for client-side use
|
|
56
|
-
*
|
|
57
|
-
* Creates a cryptographically secure random token using the browser's
|
|
58
|
-
* crypto.getRandomValues() function.
|
|
59
|
-
*
|
|
60
|
-
* @param {number} [length=32] - Token length in bytes
|
|
61
|
-
* @returns {string} Hex-encoded random token
|
|
62
|
-
* @export
|
|
63
|
-
*/
|
|
64
|
-
export declare function generateSecureToken(length?: number): string;
|
|
65
|
-
/**
|
|
66
|
-
* Environment-specific secret derivation
|
|
67
|
-
*
|
|
68
|
-
* Returns environment-specific secrets for HMAC signing. In a real implementation,
|
|
69
|
-
* this would derive from environment variables. For client-side use, this provides
|
|
70
|
-
* basic tamper resistance only.
|
|
71
|
-
*
|
|
72
|
-
* WARNING: Client-side secrets are NOT secure against determined attackers.
|
|
73
|
-
* This is defense-in-depth only. Real security comes from server-side validation.
|
|
74
|
-
*
|
|
75
|
-
* @param {'production' | 'staging'} environment - Target environment
|
|
76
|
-
* @returns {string} Environment-specific secret
|
|
77
|
-
* @export
|
|
78
|
-
*/
|
|
79
|
-
export declare function getSigningSecret(environment: 'production' | 'staging'): string;
|
|
80
|
-
/**
|
|
81
|
-
* Create signed state parameter with timestamp and integrity protection
|
|
82
|
-
*
|
|
83
|
-
* Creates a signed state parameter containing the payload with added timestamp
|
|
84
|
-
* and nonce for freshness and replay protection. The entire payload is signed
|
|
85
|
-
* using HMAC-SHA256 and base64 encoded for URL safety.
|
|
86
|
-
*
|
|
87
|
-
* @param {unknown} payload - Data to include in state parameter
|
|
88
|
-
* @param {'production' | 'staging'} environment - Target environment
|
|
89
|
-
* @returns {Promise<string>} Promise resolving to base64-encoded signed state
|
|
90
|
-
* @export
|
|
91
|
-
*/
|
|
92
|
-
export declare function createSignedState(payload: unknown, environment: 'production' | 'staging'): Promise<string>;
|
|
93
|
-
/**
|
|
94
|
-
* Verify and parse signed state parameter
|
|
95
|
-
*
|
|
96
|
-
* Parses and verifies a signed state parameter, checking the HMAC signature
|
|
97
|
-
* and timestamp freshness. Returns the original payload if validation succeeds.
|
|
98
|
-
*
|
|
99
|
-
* @param {string} signedState - Base64-encoded signed state parameter
|
|
100
|
-
* @param {'production' | 'staging'} environment - Source environment
|
|
101
|
-
* @param {number} [maxAge] - Maximum age in milliseconds
|
|
102
|
-
* @returns {Promise<unknown | null>} Promise resolving to payload or null if invalid
|
|
103
|
-
* @export
|
|
104
|
-
*/
|
|
105
|
-
export declare function parseSignedState(signedState: string, environment: 'production' | 'staging', maxAge?: number): Promise<unknown | null>;
|
package/dist/utils/crypto.js
DELETED
|
@@ -1,216 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Crypto utilities for SafePassage SDK - Browser compatible
|
|
3
|
-
*
|
|
4
|
-
* Provides cryptographic functions for the SafePassage SDK using Web Crypto API.
|
|
5
|
-
* These utilities handle HMAC signing, state parameter protection, and secure
|
|
6
|
-
* token generation for client-side security measures.
|
|
7
|
-
*
|
|
8
|
-
* IMPORTANT SECURITY NOTE:
|
|
9
|
-
* Client-side cryptography provides defense-in-depth but cannot be considered
|
|
10
|
-
* secure against determined attackers. True security comes from server-side
|
|
11
|
-
* validation. These functions are primarily for tamper resistance and
|
|
12
|
-
* integrity checking.
|
|
13
|
-
*
|
|
14
|
-
* Features:
|
|
15
|
-
* - HMAC-SHA256 signing and verification using Web Crypto API
|
|
16
|
-
* - Constant-time string comparison to prevent timing attacks
|
|
17
|
-
* - Secure random token generation
|
|
18
|
-
* - Signed state parameter creation and parsing
|
|
19
|
-
* - Environment-specific secret derivation
|
|
20
|
-
* - Timestamp and nonce-based replay protection
|
|
21
|
-
*
|
|
22
|
-
* @author SafePassage Engineering
|
|
23
|
-
* @version 1.0.0
|
|
24
|
-
*/
|
|
25
|
-
var __rest = (this && this.__rest) || function (s, e) {
|
|
26
|
-
var t = {};
|
|
27
|
-
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
28
|
-
t[p] = s[p];
|
|
29
|
-
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
30
|
-
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
31
|
-
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
32
|
-
t[p[i]] = s[p[i]];
|
|
33
|
-
}
|
|
34
|
-
return t;
|
|
35
|
-
};
|
|
36
|
-
import { STATE_EXPIRY_MS } from './validation';
|
|
37
|
-
/**
|
|
38
|
-
* Generate HMAC-SHA256 signature using Web Crypto API
|
|
39
|
-
*
|
|
40
|
-
* Creates an HMAC-SHA256 signature for the given data using the provided secret.
|
|
41
|
-
* Uses the browser's Web Crypto API for cryptographic operations.
|
|
42
|
-
*
|
|
43
|
-
* SECURITY NOTE: This is client-side HMAC where the secret is known to the client.
|
|
44
|
-
* It provides tamper resistance but not true security. Server-side verification
|
|
45
|
-
* is required for actual security.
|
|
46
|
-
*
|
|
47
|
-
* @param {string} data - Data to sign
|
|
48
|
-
* @param {string} secret - HMAC secret key
|
|
49
|
-
* @returns {Promise<string>} Promise resolving to hex-encoded HMAC signature
|
|
50
|
-
* @export
|
|
51
|
-
*/
|
|
52
|
-
export async function generateHMAC(data, secret) {
|
|
53
|
-
// Encode the secret and data as Uint8Array
|
|
54
|
-
const encoder = new TextEncoder();
|
|
55
|
-
const keyData = encoder.encode(secret);
|
|
56
|
-
const dataBuffer = encoder.encode(data);
|
|
57
|
-
// Import the secret as a key
|
|
58
|
-
const key = await crypto.subtle.importKey('raw', keyData, { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
|
|
59
|
-
// Generate the signature
|
|
60
|
-
const signature = await crypto.subtle.sign('HMAC', key, dataBuffer);
|
|
61
|
-
// Convert to hex string
|
|
62
|
-
return Array.from(new Uint8Array(signature))
|
|
63
|
-
.map((b) => b.toString(16).padStart(2, '0'))
|
|
64
|
-
.join('');
|
|
65
|
-
}
|
|
66
|
-
/**
|
|
67
|
-
* Verify HMAC-SHA256 signature using Web Crypto API
|
|
68
|
-
*
|
|
69
|
-
* Verifies an HMAC-SHA256 signature against the provided data and secret.
|
|
70
|
-
* Uses constant-time comparison to prevent timing attacks.
|
|
71
|
-
*
|
|
72
|
-
* @param {string} data - Original data that was signed
|
|
73
|
-
* @param {string} signature - HMAC signature to verify
|
|
74
|
-
* @param {string} secret - HMAC secret key
|
|
75
|
-
* @returns {Promise<boolean>} Promise resolving to true if signature is valid
|
|
76
|
-
* @export
|
|
77
|
-
*/
|
|
78
|
-
export async function verifyHMAC(data, signature, secret) {
|
|
79
|
-
try {
|
|
80
|
-
const expectedSignature = await generateHMAC(data, secret);
|
|
81
|
-
return constantTimeCompare(signature, expectedSignature);
|
|
82
|
-
}
|
|
83
|
-
catch (_a) {
|
|
84
|
-
return false;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
/**
|
|
88
|
-
* Constant-time string comparison to prevent timing attacks
|
|
89
|
-
*
|
|
90
|
-
* Compares two strings in constant time to prevent timing-based attacks
|
|
91
|
-
* that could leak information about the expected signature.
|
|
92
|
-
*
|
|
93
|
-
* @param {string} a - First string to compare
|
|
94
|
-
* @param {string} b - Second string to compare
|
|
95
|
-
* @returns {boolean} True if strings are equal
|
|
96
|
-
*/
|
|
97
|
-
function constantTimeCompare(a, b) {
|
|
98
|
-
if (a.length !== b.length) {
|
|
99
|
-
return false;
|
|
100
|
-
}
|
|
101
|
-
let result = 0;
|
|
102
|
-
for (let i = 0; i < a.length; i++) {
|
|
103
|
-
result |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
104
|
-
}
|
|
105
|
-
return result === 0;
|
|
106
|
-
}
|
|
107
|
-
/**
|
|
108
|
-
* Generate a secure random token for client-side use
|
|
109
|
-
*
|
|
110
|
-
* Creates a cryptographically secure random token using the browser's
|
|
111
|
-
* crypto.getRandomValues() function.
|
|
112
|
-
*
|
|
113
|
-
* @param {number} [length=32] - Token length in bytes
|
|
114
|
-
* @returns {string} Hex-encoded random token
|
|
115
|
-
* @export
|
|
116
|
-
*/
|
|
117
|
-
export function generateSecureToken(length = 32) {
|
|
118
|
-
const array = new Uint8Array(length);
|
|
119
|
-
crypto.getRandomValues(array);
|
|
120
|
-
return Array.from(array, (b) => b.toString(16).padStart(2, '0')).join('');
|
|
121
|
-
}
|
|
122
|
-
/**
|
|
123
|
-
* Environment-specific secret derivation
|
|
124
|
-
*
|
|
125
|
-
* Returns environment-specific secrets for HMAC signing. In a real implementation,
|
|
126
|
-
* this would derive from environment variables. For client-side use, this provides
|
|
127
|
-
* basic tamper resistance only.
|
|
128
|
-
*
|
|
129
|
-
* WARNING: Client-side secrets are NOT secure against determined attackers.
|
|
130
|
-
* This is defense-in-depth only. Real security comes from server-side validation.
|
|
131
|
-
*
|
|
132
|
-
* @param {'production' | 'staging'} environment - Target environment
|
|
133
|
-
* @returns {string} Environment-specific secret
|
|
134
|
-
* @export
|
|
135
|
-
*/
|
|
136
|
-
export function getSigningSecret(environment) {
|
|
137
|
-
// WARNING: Client-side secrets are NOT secure against determined attackers
|
|
138
|
-
// This is defense-in-depth only. Real security comes from server-side validation.
|
|
139
|
-
const baseSecrets = {
|
|
140
|
-
production: 'safepassage-prod-hmac-2025',
|
|
141
|
-
staging: 'safepassage-stage-hmac-2025',
|
|
142
|
-
};
|
|
143
|
-
return baseSecrets[environment];
|
|
144
|
-
}
|
|
145
|
-
/**
|
|
146
|
-
* Create signed state parameter with timestamp and integrity protection
|
|
147
|
-
*
|
|
148
|
-
* Creates a signed state parameter containing the payload with added timestamp
|
|
149
|
-
* and nonce for freshness and replay protection. The entire payload is signed
|
|
150
|
-
* using HMAC-SHA256 and base64 encoded for URL safety.
|
|
151
|
-
*
|
|
152
|
-
* @param {unknown} payload - Data to include in state parameter
|
|
153
|
-
* @param {'production' | 'staging'} environment - Target environment
|
|
154
|
-
* @returns {Promise<string>} Promise resolving to base64-encoded signed state
|
|
155
|
-
* @export
|
|
156
|
-
*/
|
|
157
|
-
export async function createSignedState(payload, environment) {
|
|
158
|
-
// Add timestamp for freshness
|
|
159
|
-
const timestampedPayload = Object.assign(Object.assign({}, payload), { timestamp: Date.now(), nonce: generateSecureToken(16) });
|
|
160
|
-
const dataString = JSON.stringify(timestampedPayload);
|
|
161
|
-
const secret = getSigningSecret(environment);
|
|
162
|
-
const signature = await generateHMAC(dataString, secret);
|
|
163
|
-
// Combine data and signature
|
|
164
|
-
const signedPayload = {
|
|
165
|
-
data: timestampedPayload,
|
|
166
|
-
signature,
|
|
167
|
-
};
|
|
168
|
-
return btoa(JSON.stringify(signedPayload));
|
|
169
|
-
}
|
|
170
|
-
/**
|
|
171
|
-
* Verify and parse signed state parameter
|
|
172
|
-
*
|
|
173
|
-
* Parses and verifies a signed state parameter, checking the HMAC signature
|
|
174
|
-
* and timestamp freshness. Returns the original payload if validation succeeds.
|
|
175
|
-
*
|
|
176
|
-
* @param {string} signedState - Base64-encoded signed state parameter
|
|
177
|
-
* @param {'production' | 'staging'} environment - Source environment
|
|
178
|
-
* @param {number} [maxAge] - Maximum age in milliseconds
|
|
179
|
-
* @returns {Promise<unknown | null>} Promise resolving to payload or null if invalid
|
|
180
|
-
* @export
|
|
181
|
-
*/
|
|
182
|
-
export async function parseSignedState(signedState, environment, maxAge = STATE_EXPIRY_MS) {
|
|
183
|
-
try {
|
|
184
|
-
const json = atob(signedState);
|
|
185
|
-
const signedPayload = JSON.parse(json);
|
|
186
|
-
if (!signedPayload.data || !signedPayload.signature) {
|
|
187
|
-
console.warn('SafePassage: Invalid signed state format');
|
|
188
|
-
return null;
|
|
189
|
-
}
|
|
190
|
-
const { data, signature } = signedPayload;
|
|
191
|
-
const dataString = JSON.stringify(data);
|
|
192
|
-
const secret = getSigningSecret(environment);
|
|
193
|
-
// Verify signature
|
|
194
|
-
const isValid = await verifyHMAC(dataString, signature, secret);
|
|
195
|
-
if (!isValid) {
|
|
196
|
-
console.warn('SafePassage: State signature verification failed');
|
|
197
|
-
return null;
|
|
198
|
-
}
|
|
199
|
-
// Check timestamp freshness
|
|
200
|
-
if (data.timestamp) {
|
|
201
|
-
const age = Date.now() - data.timestamp;
|
|
202
|
-
if (age > maxAge) {
|
|
203
|
-
console.warn('SafePassage: State parameter expired', { age, maxAge });
|
|
204
|
-
return null;
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
// Remove internal fields before returning
|
|
208
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
209
|
-
const { timestamp, nonce } = data, payload = __rest(data, ["timestamp", "nonce"]);
|
|
210
|
-
return payload;
|
|
211
|
-
}
|
|
212
|
-
catch (error) {
|
|
213
|
-
console.warn('SafePassage: Failed to parse signed state', error);
|
|
214
|
-
return null;
|
|
215
|
-
}
|
|
216
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Environment utilities for SafePassage SDK with security enforcement
|
|
3
|
-
*/
|
|
4
|
-
export declare function getEnvironmentUrl(environment: 'production' | 'staging'): string;
|
|
5
|
-
export declare function getApiUrl(environment: 'production' | 'staging'): string;
|
|
6
|
-
/**
|
|
7
|
-
* Detect environment - always defaults to production unless staging is detected
|
|
8
|
-
*/
|
|
9
|
-
export declare function detectEnvironment(): 'production' | 'staging';
|
|
10
|
-
/**
|
|
11
|
-
* Validate environment configuration on startup
|
|
12
|
-
*/
|
|
13
|
-
export declare function validateEnvironmentSecurity(environment: 'production' | 'staging'): void;
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Environment utilities for SafePassage SDK with security enforcement
|
|
3
|
-
*/
|
|
4
|
-
const ENVIRONMENT_URLS = {
|
|
5
|
-
production: 'https://av.safepassageapp.com',
|
|
6
|
-
staging: 'https://av.staging.safepassageapp.com',
|
|
7
|
-
};
|
|
8
|
-
export function getEnvironmentUrl(environment) {
|
|
9
|
-
// Default to production for unknown environments (like 'development')
|
|
10
|
-
const url = ENVIRONMENT_URLS[environment] || ENVIRONMENT_URLS.production;
|
|
11
|
-
// Security check: Ensure production/staging always use HTTPS
|
|
12
|
-
if (!url || !url.startsWith('https://')) {
|
|
13
|
-
throw new Error(`HTTPS required for ${environment} environment`);
|
|
14
|
-
}
|
|
15
|
-
return url;
|
|
16
|
-
}
|
|
17
|
-
export function getApiUrl(environment) {
|
|
18
|
-
const apiUrls = {
|
|
19
|
-
production: 'https://api.safepassageapp.com',
|
|
20
|
-
staging: 'https://api.staging.safepassageapp.com',
|
|
21
|
-
};
|
|
22
|
-
// Default to production for unknown environments (like 'development')
|
|
23
|
-
const url = apiUrls[environment] || apiUrls.production;
|
|
24
|
-
// Security check: Ensure HTTPS is always used
|
|
25
|
-
if (!url || !url.startsWith('https://')) {
|
|
26
|
-
throw new Error(`HTTPS required for API URLs in ${environment} environment`);
|
|
27
|
-
}
|
|
28
|
-
return url;
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* Detect environment - always defaults to production unless staging is detected
|
|
32
|
-
*/
|
|
33
|
-
export function detectEnvironment() {
|
|
34
|
-
const hostname = window.location.hostname;
|
|
35
|
-
if (hostname.includes('staging') || hostname.includes('stage')) {
|
|
36
|
-
// Enforce HTTPS in staging
|
|
37
|
-
if (window.location.protocol !== 'https:') {
|
|
38
|
-
console.error('SafePassage Security Error: HTTPS required in staging environment');
|
|
39
|
-
}
|
|
40
|
-
return 'staging';
|
|
41
|
-
}
|
|
42
|
-
// Default to production environment - strict HTTPS enforcement
|
|
43
|
-
if (window.location.protocol !== 'https:') {
|
|
44
|
-
console.warn('SafePassage Warning: HTTPS recommended for production environment');
|
|
45
|
-
}
|
|
46
|
-
return 'production';
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* Validate environment configuration on startup
|
|
50
|
-
*/
|
|
51
|
-
export function validateEnvironmentSecurity(environment) {
|
|
52
|
-
// Check current page protocol
|
|
53
|
-
const isSecure = window.location.protocol === 'https:';
|
|
54
|
-
switch (environment) {
|
|
55
|
-
case 'production':
|
|
56
|
-
if (!isSecure) {
|
|
57
|
-
console.warn('SafePassage Warning: HTTPS recommended for production environment');
|
|
58
|
-
}
|
|
59
|
-
break;
|
|
60
|
-
case 'staging':
|
|
61
|
-
if (!isSecure) {
|
|
62
|
-
console.warn('SafePassage Warning: HTTPS strongly recommended in staging environment');
|
|
63
|
-
}
|
|
64
|
-
break;
|
|
65
|
-
}
|
|
66
|
-
// Validate environment URLs
|
|
67
|
-
try {
|
|
68
|
-
getEnvironmentUrl(environment);
|
|
69
|
-
getApiUrl(environment);
|
|
70
|
-
}
|
|
71
|
-
catch (error) {
|
|
72
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
73
|
-
throw new Error(`Environment configuration validation failed: ${errorMessage}`);
|
|
74
|
-
}
|
|
75
|
-
}
|
package/dist/utils/polyfills.js
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Polyfills for browser compatibility
|
|
3
|
-
*/
|
|
4
|
-
/**
|
|
5
|
-
* Polyfill for crypto.randomUUID (not supported in older browsers)
|
|
6
|
-
* Generates a UUID v4 using crypto.getRandomValues
|
|
7
|
-
*/
|
|
8
|
-
export function setupPolyfills() {
|
|
9
|
-
// Polyfill crypto.randomUUID if not available
|
|
10
|
-
if (!crypto.randomUUID) {
|
|
11
|
-
crypto.randomUUID =
|
|
12
|
-
function () {
|
|
13
|
-
// Generate 16 random bytes
|
|
14
|
-
const array = new Uint8Array(16);
|
|
15
|
-
crypto.getRandomValues(array);
|
|
16
|
-
// Set version (4) and variant bits
|
|
17
|
-
array[6] = (array[6] & 0x0f) | 0x40; // Version 4
|
|
18
|
-
array[8] = (array[8] & 0x3f) | 0x80; // Variant 10
|
|
19
|
-
// Convert to hex string with dashes
|
|
20
|
-
const hex = Array.from(array)
|
|
21
|
-
.map((b) => b.toString(16).padStart(2, '0'))
|
|
22
|
-
.join('');
|
|
23
|
-
return [
|
|
24
|
-
hex.slice(0, 8),
|
|
25
|
-
hex.slice(8, 12),
|
|
26
|
-
hex.slice(12, 16),
|
|
27
|
-
hex.slice(16, 20),
|
|
28
|
-
hex.slice(20, 32),
|
|
29
|
-
].join('-');
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
// Note: URLSearchParams is widely supported (IE 11 doesn't support it, but we're not targeting IE 11)
|
|
33
|
-
// If needed, a polyfill can be added here
|
|
34
|
-
}
|
|
35
|
-
/**
|
|
36
|
-
* Check browser compatibility and warn about unsupported features
|
|
37
|
-
*/
|
|
38
|
-
export function checkBrowserCompatibility() {
|
|
39
|
-
const warnings = [];
|
|
40
|
-
// Check for required features
|
|
41
|
-
if (!window.crypto || !window.crypto.getRandomValues) {
|
|
42
|
-
throw new Error('SafePassage SDK requires Web Crypto API support');
|
|
43
|
-
}
|
|
44
|
-
if (!window.crypto.subtle) {
|
|
45
|
-
throw new Error('SafePassage SDK requires Web Crypto subtle API for HMAC operations');
|
|
46
|
-
}
|
|
47
|
-
// Check for features that we can polyfill
|
|
48
|
-
if (!crypto.randomUUID) {
|
|
49
|
-
warnings.push('crypto.randomUUID not supported, using polyfill');
|
|
50
|
-
}
|
|
51
|
-
if (!window.URLSearchParams) {
|
|
52
|
-
warnings.push('URLSearchParams not supported, consider adding a polyfill for IE 11 support');
|
|
53
|
-
}
|
|
54
|
-
// Log warnings if any
|
|
55
|
-
if (warnings.length > 0) {
|
|
56
|
-
console.warn('SafePassage SDK Browser Compatibility:', warnings.join('; '));
|
|
57
|
-
}
|
|
58
|
-
}
|