unshared-clientjs-sdk 2.0.0-rc.13 → 2.0.0-rc.15

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.
Files changed (33) hide show
  1. package/dist/esm/middleware/index.mjs +1 -1
  2. package/dist/esm/middleware/injection/fingerprint-script.d.mts +11 -5
  3. package/dist/esm/middleware/injection/fingerprint-script.mjs +1 -1
  4. package/dist/esm/middleware/response-interceptor.d.mts +9 -7
  5. package/dist/esm/middleware/response-interceptor.mjs +1 -1
  6. package/dist/esm/middleware/routes/submit-fp.mjs +1 -1
  7. package/dist/esm/middleware/routes/verify.mjs +1 -1
  8. package/dist/esm/middleware/utils/cookies.d.mts +6 -0
  9. package/dist/esm/middleware/utils/cookies.mjs +1 -0
  10. package/dist/esm/middleware/utils/device-id.d.mts +5 -0
  11. package/dist/esm/middleware/utils/device-id.mjs +1 -0
  12. package/dist/esm/middleware/utils/secure.d.mts +3 -0
  13. package/dist/esm/middleware/utils/secure.mjs +1 -0
  14. package/dist/esm/middleware/utils/skip-paths.mjs +1 -1
  15. package/dist/esm/middleware/verdict-cache.d.mts +12 -1
  16. package/dist/esm/middleware/verdict-cache.mjs +1 -1
  17. package/dist/middleware/index.js +1 -1
  18. package/dist/middleware/injection/fingerprint-script.d.ts +11 -5
  19. package/dist/middleware/injection/fingerprint-script.js +1 -1
  20. package/dist/middleware/response-interceptor.d.ts +9 -7
  21. package/dist/middleware/response-interceptor.js +1 -1
  22. package/dist/middleware/routes/submit-fp.js +1 -1
  23. package/dist/middleware/routes/verify.js +1 -1
  24. package/dist/middleware/utils/cookies.d.ts +6 -0
  25. package/dist/middleware/utils/cookies.js +1 -0
  26. package/dist/middleware/utils/device-id.d.ts +5 -0
  27. package/dist/middleware/utils/device-id.js +1 -0
  28. package/dist/middleware/utils/secure.d.ts +3 -0
  29. package/dist/middleware/utils/secure.js +1 -0
  30. package/dist/middleware/utils/skip-paths.js +1 -1
  31. package/dist/middleware/verdict-cache.d.ts +12 -1
  32. package/dist/middleware/verdict-cache.js +1 -1
  33. package/package.json +1 -1
@@ -1 +1 @@
1
- import{readFileSync}from"fs";import{VerdictCache}from"./verdict-cache";import{RateLimitBackoff}from"./rate-limit-backoff";import{interceptResponse}from"./response-interceptor";import{generateFingerprintScript}from"./injection/fingerprint-script";import{handleSubmitFingerprint}from"./routes/submit-fp";import{handleVerifyTrigger,handleVerify}from"./routes/verify";import{isHtmlContentType}from"./utils/content-type";import{shouldSkipPath}from"./utils/skip-paths";import{isBot}from"./utils/is-bot";import{extractClientIp}from"./utils/client-ip";export{VerdictCache};const CHECK_USER_TIMEOUT_MS=500;export function unsharedBoundToUser(e,t){if(!t.userId)throw new Error("[Unshared] userId resolver is required");if(!t.emailAddress){let e=!1;try{require.resolve("unshared-frontend-sdk"),e=!0}catch{}e||console.warn("[Unshared] Warning: emailAddress resolver is not configured and unshared-frontend-sdk is not installed.\nNo user events will be submitted. Either install unshared-frontend-sdk (Tier 1) or\nprovide emailAddress in your middleware config (Tier 2).")}const{userId:r,emailAddress:i,routePrefix:n="/__unshared",corsOrigins:o,cacheTTL:s=6e4,skipPaths:c,sessionId:d,deviceId:a,onFlagged:l}=t,u=new VerdictCache(s),p=new RateLimitBackoff,f=Date.now().toString(36),m=generateFingerprintScript(n,f);let h="";try{const e=require.resolve("unshared-frontend-sdk/dist/index.umd.js");h=readFileSync(e,"utf8")}catch{}const v=handleSubmitFingerprint({client:e,verdictCache:u,rateLimitBackoff:p,resolveUserId:r,resolveEmailAddress:i,resolveSessionId:d,resolveDeviceId:a}),C=handleVerifyTrigger({client:e,verdictCache:u,resolveEmailAddress:i,resolveDeviceId:a}),I=handleVerify({client:e,verdictCache:u,resolveEmailAddress:i,resolveDeviceId:a}),g=o?Array.isArray(o)?o:[o]:null,y=`${n}/fp.js`,k=`${n}/submit-fp`,S=`${n}/verify-trigger`,_=`${n}/verify`;return function(t,o,s){const f=t.path;if(f.startsWith(n+"/"))return function(e,t){if(!g)return;const r=e.headers.origin??"",i=g.includes("*");(i||g.includes(r))&&(t.setHeader("Access-Control-Allow-Origin",i?"*":r),t.setHeader("Access-Control-Allow-Methods","POST, OPTIONS"),t.setHeader("Access-Control-Allow-Headers","Content-Type, X-Idempotency-Key, X-Session-Id, X-Device-Id"),t.setHeader("Access-Control-Allow-Credentials","true"))}(t,o),"OPTIONS"===t.method?void o.status(204).end():"GET"===t.method&&f===y?(o.setHeader("Content-Type","application/javascript"),o.setHeader("Cache-Control","public, max-age=3600"),void o.status(200).end(h)):"POST"===t.method&&f===k?void v(t,o):"POST"===t.method&&f===S?void C(t,o):"POST"===t.method&&f===_?void I(t,o):void o.status(404).json({success:!1,error:{code:"NOT_FOUND",message:"Unknown route"}});if(shouldSkipPath(f,c))return void s();let A;try{A=r(t)}catch{}if(!A)return clearEmailCookieIfPresent(t,o),interceptForInjection(t,o,m),void s();const T=resolveEmail(t,i);if(setUserIdCookie(o,A),T&&setEmailCookie(o,T),!T)return interceptForInjection(t,o,m),void s();const x=extractSessionId(t,d),E=extractDeviceId(t,a),F=extractFingerprintId(t),w=t.headers["user-agent"]??"",P=extractClientIp(t);if(isBot(w))return void s();if("unknown"===x)return interceptForInjection(t,o,m),void s();p.isPaused()||dispatchUserEvent(e,u,p,{userId:A,emailAddress:T,sessionId:x,deviceId:E,fingerprintId:F,userAgent:w,ipAddress:P,eventType:`${t.method} ${t.path}`});const O=u.get(A);O?(u.isStale(A)&&!u.isRefreshing(A)&&(u.markRefreshing(A),fetchAndCacheVerdict(e,u,A,T,E,F,x).finally(()=>u.clearRefreshing(A))),applyVerdict(O,A,T,t,o,s,m,l)):fetchAndCacheVerdict(e,u,A,T,E,F,x).then(e=>{applyVerdict(e,A,T,t,o,s,m,l)}).catch(()=>{interceptForInjection(t,o,m),s()})}}function resolveEmail(e,t){if(t)try{const r=t(e);if(r)return r}catch{}const r=parseCookie(e,"__unshared_email");if(r)return r;const i=e.body?.email;return"string"==typeof i&&i?i:void 0}function applyVerdict(e,t,r,i,n,o,s,c){if(interceptForInjection(i,n,s),e.isFlagged&&!e.isVerified&&c)try{c({userId:t,emailAddress:r,verdict:e,req:i,res:n,next:o})}catch{o()}else o()}function preventHtmlCaching(e,t){delete e.headers["if-none-match"],delete e.headers["if-modified-since"];const r=t.writeHead.bind(t);t.writeHead=function(e,...i){const n=t.getHeader("content-type");return n&&String(n).includes("text/html")&&(t.setHeader("Cache-Control","no-store"),t.removeHeader("ETag"),t.removeHeader("Last-Modified")),r(e,...i)}}function interceptForInjection(e,t,r){preventHtmlCaching(e,t),interceptResponse(t,(e,t)=>{if(!isHtmlContentType(t))return null;const i=e.toString("utf8"),n=i.lastIndexOf("</body>");return-1===n?i+r:i.slice(0,n)+r+i.slice(n)})}function dispatchUserEvent(e,t,r,i){e.processUserEvent({eventType:i.eventType,userId:i.userId,emailAddress:i.emailAddress,ipAddress:i.ipAddress,deviceId:i.deviceId,fingerprintId:i.fingerprintId,sessionHash:i.sessionId,userAgent:i.userAgent}).then(e=>{e.success&&e.data?.analysis&&t.update(i.userId,{isFlagged:e.data.analysis.is_user_flagged}),!e.success&&e.error?.retryAfter&&r.pause(1e3*e.error.retryAfter)}).catch(()=>{})}async function fetchAndCacheVerdict(e,t,r,i,n,o,s){const c={};n&&"unknown"!==n&&(c.deviceId=n),o&&(c.fingerprintId=o);const d=await Promise.race([e.checkUser(i,c),new Promise(e=>setTimeout(()=>e(null),500))]);if(!d)return{isFlagged:!1,isVerified:!1,emailAddress:i,sessionId:s,cachedAt:0,ttl:0};const a=d.data?.is_user_flagged??!1;return t.set(r,{isFlagged:a,isVerified:!1,emailAddress:i,sessionId:s}),t.get(r)}function parseCookie(e,t){const r=e.headers.cookie;if(!r)return;const i=r.match(new RegExp(`(?:^|; )${t}=([^;]*)`));return i?decodeURIComponent(i[1]):void 0}function extractSessionId(e,t){if(t)try{const r=t(e);if(r)return r}catch{}return parseCookie(e,"__unshared_sid")??"unknown"}function extractDeviceId(e,t){if(t)try{const r=t(e);if(r)return r}catch{}const r=parseCookie(e,"__unshared_fp_id");if(r)return r;const i=e.headers["x-device-id"];return"string"==typeof i&&i?i:"unknown"}function extractFingerprintId(e){return parseCookie(e,"__unshared_fingerprint_id")||void 0}function appendSetCookie(e,t){const r=e.getHeader("Set-Cookie");if(r){const i=Array.isArray(r)?[...r]:[String(r)];i.push(t),e.setHeader("Set-Cookie",i)}else e.setHeader("Set-Cookie",t)}function setUserIdCookie(e,t){appendSetCookie(e,`__unshared_uid=${encodeURIComponent(t)}; Path=/; SameSite=Lax`)}function setEmailCookie(e,t){appendSetCookie(e,`__unshared_email=${encodeURIComponent(t)}; HttpOnly; Path=/; SameSite=Lax`)}function clearEmailCookieIfPresent(e,t){parseCookie(e,"__unshared_email")&&appendSetCookie(t,"__unshared_email=; HttpOnly; Path=/; SameSite=Lax; Max-Age=0")}
1
+ import{readFileSync}from"fs";import{VerdictCache}from"./verdict-cache";import{RateLimitBackoff}from"./rate-limit-backoff";import{interceptResponse}from"./response-interceptor";import{generateFingerprintScript}from"./injection/fingerprint-script";import{handleSubmitFingerprint}from"./routes/submit-fp";import{handleVerifyTrigger,handleVerify}from"./routes/verify";import{isHtmlContentType}from"./utils/content-type";import{shouldSkipPath}from"./utils/skip-paths";import{isBot}from"./utils/is-bot";import{extractClientIp}from"./utils/client-ip";import{parseCookie}from"./utils/cookies";import{extractDeviceId}from"./utils/device-id";import{isSecureRequest}from"./utils/secure";export{VerdictCache};const CHECK_USER_TIMEOUT_MS=500;export function unsharedBoundToUser(e,r){if(!r.userId)throw new Error("[Unshared] userId resolver is required");if(!r.emailAddress){let e=!1;try{require.resolve("unshared-frontend-sdk"),e=!0}catch{}e||console.warn("[Unshared] Warning: emailAddress resolver is not configured and unshared-frontend-sdk is not installed.\nNo user events will be submitted. Either install unshared-frontend-sdk (Tier 1) or\nprovide emailAddress in your middleware config (Tier 2).")}const{userId:t,emailAddress:i,routePrefix:n="/__unshared",corsOrigins:o,cacheTTL:s=6e4,skipPaths:c,sessionId:d,deviceId:a,onFlagged:l}=r,u=new VerdictCache(s),p=new RateLimitBackoff,f=Date.now().toString(36),m=generateFingerprintScript(n,f);let h="";try{const e=require.resolve("unshared-frontend-sdk/dist/index.umd.js");h=readFileSync(e,"utf8")}catch{}const v=handleSubmitFingerprint({client:e,verdictCache:u,rateLimitBackoff:p,resolveUserId:t,resolveEmailAddress:i,resolveSessionId:d,resolveDeviceId:a}),C=handleVerifyTrigger({client:e,verdictCache:u,resolveEmailAddress:i,resolveDeviceId:a}),I=handleVerify({client:e,verdictCache:u,resolveEmailAddress:i,resolveDeviceId:a}),S=o?Array.isArray(o)?o:[o]:null,g=`${n}/fp.js`,k=`${n}/submit-fp`,y=`${n}/verify-trigger`,_=`${n}/verify`;return function(r,o,s){const f=r.path;if(f.startsWith(n+"/"))return function(e,r){if(!S)return;const t=e.headers.origin??"",i=S.includes("*");(i||S.includes(t))&&(r.setHeader("Access-Control-Allow-Origin",i?"*":t),r.setHeader("Access-Control-Allow-Methods","POST, OPTIONS"),r.setHeader("Access-Control-Allow-Headers","Content-Type, X-Idempotency-Key, X-Session-Id, X-Device-Id"),r.setHeader("Access-Control-Allow-Credentials","true"))}(r,o),"OPTIONS"===r.method?void o.status(204).end():"GET"===r.method&&f===g?(o.setHeader("Content-Type","application/javascript"),o.setHeader("Cache-Control","public, max-age=3600"),void o.status(200).end(h)):"POST"===r.method&&f===k?void v(r,o):"POST"===r.method&&f===y?void C(r,o):"POST"===r.method&&f===_?void I(r,o):void o.status(404).json({success:!1,error:{code:"NOT_FOUND",message:"Unknown route"}});if(shouldSkipPath(f,c))return void s();let A;try{A=t(r)}catch{}if(!A)return clearUserIdCookieIfPresent(r,o),clearEmailCookieIfPresent(r,o),interceptForInjection(r,o,m),void s();const T=resolveEmail(r,i);if(setUserIdCookie(r,o,A),T&&setEmailCookie(r,o,T),!T)return interceptForInjection(r,o,m),void s();const x=extractSessionId(r,d),P=extractDeviceId(r,a),F=extractFingerprintId(r),E=r.headers["user-agent"]??"",w=extractClientIp(r);if(isBot(E))return void s();if("unknown"===x)return interceptForInjection(r,o,m),void s();p.isPaused()||dispatchUserEvent(e,u,p,{userId:A,emailAddress:T,sessionId:x,deviceId:P,fingerprintId:F,userAgent:E,ipAddress:w,eventType:`${r.method} ${r.path}`});const U=u.get(A);U?(u.isStale(A)&&!u.isRefreshing(A)&&(u.markRefreshing(A),fetchAndCacheVerdict(e,u,A,T,P,F,x).finally(()=>u.clearRefreshing(A))),applyVerdict(U,A,T,r,o,s,m,l)):fetchAndCacheVerdict(e,u,A,T,P,F,x).then(e=>{applyVerdict(e,A,T,r,o,s,m,l)}).catch(()=>{interceptForInjection(r,o,m),s()})}}function resolveEmail(e,r){if(r)try{const t=r(e);if(t)return t}catch{}const t=parseCookie(e,"__unshared_email");if(t)return t;const i=e.body?.email;return"string"==typeof i&&i?i:void 0}function applyVerdict(e,r,t,i,n,o,s,c){if(interceptForInjection(i,n,s),e.isFlagged&&!e.isVerified&&c)try{c({userId:r,emailAddress:t,verdict:e,req:i,res:n,next:o})}catch{o()}else o()}function interceptForInjection(e,r,t){delete e.headers["if-none-match"],delete e.headers["if-modified-since"],interceptResponse(r,(e,r)=>{if(!isHtmlContentType(r))return null;const i=e.toString("utf8"),n=i.lastIndexOf("</body>");return-1===n?i+t:i.slice(0,n)+t+i.slice(n)},{preventCaching:!0})}function dispatchUserEvent(e,r,t,i){e.processUserEvent({eventType:i.eventType,userId:i.userId,emailAddress:i.emailAddress,ipAddress:i.ipAddress,deviceId:i.deviceId,fingerprintId:i.fingerprintId,sessionHash:i.sessionId,userAgent:i.userAgent}).then(e=>{e.success&&e.data?.analysis&&r.update(i.userId,{isFlagged:e.data.analysis.is_user_flagged}),!e.success&&e.error?.retryAfter&&t.pause(1e3*e.error.retryAfter)}).catch(()=>{})}async function fetchAndCacheVerdict(e,r,t,i,n,o,s){const c={};let d;n&&"unknown"!==n&&(c.deviceId=n),o&&(c.fingerprintId=o);const a=await Promise.race([e.checkUser(i,c),new Promise(e=>{d=setTimeout(()=>e(null),500)})]);if(clearTimeout(d),!a)return{isFlagged:!1,isVerified:!1,emailAddress:i,sessionId:s,cachedAt:0,ttl:0};const l=a.data?.is_user_flagged??!1;return r.set(t,{isFlagged:l,isVerified:!1,emailAddress:i,sessionId:s}),r.get(t)}function extractSessionId(e,r){if(r)try{const t=r(e);if(t)return t}catch{}return parseCookie(e,"__unshared_sid")??"unknown"}function extractFingerprintId(e){return parseCookie(e,"__unshared_fingerprint_id")||void 0}function appendSetCookie(e,r){const t=e.getHeader("Set-Cookie");if(t){const i=Array.isArray(t)?[...t]:[String(t)];i.push(r),e.setHeader("Set-Cookie",i)}else e.setHeader("Set-Cookie",r)}function setUserIdCookie(e,r,t){const i=isSecureRequest(e)?"; Secure":"";appendSetCookie(r,`__unshared_uid=${encodeURIComponent(t)}; Path=/; SameSite=Lax${i}`)}function setEmailCookie(e,r,t){const i=isSecureRequest(e)?"; Secure":"";appendSetCookie(r,`__unshared_email=${encodeURIComponent(t)}; HttpOnly; Path=/; SameSite=Lax${i}`)}function clearUserIdCookieIfPresent(e,r){parseCookie(e,"__unshared_uid")&&appendSetCookie(r,"__unshared_uid=; Path=/; SameSite=Lax; Max-Age=0"+(isSecureRequest(e)?"; Secure":""))}function clearEmailCookieIfPresent(e,r){parseCookie(e,"__unshared_email")&&appendSetCookie(r,"__unshared_email=; HttpOnly; Path=/; SameSite=Lax; Max-Age=0"+(isSecureRequest(e)?"; Secure":""))}
@@ -1,10 +1,16 @@
1
1
  /**
2
- * Generates a small inline loader script that:
3
- * 1. Loads the real fingerprint SDK from /__unshared/fp.js
4
- * 2. Collects a full fingerprint (31+ signals, MurmurHash3 Merkle tree)
5
- * 3. POSTs the result to /__unshared/submit-fp
2
+ * Generates an inline loader script that:
3
+ * 1. Loads the fingerprint SDK from /__unshared/fp.js
4
+ * 2. Collects a fingerprint and POSTs to /__unshared/submit-fp
5
+ * 3. Caches fingerprint in sessionStorage for reuse on SPA navigations
6
+ * 4. Patches History API to detect SPA route changes → re-submits fingerprint
7
+ * 5. Patches fetch/XHR to detect 403 account_flagged → dispatches "unshared:flagged" event
6
8
  *
7
9
  * The actual SDK UMD bundle is served by the middleware at /__unshared/fp.js.
8
- * This keeps the injected HTML small (~500 bytes) while using the full library.
10
+ *
11
+ * Event contract:
12
+ * window.addEventListener("unshared:flagged", (e) => {
13
+ * e.detail.email — the flagged user's email (from 403 response body)
14
+ * });
9
15
  */
10
16
  export declare function generateFingerprintScript(routePrefix: string, version?: string): string;
@@ -1 +1 @@
1
- export function generateFingerprintScript(e,n){const t=n?`?v=${escapeJavaScript(n)}`:"";return`<script>\n(function(){\ntry{\nvar pfx="${escapeJavaScript(e)}";\n\n// Session cookie helpers\nfunction getCookie(n){var m=document.cookie.match(new RegExp("(?:^|; )"+n+"=([^;]*)"));return m?decodeURIComponent(m[1]):null}\nfunction setCookie(n,v,d){var e="";if(d){var dt=new Date();dt.setTime(dt.getTime()+d*864e5);e="; expires="+dt.toUTCString()}document.cookie=n+"="+encodeURIComponent(v)+e+"; path=/; SameSite=Lax"}\n\n// UUID helper\nfunction uuid(){return(typeof crypto!=="undefined"&&crypto.randomUUID)?crypto.randomUUID():("xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(c){var r=Math.random()*16|0;return(c==="x"?r:r&0x3|0x8).toString(16)}))}\n\n// Ensure session ID cookie exists\nvar sid=getCookie("__unshared_sid");\nif(!sid){sid=uuid();setCookie("__unshared_sid",sid,365)}\n\n// Persistent device ID (survives across sessions via localStorage)\nvar did="";\ntry{did=localStorage.getItem("__unshared_device_id")||"";if(!did){did=uuid();localStorage.setItem("__unshared_device_id",did)}}catch(e){did=did||uuid()}\n\nvar uid=getCookie("__unshared_uid")||"";\n\n// Collect on every page load if a userId is present\nif(uid){\n var s=document.createElement("script");\n s.src=pfx+"/fp.js${t}";\n s.onload=function(){\n try{\n var client=new UnsharedLabsBrowser.UnsharedLabsBrowser({baseUrl:""});\n client.collect({exclude:["timing","navigatorConnection"]}).then(function(fp){\n var body={\n hash:fp.full_hash,\n stable_hash:fp.fingerprint_id,\n collected_at:fp.timestamp,\n is_incognito:fp.isIncognito,\n components:fp.components,\n version:fp.version,\n session_id:sid,\n user_id:uid\n };\n var xhr=new XMLHttpRequest();\n xhr.open("POST",pfx+"/submit-fp",true);\n xhr.setRequestHeader("Content-Type","application/json");\n xhr.setRequestHeader("X-Session-Id",sid);\n xhr.setRequestHeader("X-Device-Id",did);\n xhr.send(JSON.stringify(body));\n });\n }catch(e){}\n };\n document.head.appendChild(s);\n}\n}catch(e){}\n})();\n<\/script>`}function escapeJavaScript(e){return e.replace(/\\/g,"\\\\").replace(/"/g,'\\"').replace(/'/g,"\\'")}
1
+ export function generateFingerprintScript(e,n){const t=n?`?v=${escapeJavaScript(n)}`:"";return`<script>\n(function(){\ntry{\nvar pfx="${escapeJavaScript(e)}";\nvar SS_FP="__unshared_fp";\n\n// --- Helpers ---\nfunction gC(n){var m=document.cookie.match(new RegExp("(?:^|; )"+n+"=([^;]*)"));return m?decodeURIComponent(m[1]):null}\nfunction sC(n,v,d){var e="";if(d){var dt=new Date();dt.setTime(dt.getTime()+d*864e5);e="; expires="+dt.toUTCString()}document.cookie=n+"="+encodeURIComponent(v)+e+"; path=/; SameSite=Lax"}\nfunction uuid(){return(typeof crypto!=="undefined"&&crypto.randomUUID)?crypto.randomUUID():("xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(c){var r=Math.random()*16|0;return(c==="x"?r:r&0x3|0x8).toString(16)}))}\n\n// --- Session + device IDs ---\nvar sid=gC("__unshared_sid");\nif(!sid){sid=uuid();sC("__unshared_sid",sid,365)}\nvar did="";\ntry{did=localStorage.getItem("__unshared_device_id")||"";if(!did){did=uuid();localStorage.setItem("__unshared_device_id",did)}}catch(e){did=did||uuid()}\nsC("__unshared_fp_id",did,365);\n\n// --- Fingerprint cache (sessionStorage) ---\nfunction getFP(){try{var r=sessionStorage.getItem(SS_FP);return r?JSON.parse(r):null}catch(e){return null}}\nfunction setFP(fp){try{sessionStorage.setItem(SS_FP,JSON.stringify(fp))}catch(e){}}\n\n// --- Submit fingerprint to backend ---\nfunction submitFP(fp,evType){\n var uid=gC("__unshared_uid");\n if(!uid)return;\n var body={hash:fp.full_hash,stable_hash:fp.fingerprint_id,collected_at:fp.timestamp,is_incognito:fp.isIncognito,components:fp.components,version:fp.version,session_id:sid,user_id:uid,event_type:evType||"page_load"};\n var xhr=new XMLHttpRequest();\n xhr.open("POST",pfx+"/submit-fp",true);\n xhr.setRequestHeader("Content-Type","application/json");\n xhr.setRequestHeader("X-Session-Id",sid);\n xhr.setRequestHeader("X-Device-Id",did);\n xhr.send(JSON.stringify(body));\n}\n\n// --- Collect fingerprint (loads fp.js if needed) then submit ---\nvar fpReady=false;\nfunction collectAndSubmit(evType){\n var uid=gC("__unshared_uid");\n if(!uid)return;\n var cached=getFP();\n if(cached){submitFP(cached,evType);return}\n if(!fpReady)return;\n try{\n var c=new UnsharedLabsBrowser.UnsharedLabsBrowser({baseUrl:""});\n c.collect({exclude:["timing","navigatorConnection"]}).then(function(fp){setFP(fp);submitFP(fp,evType)});\n }catch(e){}\n}\n\n// --- Load fp.js (always — browser caches it for 1h) ---\n// Submit cached FP immediately if available; load fp.js for fresh collection\nvar pageLoadSubmitted=false;\nif(getFP()&&gC("__unshared_uid")){submitFP(getFP(),"page_load");pageLoadSubmitted=true}\nvar s=document.createElement("script");\ns.src=pfx+"/fp.js${t}";\ns.onload=function(){fpReady=true;if(!pageLoadSubmitted)collectAndSubmit("page_load")};\ndocument.head.appendChild(s);\n\n// --- SPA route change tracking (History API + popstate) ---\nvar oPush=history.pushState,oReplace=history.replaceState;\nhistory.pushState=function(){oPush.apply(this,arguments);try{collectAndSubmit("route_change")}catch(e){}};\nhistory.replaceState=function(){oReplace.apply(this,arguments);try{collectAndSubmit("route_change")}catch(e){}};\nwindow.addEventListener("popstate",function(){try{collectAndSubmit("route_change")}catch(e){}});\n\n// --- 403 interception: dispatch "unshared:flagged" event ---\nfunction emitFlagged(body){\n try{window.dispatchEvent(new CustomEvent("unshared:flagged",{detail:{email:body.email||""}}))}catch(e){}\n}\n\n// Patch fetch\nvar oFetch=window.fetch;\nif(oFetch){window.fetch=function(){return oFetch.apply(this,arguments).then(function(r){if(r.status===403){try{var cl=r.clone();cl.json().then(function(b){if(b&&b.error==="account_flagged")emitFlagged(b)}).catch(function(){})}catch(e){}}return r})}}\n\n// Patch XMLHttpRequest\nvar oXSend=XMLHttpRequest.prototype.send;\nXMLHttpRequest.prototype.send=function(){var x=this;x.addEventListener("load",function(){if(x.status===403){try{var b=JSON.parse(x.responseText);if(b&&b.error==="account_flagged")emitFlagged(b)}catch(e){}}});return oXSend.apply(this,arguments)};\n\n}catch(e){}\n})();\n<\/script>`}function escapeJavaScript(e){return e.replace(/\\/g,"\\\\").replace(/"/g,'\\"').replace(/'/g,"\\'")}
@@ -2,12 +2,14 @@ import type { Response } from 'express';
2
2
  /**
3
3
  * Intercepts the response body by wrapping res.write() and res.end().
4
4
  *
5
- * Collects all chunks written to the response. When res.end() is called,
6
- * invokes the `transform` callback with the complete body buffer and the
7
- * Content-Type header. The transform can return modified content or null
8
- * to pass through unchanged.
5
+ * Only buffers HTML responses (text/html). Non-HTML responses (JSON, images,
6
+ * CSS, JS, etc.) pass through to the original write/end without buffering,
7
+ * avoiding unnecessary memory usage on large payloads.
9
8
  *
10
- * Does NOT monkey-patch res.send uses the lower-level write/end API
11
- * as required by the spec.
9
+ * When res.end() is called on an HTML response, invokes the `transform`
10
+ * callback with the complete body buffer and the Content-Type header.
11
+ * The transform can return modified content or null to pass through unchanged.
12
12
  */
13
- export declare function interceptResponse(res: Response, transform: (body: Buffer, contentType: string | undefined) => Buffer | string | null): void;
13
+ export declare function interceptResponse(res: Response, transform: (body: Buffer, contentType: string | undefined) => Buffer | string | null, options?: {
14
+ preventCaching?: boolean;
15
+ }): void;
@@ -1 +1 @@
1
- export function interceptResponse(n,t){const f=[],e=n.write.bind(n),u=n.end.bind(n);let o=!1;n.write=function(n,t,e){if(null!=n){const e=Buffer.isBuffer(n)?n:Buffer.from(n,"string"==typeof t?t:"utf8");f.push(e)}return"function"==typeof t&&t(null),"function"==typeof e&&e(null),!0},n.end=function(r,c,l){if(o)return n;if(o=!0,null!=r){const n=Buffer.isBuffer(r)?r:Buffer.from(r,"string"==typeof c?c:"utf8");f.push(n)}const i=Buffer.concat(f),s=n.getHeader("content-type");let p;try{p=t(i,s)}catch{p=null}if(null!=p){const t=Buffer.isBuffer(p)?p:Buffer.from(p,"utf8");n.setHeader("Content-Length",t.length),n.removeHeader("Content-Encoding"),e(t)}else i.length>0&&e(i);const y="function"==typeof c?c:l;return y?u(y):u(),n}}
1
+ export function interceptResponse(n,t,f){const e=f?.preventCaching??!1,o=n.write.bind(n),u=n.end.bind(n),r=n.writeHead.bind(n),c=[];let i=!1,l=null;function s(){if(null!==l)return;const t=n.getHeader("content-type");null!=t&&(l=String(t).includes("text/html"),l||function(){n.write=o,n.end=u;for(const n of c)o(n);c.length=0}())}n.writeHead=function(t,...f){return s(),e&&l&&(n.setHeader("Cache-Control","no-store"),n.removeHeader("ETag"),n.removeHeader("Last-Modified")),r(t,...f)},n.write=function(n,t,f){if(s(),!1===l)return o(n,t,f);if(null!=n){const f=Buffer.isBuffer(n)?n:Buffer.from(n,"string"==typeof t?t:"utf8");c.push(f)}return"function"==typeof t&&t(null),"function"==typeof f&&f(null),!0},n.end=function(f,e,r){if(i)return n;if(i=!0,s(),!1===l)return u(f,e,r);if(null!=f){const n=Buffer.isBuffer(f)?f:Buffer.from(f,"string"==typeof e?e:"utf8");c.push(n)}const p=Buffer.concat(c),y=n.getHeader("content-type");let B;try{B=t(p,y)}catch{B=null}if(null!=B){const t=Buffer.isBuffer(B)?B:Buffer.from(B,"utf8");n.setHeader("Content-Length",t.length),n.removeHeader("Content-Encoding"),o(t)}else p.length>0&&o(p);const g="function"==typeof e?e:r;return g?u(g):u(),n}}
@@ -1 +1 @@
1
- import{isBot}from"../utils/is-bot";import{extractClientIp}from"../utils/client-ip";export function handleSubmitFingerprint(e){return async(t,n)=>{try{const i=t.body??{},o={full_hash:i.hash??"",fingerprint_id:i.stable_hash??"",timestamp:i.collected_at??(new Date).toISOString(),isIncognito:i.is_incognito??!1,components:i.components??{},version:i.version??"inline-1.0.0"};let s,r,c;try{s=e.resolveUserId?e.resolveUserId(t):void 0}catch{}s=s??i.user_id??void 0;try{r=e.resolveEmailAddress?e.resolveEmailAddress(t):void 0}catch{}r=r??parseCookie(t,"__unshared_email")??i.email??void 0;try{c=e.resolveSessionId?e.resolveSessionId(t):void 0}catch{}c=c??i.session_id??parseCookie(t,"__unshared_sid");const a=extractClientIp(t),d=t.headers["user-agent"]??"";if(isBot(d))return void n.status(200).json({success:!0});const u=extractDeviceId(t,e.resolveDeviceId),p=o.fingerprint_id||void 0,_=[];if(p&&!parseCookie(t,"__unshared_fingerprint_id")&&_.push(`__unshared_fingerprint_id=${encodeURIComponent(p)}; HttpOnly; Path=/; SameSite=Lax`),r&&!parseCookie(t,"__unshared_email")&&_.push(`__unshared_email=${encodeURIComponent(r)}; HttpOnly; Path=/; SameSite=Lax`),_.length>0){const e=n.getHeader("Set-Cookie");if(e){const t=Array.isArray(e)?[...e]:[String(e)];t.push(..._),n.setHeader("Set-Cookie",t)}else n.setHeader("Set-Cookie",_)}s&&e.client.submitFingerprintEvent(o,{userId:s,emailAddress:r,sessionHash:c,eventType:"auto_collect",ipAddress:a,userAgent:d}).catch(()=>{}),s&&r&&!e.rateLimitBackoff.isPaused()&&e.client.processUserEvent({eventType:"auto_collect",userId:s,emailAddress:r,ipAddress:a,deviceId:u,fingerprintId:p,sessionHash:c??"unknown",userAgent:d}).then(t=>{t.success&&t.data?.analysis&&e.verdictCache.update(s,{isFlagged:t.data.analysis.is_user_flagged}),!t.success&&t.error?.retryAfter&&e.rateLimitBackoff.pause(1e3*t.error.retryAfter)}).catch(()=>{}),n.status(200).json({success:!0})}catch{n.status(200).json({success:!0})}}}function extractDeviceId(e,t){if(t)try{const n=t(e);if(n)return n}catch{}const n=parseCookie(e,"__unshared_fp_id");if(n)return n;const i=e.headers["x-device-id"];return"string"==typeof i&&i?i:"unknown"}function parseCookie(e,t){const n=e.headers.cookie;if(!n)return;const i=n.match(new RegExp(`(?:^|; )${t}=([^;]*)`));return i?decodeURIComponent(i[1]):void 0}
1
+ import{isBot}from"../utils/is-bot";import{extractClientIp}from"../utils/client-ip";import{parseCookie}from"../utils/cookies";import{extractDeviceId}from"../utils/device-id";import{isSecureRequest}from"../utils/secure";export function handleSubmitFingerprint(e){return async(i,t)=>{try{const s=i.body??{},o={full_hash:s.hash??"",fingerprint_id:s.stable_hash??"",timestamp:s.collected_at??(new Date).toISOString(),isIncognito:s.is_incognito??!1,components:s.components??{},version:s.version??"inline-1.0.0"};let r,n,c;try{r=e.resolveUserId?e.resolveUserId(i):void 0}catch{}r=r??s.user_id??void 0;try{n=e.resolveEmailAddress?e.resolveEmailAddress(i):void 0}catch{}n=n??parseCookie(i,"__unshared_email")??s.email??void 0;try{c=e.resolveSessionId?e.resolveSessionId(i):void 0}catch{}c=c??s.session_id??parseCookie(i,"__unshared_sid");const a=extractClientIp(i),d=i.headers["user-agent"]??"";if(isBot(d))return void t.status(200).json({success:!0});const u=extractDeviceId(i,e.resolveDeviceId),p=o.fingerprint_id||void 0,l=isSecureRequest(i)?"; Secure":"",_=[];if(p&&!parseCookie(i,"__unshared_fingerprint_id")&&_.push(`__unshared_fingerprint_id=${encodeURIComponent(p)}; HttpOnly; Path=/; SameSite=Lax${l}`),n&&!parseCookie(i,"__unshared_email")&&_.push(`__unshared_email=${encodeURIComponent(n)}; HttpOnly; Path=/; SameSite=Lax${l}`),_.length>0){const e=t.getHeader("Set-Cookie");if(e){const i=Array.isArray(e)?[...e]:[String(e)];i.push(..._),t.setHeader("Set-Cookie",i)}else t.setHeader("Set-Cookie",_)}r&&e.client.submitFingerprintEvent(o,{userId:r,emailAddress:n,sessionHash:c,eventType:"auto_collect",ipAddress:a,userAgent:d}).catch(()=>{}),r&&n&&!e.rateLimitBackoff.isPaused()&&e.client.processUserEvent({eventType:"auto_collect",userId:r,emailAddress:n,ipAddress:a,deviceId:u,fingerprintId:p,sessionHash:c??"unknown",userAgent:d}).then(i=>{i.success&&i.data?.analysis&&e.verdictCache.update(r,{isFlagged:i.data.analysis.is_user_flagged}),!i.success&&i.error?.retryAfter&&e.rateLimitBackoff.pause(1e3*i.error.retryAfter)}).catch(()=>{}),t.status(200).json({success:!0})}catch{t.status(200).json({success:!0})}}}
@@ -1 +1 @@
1
- export function handleVerifyTrigger(e){return async(r,i)=>{try{const s=resolveEmail(r,r.body??{},e.resolveEmailAddress);if(!s)return void i.status(400).json({success:!1,error:{code:"VALIDATION_ERROR",message:"Email is required"}});const n=extractDeviceId(r,e.resolveDeviceId),o=parseCookie(r,"__unshared_fingerprint_id")||void 0,t=await e.client.triggerEmailVerification(s,n,{fingerprintId:o});t.success?i.status(200).json({success:!0,data:t.data}):i.status(200).json({success:!1,error:t.error??{code:"TRIGGER_FAILED",message:"Failed to send verification email"}})}catch{i.status(200).json({success:!1,error:{code:"INTERNAL_ERROR",message:"Failed to trigger verification"}})}}}export function handleVerify(e){return async(r,i)=>{try{const s=r.body??{},n=resolveEmail(r,s,e.resolveEmailAddress),o=s.code;if(!n||!o)return void i.status(400).json({success:!1,error:{code:"VALIDATION_ERROR",message:"Email and code are required"}});const t=extractDeviceId(r,e.resolveDeviceId),c=parseCookie(r,"__unshared_fingerprint_id")||void 0,a=await e.client.verify(n,t,o,{fingerprintId:c});if(a.success){const s=parseCookie(r,"__unshared_uid");s&&e.verdictCache.update(s,{isVerified:!0}),i.status(200).json({success:!0,data:{verified:!0}})}else i.status(200).json({success:!1,error:a.error??{code:"VERIFICATION_FAILED",message:"Verification failed"}})}catch{i.status(200).json({success:!1,error:{code:"INTERNAL_ERROR",message:"Verification failed"}})}}}function resolveEmail(e,r,i){if(i)try{const r=i(e);if(r)return r}catch{}const s=parseCookie(e,"__unshared_email");if(s)return s;const n=r.email;return"string"==typeof n&&n?n:void 0}function extractDeviceId(e,r){if(r)try{const i=r(e);if(i)return i}catch{}const i=parseCookie(e,"__unshared_fp_id");if(i)return i;const s=e.headers["x-device-id"];return"string"==typeof s&&s?s:"unknown"}function parseCookie(e,r){const i=e.headers.cookie;if(!i)return;const s=i.match(new RegExp(`(?:^|; )${r}=([^;]*)`));return s?decodeURIComponent(s[1]):void 0}
1
+ import{parseCookie}from"../utils/cookies";import{extractDeviceId}from"../utils/device-id";export function handleVerifyTrigger(e){return async(r,i)=>{try{const s=resolveEmail(r,r.body??{},e.resolveEmailAddress);if(!s)return void i.status(400).json({success:!1,error:{code:"VALIDATION_ERROR",message:"Email is required"}});const o=extractDeviceId(r,e.resolveDeviceId),c=parseCookie(r,"__unshared_fingerprint_id")||void 0,t=await e.client.triggerEmailVerification(s,o,{fingerprintId:c});t.success?i.status(200).json({success:!0,data:t.data}):i.status(200).json({success:!1,error:t.error??{code:"TRIGGER_FAILED",message:"Failed to send verification email"}})}catch{i.status(200).json({success:!1,error:{code:"INTERNAL_ERROR",message:"Failed to trigger verification"}})}}}export function handleVerify(e){return async(r,i)=>{try{const s=r.body??{},o=resolveEmail(r,s,e.resolveEmailAddress),c=s.code;if(!o||!c)return void i.status(400).json({success:!1,error:{code:"VALIDATION_ERROR",message:"Email and code are required"}});const t=extractDeviceId(r,e.resolveDeviceId),a=parseCookie(r,"__unshared_fingerprint_id")||void 0,n=await e.client.verify(o,t,c,{fingerprintId:a});if(n.success){const s=parseCookie(r,"__unshared_uid");s&&e.verdictCache.update(s,{isVerified:!0}),i.status(200).json({success:!0,data:{verified:!0}})}else i.status(200).json({success:!1,error:n.error??{code:"VERIFICATION_FAILED",message:"Verification failed"}})}catch{i.status(200).json({success:!1,error:{code:"INTERNAL_ERROR",message:"Verification failed"}})}}}function resolveEmail(e,r,i){if(i)try{const r=i(e);if(r)return r}catch{}const s=parseCookie(e,"__unshared_email");if(s)return s;const o=r.email;return"string"==typeof o&&o?o:void 0}
@@ -0,0 +1,6 @@
1
+ import type { Request } from 'express';
2
+ /**
3
+ * Reads a single cookie value from the raw Cookie header.
4
+ * Works without cookie-parser middleware.
5
+ */
6
+ export declare function parseCookie(req: Request, name: string): string | undefined;
@@ -0,0 +1 @@
1
+ export function parseCookie(e,o){const n=e.headers.cookie;if(!n)return;const t=n.match(new RegExp(`(?:^|; )${o}=([^;]*)`));return t?decodeURIComponent(t[1]):void 0}
@@ -0,0 +1,5 @@
1
+ import type { Request } from 'express';
2
+ /**
3
+ * Resolves device ID from: custom resolver → __unshared_fp_id cookie → X-Device-Id header.
4
+ */
5
+ export declare function extractDeviceId(req: Request, resolveDeviceId?: (req: Request) => string | undefined): string;
@@ -0,0 +1 @@
1
+ import{parseCookie}from"./cookies";export function extractDeviceId(o,r){if(r)try{const t=r(o);if(t)return t}catch{}const t=parseCookie(o,"__unshared_fp_id");if(t)return t;const e=o.headers["x-device-id"];return"string"==typeof e&&e?e:"unknown"}
@@ -0,0 +1,3 @@
1
+ import type { Request } from 'express';
2
+ /** Returns true if the request arrived over HTTPS (direct or via reverse proxy). */
3
+ export declare function isSecureRequest(req: Request): boolean;
@@ -0,0 +1 @@
1
+ export function isSecureRequest(e){return e.secure||"https"===e.headers["x-forwarded-proto"]}
@@ -1 +1 @@
1
- const STATIC_EXTENSIONS=new Set([".js",".mjs",".cjs",".css",".map",".png",".jpg",".jpeg",".gif",".svg",".ico",".webp",".avif",".woff",".woff2",".ttf",".otf",".eot",".mp3",".mp4",".webm",".ogg",".wasm",".json",".xml",".txt",".pdf"]),STATIC_PATH_PREFIXES=["/static/","/assets/","/public/","/_next/","/__vite/","/favicon"];export function shouldSkipPath(t,f){if(f)for(const o of f)if(t.startsWith(o))return!0;const o=t.lastIndexOf(".");if(-1!==o){const f=t.slice(o).toLowerCase().split("?")[0];if(STATIC_EXTENSIONS.has(f))return!0}for(const f of STATIC_PATH_PREFIXES)if(t.startsWith(f))return!0;return!1}
1
+ const STATIC_EXTENSIONS=new Set([".js",".mjs",".cjs",".css",".map",".png",".jpg",".jpeg",".gif",".svg",".ico",".webp",".avif",".woff",".woff2",".ttf",".otf",".eot",".mp3",".mp4",".webm",".ogg",".wasm",".xml",".txt",".pdf"]),STATIC_PATH_PREFIXES=["/static/","/assets/","/public/","/_next/","/__vite/","/favicon"];export function shouldSkipPath(t,f){if(f)for(const o of f)if(t.startsWith(o))return!0;const o=t.lastIndexOf(".");if(-1!==o){const f=t.slice(o).toLowerCase().split("?")[0];if(STATIC_EXTENSIONS.has(f))return!0}for(const f of STATIC_PATH_PREFIXES)if(t.startsWith(f))return!0;return!1}
@@ -10,7 +10,9 @@ export declare class VerdictCache {
10
10
  private readonly _entries;
11
11
  private readonly _activeRefreshes;
12
12
  private readonly _defaultTtlMs;
13
- constructor(defaultTTL?: number);
13
+ private readonly _maxSize;
14
+ private _sweepTimer;
15
+ constructor(defaultTTL?: number, maxSize?: number);
14
16
  get(userId: string): Verdict | undefined;
15
17
  set(userId: string, verdict: Omit<Verdict, 'cachedAt' | 'ttl'>, ttl?: number): void;
16
18
  /**
@@ -33,4 +35,13 @@ export declare class VerdictCache {
33
35
  /** Number of cached entries. */
34
36
  get size(): number;
35
37
  clear(): void;
38
+ /**
39
+ * Stops the periodic sweep timer. Call this when shutting down
40
+ * or when the middleware is no longer needed (e.g., in tests).
41
+ */
42
+ destroy(): void;
43
+ /** Remove all entries that are past their TTL + a 2x grace period. */
44
+ private _sweep;
45
+ /** Evict the oldest entry by cachedAt to make room. */
46
+ private _evictOldest;
36
47
  }
@@ -1 +1 @@
1
- export class VerdictCache{constructor(t=6e4){this.t=new Map,this.i=new Set,this.h=t}get(t){return this.t.get(t)}set(t,e,s){this.t.set(t,{...e,cachedAt:Date.now(),ttl:s??this.h})}update(t,e){const s=this.t.get(t);s?(void 0!==e.isFlagged&&(s.isFlagged=e.isFlagged),void 0!==e.isVerified&&(s.isVerified=e.isVerified),s.cachedAt=Date.now()):this.t.set(t,{isFlagged:e.isFlagged??!1,isVerified:e.isVerified??!1,emailAddress:"",sessionId:"",cachedAt:Date.now(),ttl:this.h})}delete(t){this.t.delete(t),this.i.delete(t)}isStale(t){const e=this.t.get(t);return!!e&&Date.now()-e.cachedAt>e.ttl}isRefreshing(t){return this.i.has(t)}markRefreshing(t){this.i.add(t)}clearRefreshing(t){this.i.delete(t)}get size(){return this.t.size}clear(){this.t.clear(),this.i.clear()}}
1
+ const DEFAULT_TTL_MS=6e4,DEFAULT_MAX_SIZE=1e4,SWEEP_INTERVAL_MS=3e5;export class VerdictCache{constructor(t=6e4,s=1e4){this.t=new Map,this.i=new Set,this.h=null,this.l=t,this.o=s,this.h=setInterval(()=>this.u(),3e5),this.h&&"function"==typeof this.h.unref&&this.h.unref()}get(t){return this.t.get(t)}set(t,s,i){!this.t.has(t)&&this.t.size>=this.o&&this._(),this.t.set(t,{...s,cachedAt:Date.now(),ttl:i??this.l})}update(t,s){const i=this.t.get(t);i?(void 0!==s.isFlagged&&(i.isFlagged=s.isFlagged),void 0!==s.isVerified&&(i.isVerified=s.isVerified),i.cachedAt=Date.now()):(this.t.size>=this.o&&this._(),this.t.set(t,{isFlagged:s.isFlagged??!1,isVerified:s.isVerified??!1,emailAddress:"",sessionId:"",cachedAt:Date.now(),ttl:this.l}))}delete(t){this.t.delete(t),this.i.delete(t)}isStale(t){const s=this.t.get(t);return!!s&&Date.now()-s.cachedAt>s.ttl}isRefreshing(t){return this.i.has(t)}markRefreshing(t){this.i.add(t)}clearRefreshing(t){this.i.delete(t)}get size(){return this.t.size}clear(){this.t.clear(),this.i.clear()}destroy(){this.h&&(clearInterval(this.h),this.h=null),this.clear()}u(){const t=Date.now();for(const[s,i]of this.t)t-i.cachedAt>2*i.ttl&&(this.t.delete(s),this.i.delete(s))}_(){let t=null,s=1/0;for(const[i,e]of this.t)e.cachedAt<s&&(s=e.cachedAt,t=i);t&&(this.t.delete(t),this.i.delete(t))}}
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"t",{value:!0}),exports.VerdictCache=void 0,exports.unsharedBoundToUser=unsharedBoundToUser;const fs_1=require("fs"),verdict_cache_1=require("./verdict-cache");Object.defineProperty(exports,"VerdictCache",{enumerable:!0,get:function(){return verdict_cache_1.VerdictCache}});const rate_limit_backoff_1=require("./rate-limit-backoff"),response_interceptor_1=require("./response-interceptor"),fingerprint_script_1=require("./injection/fingerprint-script"),submit_fp_1=require("./routes/submit-fp"),verify_1=require("./routes/verify"),content_type_1=require("./utils/content-type"),skip_paths_1=require("./utils/skip-paths"),is_bot_1=require("./utils/is-bot"),client_ip_1=require("./utils/client-ip"),CHECK_USER_TIMEOUT_MS=500;function unsharedBoundToUser(e,t){if(!t.userId)throw new Error("[Unshared] userId resolver is required");if(!t.emailAddress){let e=!1;try{require.resolve("unshared-frontend-sdk"),e=!0}catch{}e||console.warn("[Unshared] Warning: emailAddress resolver is not configured and unshared-frontend-sdk is not installed.\nNo user events will be submitted. Either install unshared-frontend-sdk (Tier 1) or\nprovide emailAddress in your middleware config (Tier 2).")}const{userId:r,emailAddress:i,routePrefix:n="/__unshared",corsOrigins:o,cacheTTL:s=6e4,skipPaths:c,sessionId:d,deviceId:a,onFlagged:u}=t,l=new verdict_cache_1.VerdictCache(s),p=new rate_limit_backoff_1.RateLimitBackoff,f=Date.now().toString(36),_=(0,fingerprint_script_1.generateFingerprintScript)(n,f);let v="";try{const e=require.resolve("unshared-frontend-sdk/dist/index.umd.js");v=(0,fs_1.readFileSync)(e,"utf8")}catch{}const h=(0,submit_fp_1.handleSubmitFingerprint)({client:e,verdictCache:l,rateLimitBackoff:p,resolveUserId:r,resolveEmailAddress:i,resolveSessionId:d,resolveDeviceId:a}),m=(0,verify_1.handleVerifyTrigger)({client:e,verdictCache:l,resolveEmailAddress:i,resolveDeviceId:a}),C=(0,verify_1.handleVerify)({client:e,verdictCache:l,resolveEmailAddress:i,resolveDeviceId:a}),I=o?Array.isArray(o)?o:[o]:null,g=`${n}/fp.js`,k=`${n}/submit-fp`,y=`${n}/verify-trigger`,A=`${n}/verify`;return function(t,o,s){const f=t.path;if(f.startsWith(n+"/"))return function(e,t){if(!I)return;const r=e.headers.origin??"",i=I.includes("*");(i||I.includes(r))&&(t.setHeader("Access-Control-Allow-Origin",i?"*":r),t.setHeader("Access-Control-Allow-Methods","POST, OPTIONS"),t.setHeader("Access-Control-Allow-Headers","Content-Type, X-Idempotency-Key, X-Session-Id, X-Device-Id"),t.setHeader("Access-Control-Allow-Credentials","true"))}(t,o),"OPTIONS"===t.method?void o.status(204).end():"GET"===t.method&&f===g?(o.setHeader("Content-Type","application/javascript"),o.setHeader("Cache-Control","public, max-age=3600"),void o.status(200).end(v)):"POST"===t.method&&f===k?void h(t,o):"POST"===t.method&&f===y?void m(t,o):"POST"===t.method&&f===A?void C(t,o):void o.status(404).json({success:!1,error:{code:"NOT_FOUND",message:"Unknown route"}});if((0,skip_paths_1.shouldSkipPath)(f,c))return void s();let S;try{S=r(t)}catch{}if(!S)return clearEmailCookieIfPresent(t,o),interceptForInjection(t,o,_),void s();const T=resolveEmail(t,i);if(setUserIdCookie(o,S),T&&setEmailCookie(o,T),!T)return interceptForInjection(t,o,_),void s();const x=extractSessionId(t,d),E=extractDeviceId(t,a),w=extractFingerprintId(t),b=t.headers["user-agent"]??"",O=(0,client_ip_1.extractClientIp)(t);if((0,is_bot_1.isBot)(b))return void s();if("unknown"===x)return interceptForInjection(t,o,_),void s();p.isPaused()||dispatchUserEvent(e,l,p,{userId:S,emailAddress:T,sessionId:x,deviceId:E,fingerprintId:w,userAgent:b,ipAddress:O,eventType:`${t.method} ${t.path}`});const U=l.get(S);U?(l.isStale(S)&&!l.isRefreshing(S)&&(l.markRefreshing(S),fetchAndCacheVerdict(e,l,S,T,E,w,x).finally(()=>l.clearRefreshing(S))),applyVerdict(U,S,T,t,o,s,_,u)):fetchAndCacheVerdict(e,l,S,T,E,w,x).then(e=>{applyVerdict(e,S,T,t,o,s,_,u)}).catch(()=>{interceptForInjection(t,o,_),s()})}}function resolveEmail(e,t){if(t)try{const r=t(e);if(r)return r}catch{}const r=parseCookie(e,"__unshared_email");if(r)return r;const i=e.body?.email;return"string"==typeof i&&i?i:void 0}function applyVerdict(e,t,r,i,n,o,s,c){if(interceptForInjection(i,n,s),e.isFlagged&&!e.isVerified&&c)try{c({userId:t,emailAddress:r,verdict:e,req:i,res:n,next:o})}catch{o()}else o()}function preventHtmlCaching(e,t){delete e.headers["if-none-match"],delete e.headers["if-modified-since"];const r=t.writeHead.bind(t);t.writeHead=function(e,...i){const n=t.getHeader("content-type");return n&&String(n).includes("text/html")&&(t.setHeader("Cache-Control","no-store"),t.removeHeader("ETag"),t.removeHeader("Last-Modified")),r(e,...i)}}function interceptForInjection(e,t,r){preventHtmlCaching(e,t),(0,response_interceptor_1.interceptResponse)(t,(e,t)=>{if(!(0,content_type_1.isHtmlContentType)(t))return null;const i=e.toString("utf8"),n=i.lastIndexOf("</body>");return-1===n?i+r:i.slice(0,n)+r+i.slice(n)})}function dispatchUserEvent(e,t,r,i){e.processUserEvent({eventType:i.eventType,userId:i.userId,emailAddress:i.emailAddress,ipAddress:i.ipAddress,deviceId:i.deviceId,fingerprintId:i.fingerprintId,sessionHash:i.sessionId,userAgent:i.userAgent}).then(e=>{e.success&&e.data?.analysis&&t.update(i.userId,{isFlagged:e.data.analysis.is_user_flagged}),!e.success&&e.error?.retryAfter&&r.pause(1e3*e.error.retryAfter)}).catch(()=>{})}async function fetchAndCacheVerdict(e,t,r,i,n,o,s){const c={};n&&"unknown"!==n&&(c.deviceId=n),o&&(c.fingerprintId=o);const d=await Promise.race([e.checkUser(i,c),new Promise(e=>setTimeout(()=>e(null),500))]);if(!d)return{isFlagged:!1,isVerified:!1,emailAddress:i,sessionId:s,cachedAt:0,ttl:0};const a=d.data?.is_user_flagged??!1;return t.set(r,{isFlagged:a,isVerified:!1,emailAddress:i,sessionId:s}),t.get(r)}function parseCookie(e,t){const r=e.headers.cookie;if(!r)return;const i=r.match(new RegExp(`(?:^|; )${t}=([^;]*)`));return i?decodeURIComponent(i[1]):void 0}function extractSessionId(e,t){if(t)try{const r=t(e);if(r)return r}catch{}return parseCookie(e,"__unshared_sid")??"unknown"}function extractDeviceId(e,t){if(t)try{const r=t(e);if(r)return r}catch{}const r=parseCookie(e,"__unshared_fp_id");if(r)return r;const i=e.headers["x-device-id"];return"string"==typeof i&&i?i:"unknown"}function extractFingerprintId(e){return parseCookie(e,"__unshared_fingerprint_id")||void 0}function appendSetCookie(e,t){const r=e.getHeader("Set-Cookie");if(r){const i=Array.isArray(r)?[...r]:[String(r)];i.push(t),e.setHeader("Set-Cookie",i)}else e.setHeader("Set-Cookie",t)}function setUserIdCookie(e,t){appendSetCookie(e,`__unshared_uid=${encodeURIComponent(t)}; Path=/; SameSite=Lax`)}function setEmailCookie(e,t){appendSetCookie(e,`__unshared_email=${encodeURIComponent(t)}; HttpOnly; Path=/; SameSite=Lax`)}function clearEmailCookieIfPresent(e,t){parseCookie(e,"__unshared_email")&&appendSetCookie(t,"__unshared_email=; HttpOnly; Path=/; SameSite=Lax; Max-Age=0")}
1
+ "use strict";Object.defineProperty(exports,"i",{value:!0}),exports.VerdictCache=void 0,exports.unsharedBoundToUser=unsharedBoundToUser;const fs_1=require("fs"),verdict_cache_1=require("./verdict-cache");Object.defineProperty(exports,"VerdictCache",{enumerable:!0,get:function(){return verdict_cache_1.VerdictCache}});const rate_limit_backoff_1=require("./rate-limit-backoff"),response_interceptor_1=require("./response-interceptor"),fingerprint_script_1=require("./injection/fingerprint-script"),submit_fp_1=require("./routes/submit-fp"),verify_1=require("./routes/verify"),content_type_1=require("./utils/content-type"),skip_paths_1=require("./utils/skip-paths"),is_bot_1=require("./utils/is-bot"),client_ip_1=require("./utils/client-ip"),cookies_1=require("./utils/cookies"),device_id_1=require("./utils/device-id"),secure_1=require("./utils/secure"),CHECK_USER_TIMEOUT_MS=500;function unsharedBoundToUser(e,r){if(!r.userId)throw new Error("[Unshared] userId resolver is required");if(!r.emailAddress){let e=!1;try{require.resolve("unshared-frontend-sdk"),e=!0}catch{}e||console.warn("[Unshared] Warning: emailAddress resolver is not configured and unshared-frontend-sdk is not installed.\nNo user events will be submitted. Either install unshared-frontend-sdk (Tier 1) or\nprovide emailAddress in your middleware config (Tier 2).")}const{userId:i,emailAddress:t,routePrefix:n="/__unshared",corsOrigins:s,cacheTTL:o=6e4,skipPaths:c,sessionId:d,deviceId:a,onFlagged:u}=r,l=new verdict_cache_1.VerdictCache(o),_=new rate_limit_backoff_1.RateLimitBackoff,f=Date.now().toString(36),p=(0,fingerprint_script_1.generateFingerprintScript)(n,f);let v="";try{const e=require.resolve("unshared-frontend-sdk/dist/index.umd.js");v=(0,fs_1.readFileSync)(e,"utf8")}catch{}const h=(0,submit_fp_1.handleSubmitFingerprint)({client:e,verdictCache:l,rateLimitBackoff:_,resolveUserId:i,resolveEmailAddress:t,resolveSessionId:d,resolveDeviceId:a}),m=(0,verify_1.handleVerifyTrigger)({client:e,verdictCache:l,resolveEmailAddress:t,resolveDeviceId:a}),I=(0,verify_1.handleVerify)({client:e,verdictCache:l,resolveEmailAddress:t,resolveDeviceId:a}),k=s?Array.isArray(s)?s:[s]:null,C=`${n}/fp.js`,g=`${n}/submit-fp`,S=`${n}/verify-trigger`,y=`${n}/verify`;return function(r,s,o){const f=r.path;if(f.startsWith(n+"/"))return function(e,r){if(!k)return;const i=e.headers.origin??"",t=k.includes("*");(t||k.includes(i))&&(r.setHeader("Access-Control-Allow-Origin",t?"*":i),r.setHeader("Access-Control-Allow-Methods","POST, OPTIONS"),r.setHeader("Access-Control-Allow-Headers","Content-Type, X-Idempotency-Key, X-Session-Id, X-Device-Id"),r.setHeader("Access-Control-Allow-Credentials","true"))}(r,s),"OPTIONS"===r.method?void s.status(204).end():"GET"===r.method&&f===C?(s.setHeader("Content-Type","application/javascript"),s.setHeader("Cache-Control","public, max-age=3600"),void s.status(200).end(v)):"POST"===r.method&&f===g?void h(r,s):"POST"===r.method&&f===S?void m(r,s):"POST"===r.method&&f===y?void I(r,s):void s.status(404).json({success:!1,error:{code:"NOT_FOUND",message:"Unknown route"}});if((0,skip_paths_1.shouldSkipPath)(f,c))return void o();let A;try{A=i(r)}catch{}if(!A)return clearUserIdCookieIfPresent(r,s),clearEmailCookieIfPresent(r,s),interceptForInjection(r,s,p),void o();const T=resolveEmail(r,t);if(setUserIdCookie(r,s,A),T&&setEmailCookie(r,s,T),!T)return interceptForInjection(r,s,p),void o();const q=extractSessionId(r,d),x=(0,device_id_1.extractDeviceId)(r,a),P=extractFingerprintId(r),b=r.headers["user-agent"]??"",E=(0,client_ip_1.extractClientIp)(r);if((0,is_bot_1.isBot)(b))return void o();if("unknown"===q)return interceptForInjection(r,s,p),void o();_.isPaused()||dispatchUserEvent(e,l,_,{userId:A,emailAddress:T,sessionId:q,deviceId:x,fingerprintId:P,userAgent:b,ipAddress:E,eventType:`${r.method} ${r.path}`});const O=l.get(A);O?(l.isStale(A)&&!l.isRefreshing(A)&&(l.markRefreshing(A),fetchAndCacheVerdict(e,l,A,T,x,P,q).finally(()=>l.clearRefreshing(A))),applyVerdict(O,A,T,r,s,o,p,u)):fetchAndCacheVerdict(e,l,A,T,x,P,q).then(e=>{applyVerdict(e,A,T,r,s,o,p,u)}).catch(()=>{interceptForInjection(r,s,p),o()})}}function resolveEmail(e,r){if(r)try{const i=r(e);if(i)return i}catch{}const i=(0,cookies_1.parseCookie)(e,"__unshared_email");if(i)return i;const t=e.body?.email;return"string"==typeof t&&t?t:void 0}function applyVerdict(e,r,i,t,n,s,o,c){if(interceptForInjection(t,n,o),e.isFlagged&&!e.isVerified&&c)try{c({userId:r,emailAddress:i,verdict:e,req:t,res:n,next:s})}catch{s()}else s()}function interceptForInjection(e,r,i){delete e.headers["if-none-match"],delete e.headers["if-modified-since"],(0,response_interceptor_1.interceptResponse)(r,(e,r)=>{if(!(0,content_type_1.isHtmlContentType)(r))return null;const t=e.toString("utf8"),n=t.lastIndexOf("</body>");return-1===n?t+i:t.slice(0,n)+i+t.slice(n)},{preventCaching:!0})}function dispatchUserEvent(e,r,i,t){e.processUserEvent({eventType:t.eventType,userId:t.userId,emailAddress:t.emailAddress,ipAddress:t.ipAddress,deviceId:t.deviceId,fingerprintId:t.fingerprintId,sessionHash:t.sessionId,userAgent:t.userAgent}).then(e=>{e.success&&e.data?.analysis&&r.update(t.userId,{isFlagged:e.data.analysis.is_user_flagged}),!e.success&&e.error?.retryAfter&&i.pause(1e3*e.error.retryAfter)}).catch(()=>{})}async function fetchAndCacheVerdict(e,r,i,t,n,s,o){const c={};let d;n&&"unknown"!==n&&(c.deviceId=n),s&&(c.fingerprintId=s);const a=await Promise.race([e.checkUser(t,c),new Promise(e=>{d=setTimeout(()=>e(null),500)})]);if(clearTimeout(d),!a)return{isFlagged:!1,isVerified:!1,emailAddress:t,sessionId:o,cachedAt:0,ttl:0};const u=a.data?.is_user_flagged??!1;return r.set(i,{isFlagged:u,isVerified:!1,emailAddress:t,sessionId:o}),r.get(i)}function extractSessionId(e,r){if(r)try{const i=r(e);if(i)return i}catch{}return(0,cookies_1.parseCookie)(e,"__unshared_sid")??"unknown"}function extractFingerprintId(e){return(0,cookies_1.parseCookie)(e,"__unshared_fingerprint_id")||void 0}function appendSetCookie(e,r){const i=e.getHeader("Set-Cookie");if(i){const t=Array.isArray(i)?[...i]:[String(i)];t.push(r),e.setHeader("Set-Cookie",t)}else e.setHeader("Set-Cookie",r)}function setUserIdCookie(e,r,i){const t=(0,secure_1.isSecureRequest)(e)?"; Secure":"";appendSetCookie(r,`__unshared_uid=${encodeURIComponent(i)}; Path=/; SameSite=Lax${t}`)}function setEmailCookie(e,r,i){const t=(0,secure_1.isSecureRequest)(e)?"; Secure":"";appendSetCookie(r,`__unshared_email=${encodeURIComponent(i)}; HttpOnly; Path=/; SameSite=Lax${t}`)}function clearUserIdCookieIfPresent(e,r){(0,cookies_1.parseCookie)(e,"__unshared_uid")&&appendSetCookie(r,"__unshared_uid=; Path=/; SameSite=Lax; Max-Age=0"+((0,secure_1.isSecureRequest)(e)?"; Secure":""))}function clearEmailCookieIfPresent(e,r){(0,cookies_1.parseCookie)(e,"__unshared_email")&&appendSetCookie(r,"__unshared_email=; HttpOnly; Path=/; SameSite=Lax; Max-Age=0"+((0,secure_1.isSecureRequest)(e)?"; Secure":""))}
@@ -1,10 +1,16 @@
1
1
  /**
2
- * Generates a small inline loader script that:
3
- * 1. Loads the real fingerprint SDK from /__unshared/fp.js
4
- * 2. Collects a full fingerprint (31+ signals, MurmurHash3 Merkle tree)
5
- * 3. POSTs the result to /__unshared/submit-fp
2
+ * Generates an inline loader script that:
3
+ * 1. Loads the fingerprint SDK from /__unshared/fp.js
4
+ * 2. Collects a fingerprint and POSTs to /__unshared/submit-fp
5
+ * 3. Caches fingerprint in sessionStorage for reuse on SPA navigations
6
+ * 4. Patches History API to detect SPA route changes → re-submits fingerprint
7
+ * 5. Patches fetch/XHR to detect 403 account_flagged → dispatches "unshared:flagged" event
6
8
  *
7
9
  * The actual SDK UMD bundle is served by the middleware at /__unshared/fp.js.
8
- * This keeps the injected HTML small (~500 bytes) while using the full library.
10
+ *
11
+ * Event contract:
12
+ * window.addEventListener("unshared:flagged", (e) => {
13
+ * e.detail.email — the flagged user's email (from 403 response body)
14
+ * });
9
15
  */
10
16
  export declare function generateFingerprintScript(routePrefix: string, version?: string): string;
@@ -1 +1 @@
1
- "use strict";function generateFingerprintScript(e,n){const t=n?`?v=${escapeJavaScript(n)}`:"";return`<script>\n(function(){\ntry{\nvar pfx="${escapeJavaScript(e)}";\n\n// Session cookie helpers\nfunction getCookie(n){var m=document.cookie.match(new RegExp("(?:^|; )"+n+"=([^;]*)"));return m?decodeURIComponent(m[1]):null}\nfunction setCookie(n,v,d){var e="";if(d){var dt=new Date();dt.setTime(dt.getTime()+d*864e5);e="; expires="+dt.toUTCString()}document.cookie=n+"="+encodeURIComponent(v)+e+"; path=/; SameSite=Lax"}\n\n// UUID helper\nfunction uuid(){return(typeof crypto!=="undefined"&&crypto.randomUUID)?crypto.randomUUID():("xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(c){var r=Math.random()*16|0;return(c==="x"?r:r&0x3|0x8).toString(16)}))}\n\n// Ensure session ID cookie exists\nvar sid=getCookie("__unshared_sid");\nif(!sid){sid=uuid();setCookie("__unshared_sid",sid,365)}\n\n// Persistent device ID (survives across sessions via localStorage)\nvar did="";\ntry{did=localStorage.getItem("__unshared_device_id")||"";if(!did){did=uuid();localStorage.setItem("__unshared_device_id",did)}}catch(e){did=did||uuid()}\n\nvar uid=getCookie("__unshared_uid")||"";\n\n// Collect on every page load if a userId is present\nif(uid){\n var s=document.createElement("script");\n s.src=pfx+"/fp.js${t}";\n s.onload=function(){\n try{\n var client=new UnsharedLabsBrowser.UnsharedLabsBrowser({baseUrl:""});\n client.collect({exclude:["timing","navigatorConnection"]}).then(function(fp){\n var body={\n hash:fp.full_hash,\n stable_hash:fp.fingerprint_id,\n collected_at:fp.timestamp,\n is_incognito:fp.isIncognito,\n components:fp.components,\n version:fp.version,\n session_id:sid,\n user_id:uid\n };\n var xhr=new XMLHttpRequest();\n xhr.open("POST",pfx+"/submit-fp",true);\n xhr.setRequestHeader("Content-Type","application/json");\n xhr.setRequestHeader("X-Session-Id",sid);\n xhr.setRequestHeader("X-Device-Id",did);\n xhr.send(JSON.stringify(body));\n });\n }catch(e){}\n };\n document.head.appendChild(s);\n}\n}catch(e){}\n})();\n<\/script>`}function escapeJavaScript(e){return e.replace(/\\/g,"\\\\").replace(/"/g,'\\"').replace(/'/g,"\\'")}Object.defineProperty(exports,"t",{value:!0}),exports.generateFingerprintScript=generateFingerprintScript;
1
+ "use strict";function generateFingerprintScript(e,n){const t=n?`?v=${escapeJavaScript(n)}`:"";return`<script>\n(function(){\ntry{\nvar pfx="${escapeJavaScript(e)}";\nvar SS_FP="__unshared_fp";\n\n// --- Helpers ---\nfunction gC(n){var m=document.cookie.match(new RegExp("(?:^|; )"+n+"=([^;]*)"));return m?decodeURIComponent(m[1]):null}\nfunction sC(n,v,d){var e="";if(d){var dt=new Date();dt.setTime(dt.getTime()+d*864e5);e="; expires="+dt.toUTCString()}document.cookie=n+"="+encodeURIComponent(v)+e+"; path=/; SameSite=Lax"}\nfunction uuid(){return(typeof crypto!=="undefined"&&crypto.randomUUID)?crypto.randomUUID():("xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(c){var r=Math.random()*16|0;return(c==="x"?r:r&0x3|0x8).toString(16)}))}\n\n// --- Session + device IDs ---\nvar sid=gC("__unshared_sid");\nif(!sid){sid=uuid();sC("__unshared_sid",sid,365)}\nvar did="";\ntry{did=localStorage.getItem("__unshared_device_id")||"";if(!did){did=uuid();localStorage.setItem("__unshared_device_id",did)}}catch(e){did=did||uuid()}\nsC("__unshared_fp_id",did,365);\n\n// --- Fingerprint cache (sessionStorage) ---\nfunction getFP(){try{var r=sessionStorage.getItem(SS_FP);return r?JSON.parse(r):null}catch(e){return null}}\nfunction setFP(fp){try{sessionStorage.setItem(SS_FP,JSON.stringify(fp))}catch(e){}}\n\n// --- Submit fingerprint to backend ---\nfunction submitFP(fp,evType){\n var uid=gC("__unshared_uid");\n if(!uid)return;\n var body={hash:fp.full_hash,stable_hash:fp.fingerprint_id,collected_at:fp.timestamp,is_incognito:fp.isIncognito,components:fp.components,version:fp.version,session_id:sid,user_id:uid,event_type:evType||"page_load"};\n var xhr=new XMLHttpRequest();\n xhr.open("POST",pfx+"/submit-fp",true);\n xhr.setRequestHeader("Content-Type","application/json");\n xhr.setRequestHeader("X-Session-Id",sid);\n xhr.setRequestHeader("X-Device-Id",did);\n xhr.send(JSON.stringify(body));\n}\n\n// --- Collect fingerprint (loads fp.js if needed) then submit ---\nvar fpReady=false;\nfunction collectAndSubmit(evType){\n var uid=gC("__unshared_uid");\n if(!uid)return;\n var cached=getFP();\n if(cached){submitFP(cached,evType);return}\n if(!fpReady)return;\n try{\n var c=new UnsharedLabsBrowser.UnsharedLabsBrowser({baseUrl:""});\n c.collect({exclude:["timing","navigatorConnection"]}).then(function(fp){setFP(fp);submitFP(fp,evType)});\n }catch(e){}\n}\n\n// --- Load fp.js (always — browser caches it for 1h) ---\n// Submit cached FP immediately if available; load fp.js for fresh collection\nvar pageLoadSubmitted=false;\nif(getFP()&&gC("__unshared_uid")){submitFP(getFP(),"page_load");pageLoadSubmitted=true}\nvar s=document.createElement("script");\ns.src=pfx+"/fp.js${t}";\ns.onload=function(){fpReady=true;if(!pageLoadSubmitted)collectAndSubmit("page_load")};\ndocument.head.appendChild(s);\n\n// --- SPA route change tracking (History API + popstate) ---\nvar oPush=history.pushState,oReplace=history.replaceState;\nhistory.pushState=function(){oPush.apply(this,arguments);try{collectAndSubmit("route_change")}catch(e){}};\nhistory.replaceState=function(){oReplace.apply(this,arguments);try{collectAndSubmit("route_change")}catch(e){}};\nwindow.addEventListener("popstate",function(){try{collectAndSubmit("route_change")}catch(e){}});\n\n// --- 403 interception: dispatch "unshared:flagged" event ---\nfunction emitFlagged(body){\n try{window.dispatchEvent(new CustomEvent("unshared:flagged",{detail:{email:body.email||""}}))}catch(e){}\n}\n\n// Patch fetch\nvar oFetch=window.fetch;\nif(oFetch){window.fetch=function(){return oFetch.apply(this,arguments).then(function(r){if(r.status===403){try{var cl=r.clone();cl.json().then(function(b){if(b&&b.error==="account_flagged")emitFlagged(b)}).catch(function(){})}catch(e){}}return r})}}\n\n// Patch XMLHttpRequest\nvar oXSend=XMLHttpRequest.prototype.send;\nXMLHttpRequest.prototype.send=function(){var x=this;x.addEventListener("load",function(){if(x.status===403){try{var b=JSON.parse(x.responseText);if(b&&b.error==="account_flagged")emitFlagged(b)}catch(e){}}});return oXSend.apply(this,arguments)};\n\n}catch(e){}\n})();\n<\/script>`}function escapeJavaScript(e){return e.replace(/\\/g,"\\\\").replace(/"/g,'\\"').replace(/'/g,"\\'")}Object.defineProperty(exports,"t",{value:!0}),exports.generateFingerprintScript=generateFingerprintScript;
@@ -2,12 +2,14 @@ import type { Response } from 'express';
2
2
  /**
3
3
  * Intercepts the response body by wrapping res.write() and res.end().
4
4
  *
5
- * Collects all chunks written to the response. When res.end() is called,
6
- * invokes the `transform` callback with the complete body buffer and the
7
- * Content-Type header. The transform can return modified content or null
8
- * to pass through unchanged.
5
+ * Only buffers HTML responses (text/html). Non-HTML responses (JSON, images,
6
+ * CSS, JS, etc.) pass through to the original write/end without buffering,
7
+ * avoiding unnecessary memory usage on large payloads.
9
8
  *
10
- * Does NOT monkey-patch res.send uses the lower-level write/end API
11
- * as required by the spec.
9
+ * When res.end() is called on an HTML response, invokes the `transform`
10
+ * callback with the complete body buffer and the Content-Type header.
11
+ * The transform can return modified content or null to pass through unchanged.
12
12
  */
13
- export declare function interceptResponse(res: Response, transform: (body: Buffer, contentType: string | undefined) => Buffer | string | null): void;
13
+ export declare function interceptResponse(res: Response, transform: (body: Buffer, contentType: string | undefined) => Buffer | string | null, options?: {
14
+ preventCaching?: boolean;
15
+ }): void;
@@ -1 +1 @@
1
- "use strict";function interceptResponse(t,n){const e=[],f=t.write.bind(t),u=t.end.bind(t);let o=!1;t.write=function(t,n,f){if(null!=t){const f=Buffer.isBuffer(t)?t:Buffer.from(t,"string"==typeof n?n:"utf8");e.push(f)}return"function"==typeof n&&n(null),"function"==typeof f&&f(null),!0},t.end=function(r,c,s){if(o)return t;if(o=!0,null!=r){const t=Buffer.isBuffer(r)?r:Buffer.from(r,"string"==typeof c?c:"utf8");e.push(t)}const l=Buffer.concat(e),i=t.getHeader("content-type");let p;try{p=n(l,i)}catch{p=null}if(null!=p){const n=Buffer.isBuffer(p)?p:Buffer.from(p,"utf8");t.setHeader("Content-Length",n.length),t.removeHeader("Content-Encoding"),f(n)}else l.length>0&&f(l);const y="function"==typeof c?c:s;return y?u(y):u(),t}}Object.defineProperty(exports,"t",{value:!0}),exports.interceptResponse=interceptResponse;
1
+ "use strict";function interceptResponse(t,n,e){const f=e?.preventCaching??!1,o=t.write.bind(t),u=t.end.bind(t),r=t.writeHead.bind(t),c=[];let i=!1,l=null;function s(){if(null!==l)return;const n=t.getHeader("content-type");null!=n&&(l=String(n).includes("text/html"),l||function(){t.write=o,t.end=u;for(const t of c)o(t);c.length=0}())}t.writeHead=function(n,...e){return s(),f&&l&&(t.setHeader("Cache-Control","no-store"),t.removeHeader("ETag"),t.removeHeader("Last-Modified")),r(n,...e)},t.write=function(t,n,e){if(s(),!1===l)return o(t,n,e);if(null!=t){const e=Buffer.isBuffer(t)?t:Buffer.from(t,"string"==typeof n?n:"utf8");c.push(e)}return"function"==typeof n&&n(null),"function"==typeof e&&e(null),!0},t.end=function(e,f,r){if(i)return t;if(i=!0,s(),!1===l)return u(e,f,r);if(null!=e){const t=Buffer.isBuffer(e)?e:Buffer.from(e,"string"==typeof f?f:"utf8");c.push(t)}const p=Buffer.concat(c),y=t.getHeader("content-type");let B;try{B=n(p,y)}catch{B=null}if(null!=B){const n=Buffer.isBuffer(B)?B:Buffer.from(B,"utf8");t.setHeader("Content-Length",n.length),t.removeHeader("Content-Encoding"),o(n)}else p.length>0&&o(p);const g="function"==typeof f?f:r;return g?u(g):u(),t}}Object.defineProperty(exports,"t",{value:!0}),exports.interceptResponse=interceptResponse;
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"t",{value:!0}),exports.handleSubmitFingerprint=handleSubmitFingerprint;const is_bot_1=require("../utils/is-bot"),client_ip_1=require("../utils/client-ip");function handleSubmitFingerprint(e){return async(t,i)=>{try{const n=t.body??{},s={full_hash:n.hash??"",fingerprint_id:n.stable_hash??"",timestamp:n.collected_at??(new Date).toISOString(),isIncognito:n.is_incognito??!1,components:n.components??{},version:n.version??"inline-1.0.0"};let o,r,c;try{o=e.resolveUserId?e.resolveUserId(t):void 0}catch{}o=o??n.user_id??void 0;try{r=e.resolveEmailAddress?e.resolveEmailAddress(t):void 0}catch{}r=r??parseCookie(t,"__unshared_email")??n.email??void 0;try{c=e.resolveSessionId?e.resolveSessionId(t):void 0}catch{}c=c??n.session_id??parseCookie(t,"__unshared_sid");const a=(0,client_ip_1.extractClientIp)(t),d=t.headers["user-agent"]??"";if((0,is_bot_1.isBot)(d))return void i.status(200).json({success:!0});const u=extractDeviceId(t,e.resolveDeviceId),_=s.fingerprint_id||void 0,p=[];if(_&&!parseCookie(t,"__unshared_fingerprint_id")&&p.push(`__unshared_fingerprint_id=${encodeURIComponent(_)}; HttpOnly; Path=/; SameSite=Lax`),r&&!parseCookie(t,"__unshared_email")&&p.push(`__unshared_email=${encodeURIComponent(r)}; HttpOnly; Path=/; SameSite=Lax`),p.length>0){const e=i.getHeader("Set-Cookie");if(e){const t=Array.isArray(e)?[...e]:[String(e)];t.push(...p),i.setHeader("Set-Cookie",t)}else i.setHeader("Set-Cookie",p)}o&&e.client.submitFingerprintEvent(s,{userId:o,emailAddress:r,sessionHash:c,eventType:"auto_collect",ipAddress:a,userAgent:d}).catch(()=>{}),o&&r&&!e.rateLimitBackoff.isPaused()&&e.client.processUserEvent({eventType:"auto_collect",userId:o,emailAddress:r,ipAddress:a,deviceId:u,fingerprintId:_,sessionHash:c??"unknown",userAgent:d}).then(t=>{t.success&&t.data?.analysis&&e.verdictCache.update(o,{isFlagged:t.data.analysis.is_user_flagged}),!t.success&&t.error?.retryAfter&&e.rateLimitBackoff.pause(1e3*t.error.retryAfter)}).catch(()=>{}),i.status(200).json({success:!0})}catch{i.status(200).json({success:!0})}}}function extractDeviceId(e,t){if(t)try{const i=t(e);if(i)return i}catch{}const i=parseCookie(e,"__unshared_fp_id");if(i)return i;const n=e.headers["x-device-id"];return"string"==typeof n&&n?n:"unknown"}function parseCookie(e,t){const i=e.headers.cookie;if(!i)return;const n=i.match(new RegExp(`(?:^|; )${t}=([^;]*)`));return n?decodeURIComponent(n[1]):void 0}
1
+ "use strict";Object.defineProperty(exports,"i",{value:!0}),exports.handleSubmitFingerprint=handleSubmitFingerprint;const is_bot_1=require("../utils/is-bot"),client_ip_1=require("../utils/client-ip"),cookies_1=require("../utils/cookies"),device_id_1=require("../utils/device-id"),secure_1=require("../utils/secure");function handleSubmitFingerprint(e){return async(i,s)=>{try{const t=i.body??{},n={full_hash:t.hash??"",fingerprint_id:t.stable_hash??"",timestamp:t.collected_at??(new Date).toISOString(),isIncognito:t.is_incognito??!1,components:t.components??{},version:t.version??"inline-1.0.0"};let o,r,c;try{o=e.resolveUserId?e.resolveUserId(i):void 0}catch{}o=o??t.user_id??void 0;try{r=e.resolveEmailAddress?e.resolveEmailAddress(i):void 0}catch{}r=r??(0,cookies_1.parseCookie)(i,"__unshared_email")??t.email??void 0;try{c=e.resolveSessionId?e.resolveSessionId(i):void 0}catch{}c=c??t.session_id??(0,cookies_1.parseCookie)(i,"__unshared_sid");const _=(0,client_ip_1.extractClientIp)(i),d=i.headers["user-agent"]??"";if((0,is_bot_1.isBot)(d))return void s.status(200).json({success:!0});const u=(0,device_id_1.extractDeviceId)(i,e.resolveDeviceId),a=n.fingerprint_id||void 0,l=(0,secure_1.isSecureRequest)(i)?"; Secure":"",p=[];if(a&&!(0,cookies_1.parseCookie)(i,"__unshared_fingerprint_id")&&p.push(`__unshared_fingerprint_id=${encodeURIComponent(a)}; HttpOnly; Path=/; SameSite=Lax${l}`),r&&!(0,cookies_1.parseCookie)(i,"__unshared_email")&&p.push(`__unshared_email=${encodeURIComponent(r)}; HttpOnly; Path=/; SameSite=Lax${l}`),p.length>0){const e=s.getHeader("Set-Cookie");if(e){const i=Array.isArray(e)?[...e]:[String(e)];i.push(...p),s.setHeader("Set-Cookie",i)}else s.setHeader("Set-Cookie",p)}o&&e.client.submitFingerprintEvent(n,{userId:o,emailAddress:r,sessionHash:c,eventType:"auto_collect",ipAddress:_,userAgent:d}).catch(()=>{}),o&&r&&!e.rateLimitBackoff.isPaused()&&e.client.processUserEvent({eventType:"auto_collect",userId:o,emailAddress:r,ipAddress:_,deviceId:u,fingerprintId:a,sessionHash:c??"unknown",userAgent:d}).then(i=>{i.success&&i.data?.analysis&&e.verdictCache.update(o,{isFlagged:i.data.analysis.is_user_flagged}),!i.success&&i.error?.retryAfter&&e.rateLimitBackoff.pause(1e3*i.error.retryAfter)}).catch(()=>{}),s.status(200).json({success:!0})}catch{s.status(200).json({success:!0})}}}
@@ -1 +1 @@
1
- "use strict";function handleVerifyTrigger(e){return async(r,i)=>{try{const s=resolveEmail(r,r.body??{},e.resolveEmailAddress);if(!s)return void i.status(400).json({success:!1,error:{code:"VALIDATION_ERROR",message:"Email is required"}});const t=extractDeviceId(r,e.resolveDeviceId),n=parseCookie(r,"__unshared_fingerprint_id")||void 0,o=await e.client.triggerEmailVerification(s,t,{fingerprintId:n});o.success?i.status(200).json({success:!0,data:o.data}):i.status(200).json({success:!1,error:o.error??{code:"TRIGGER_FAILED",message:"Failed to send verification email"}})}catch{i.status(200).json({success:!1,error:{code:"INTERNAL_ERROR",message:"Failed to trigger verification"}})}}}function handleVerify(e){return async(r,i)=>{try{const s=r.body??{},t=resolveEmail(r,s,e.resolveEmailAddress),n=s.code;if(!t||!n)return void i.status(400).json({success:!1,error:{code:"VALIDATION_ERROR",message:"Email and code are required"}});const o=extractDeviceId(r,e.resolveDeviceId),c=parseCookie(r,"__unshared_fingerprint_id")||void 0,a=await e.client.verify(t,o,n,{fingerprintId:c});if(a.success){const s=parseCookie(r,"__unshared_uid");s&&e.verdictCache.update(s,{isVerified:!0}),i.status(200).json({success:!0,data:{verified:!0}})}else i.status(200).json({success:!1,error:a.error??{code:"VERIFICATION_FAILED",message:"Verification failed"}})}catch{i.status(200).json({success:!1,error:{code:"INTERNAL_ERROR",message:"Verification failed"}})}}}function resolveEmail(e,r,i){if(i)try{const r=i(e);if(r)return r}catch{}const s=parseCookie(e,"__unshared_email");if(s)return s;const t=r.email;return"string"==typeof t&&t?t:void 0}function extractDeviceId(e,r){if(r)try{const i=r(e);if(i)return i}catch{}const i=parseCookie(e,"__unshared_fp_id");if(i)return i;const s=e.headers["x-device-id"];return"string"==typeof s&&s?s:"unknown"}function parseCookie(e,r){const i=e.headers.cookie;if(!i)return;const s=i.match(new RegExp(`(?:^|; )${r}=([^;]*)`));return s?decodeURIComponent(s[1]):void 0}Object.defineProperty(exports,"i",{value:!0}),exports.handleVerifyTrigger=handleVerifyTrigger,exports.handleVerify=handleVerify;
1
+ "use strict";Object.defineProperty(exports,"i",{value:!0}),exports.handleVerifyTrigger=handleVerifyTrigger,exports.handleVerify=handleVerify;const cookies_1=require("../utils/cookies"),device_id_1=require("../utils/device-id");function handleVerifyTrigger(e){return async(i,r)=>{try{const s=resolveEmail(i,i.body??{},e.resolveEmailAddress);if(!s)return void r.status(400).json({success:!1,error:{code:"VALIDATION_ERROR",message:"Email is required"}});const c=(0,device_id_1.extractDeviceId)(i,e.resolveDeviceId),o=(0,cookies_1.parseCookie)(i,"__unshared_fingerprint_id")||void 0,t=await e.client.triggerEmailVerification(s,c,{fingerprintId:o});t.success?r.status(200).json({success:!0,data:t.data}):r.status(200).json({success:!1,error:t.error??{code:"TRIGGER_FAILED",message:"Failed to send verification email"}})}catch{r.status(200).json({success:!1,error:{code:"INTERNAL_ERROR",message:"Failed to trigger verification"}})}}}function handleVerify(e){return async(i,r)=>{try{const s=i.body??{},c=resolveEmail(i,s,e.resolveEmailAddress),o=s.code;if(!c||!o)return void r.status(400).json({success:!1,error:{code:"VALIDATION_ERROR",message:"Email and code are required"}});const t=(0,device_id_1.extractDeviceId)(i,e.resolveDeviceId),n=(0,cookies_1.parseCookie)(i,"__unshared_fingerprint_id")||void 0,d=await e.client.verify(c,t,o,{fingerprintId:n});if(d.success){const s=(0,cookies_1.parseCookie)(i,"__unshared_uid");s&&e.verdictCache.update(s,{isVerified:!0}),r.status(200).json({success:!0,data:{verified:!0}})}else r.status(200).json({success:!1,error:d.error??{code:"VERIFICATION_FAILED",message:"Verification failed"}})}catch{r.status(200).json({success:!1,error:{code:"INTERNAL_ERROR",message:"Verification failed"}})}}}function resolveEmail(e,i,r){if(r)try{const i=r(e);if(i)return i}catch{}const s=(0,cookies_1.parseCookie)(e,"__unshared_email");if(s)return s;const c=i.email;return"string"==typeof c&&c?c:void 0}
@@ -0,0 +1,6 @@
1
+ import type { Request } from 'express';
2
+ /**
3
+ * Reads a single cookie value from the raw Cookie header.
4
+ * Works without cookie-parser middleware.
5
+ */
6
+ export declare function parseCookie(req: Request, name: string): string | undefined;
@@ -0,0 +1 @@
1
+ "use strict";function parseCookie(e,o){const t=e.headers.cookie;if(!t)return;const n=t.match(new RegExp(`(?:^|; )${o}=([^;]*)`));return n?decodeURIComponent(n[1]):void 0}Object.defineProperty(exports,"o",{value:!0}),exports.parseCookie=parseCookie;
@@ -0,0 +1,5 @@
1
+ import type { Request } from 'express';
2
+ /**
3
+ * Resolves device ID from: custom resolver → __unshared_fp_id cookie → X-Device-Id header.
4
+ */
5
+ export declare function extractDeviceId(req: Request, resolveDeviceId?: (req: Request) => string | undefined): string;
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,"t",{value:!0}),exports.extractDeviceId=extractDeviceId;const cookies_1=require("./cookies");function extractDeviceId(e,t){if(t)try{const c=t(e);if(c)return c}catch{}const c=(0,cookies_1.parseCookie)(e,"__unshared_fp_id");if(c)return c;const o=e.headers["x-device-id"];return"string"==typeof o&&o?o:"unknown"}
@@ -0,0 +1,3 @@
1
+ import type { Request } from 'express';
2
+ /** Returns true if the request arrived over HTTPS (direct or via reverse proxy). */
3
+ export declare function isSecureRequest(req: Request): boolean;
@@ -0,0 +1 @@
1
+ "use strict";function isSecureRequest(e){return e.secure||"https"===e.headers["x-forwarded-proto"]}Object.defineProperty(exports,"t",{value:!0}),exports.isSecureRequest=isSecureRequest;
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"t",{value:!0}),exports.shouldSkipPath=shouldSkipPath;const STATIC_EXTENSIONS=new Set([".js",".mjs",".cjs",".css",".map",".png",".jpg",".jpeg",".gif",".svg",".ico",".webp",".avif",".woff",".woff2",".ttf",".otf",".eot",".mp3",".mp4",".webm",".ogg",".wasm",".json",".xml",".txt",".pdf"]),STATIC_PATH_PREFIXES=["/static/","/assets/","/public/","/_next/","/__vite/","/favicon"];function shouldSkipPath(t,s){if(s)for(const o of s)if(t.startsWith(o))return!0;const o=t.lastIndexOf(".");if(-1!==o){const s=t.slice(o).toLowerCase().split("?")[0];if(STATIC_EXTENSIONS.has(s))return!0}for(const s of STATIC_PATH_PREFIXES)if(t.startsWith(s))return!0;return!1}
1
+ "use strict";Object.defineProperty(exports,"t",{value:!0}),exports.shouldSkipPath=shouldSkipPath;const STATIC_EXTENSIONS=new Set([".js",".mjs",".cjs",".css",".map",".png",".jpg",".jpeg",".gif",".svg",".ico",".webp",".avif",".woff",".woff2",".ttf",".otf",".eot",".mp3",".mp4",".webm",".ogg",".wasm",".xml",".txt",".pdf"]),STATIC_PATH_PREFIXES=["/static/","/assets/","/public/","/_next/","/__vite/","/favicon"];function shouldSkipPath(t,s){if(s)for(const o of s)if(t.startsWith(o))return!0;const o=t.lastIndexOf(".");if(-1!==o){const s=t.slice(o).toLowerCase().split("?")[0];if(STATIC_EXTENSIONS.has(s))return!0}for(const s of STATIC_PATH_PREFIXES)if(t.startsWith(s))return!0;return!1}
@@ -10,7 +10,9 @@ export declare class VerdictCache {
10
10
  private readonly _entries;
11
11
  private readonly _activeRefreshes;
12
12
  private readonly _defaultTtlMs;
13
- constructor(defaultTTL?: number);
13
+ private readonly _maxSize;
14
+ private _sweepTimer;
15
+ constructor(defaultTTL?: number, maxSize?: number);
14
16
  get(userId: string): Verdict | undefined;
15
17
  set(userId: string, verdict: Omit<Verdict, 'cachedAt' | 'ttl'>, ttl?: number): void;
16
18
  /**
@@ -33,4 +35,13 @@ export declare class VerdictCache {
33
35
  /** Number of cached entries. */
34
36
  get size(): number;
35
37
  clear(): void;
38
+ /**
39
+ * Stops the periodic sweep timer. Call this when shutting down
40
+ * or when the middleware is no longer needed (e.g., in tests).
41
+ */
42
+ destroy(): void;
43
+ /** Remove all entries that are past their TTL + a 2x grace period. */
44
+ private _sweep;
45
+ /** Evict the oldest entry by cachedAt to make room. */
46
+ private _evictOldest;
36
47
  }
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"t",{value:!0}),exports.VerdictCache=void 0;class VerdictCache{constructor(t=6e4){this.i=new Map,this.h=new Set,this.o=t}get(t){return this.i.get(t)}set(t,e,s){this.i.set(t,{...e,cachedAt:Date.now(),ttl:s??this.o})}update(t,e){const s=this.i.get(t);s?(void 0!==e.isFlagged&&(s.isFlagged=e.isFlagged),void 0!==e.isVerified&&(s.isVerified=e.isVerified),s.cachedAt=Date.now()):this.i.set(t,{isFlagged:e.isFlagged??!1,isVerified:e.isVerified??!1,emailAddress:"",sessionId:"",cachedAt:Date.now(),ttl:this.o})}delete(t){this.i.delete(t),this.h.delete(t)}isStale(t){const e=this.i.get(t);return!!e&&Date.now()-e.cachedAt>e.ttl}isRefreshing(t){return this.h.has(t)}markRefreshing(t){this.h.add(t)}clearRefreshing(t){this.h.delete(t)}get size(){return this.i.size}clear(){this.i.clear(),this.h.clear()}}exports.VerdictCache=VerdictCache;
1
+ "use strict";Object.defineProperty(exports,"t",{value:!0}),exports.VerdictCache=void 0;const DEFAULT_TTL_MS=6e4,DEFAULT_MAX_SIZE=1e4,SWEEP_INTERVAL_MS=3e5;class VerdictCache{constructor(t=6e4,s=1e4){this.i=new Map,this.h=new Set,this.o=null,this.l=t,this.u=s,this.o=setInterval(()=>this._(),3e5),this.o&&"function"==typeof this.o.unref&&this.o.unref()}get(t){return this.i.get(t)}set(t,s,e){!this.i.has(t)&&this.i.size>=this.u&&this.p(),this.i.set(t,{...s,cachedAt:Date.now(),ttl:e??this.l})}update(t,s){const e=this.i.get(t);e?(void 0!==s.isFlagged&&(e.isFlagged=s.isFlagged),void 0!==s.isVerified&&(e.isVerified=s.isVerified),e.cachedAt=Date.now()):(this.i.size>=this.u&&this.p(),this.i.set(t,{isFlagged:s.isFlagged??!1,isVerified:s.isVerified??!1,emailAddress:"",sessionId:"",cachedAt:Date.now(),ttl:this.l}))}delete(t){this.i.delete(t),this.h.delete(t)}isStale(t){const s=this.i.get(t);return!!s&&Date.now()-s.cachedAt>s.ttl}isRefreshing(t){return this.h.has(t)}markRefreshing(t){this.h.add(t)}clearRefreshing(t){this.h.delete(t)}get size(){return this.i.size}clear(){this.i.clear(),this.h.clear()}destroy(){this.o&&(clearInterval(this.o),this.o=null),this.clear()}_(){const t=Date.now();for(const[s,e]of this.i)t-e.cachedAt>2*e.ttl&&(this.i.delete(s),this.h.delete(s))}p(){let t=null,s=1/0;for(const[e,i]of this.i)i.cachedAt<s&&(s=i.cachedAt,t=e);t&&(this.i.delete(t),this.h.delete(t))}}exports.VerdictCache=VerdictCache;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "unshared-clientjs-sdk",
3
- "version": "2.0.0-rc.13",
3
+ "version": "2.0.0-rc.15",
4
4
  "description": "Server-side Node.js SDK for the Unshared Labs V2 API",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/esm/index.mjs",