unshared-clientjs-sdk 3.0.0-rc.14 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/README.md +187 -15
  2. package/dist/esm/middleware/dispatch-processing.d.mts +5 -2
  3. package/dist/esm/middleware/dispatch-processing.mjs +1 -1
  4. package/dist/esm/middleware/index.d.mts +3 -1
  5. package/dist/esm/middleware/index.mjs +1 -1
  6. package/dist/esm/middleware/injection/fingerprint-script.d.mts +2 -0
  7. package/dist/esm/middleware/injection/fingerprint-script.mjs +1 -1
  8. package/dist/esm/middleware/routes/submit-fp.mjs +1 -1
  9. package/dist/esm/middleware/routes/verify.d.mts +2 -1
  10. package/dist/esm/middleware/routes/verify.mjs +1 -1
  11. package/dist/esm/middleware/utils/device-id.d.mts +4 -2
  12. package/dist/esm/middleware/utils/device-id.mjs +1 -1
  13. package/dist/esm/web/protection-handler.d.mts +1 -1
  14. package/dist/esm/web/protection-handler.mjs +1 -1
  15. package/dist/esm/web/types.d.mts +4 -2
  16. package/dist/esm/web/web-helpers.d.mts +2 -1
  17. package/dist/esm/web/web-helpers.mjs +1 -1
  18. package/dist/middleware/dispatch-processing.d.ts +5 -2
  19. package/dist/middleware/dispatch-processing.js +1 -1
  20. package/dist/middleware/index.d.ts +3 -1
  21. package/dist/middleware/index.js +1 -1
  22. package/dist/middleware/injection/fingerprint-script.d.ts +2 -0
  23. package/dist/middleware/injection/fingerprint-script.js +1 -1
  24. package/dist/middleware/routes/submit-fp.js +1 -1
  25. package/dist/middleware/routes/verify.d.ts +2 -1
  26. package/dist/middleware/routes/verify.js +1 -1
  27. package/dist/middleware/utils/device-id.d.ts +4 -2
  28. package/dist/middleware/utils/device-id.js +1 -1
  29. package/dist/web/protection-handler.d.ts +1 -1
  30. package/dist/web/protection-handler.js +1 -1
  31. package/dist/web/types.d.ts +4 -2
  32. package/dist/web/web-helpers.d.ts +2 -1
  33. package/dist/web/web-helpers.js +1 -1
  34. package/package.json +2 -2
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"t",{value:!0}),exports.__test_injectIntoHtmlResponse=exports.__test_fetchAndCacheVerdict=void 0,exports.createWebProtectionMiddleware=createWebProtectionMiddleware,exports.__test_resetInjectWarning=__test_resetInjectWarning;const webcrypto_1=require("../webcrypto"),verdict_cache_1=require("../middleware/verdict-cache"),rate_limit_backoff_1=require("../middleware/rate-limit-backoff"),dispatch_dedupe_1=require("../middleware/dispatch-dedupe"),fingerprint_script_1=require("../middleware/injection/fingerprint-script"),gate_page_1=require("../middleware/injection/gate-page"),flagged_response_1=require("../middleware/utils/flagged-response"),mask_email_1=require("../middleware/utils/mask-email"),content_type_1=require("../middleware/utils/content-type"),skip_paths_1=require("../middleware/utils/skip-paths"),include_path_1=require("../middleware/utils/include-path"),is_bot_1=require("../middleware/utils/is-bot"),sentinel_user_id_1=require("../middleware/utils/sentinel-user-id"),dispatch_processing_1=require("../middleware/dispatch-processing"),web_helpers_1=require("./web-helpers"),CHECK_USER_TIMEOUT_MS=1500,FAIL_OPEN_TTL_MS=5e3;function createWebProtectionMiddleware(e,t){if(!t.userId)throw new Error("[Unshared] userId resolver is required");const{userId:r,emailAddress:s,routePrefix:n="/__unshared",corsOrigins:i,cacheTTL:a=6e4,skipPaths:o,includePathPrefix:d,sessionId:c,deviceId:_,fingerprintSdkBundle:l="",onFlagged:u,onError:p,onFailOpen:h,disableBotFilter:f=!1,checkUserTimeoutMs:m=CHECK_USER_TIMEOUT_MS,blockFlagged:g=!1,flaggedMode:w,autoInterstitial:b=!1,interstitialFlowType:v="email_verification"}=t,y=w??(g?"gate":void 0),I=b||"overlay"===y,x=e=>{if(h)try{h(e)}catch{}},S=Date.now().toString(36),A=void 0!==y||I,R="gate"===y?"flaggedMode 'gate' (blockFlagged)":"overlay"===y?"flaggedMode 'overlay'":"autoInterstitial";if(A&&!l)throw new Error(`[Unshared] ${R} requires fingerprintSdkBundle (the browser SDK UMD served at {routePrefix}/fp.js renders the interstitial).`);if(A&&"/__unshared"!==n)throw new Error(`[Unshared] ${R} requires the default routePrefix ("/__unshared"); the browser SDK proxy routes are fixed to that prefix.`);const E="gate"===y?(0,gate_page_1.generateGatePage)(n,S):"",T=new verdict_cache_1.VerdictCache(a),k=new Map,C=new rate_limit_backoff_1.RateLimitBackoff,P=new dispatch_dedupe_1.DispatchDedupe,U=(0,fingerprint_script_1.generateFingerprintScript)(n,S,{autoInterstitial:I,interstitialFlowType:v}),$={emitted:!1},j=`${n}/fp.js`,D=`${n}/submit-fp`,O=`${n}/verify-trigger`,M=`${n}/verify`,F=`${n}/status`,q=i?Array.isArray(i)?i:[i]:null;return async function(t,i,a){const h=e=>{a?.waitUntil&&a.waitUntil(e)};let g,w;try{const e=new URL(t.url);g=e.pathname,w=e.search}catch{return i(t)}if(g.startsWith(n+"/")){const n=function(e){if(!q)return{};const t=e.headers.get("origin")??"",r=q.includes("*");if(!r&&!q.includes(t))return{};const s={"Access-Control-Allow-Origin":r?"*":t,"Access-Control-Allow-Methods":"POST, OPTIONS","Access-Control-Allow-Headers":"Content-Type, X-Idempotency-Key, X-Session-Id, X-Device-Id, X-Permanent-Device-Id","Access-Control-Expose-Headers":"X-Unshared-Enforcement-Reconciled"};return r||(s["Access-Control-Allow-Credentials"]="true"),s}(t);if("OPTIONS"===t.method)return(0,web_helpers_1.emptyResponse)(204,n);if(("GET"===t.method||"HEAD"===t.method)&&g===j){if(!l)return(0,web_helpers_1.jsonResponse)(404,{success:!1,error:{code:"NOT_FOUND",message:"Fingerprint SDK bundle not configured. Pass fingerprintSdkBundle in config."}},n);const e={...n,"Content-Type":"application/javascript","Cache-Control":"public, max-age=3600","Content-Length":String((new TextEncoder).encode(l).byteLength)};return"HEAD"===t.method?new Response(null,{status:200,headers:new Headers(e)}):(0,web_helpers_1.bodyResponse)(200,l,e)}if("POST"===t.method&&(g===D||g===O||g===M)){const i=await(0,web_helpers_1.readJsonRequest)(t);if(!i.success)return(0,web_helpers_1.jsonResponse)(i.status,{success:!1,error:i.error},n);const a=i.data;return g===D?handleSubmitFp(t,a,{client:e,verdictCache:T,rateLimitBackoff:C,dispatchDedupe:P,resolveUserId:r,resolveEmailAddress:s,resolveSessionId:c,resolveDeviceId:_,disableBotFilter:f,onError:p,registerBackground:h},n):g===O?handleVerifyTriggerWeb(t,a,{client:e,verdictCache:T,resolveUserId:r,resolveEmailAddress:s,resolveDeviceId:_,onError:p},n):handleVerifyWeb(t,a,{client:e,verdictCache:T,resolveUserId:r,resolveEmailAddress:s,resolveDeviceId:_,onError:p},n)}if("GET"===t.method&&g===F){let i;try{i=r(t)}catch{}if(!i)return(0,web_helpers_1.jsonResponse)(200,{status:"anonymous"},n);const a=resolveEmail(t,s);let o=T.get(i);if((!o||T.isStale(i))&&a&&!C.isPaused()&&!T.isRefreshing(i)){T.markRefreshing(i);try{const r=(0,web_helpers_1.extractDeviceIdFromRequest)(t,_),s=(0,web_helpers_1.parseCookieFromRequest)(t,"__unshared_fingerprint_id")||void 0,n=extractPermanentDeviceIdFromRequest(t),d=extractSessionIdFromRequest(t,c),l=r??s??"unknown";await fetchAndCacheVerdict(e,T,i,a,l,s,n,d,m,(e,t)=>x({operation:"checkUser",reason:e,status:t,userId:i,emailAddress:a})),o=T.get(i)}catch(e){p&&p(e,{operation:"checkUser",userId:i,emailAddress:a}),x({operation:"checkUser",reason:"exception",userId:i,emailAddress:a})}finally{T.clearRefreshing(i)}}return o&&o.isFlagged&&!o.isVerified&&(void 0!==y||u)&&a?(0,web_helpers_1.jsonResponse)(200,{status:"flagged",email:(0,mask_email_1.maskEmail)(a)},n):(0,web_helpers_1.jsonResponse)(200,{status:"ok"},n)}return(0,web_helpers_1.jsonResponse)(404,{success:!1,error:{code:"NOT_FOUND",message:"Unknown route"}},n)}const b=void 0!==y;if((0,skip_paths_1.shouldSkipPath)(g,o,b))return i(t);if(!(0,include_path_1.shouldIncludePath)(g,d))return injectIntoHtmlResponse(await i(t),U,void 0,$);let v;try{v=r(t)}catch{}if((0,sentinel_user_id_1.isSentinelUserId)(v)){const e=(0,web_helpers_1.parseCookieFromRequest)(t,"__unshared_uid"),r=(0,web_helpers_1.parseCookieFromRequest)(t,"__unshared_uid_at"),s=r?Number(r):NaN,n=Number.isFinite(s)&&Date.now()-s<=sentinel_user_id_1.SENTINEL_STICKINESS_TTL_MS;v=e&&n?e:void 0}if(!v){const e=(0,web_helpers_1.isSecureWebRequest)(t)?"; Secure":"",r=[`__unshared_uid=; Path=/; SameSite=Lax; Max-Age=0${e}`,`__unshared_uid_at=; Path=/; SameSite=Lax; Max-Age=0${e}`,`__unshared_sid=; Path=/; SameSite=Lax; Max-Age=0${e}`,`__unshared_email=; Path=/; SameSite=Lax; Max-Age=0${e}`,`__unshared_verification_id=; HttpOnly; Path=/; SameSite=Lax; Max-Age=0${e}`];return injectIntoHtmlResponse(await i(t),U,r,$)}const I=resolveEmail(t,s),S=[],A=(0,web_helpers_1.isSecureWebRequest)(t)?"; Secure":"";if(S.push(`__unshared_uid=${encodeURIComponent(v)}; Path=/; SameSite=Lax; Max-Age=31536000${A}`),S.push(`__unshared_uid_at=${Date.now()}; Path=/; SameSite=Lax; Max-Age=31536000${A}`),I&&S.push(`__unshared_email=${encodeURIComponent(I)}; HttpOnly; Path=/; SameSite=Lax; Max-Age=31536000${A}`),!I)return injectIntoHtmlResponse(await i(t),U,S,$);const R=extractSessionIdFromRequest(t,c),L=(0,web_helpers_1.extractDeviceIdFromRequest)(t,_),H=(0,web_helpers_1.parseCookieFromRequest)(t,"__unshared_fingerprint_id")||void 0,N=extractPermanentDeviceIdFromRequest(t),V=t.headers.get("user-agent")??"",W=(0,web_helpers_1.extractClientIpFromRequest)(t),B=L??H;if(!f&&(0,is_bot_1.isBot)(V))return i(t);let K=T.get(v);if(K)T.isStale(v)&&!T.isRefreshing(v)&&(T.markRefreshing(v),h(fetchAndCacheVerdict(e,T,v,I,B??"unknown",H,N,R,m,(e,t)=>x({operation:"checkUser",reason:e,status:t,userId:v,emailAddress:I})).catch(()=>x({operation:"checkUser",reason:"exception",userId:v,emailAddress:I})).finally(()=>T.clearRefreshing(v))));else try{let t=k.get(v);t||(t=fetchAndCacheVerdict(e,T,v,I,B??"unknown",H,N,R,m,(e,t)=>x({operation:"checkUser",reason:e,status:t,userId:v,emailAddress:I})).finally(()=>k.delete(v)),k.set(v,t)),K=await t}catch{return x({operation:"checkUser",reason:"exception",userId:v,emailAddress:I}),injectIntoHtmlResponse(await i(t),U,S,$)}const X=K.isFlagged&&!K.isVerified;if("gate"===y&&X)return(0,content_type_1.isHtmlNavigation)(t.method,t.headers.get("accept")??void 0)?(0,web_helpers_1.bodyResponse)(200,E,{"Content-Type":"text/html; charset=utf-8","Cache-Control":"no-store"}):(0,web_helpers_1.jsonResponse)(403,(0,flagged_response_1.flaggedResponse)(I));if("overlay"===y&&X)return(0,content_type_1.isHtmlNavigation)(t.method,t.headers.get("accept")??void 0)?injectIntoHtmlResponse(await i(t),U,S,$):(0,web_helpers_1.jsonResponse)(403,(0,flagged_response_1.flaggedResponse)(I));if(K.isFlagged&&!K.isVerified&&u)try{const e=await u({userId:v,emailAddress:I,verdict:K,request:t});if(e)return injectIntoHtmlResponse(e,U,S,$)}catch(e){p&&p(e,{operation:"checkUser",userId:v,emailAddress:I})}return K.isFlagged||"unknown"===R||!B||C.isPaused()||h(dispatchUserEvent(e,T,C,P,{userId:v,emailAddress:I,sessionId:R,deviceId:B,fingerprintId:H,permanentDeviceId:N,userAgent:V,ipAddress:W,eventType:g+w},p)),injectIntoHtmlResponse(await i(t),U,S,$)}}const defaultInjectWarnState={emitted:!1};function passthroughResponse(e,t,r){if(!t||0===t.length)return e;const s=(0,web_helpers_1.mergeResponseHeaders)(e.headers,void 0,t);return new Response(r,{status:e.status,statusText:e.statusText,headers:s})}async function injectIntoHtmlResponse(e,t,r,s=defaultInjectWarnState){const n=e.headers.get("content-type");if(204===e.status||304===e.status)return passthroughResponse(e,r,null);if(null===e.body)return n||s.emitted||(s.emitted=!0,console.warn("[Unshared] The response returned by your handler has no body (e.g. NextResponse.next()), so fingerprint injection did not run. In Next.js middleware, fingerprints must be collected by the browser/React SDK, while this middleware handles verdict + enforcement. See the Next.js section of the integration guide.")),passthroughResponse(e,r,null);if(!(0,content_type_1.isHtmlContentType)(n??void 0))return passthroughResponse(e,r,e.body);const i=await e.text(),a=i.lastIndexOf("</body>"),o=-1===a?i+t:i.slice(0,a)+t+i.slice(a),d=(0,web_helpers_1.mergeResponseHeaders)(e.headers,{"Cache-Control":"no-store","Content-Length":String((new TextEncoder).encode(o).length)},r);return d.delete("ETag"),d.delete("Last-Modified"),d.delete("Content-Encoding"),new Response(o,{status:e.status,statusText:e.statusText,headers:d})}function resolveEmail(e,t){if(t)try{const r=t(e);if(r)return r}catch{}const r=(0,web_helpers_1.parseCookieFromRequest)(e,"__unshared_email");if(r)return r}function resolveEmailWithBody(e,t,r){const s=resolveEmail(e,r);if(s)return s;const n=t.email;return"string"==typeof n&&n?n:void 0}function extractSessionIdFromRequest(e,t){if(t)try{const r=t(e);if(r)return r}catch{}return(0,web_helpers_1.parseCookieFromRequest)(e,"__unshared_sid")??"unknown"}function isPermanentDeviceId(e){return"string"==typeof e&&/^upid_[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(e)}function extractPermanentDeviceIdFromRequest(e){const t=(0,web_helpers_1.parseCookieFromRequest)(e,"__unshared_device_id");if(isPermanentDeviceId(t))return t;const r=e.headers.get("x-permanent-device-id");return isPermanentDeviceId(r)?r:void 0}function dispatchUserEvent(e,t,r,s,n,i){return s.mark(n.userId,n.eventType),(0,dispatch_processing_1.runProcessUserEvent)({client:e,cache:t,backoff:r,onError:i},{eventType:n.eventType,userId:n.userId,emailAddress:n.emailAddress,ipAddress:n.ipAddress,deviceId:n.deviceId,fingerprintId:n.fingerprintId,permanentDeviceId:n.permanentDeviceId,sessionHash:n.sessionId,userAgent:n.userAgent},dispatch_processing_1.DISPATCH_TIMEOUT_MS)}async function fetchAndCacheVerdict(e,t,r,s,n,i,a,o,d=CHECK_USER_TIMEOUT_MS,c){const _=()=>{const e=t.get(r);return t.set(r,{isFlagged:e?.isFlagged??!1,isVerified:e?.isVerified??!1,emailAddress:s,sessionId:o},5e3),t.get(r)},l={timeoutMs:d,maxRetries:0};let u;n&&"unknown"!==n&&(l.deviceId=n),i&&(l.fullHash=i),a&&(l.permanentDeviceId=a);const p=await Promise.race([e.checkUser(s,l),new Promise(e=>{u=setTimeout(()=>e(null),d)})]);return clearTimeout(u),p?p.failedOpen?(c?.(p.failedOpen.reason??"http_error",p.failedOpen.status),_()):(t.set(r,{isFlagged:p.data?.is_user_flagged??!1,isVerified:!1,emailAddress:s,sessionId:o}),t.get(r)):(c?.("timeout"),_())}async function handleSubmitFp(e,t,r,s){try{const n=t?.fingerprint&&"object"==typeof t.fingerprint?t.fingerprint:t??{},i=t?.context&&"object"==typeof t.context?t.context:{},a=i.identity&&"object"==typeof i.identity?i.identity:{},o=i.identifiers&&"object"==typeof i.identifiers?i.identifiers:{},d=i.event&&"object"==typeof i.event?i.event:{},c={full_hash:n.hash??n.full_hash??"",fingerprint_id:n.stableHash??n.stable_hash??n.fingerprint_id??""};let _,l,u;try{const t=r.resolveUserId(e);t&&!(0,sentinel_user_id_1.isSentinelUserId)(t)&&(_=t)}catch{}if(!_){const e="string"==typeof a.user_id?a.user_id:"string"==typeof t.user_id?t.user_id:void 0;e&&!(0,sentinel_user_id_1.isSentinelUserId)(e)&&(_=e)}if(!_){const t=(0,web_helpers_1.parseCookieFromRequest)(e,"__unshared_uid");t&&!(0,sentinel_user_id_1.isSentinelUserId)(t)&&(_=t)}try{l=r.resolveEmailAddress?r.resolveEmailAddress(e):void 0}catch{}l=l??(0,web_helpers_1.parseCookieFromRequest)(e,"__unshared_email")??a.email_address??t.email??void 0;try{u=r.resolveSessionId?r.resolveSessionId(e):void 0}catch{}u=u??o.session_hash??t.session_id??(0,web_helpers_1.parseCookieFromRequest)(e,"__unshared_sid");const p=(0,web_helpers_1.extractClientIpFromRequest)(e),h=e.headers.get("user-agent")??"";if(!r.disableBotFilter&&(0,is_bot_1.isBot)(h))return(0,web_helpers_1.jsonResponse)(200,{success:!0,data:{skipped:!0}},s);const f=(c.fingerprint_id&&c.fingerprint_id.length>0?c.fingerprint_id:void 0)??(0,web_helpers_1.extractDeviceIdFromRequestOrUnknown)(e,r.resolveDeviceId),m=c.fingerprint_id||void 0,g=c.full_hash||void 0,w=extractPermanentDeviceIdFromRequest(e),b=e.headers.get("x-permanent-device-id")??o.unshared_device_id??t.permanent_device_id,v=w??(isPermanentDeviceId(b)?b:`upid_${crypto.randomUUID()}`),y=(0,web_helpers_1.isSecureWebRequest)(e)?"; Secure":"",I=[];I.push(`__unshared_device_id=${encodeURIComponent(v)}; Path=/; SameSite=Lax; Max-Age=34560000${y}`);const x=(0,web_helpers_1.parseCookieFromRequest)(e,"__unshared_fingerprint_id")||g;let S;if(x&&I.push(`__unshared_fingerprint_id=${encodeURIComponent(x)}; HttpOnly; Path=/; SameSite=Lax; Max-Age=31536000${y}`),m&&I.push(`__unshared_fp_id=${encodeURIComponent(m)}; Path=/; SameSite=Lax; Max-Age=31536000${y}`),l&&I.push(`__unshared_email=${encodeURIComponent(l)}; HttpOnly; Path=/; SameSite=Lax; Max-Age=31536000${y}`),"string"==typeof d.type&&d.type)S=d.type;else if("string"==typeof t.event_type&&t.event_type)S=t.event_type;else{const t=e.headers.get("referer")??e.headers.get("referrer");let r="unknown";if(t)try{const e=new URL(t);r=(e.pathname||"/")+(e.search||"")}catch{}S=r}const A=e.headers.get("x-idempotency-key")||void 0,R=Date.now();let E;A?E=`${A}|${R}`:m&&_&&(E=`${await(0,webcrypto_1.sha256HexWeb)(`${m}|${_}|${S}`)}|${R}`);const T={...o,unshared_device_id:v,...f&&"unknown"!==f?{device_id:f}:{},...m?{stable_hash:m}:{},...g?{full_hash:g}:{},...u?{session_hash:u}:{}},k=i.sdk&&"object"==typeof i.sdk?i.sdk.name:void 0,C="string"==typeof k&&k.startsWith("unshared-")&&Object.prototype.hasOwnProperty.call(t,"fingerprint")&&t.context&&"object"==typeof t.context&&!Array.isArray(t.context)&&i.identity&&"object"==typeof i.identity&&!Array.isArray(i.identity)&&i.identifiers&&"object"==typeof i.identifiers&&!Array.isArray(i.identifiers)&&i.event&&"object"==typeof i.event&&!Array.isArray(i.event),P=!Object.prototype.hasOwnProperty.call(t,"context")&&"string"==typeof t.hash&&"string"==typeof t.stable_hash&&"string"==typeof t.collected_at&&t.components&&"object"==typeof t.components&&!Array.isArray(t.components),U=C||P?{...t,fingerprint:n,context:{...i,identity:{...a,..._?{user_id:_}:{},...l?{email_address:l}:{}},identifiers:T,event:{...d,type:S,ip_address:p,user_agent:h}}}:t,$=await r.client.submitFingerprint(U,{idempotencyKey:E});if($.success||r.onError?.($.error,{operation:"submitFingerprintEvent",userId:_,emailAddress:l}),_&&l&&!r.rateLimitBackoff.isPaused()&&!r.dispatchDedupe.wasRecentlyDispatched(_,S)){const e=(0,dispatch_processing_1.runCheckUser)({client:r.client,cache:r.verdictCache,backoff:r.rateLimitBackoff,onError:r.onError},{user_id:_,email_address:l,identifiers:T},_,l,dispatch_processing_1.SUBMIT_FP_TIMEOUT_MS);r.registerBackground?.(e)}const j={...s,"Content-Type":"application/json"},D=new Response(JSON.stringify($.success?{success:!0,data:$.data}:{success:!1,error:$.error}),{status:200,headers:j});for(const e of I)D.headers.append("Set-Cookie",e);return D}catch{return(0,web_helpers_1.jsonResponse)(200,{success:!0,data:{accepted:!1}},s)}}async function handleVerifyTriggerWeb(e,t,r,s){try{const n=resolveEmailWithBody(e,t??{},r.resolveEmailAddress);if(!n)return(0,web_helpers_1.jsonResponse)(400,{success:!1,error:{code:"VALIDATION_ERROR",message:"Email is required"}},s);const i=(0,web_helpers_1.extractDeviceIdFromRequestOrUnknown)(e,r.resolveDeviceId),a=(0,web_helpers_1.parseCookieFromRequest)(e,"__unshared_fp_id")||void 0,o=(0,web_helpers_1.parseCookieFromRequest)(e,"__unshared_fingerprint_id")||void 0,d=extractPermanentDeviceIdFromRequest(e),c=await r.client.triggerEmailVerification(n,i,{fingerprintId:a,fullHash:o,permanentDeviceId:d});if(c.success){const t=(0,web_helpers_1.jsonResponse)(200,{success:!0,data:c.data},s);if(c.data?.verification_id){const r=(0,web_helpers_1.isSecureWebRequest)(e)?"; Secure":"";t.headers.append("Set-Cookie",`__unshared_verification_id=${encodeURIComponent(c.data.verification_id)}; HttpOnly; Path=/; SameSite=Lax; Max-Age=600${r}`)}return t}return(0,web_helpers_1.jsonResponse)(200,{success:!1,error:c.error??{code:"TRIGGER_FAILED",message:"Failed to send verification email"}},s)}catch(e){return r.onError&&r.onError(e,{operation:"verifyTrigger"}),(0,web_helpers_1.jsonResponse)(200,{success:!1,error:{code:"INTERNAL_ERROR",message:"Failed to trigger verification"}},s)}}async function handleVerifyWeb(e,t,r,s){try{const n=t?.code,i=(0,web_helpers_1.parseCookieFromRequest)(e,"__unshared_verification_id");if(!i||!n)return(0,web_helpers_1.jsonResponse)(400,{success:!1,error:{code:"VALIDATION_ERROR",message:"Verification ID and code are required"}},s);const a=await r.client.verifyEmail({verification_id:i,code:n});if(a.success&&a.data?.verified){let t,n=!1;try{t=r.resolveUserId(e)}catch{}const i=resolveEmail(e,r.resolveEmailAddress),o=(0,web_helpers_1.extractDeviceIdFromRequest)(e,r.resolveDeviceId),d=(0,web_helpers_1.parseCookieFromRequest)(e,"__unshared_fp_id")||void 0,c=(0,web_helpers_1.parseCookieFromRequest)(e,"__unshared_fingerprint_id")||void 0,_=extractPermanentDeviceIdFromRequest(e);if(t&&i){const e={...o?{device_id:o}:{},...d?{stable_hash:d}:{},...c?{full_hash:c}:{},..._?{unshared_device_id:_}:{}},s=await r.client.checkUser({user_id:t,email_address:i,identifiers:e},{maxRetries:0});s.success&&s.data&&!s.failedOpen&&!s.data.is_user_flagged&&(r.verdictCache.update(t,{isVerified:!0}),n=!0)}const l=(0,web_helpers_1.jsonResponse)(200,{success:!0,data:a.data},s);n&&l.headers.set("X-Unshared-Enforcement-Reconciled","clean");const u=(0,web_helpers_1.isSecureWebRequest)(e)?"; Secure":"";return l.headers.append("Set-Cookie",`__unshared_verification_id=; HttpOnly; Path=/; SameSite=Lax; Max-Age=0${u}`),l}return a.success?(0,web_helpers_1.jsonResponse)(200,{success:!0,data:a.data},s):(0,web_helpers_1.jsonResponse)(200,{success:!1,error:a.error??{code:"VERIFICATION_FAILED",message:"Verification failed"}},s)}catch(e){return r.onError&&r.onError(e,{operation:"verify"}),(0,web_helpers_1.jsonResponse)(200,{success:!1,error:{code:"INTERNAL_ERROR",message:"Verification failed"}},s)}}function __test_resetInjectWarning(){defaultInjectWarnState.emitted=!1}exports.__test_fetchAndCacheVerdict=fetchAndCacheVerdict,exports.__test_injectIntoHtmlResponse=injectIntoHtmlResponse;
1
+ "use strict";Object.defineProperty(exports,"t",{value:!0}),exports.__test_injectIntoHtmlResponse=exports.__test_fetchAndCacheVerdict=void 0,exports.createWebProtectionMiddleware=createWebProtectionMiddleware,exports.__test_resetInjectWarning=__test_resetInjectWarning;const webcrypto_1=require("../webcrypto"),verdict_cache_1=require("../middleware/verdict-cache"),rate_limit_backoff_1=require("../middleware/rate-limit-backoff"),dispatch_dedupe_1=require("../middleware/dispatch-dedupe"),fingerprint_script_1=require("../middleware/injection/fingerprint-script"),gate_page_1=require("../middleware/injection/gate-page"),flagged_response_1=require("../middleware/utils/flagged-response"),mask_email_1=require("../middleware/utils/mask-email"),content_type_1=require("../middleware/utils/content-type"),skip_paths_1=require("../middleware/utils/skip-paths"),include_path_1=require("../middleware/utils/include-path"),is_bot_1=require("../middleware/utils/is-bot"),sentinel_user_id_1=require("../middleware/utils/sentinel-user-id"),dispatch_processing_1=require("../middleware/dispatch-processing"),web_helpers_1=require("./web-helpers"),CHECK_USER_TIMEOUT_MS=1500,FAIL_OPEN_TTL_MS=5e3;function createWebProtectionMiddleware(e,t){if(!t.userId)throw new Error("[Unshared] userId resolver is required");const{userId:r,emailAddress:s,routePrefix:n="/__unshared",corsOrigins:i,cacheTTL:a=6e4,skipPaths:o,includePathPrefix:d,sessionId:_,deviceId:c,fingerprintSdkBundle:l="",onFlagged:u,onError:h,onFailOpen:p,disableBotFilter:f=!1,checkUserTimeoutMs:m=CHECK_USER_TIMEOUT_MS,blockFlagged:w=!1,flaggedMode:g,autoInterstitial:b=!1,interstitialFlowType:v="email_verification"}=t,y=g??(w?"gate":void 0),I=b||"overlay"===y,x=e=>{if(p)try{p(e)}catch{}},S=Date.now().toString(36),R=void 0!==y||I,A="gate"===y?"flaggedMode 'gate' (blockFlagged)":"overlay"===y?"flaggedMode 'overlay'":"autoInterstitial";if(R&&!l)throw new Error(`[Unshared] ${A} requires fingerprintSdkBundle (the browser SDK UMD served at {routePrefix}/fp.js renders the interstitial).`);if(R&&"/__unshared"!==n)throw new Error(`[Unshared] ${A} requires the default routePrefix ("/__unshared"); the browser SDK proxy routes are fixed to that prefix.`);const E="gate"===y?(0,gate_page_1.generateGatePage)(n,S):"",k=new verdict_cache_1.VerdictCache(a),C=new Map,T=new rate_limit_backoff_1.RateLimitBackoff,P=new dispatch_dedupe_1.DispatchDedupe,$=(0,fingerprint_script_1.generateFingerprintScript)(n,S,{autoInterstitial:I,interstitialFlowType:v,debug:t.debug,disableBotFilter:t.disableBotFilter}),U={emitted:!1},j=`${n}/fp.js`,D=`${n}/submit-fp`,M=`${n}/verify-trigger`,q=`${n}/verify`,F=`${n}/status`,O=i?Array.isArray(i)?i:[i]:null;return async function(t,i,a){const p=e=>{a?.waitUntil&&a.waitUntil(e)};let w,g;try{const e=new URL(t.url);w=e.pathname,g=e.search}catch{return i(t)}if(w.startsWith(n+"/")){const n=function(e){if(!O)return{};const t=e.headers.get("origin")??"",r=O.includes("*");if(!r&&!O.includes(t))return{};const s={"Access-Control-Allow-Origin":r?"*":t,"Access-Control-Allow-Methods":"POST, OPTIONS","Access-Control-Allow-Headers":"Content-Type, X-Idempotency-Key, X-Session-Id, X-Device-Id, X-Permanent-Device-Id","Access-Control-Expose-Headers":"X-Unshared-Enforcement-Reconciled"};return r||(s["Access-Control-Allow-Credentials"]="true"),s}(t);if("OPTIONS"===t.method)return(0,web_helpers_1.emptyResponse)(204,n);if(("GET"===t.method||"HEAD"===t.method)&&w===j){if(!l)return new Response("HEAD"===t.method?null:"/* fingerprint SDK unavailable */",{status:503,headers:{...n,"Content-Type":"application/javascript","Cache-Control":"no-store"}});const e={...n,"Content-Type":"application/javascript","Cache-Control":"public, max-age=3600","Content-Length":String((new TextEncoder).encode(l).byteLength)};return"HEAD"===t.method?new Response(null,{status:200,headers:new Headers(e)}):(0,web_helpers_1.bodyResponse)(200,l,e)}if("POST"===t.method&&(w===D||w===M||w===q)){const i=await(0,web_helpers_1.readJsonRequest)(t);if(!i.success)return(0,web_helpers_1.jsonResponse)(i.status,{success:!1,error:i.error},n);const a=i.data;return w===D?handleSubmitFp(t,a,{client:e,verdictCache:k,rateLimitBackoff:T,dispatchDedupe:P,resolveUserId:r,resolveEmailAddress:s,resolveSessionId:_,resolveDeviceId:c,disableBotFilter:f,onError:h,registerBackground:p},n):w===M?handleVerifyTriggerWeb(t,a,{client:e,verdictCache:k,resolveUserId:r,resolveEmailAddress:s,resolveDeviceId:c,resolveSessionId:_,onError:h},n):handleVerifyWeb(t,a,{client:e,verdictCache:k,resolveUserId:r,resolveEmailAddress:s,resolveDeviceId:c,resolveSessionId:_,onError:h},n)}if("GET"===t.method&&w===F){let i;try{i=r(t)}catch{}if(!i)return(0,web_helpers_1.jsonResponse)(200,{status:"anonymous"},n);const a=resolveEmail(t,s);let o=k.get(i);if((!o||k.isStale(i))&&a&&!T.isPaused()&&!k.isRefreshing(i)){k.markRefreshing(i);try{const r=(0,web_helpers_1.extractDeviceIdFromRequest)(t,c),s=(0,web_helpers_1.parseCookieFromRequest)(t,"__unshared_stable_hash")||(0,web_helpers_1.parseCookieFromRequest)(t,"__unshared_fp_id")||void 0,n=(0,web_helpers_1.parseCookieFromRequest)(t,"__unshared_full_hash")||(0,web_helpers_1.parseCookieFromRequest)(t,"__unshared_fingerprint_id")||void 0,d=extractPermanentDeviceIdFromRequest(t),l=extractSessionIdFromRequest(t,_);await fetchAndCacheVerdict(e,k,i,a,r,s,n,d,l,m,(e,t)=>x({operation:"checkUser",reason:e,status:t,userId:i,emailAddress:a})),o=k.get(i)}catch(e){h&&h(e,{operation:"checkUser",userId:i,emailAddress:a}),x({operation:"checkUser",reason:"exception",userId:i,emailAddress:a})}finally{k.clearRefreshing(i)}}return o&&o.isFlagged&&!o.isVerified&&(void 0!==y||u)&&a?(0,web_helpers_1.jsonResponse)(200,{status:"flagged",email:(0,mask_email_1.maskEmail)(a)},n):(0,web_helpers_1.jsonResponse)(200,{status:"ok"},n)}return(0,web_helpers_1.jsonResponse)(404,{success:!1,error:{code:"NOT_FOUND",message:"Unknown route"}},n)}const b=void 0!==y;if((0,skip_paths_1.shouldSkipPath)(w,o,b))return i(t);let v;try{v=r(t)}catch{}if((0,sentinel_user_id_1.isSentinelUserId)(v)){const e=(0,web_helpers_1.parseCookieFromRequest)(t,"__unshared_uid"),r=(0,web_helpers_1.parseCookieFromRequest)(t,"__unshared_uid_at"),s=r?Number(r):NaN,n=Number.isFinite(s)&&Date.now()-s<=sentinel_user_id_1.SENTINEL_STICKINESS_TTL_MS;v=e&&n?e:void 0}if(!v){const e=(0,web_helpers_1.isSecureWebRequest)(t)?"; Secure":"",r=[`__unshared_uid=; Path=/; SameSite=Lax; Max-Age=0${e}`,`__unshared_uid_at=; Path=/; SameSite=Lax; Max-Age=0${e}`,`__unshared_sid=; Path=/; SameSite=Lax; Max-Age=0${e}`,`__unshared_email=; Path=/; SameSite=Lax; Max-Age=0${e}`,`__unshared_verification_id=; HttpOnly; Path=/; SameSite=Lax; Max-Age=0${e}`];return injectIntoHtmlResponse(await i(t),$,r,U)}const I=resolveEmail(t,s),S=[],R=(0,web_helpers_1.isSecureWebRequest)(t)?"; Secure":"";if(S.push(`__unshared_uid=${encodeURIComponent(v)}; Path=/; SameSite=Lax; Max-Age=31536000${R}`),S.push(`__unshared_uid_at=${Date.now()}; Path=/; SameSite=Lax; Max-Age=31536000${R}`),I&&S.push(`__unshared_email=${encodeURIComponent(I)}; HttpOnly; Path=/; SameSite=Lax; Max-Age=31536000${R}`),!(0,include_path_1.shouldIncludePath)(w,d))return injectIntoHtmlResponse(await i(t),$,S,U);if(!I)return injectIntoHtmlResponse(await i(t),$,S,U);const A=extractSessionIdFromRequest(t,_),L=(0,web_helpers_1.extractDeviceIdFromRequest)(t,c),H=(0,web_helpers_1.parseCookieFromRequest)(t,"__unshared_stable_hash")||(0,web_helpers_1.parseCookieFromRequest)(t,"__unshared_fp_id")||void 0,N=(0,web_helpers_1.parseCookieFromRequest)(t,"__unshared_full_hash")||(0,web_helpers_1.parseCookieFromRequest)(t,"__unshared_fingerprint_id")||void 0,V=extractPermanentDeviceIdFromRequest(t),W=t.headers.get("user-agent")??"",B=(0,web_helpers_1.extractClientIpFromRequest)(t);if(!f&&(0,is_bot_1.isBot)(W)){const e=await i(t);return passthroughResponse(e,S,e.body)}let K=k.get(v);if(K)k.isStale(v)&&!k.isRefreshing(v)&&(k.markRefreshing(v),p(fetchAndCacheVerdict(e,k,v,I,L,H,N,V,A,m,(e,t)=>x({operation:"checkUser",reason:e,status:t,userId:v,emailAddress:I})).catch(()=>x({operation:"checkUser",reason:"exception",userId:v,emailAddress:I})).finally(()=>k.clearRefreshing(v))));else try{let t=C.get(v);t||(t=fetchAndCacheVerdict(e,k,v,I,L,H,N,V,A,m,(e,t)=>x({operation:"checkUser",reason:e,status:t,userId:v,emailAddress:I})).finally(()=>C.delete(v)),C.set(v,t)),K=await t}catch{return x({operation:"checkUser",reason:"exception",userId:v,emailAddress:I}),injectIntoHtmlResponse(await i(t),$,S,U)}const X=K.isFlagged&&!K.isVerified;if("gate"===y&&X){if((0,content_type_1.isHtmlNavigation)(t.method,t.headers.get("accept")??void 0)){const e=(0,web_helpers_1.bodyResponse)(200,E,{"Content-Type":"text/html; charset=utf-8","Cache-Control":"no-store"});return passthroughResponse(e,S,e.body)}const e=(0,web_helpers_1.jsonResponse)(403,(0,flagged_response_1.flaggedResponse)(I));return passthroughResponse(e,S,e.body)}if("overlay"===y&&X){if((0,content_type_1.isHtmlNavigation)(t.method,t.headers.get("accept")??void 0))return injectIntoHtmlResponse(await i(t),$,S,U);const e=(0,web_helpers_1.jsonResponse)(403,(0,flagged_response_1.flaggedResponse)(I));return passthroughResponse(e,S,e.body)}if(K.isFlagged&&!K.isVerified&&u)try{const e=await u({userId:v,emailAddress:I,verdict:K,request:t});if(e)return injectIntoHtmlResponse(e,$,S,U)}catch(e){h&&h(e,{operation:"checkUser",userId:v,emailAddress:I})}return K.isFlagged||!A||"unknown"===A||T.isPaused()||p(dispatchUserEvent(e,k,T,P,{userId:v,emailAddress:I,sessionId:A,deviceId:L,fingerprintId:H,fullHash:N,permanentDeviceId:V,userAgent:W,ipAddress:B,eventType:w+g},h)),injectIntoHtmlResponse(await i(t),$,S,U)}}const defaultInjectWarnState={emitted:!1};function passthroughResponse(e,t,r){if(!t||0===t.length)return e;const s=(0,web_helpers_1.mergeResponseHeaders)(e.headers,void 0,t);return new Response(r,{status:e.status,statusText:e.statusText,headers:s})}async function injectIntoHtmlResponse(e,t,r,s=defaultInjectWarnState){const n=e.headers.get("content-type");if(204===e.status||304===e.status)return passthroughResponse(e,r,null);if(null===e.body)return n||s.emitted||(s.emitted=!0,console.warn("[Unshared] The response returned by your handler has no body (e.g. NextResponse.next()), so fingerprint injection did not run. In Next.js middleware, fingerprints must be collected by the browser/React SDK, while this middleware handles verdict + enforcement. See the Next.js section of the integration guide.")),passthroughResponse(e,r,null);if(!(0,content_type_1.isHtmlContentType)(n??void 0))return passthroughResponse(e,r,e.body);const i=await e.text(),a=i.lastIndexOf("</body>"),o=-1===a?i+t:i.slice(0,a)+t+i.slice(a),d=(0,web_helpers_1.mergeResponseHeaders)(e.headers,{"Cache-Control":"no-store","Content-Length":String((new TextEncoder).encode(o).length)},r);return d.delete("ETag"),d.delete("Last-Modified"),d.delete("Content-Encoding"),new Response(o,{status:e.status,statusText:e.statusText,headers:d})}function resolveEmail(e,t){if(t)try{const r=t(e);if(r)return r}catch{}const r=(0,web_helpers_1.parseCookieFromRequest)(e,"__unshared_email");if(r)return r}function resolveEmailWithBody(e,t,r){const s=resolveEmail(e,r);if(s)return s;const n=t.email;return"string"==typeof n&&n?n:void 0}function extractSessionIdFromRequest(e,t){if(t)try{const r=t(e);if(r)return r}catch{}return(0,web_helpers_1.parseCookieFromRequest)(e,"__unshared_sid")??"unknown"}function isPermanentDeviceId(e){return"string"==typeof e&&/^upid_[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(e)}function extractPermanentDeviceIdFromRequest(e){const t=(0,web_helpers_1.parseCookieFromRequest)(e,"__unshared_device_id");if(isPermanentDeviceId(t))return t;const r=e.headers.get("x-permanent-device-id");return isPermanentDeviceId(r)?r:void 0}function dispatchUserEvent(e,t,r,s,n,i){return s.mark(n.userId,n.eventType),(0,dispatch_processing_1.runProcessUserEvent)({client:e,cache:t,backoff:r,onError:i},{eventType:n.eventType,userId:n.userId,emailAddress:n.emailAddress,ipAddress:n.ipAddress,deviceId:n.deviceId,fingerprintId:n.fingerprintId,fullHash:n.fullHash,permanentDeviceId:n.permanentDeviceId,sessionHash:n.sessionId,userAgent:n.userAgent},dispatch_processing_1.DISPATCH_TIMEOUT_MS)}async function fetchAndCacheVerdict(e,t,r,s,n,i,a,o,d,_=CHECK_USER_TIMEOUT_MS,c){const l=()=>{const e=t.get(r);return t.set(r,{isFlagged:e?.isFlagged??!1,isVerified:e?.isVerified??!1,emailAddress:s,sessionId:d},5e3),t.get(r)},u={timeoutMs:_,maxRetries:0};let h;n&&"unknown"!==n&&(u.deviceId=n),i&&"unknown"!==i&&(u.fingerprintId=i),a&&"unknown"!==a&&(u.fullHash=a),o&&(u.permanentDeviceId=o),d&&"unknown"!==d&&(u.sessionHash=d);const p=await Promise.race([e.checkUser(s,u),new Promise(e=>{h=setTimeout(()=>e(null),_)})]);return clearTimeout(h),p?p.failedOpen?(c?.(p.failedOpen.reason??"http_error",p.failedOpen.status),l()):(t.set(r,{isFlagged:p.data?.is_user_flagged??!1,isVerified:!1,emailAddress:s,sessionId:d}),t.get(r)):(c?.("timeout"),l())}async function handleSubmitFp(e,t,r,s){try{const n=t?.fingerprint&&"object"==typeof t.fingerprint?t.fingerprint:t??{},i=t?.context&&"object"==typeof t.context?t.context:{},a=i.identity&&"object"==typeof i.identity?i.identity:{},o=i.identifiers&&"object"==typeof i.identifiers?i.identifiers:{},d=i.event&&"object"==typeof i.event?i.event:{},_={full_hash:n.hash??n.full_hash??"",fingerprint_id:n.stableHash??n.stable_hash??n.fingerprint_id??""};let c,l,u;try{const t=r.resolveUserId(e);t&&!(0,sentinel_user_id_1.isSentinelUserId)(t)&&(c=t)}catch{}if(!c){const e="string"==typeof a.user_id?a.user_id:"string"==typeof t.user_id?t.user_id:void 0;e&&!(0,sentinel_user_id_1.isSentinelUserId)(e)&&(c=e)}if(!c){const t=(0,web_helpers_1.parseCookieFromRequest)(e,"__unshared_uid");t&&!(0,sentinel_user_id_1.isSentinelUserId)(t)&&(c=t)}try{l=r.resolveEmailAddress?r.resolveEmailAddress(e):void 0}catch{}l=l??(0,web_helpers_1.parseCookieFromRequest)(e,"__unshared_email")??a.email_address??t.email??void 0;try{u=r.resolveSessionId?r.resolveSessionId(e):void 0}catch{}u=u??o.session_hash??t.session_id??(0,web_helpers_1.parseCookieFromRequest)(e,"__unshared_sid");const h=(0,web_helpers_1.extractClientIpFromRequest)(e),p=e.headers.get("user-agent")??"";if(!r.disableBotFilter&&(0,is_bot_1.isBot)(p))return(0,web_helpers_1.jsonResponse)(200,{success:!0,data:{skipped:!0}},s);let f;try{f=r.resolveDeviceId?.(e)}catch{}const m=[f,e.headers.get("x-device-id"),o.device_id,_.fingerprint_id,(0,web_helpers_1.parseCookieFromRequest)(e,"__unshared_stable_hash"),(0,web_helpers_1.parseCookieFromRequest)(e,"__unshared_fp_id")].find(e=>"string"==typeof e&&""!==e.trim()&&"unknown"!==e.trim()),w=_.fingerprint_id||void 0,g=_.full_hash||void 0,b=extractPermanentDeviceIdFromRequest(e),v=e.headers.get("x-permanent-device-id")??o.unshared_device_id??t.permanent_device_id,y=b??(isPermanentDeviceId(v)?v:`upid_${crypto.randomUUID()}`),I=(0,web_helpers_1.isSecureWebRequest)(e)?"; Secure":"",x=[];x.push(`__unshared_device_id=${encodeURIComponent(y)}; Path=/; SameSite=Lax; Max-Age=34560000${I}`);const S=(0,web_helpers_1.parseCookieFromRequest)(e,"__unshared_fingerprint_id")||g;let R;if(S&&x.push(`__unshared_fingerprint_id=${encodeURIComponent(S)}; HttpOnly; Path=/; SameSite=Lax; Max-Age=34560000${I}`),g&&x.push(`__unshared_full_hash=${encodeURIComponent(g)}; Path=/; SameSite=Lax; Max-Age=34560000${I}`),w&&(x.push(`__unshared_fp_id=${encodeURIComponent(w)}; Path=/; SameSite=Lax; Max-Age=34560000${I}`),x.push(`__unshared_stable_hash=${encodeURIComponent(w)}; Path=/; SameSite=Lax; Max-Age=34560000${I}`)),l&&x.push(`__unshared_email=${encodeURIComponent(l)}; HttpOnly; Path=/; SameSite=Lax; Max-Age=31536000${I}`),"string"==typeof d.type&&d.type)R=d.type;else if("string"==typeof t.event_type&&t.event_type)R=t.event_type;else{const t=e.headers.get("referer")??e.headers.get("referrer");let r="unknown";if(t)try{const e=new URL(t);r=(e.pathname||"/")+(e.search||"")}catch{}R=r}const A=e.headers.get("x-idempotency-key")||void 0,E=Date.now();let k;A?k=`${A}|${E}`:w&&c&&(k=`${await(0,webcrypto_1.sha256HexWeb)(`${w}|${c}|${R}`)}|${E}`);const C={...o,unshared_device_id:y,...m&&"unknown"!==m?{device_id:m}:{},...w?{stable_hash:w}:{},...g?{full_hash:g}:{},...u?{session_hash:u}:{}};m||delete C.device_id;const T=i.sdk&&"object"==typeof i.sdk?i.sdk.name:void 0,P="string"==typeof T&&T.startsWith("unshared-")&&Object.prototype.hasOwnProperty.call(t,"fingerprint")&&t.context&&"object"==typeof t.context&&!Array.isArray(t.context)&&i.identity&&"object"==typeof i.identity&&!Array.isArray(i.identity)&&i.identifiers&&"object"==typeof i.identifiers&&!Array.isArray(i.identifiers)&&i.event&&"object"==typeof i.event&&!Array.isArray(i.event),$=!Object.prototype.hasOwnProperty.call(t,"context")&&"string"==typeof t.hash&&"string"==typeof t.stable_hash&&"string"==typeof t.collected_at&&t.components&&"object"==typeof t.components&&!Array.isArray(t.components),U=P||$?{...t,fingerprint:n,context:{...i,identity:{...a,...c?{user_id:c}:{},...l?{email_address:l}:{}},identifiers:C,event:{...d,type:R,ip_address:h,user_agent:p}}}:t,j=await r.client.submitFingerprint(U,{idempotencyKey:k});if(j.success||r.onError?.(j.error,{operation:"submitFingerprintEvent",userId:c,emailAddress:l}),c&&l&&!r.rateLimitBackoff.isPaused()&&!r.dispatchDedupe.wasRecentlyDispatched(c,R)){const e=(0,dispatch_processing_1.runCheckUser)({client:r.client,cache:r.verdictCache,backoff:r.rateLimitBackoff,onError:r.onError},{user_id:c,email_address:l,identifiers:C},c,l,dispatch_processing_1.SUBMIT_FP_TIMEOUT_MS);r.registerBackground?.(e)}const D={...s,"Content-Type":"application/json"},M=new Response(JSON.stringify(j.success?{success:!0,data:j.data}:{success:!1,error:j.error}),{status:200,headers:D});for(const e of x)M.headers.append("Set-Cookie",e);return M}catch{return(0,web_helpers_1.jsonResponse)(200,{success:!0,data:{accepted:!1}},s)}}async function handleVerifyTriggerWeb(e,t,r,s){try{const n=resolveEmailWithBody(e,t??{},r.resolveEmailAddress);if(!n)return(0,web_helpers_1.jsonResponse)(400,{success:!1,error:{code:"VALIDATION_ERROR",message:"Email is required"}},s);const i=(0,web_helpers_1.extractDeviceIdFromRequest)(e,r.resolveDeviceId),a=(0,web_helpers_1.parseCookieFromRequest)(e,"__unshared_stable_hash")||(0,web_helpers_1.parseCookieFromRequest)(e,"__unshared_fp_id")||void 0,o=(0,web_helpers_1.parseCookieFromRequest)(e,"__unshared_full_hash")||(0,web_helpers_1.parseCookieFromRequest)(e,"__unshared_fingerprint_id")||void 0,d=extractPermanentDeviceIdFromRequest(e),_=extractSessionIdFromRequest(e,r.resolveSessionId),c=await r.client.triggerEmailVerification(n,i&&"unknown"!==i?i:"",{fingerprintId:"unknown"!==a?a:void 0,fullHash:"unknown"!==o?o:void 0,permanentDeviceId:d,sessionHash:"unknown"!==_?_:void 0});if(c.success){const t=(0,web_helpers_1.jsonResponse)(200,{success:!0,data:c.data},s);if(c.data?.verification_id){const r=(0,web_helpers_1.isSecureWebRequest)(e)?"; Secure":"";t.headers.append("Set-Cookie",`__unshared_verification_id=${encodeURIComponent(c.data.verification_id)}; HttpOnly; Path=/; SameSite=Lax; Max-Age=600${r}`)}return t}return(0,web_helpers_1.jsonResponse)(200,{success:!1,error:c.error??{code:"TRIGGER_FAILED",message:"Failed to send verification email"}},s)}catch(e){return r.onError&&r.onError(e,{operation:"verifyTrigger"}),(0,web_helpers_1.jsonResponse)(200,{success:!1,error:{code:"INTERNAL_ERROR",message:"Failed to trigger verification"}},s)}}async function handleVerifyWeb(e,t,r,s){try{const n=t?.code,i=(0,web_helpers_1.parseCookieFromRequest)(e,"__unshared_verification_id");if(!i||!n)return(0,web_helpers_1.jsonResponse)(400,{success:!1,error:{code:"VALIDATION_ERROR",message:"Verification ID and code are required"}},s);const a=await r.client.verifyEmail({verification_id:i,code:n});if(a.success&&a.data?.verified){let t,n=!1;try{t=r.resolveUserId(e)}catch{}const i=resolveEmail(e,r.resolveEmailAddress),o=(0,web_helpers_1.extractDeviceIdFromRequest)(e,r.resolveDeviceId),d=(0,web_helpers_1.parseCookieFromRequest)(e,"__unshared_stable_hash")||(0,web_helpers_1.parseCookieFromRequest)(e,"__unshared_fp_id")||void 0,_=(0,web_helpers_1.parseCookieFromRequest)(e,"__unshared_full_hash")||(0,web_helpers_1.parseCookieFromRequest)(e,"__unshared_fingerprint_id")||void 0,c=extractPermanentDeviceIdFromRequest(e),l=extractSessionIdFromRequest(e,r.resolveSessionId);if(t&&i){const e={...o&&"unknown"!==o?{device_id:o}:{},...d&&"unknown"!==d?{stable_hash:d}:{},..._&&"unknown"!==_?{full_hash:_}:{},...c?{unshared_device_id:c}:{},...l&&"unknown"!==l?{session_hash:l}:{}},s=await r.client.checkUser({user_id:t,email_address:i,identifiers:e},{maxRetries:0});s.success&&s.data&&!s.failedOpen&&!s.data.is_user_flagged&&(r.verdictCache.update(t,{isVerified:!0}),n=!0)}const u=(0,web_helpers_1.jsonResponse)(200,{success:!0,data:a.data},s);n&&u.headers.set("X-Unshared-Enforcement-Reconciled","clean");const h=(0,web_helpers_1.isSecureWebRequest)(e)?"; Secure":"";return u.headers.append("Set-Cookie",`__unshared_verification_id=; HttpOnly; Path=/; SameSite=Lax; Max-Age=0${h}`),u}return a.success?(0,web_helpers_1.jsonResponse)(200,{success:!0,data:a.data},s):(0,web_helpers_1.jsonResponse)(200,{success:!1,error:a.error??{code:"VERIFICATION_FAILED",message:"Verification failed"}},s)}catch(e){return r.onError&&r.onError(e,{operation:"verify"}),(0,web_helpers_1.jsonResponse)(200,{success:!1,error:{code:"INTERNAL_ERROR",message:"Verification failed"}},s)}}function __test_resetInjectWarning(){defaultInjectWarnState.emitted=!1}exports.__test_fetchAndCacheVerdict=fetchAndCacheVerdict,exports.__test_injectIntoHtmlResponse=injectIntoHtmlResponse;
@@ -69,13 +69,15 @@ export interface WebProtectionConfig {
69
69
  includePathPrefix?: string[];
70
70
  /** Skip the bot/crawler UA filter. Set to true in test environments so automated browsers (Playwright, Puppeteer, etc.) can observe verdicts. @default false */
71
71
  disableBotFilter?: boolean;
72
+ /** Enable diagnostic logging in the injected fingerprint script. @default false */
73
+ debug?: boolean;
72
74
  /** Hard timeout (ms) for checkUser on cache miss. Fails open on timeout. @default 1500 */
73
75
  checkUserTimeoutMs?: number;
74
76
  /** Resolves a custom session ID. Falls back to __unshared_sid cookie. */
75
77
  sessionId?: (req: Request) => string | undefined;
76
78
  /**
77
79
  * Resolves a device ID from the request.
78
- * Falls back to X-Device-Id header → __unshared_fp_id cookie.
80
+ * Falls back to X-Device-Id header, then the canonical or legacy stable-hash cookie.
79
81
  */
80
82
  deviceId?: (req: Request) => string | undefined;
81
83
  /**
@@ -91,7 +93,7 @@ export interface WebProtectionConfig {
91
93
  * createWebProtectionMiddleware(client, { ..., fingerprintSdkBundle });
92
94
  * ```
93
95
  *
94
- * If omitted, the fp.js route returns 404. The inline script degrades
96
+ * If omitted, the fp.js route returns a noncacheable 503. The inline script degrades
95
97
  * gracefully — the cached-fingerprint path still works.
96
98
  */
97
99
  fingerprintSdkBundle?: string;
@@ -18,7 +18,8 @@ export declare function parseCookieFromRequest(request: Request, name: string):
18
18
  */
19
19
  export declare function extractClientIpFromRequest(request: Request): string;
20
20
  /**
21
- * Resolves device ID from: custom resolver → X-Device-Id header → __unshared_fp_id cookie.
21
+ * Resolves device ID from the custom resolver, X-Device-Id header, then the
22
+ * canonical __unshared_stable_hash cookie or legacy __unshared_fp_id cookie.
22
23
  * Mirrors `extractDeviceIdOrUndefined` in src/middleware/utils/device-id.ts.
23
24
  */
24
25
  export declare function extractDeviceIdFromRequest(request: Request, resolveDeviceId?: (req: Request) => string | undefined): string | undefined;
@@ -1 +1 @@
1
- "use strict";function parseCookieFromRequest(e,t){const o=e.headers.get("cookie");if(!o)return;const s=o.match(new RegExp(`(?:^|; )${t}=([^;]*)`));if(s)try{return decodeURIComponent(s[1])}catch{return s[1]}}function extractClientIpFromRequest(e){const t=e.headers.get("cf-connecting-ip");if(t)return t;const o=e.headers.get("x-real-ip");if(o)return o;const s=e.headers.get("x-forwarded-for");if(s){const e=s.split(",")[0]?.trim();if(e)return e}return""}function extractDeviceIdFromRequest(e,t){if(t)try{const o=t(e);if(o)return o}catch{}const o=e.headers.get("x-device-id");if(o)return o;return parseCookieFromRequest(e,"__unshared_fp_id")||void 0}function extractDeviceIdFromRequestOrUnknown(e,t){return extractDeviceIdFromRequest(e,t)??"unknown"}function isSecureWebRequest(e){if("https"===e.headers.get("x-forwarded-proto"))return!0;try{return"https:"===new URL(e.url).protocol}catch{return!1}}async function readJsonRequest(e,t=1<<20){const o=Number(e.headers.get("content-length"));if(Number.isFinite(o)&&o>t)return{success:!1,status:413,error:{code:"REQUEST_TOO_LARGE",message:"Request body exceeds 1 MiB"}};try{if(!e.body)throw new Error("Missing body");const o=e.body.getReader(),s=[];let r=0;for(;;){const{done:e,value:n}=await o.read();if(e)break;if(r+=n.byteLength,r>t)return o.cancel().catch(()=>{}),{success:!1,status:413,error:{code:"REQUEST_TOO_LARGE",message:"Request body exceeds 1 MiB"}};s.push(n)}const n=new Uint8Array(r);let c=0;for(const e of s)n.set(e,c),c+=e.byteLength;return{success:!0,data:JSON.parse((new TextDecoder).decode(n))}}catch{return{success:!1,status:400,error:{code:"INVALID_JSON",message:"Request body is not valid JSON."}}}}function jsonResponse(e,t,o){const s=new Headers(o);return s.set("Content-Type","application/json"),new Response(JSON.stringify(t),{status:e,headers:s})}function emptyResponse(e,t){return new Response(null,{status:e,headers:new Headers(t)})}function bodyResponse(e,t,o){return new Response(t,{status:e,headers:new Headers(o)})}function appendCookieToHeaders(e,t){e.append("Set-Cookie",t)}function mergeResponseHeaders(e,t,o){const s=new Headers;e.forEach((e,t)=>{"set-cookie"!==t.toLowerCase()&&s.set(t,e)});const r="function"==typeof e.getSetCookie?e.getSetCookie():[];for(const e of r)s.append("Set-Cookie",e);if(o)for(const e of o)s.append("Set-Cookie",e);if(t)for(const[e,o]of Object.entries(t))s.set(e,o);return s}Object.defineProperty(exports,"t",{value:!0}),exports.parseCookieFromRequest=parseCookieFromRequest,exports.extractClientIpFromRequest=extractClientIpFromRequest,exports.extractDeviceIdFromRequest=extractDeviceIdFromRequest,exports.extractDeviceIdFromRequestOrUnknown=extractDeviceIdFromRequestOrUnknown,exports.isSecureWebRequest=isSecureWebRequest,exports.readJsonRequest=readJsonRequest,exports.jsonResponse=jsonResponse,exports.emptyResponse=emptyResponse,exports.bodyResponse=bodyResponse,exports.appendCookieToHeaders=appendCookieToHeaders,exports.mergeResponseHeaders=mergeResponseHeaders;
1
+ "use strict";function parseCookieFromRequest(e,t){const s=e.headers.get("cookie");if(!s)return;const o=s.match(new RegExp(`(?:^|; )${t}=([^;]*)`));if(o)try{return decodeURIComponent(o[1])}catch{return o[1]}}function extractClientIpFromRequest(e){const t=e.headers.get("cf-connecting-ip");if(t)return t;const s=e.headers.get("x-real-ip");if(s)return s;const o=e.headers.get("x-forwarded-for");if(o){const e=o.split(",")[0]?.trim();if(e)return e}return""}function extractDeviceIdFromRequest(e,t){if(t)try{const s=t(e);if(s)return s}catch{}const s=e.headers.get("x-device-id");if(s)return s;return parseCookieFromRequest(e,"__unshared_stable_hash")||parseCookieFromRequest(e,"__unshared_fp_id")||void 0}function extractDeviceIdFromRequestOrUnknown(e,t){return extractDeviceIdFromRequest(e,t)??"unknown"}function isSecureWebRequest(e){if("https"===e.headers.get("x-forwarded-proto"))return!0;try{return"https:"===new URL(e.url).protocol}catch{return!1}}async function readJsonRequest(e,t=1<<20){const s=Number(e.headers.get("content-length"));if(Number.isFinite(s)&&s>t)return{success:!1,status:413,error:{code:"REQUEST_TOO_LARGE",message:"Request body exceeds 1 MiB"}};try{if(!e.body)throw new Error("Missing body");const s=e.body.getReader(),o=[];let r=0;for(;;){const{done:e,value:n}=await s.read();if(e)break;if(r+=n.byteLength,r>t)return s.cancel().catch(()=>{}),{success:!1,status:413,error:{code:"REQUEST_TOO_LARGE",message:"Request body exceeds 1 MiB"}};o.push(n)}const n=new Uint8Array(r);let c=0;for(const e of o)n.set(e,c),c+=e.byteLength;return{success:!0,data:JSON.parse((new TextDecoder).decode(n))}}catch{return{success:!1,status:400,error:{code:"INVALID_JSON",message:"Request body is not valid JSON."}}}}function jsonResponse(e,t,s){const o=new Headers(s);return o.set("Content-Type","application/json"),new Response(JSON.stringify(t),{status:e,headers:o})}function emptyResponse(e,t){return new Response(null,{status:e,headers:new Headers(t)})}function bodyResponse(e,t,s){return new Response(t,{status:e,headers:new Headers(s)})}function appendCookieToHeaders(e,t){e.append("Set-Cookie",t)}function mergeResponseHeaders(e,t,s){const o=new Headers;e.forEach((e,t)=>{"set-cookie"!==t.toLowerCase()&&o.set(t,e)});const r="function"==typeof e.getSetCookie?e.getSetCookie():[];for(const e of r)o.append("Set-Cookie",e);if(s)for(const e of s)o.append("Set-Cookie",e);if(t)for(const[e,s]of Object.entries(t))o.set(e,s);return o}Object.defineProperty(exports,"t",{value:!0}),exports.parseCookieFromRequest=parseCookieFromRequest,exports.extractClientIpFromRequest=extractClientIpFromRequest,exports.extractDeviceIdFromRequest=extractDeviceIdFromRequest,exports.extractDeviceIdFromRequestOrUnknown=extractDeviceIdFromRequestOrUnknown,exports.isSecureWebRequest=isSecureWebRequest,exports.readJsonRequest=readJsonRequest,exports.jsonResponse=jsonResponse,exports.emptyResponse=emptyResponse,exports.bodyResponse=bodyResponse,exports.appendCookieToHeaders=appendCookieToHeaders,exports.mergeResponseHeaders=mergeResponseHeaders;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "unshared-clientjs-sdk",
3
- "version": "3.0.0-rc.14",
3
+ "version": "3.0.0",
4
4
  "description": "Server-side Node.js SDK for the Unshared Labs API",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/esm/index.mjs",
@@ -52,7 +52,7 @@
52
52
  "author": "",
53
53
  "license": "MIT",
54
54
  "dependencies": {
55
- "unshared-frontend-sdk": "3.0.0-rc.14"
55
+ "unshared-frontend-sdk": "3.0.0"
56
56
  },
57
57
  "devDependencies": {
58
58
  "@types/express": "^4.17.21",