unshared-clientjs-sdk 2.2.0 → 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/client.d.ts CHANGED
@@ -185,6 +185,8 @@ export declare class UnsharedClient {
185
185
  checkUser(emailAddress: string, opts: {
186
186
  deviceId?: string;
187
187
  fingerprintId?: string;
188
+ timeoutMs?: number;
189
+ maxRetries?: number;
188
190
  }): Promise<ApiResult<CheckUserResult>>;
189
191
  /**
190
192
  * Send a 6-digit verification code to the user's email address.
package/dist/client.js CHANGED
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"t",{value:!0}),exports.UnsharedClient=void 0;const crypto_1=require("crypto"),util_1=require("./util"),DEFAULT_BASE_URL="https://api.unshared.ai",DEFAULT_TIMEOUT_MS=1e4,DEFAULT_MAX_RETRIES=3,MAX_DELAY_MS=3e4,BASE_DELAY_MS=1e3;function sleep(e){return new Promise(s=>setTimeout(s,e))}function retryDelay(e){const s=Math.min(1e3*Math.pow(2,e-1),3e4),t=s*(.5*Math.random()-.25);return Math.max(0,s+t)}async function parseErrorBody(e){const s=await e.text().catch(()=>"");try{const t=JSON.parse(s);return t?.error?.code?{code:t.error.code,message:t.error.message??"Unknown error",details:t.error.details}:{code:"UNKNOWN_ERROR",message:s||e.statusText}}catch{return{code:"UNKNOWN_ERROR",message:s||e.statusText}}}class UnsharedClient{constructor(e){if(!e.apiKey||""===e.apiKey.trim())throw new Error("apiKey is required");this.i=e.apiKey,this.o=e.baseUrl?e.baseUrl.replace(/\/$/,""):DEFAULT_BASE_URL,this.h=e.timeout??1e4,this.l=e.maxRetries??3,this.u=(0,crypto_1.createHash)("sha256").update(e.apiKey).digest(),this._=e.fetch}p(e){return(0,util_1.encryptData)(e,this.u)}async m(e,s){const t=this.l+1;let i={success:!1,status:0,error:{code:"NETWORK_ERROR",message:"Request failed"}};for(let r=1;r<=t;r++){r>1&&await sleep(retryDelay(r-1));const t=new AbortController,a=setTimeout(()=>t.abort(),this.h);try{const r=this._??globalThis.fetch,n=await r(e,{method:s.method,headers:{"X-API-Key":this.i,...s.headers},body:s.body,signal:t.signal});if(clearTimeout(a),n.ok){const e=await n.text().catch(()=>"{}");let s;try{s=JSON.parse(e)}catch{s={}}const t="data"in s?s.data:s;return{success:!0,status:n.status,data:t}}const o=await parseErrorBody(n);if(n.status>=400&&n.status<500){if(429===n.status){const e=n.headers.get("Retry-After");if(null!=e){const s=parseInt(e,10);isNaN(s)||(o.retryAfter=s)}}return{success:!1,status:n.status,error:o}}i={success:!1,status:n.status,error:o}}catch(e){clearTimeout(a),i={success:!1,status:0,error:{code:"NETWORK_ERROR",message:e instanceof Error?e.message:String(e)}}}}return i}async submitFingerprintEvent(e,s){const t={hash:e.full_hash,stable_hash:e.fingerprint_id,collected_at:e.timestamp,is_incognito:e.isIncognito,components:e.components,version:e.version};return null!=e.source&&(t.source=e.source),null!=s?.userId&&(t.user_id=this.p(s.userId)),null!=s?.emailAddress&&(t.email_address=this.p(s.emailAddress)),null!=s?.sessionHash&&(t.session_hash=s.sessionHash),null!=s?.eventType&&(t.event_type=s.eventType),null!=s?.ipAddress&&(t.ip_address=this.p(s.ipAddress)),null!=s?.userAgent&&(t.user_agent=this.p(s.userAgent)),this.m(`${this.o}/v2/submit-fingerprint-event`,{method:"POST",headers:{"Content-Type":"application/json","X-Idempotency-Key":s?.idempotencyKey??(0,crypto_1.randomUUID)()},body:JSON.stringify(t)})}async processUserEvent(e){const s={event_type:e.eventType,user_id:this.p(e.userId),ip_address:this.p(e.ipAddress),device_id:this.p(e.deviceId),session_hash:e.sessionHash,user_agent:this.p(e.userAgent),email_address:this.p(e.emailAddress)};return null!=e.fingerprintId&&(s.fingerprint_id=this.p(e.fingerprintId)),null!=e.subscriptionStatus&&(s.subscription_status=e.subscriptionStatus),null!=e.eventDetails&&(s.event_details=e.eventDetails),this.m(`${this.o}/v2/process-user-event`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s)})}async checkUser(e,s){const t="string"==typeof s?{deviceId:s}:s;if(!t.deviceId&&!t.fingerprintId)return{success:!0,status:200,data:{is_user_flagged:!1},failedOpen:{status:0,reason:"no_device_id"}};const i=new URLSearchParams;i.set("email_address",this.p(e)),t.deviceId&&i.set("device_id",this.p(t.deviceId)),t.fingerprintId&&i.set("fingerprint_id",this.p(t.fingerprintId));const r=await this.m(`${this.o}/v2/check-user?${i}`,{method:"GET"});return r.success?r:{success:!0,status:200,data:{is_user_flagged:!1},failedOpen:{status:r.status}}}async triggerEmailVerification(e,s,t){const i={email_address:this.p(e),device_id:this.p(s)};t?.fingerprintId&&(i.fingerprint_id=this.p(t.fingerprintId));const r=await this.m(`${this.o}/v2/trigger-email-verification`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(i)});return!r.success&&(0===r.status||r.status>=500)?{success:!1,status:r.status,error:{code:"DELIVERY_FAILED",message:r.error?.message??"Delivery failed"}}:r}async verify(e,s,t,i){const r={email_address:this.p(e),device_id:this.p(s),code:this.p(t)};i?.fingerprintId&&(r.fingerprint_id=this.p(i.fingerprintId));const a=await this.m(`${this.o}/v2/verify`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(r)});return!a.success&&(0===a.status||a.status>=500)?{success:!1,status:a.status,error:{code:"DELIVERY_FAILED",message:a.error?.message??"Delivery failed"}}:a.success&&!1===a.data?.verified?{success:!1,status:a.status,error:{code:"VERIFICATION_FAILED",message:"Code is incorrect or expired",details:a.data.reason?{reason:a.data.reason}:void 0}}:a}async getInterstitialFlow(e){const s=new URLSearchParams;return s.set("flow_type",e?.flowType||"email_verification"),s.set("platform",e?.platform||"web"),this.m(`${this.o}/v2/interstitial-flow?${s}`,{method:"GET"})}}exports.UnsharedClient=UnsharedClient;
1
+ "use strict";Object.defineProperty(exports,"t",{value:!0}),exports.UnsharedClient=void 0;const crypto_1=require("crypto"),util_1=require("./util"),DEFAULT_BASE_URL="https://api.unshared.ai",DEFAULT_TIMEOUT_MS=1e4,DEFAULT_MAX_RETRIES=3,MAX_DELAY_MS=3e4,BASE_DELAY_MS=1e3;function sleep(e){return new Promise(t=>setTimeout(t,e))}function retryDelay(e){const t=Math.min(1e3*Math.pow(2,e-1),3e4),s=t*(.5*Math.random()-.25);return Math.max(0,t+s)}async function parseErrorBody(e){const t=await e.text().catch(()=>"");try{const s=JSON.parse(t);return s?.error?.code?{code:s.error.code,message:s.error.message??"Unknown error",details:s.error.details}:{code:"UNKNOWN_ERROR",message:t||e.statusText}}catch{return{code:"UNKNOWN_ERROR",message:t||e.statusText}}}class UnsharedClient{constructor(e){if(!e.apiKey||""===e.apiKey.trim())throw new Error("apiKey is required");this.i=e.apiKey,this.o=e.baseUrl?e.baseUrl.replace(/\/$/,""):DEFAULT_BASE_URL,this.h=e.timeout??1e4,this.u=e.maxRetries??3,this.l=(0,crypto_1.createHash)("sha256").update(e.apiKey).digest(),this._=e.fetch}p(e){return(0,util_1.encryptData)(e,this.l)}async m(e,t,s){const i=(s?.maxRetries??this.u)+1,r=s?.timeoutMs??this.h;let a={success:!1,status:0,error:{code:"NETWORK_ERROR",message:"Request failed"}};for(let s=1;s<=i;s++){s>1&&await sleep(retryDelay(s-1));const i=new AbortController,n=setTimeout(()=>i.abort(),r);try{const s=this._??globalThis.fetch,r=await s(e,{method:t.method,headers:{"X-API-Key":this.i,...t.headers},body:t.body,signal:i.signal});if(clearTimeout(n),r.ok){const e=await r.text().catch(()=>"{}");let t;try{t=JSON.parse(e)}catch{t={}}const s="data"in t?t.data:t;return{success:!0,status:r.status,data:s}}const o=await parseErrorBody(r);if(r.status>=400&&r.status<500){if(429===r.status){const e=r.headers.get("Retry-After");if(null!=e){const t=parseInt(e,10);isNaN(t)||(o.retryAfter=t)}}return{success:!1,status:r.status,error:o}}a={success:!1,status:r.status,error:o}}catch(e){clearTimeout(n),a={success:!1,status:0,error:{code:"NETWORK_ERROR",message:e instanceof Error?e.message:String(e)}}}}return a}async submitFingerprintEvent(e,t){const s={hash:e.full_hash,stable_hash:e.fingerprint_id,collected_at:e.timestamp,is_incognito:e.isIncognito,components:e.components,version:e.version};return null!=e.source&&(s.source=e.source),null!=t?.userId&&(s.user_id=this.p(t.userId)),null!=t?.emailAddress&&(s.email_address=this.p(t.emailAddress)),null!=t?.sessionHash&&(s.session_hash=t.sessionHash),null!=t?.eventType&&(s.event_type=t.eventType),null!=t?.ipAddress&&(s.ip_address=this.p(t.ipAddress)),null!=t?.userAgent&&(s.user_agent=this.p(t.userAgent)),this.m(`${this.o}/v2/submit-fingerprint-event`,{method:"POST",headers:{"Content-Type":"application/json","X-Idempotency-Key":t?.idempotencyKey??(0,crypto_1.randomUUID)()},body:JSON.stringify(s)})}async processUserEvent(e){const t={event_type:e.eventType,user_id:this.p(e.userId),ip_address:this.p(e.ipAddress),device_id:this.p(e.deviceId),session_hash:e.sessionHash,user_agent:this.p(e.userAgent),email_address:this.p(e.emailAddress)};return null!=e.fingerprintId&&(t.fingerprint_id=this.p(e.fingerprintId)),null!=e.subscriptionStatus&&(t.subscription_status=e.subscriptionStatus),null!=e.eventDetails&&(t.event_details=e.eventDetails),this.m(`${this.o}/v2/process-user-event`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})}async checkUser(e,t){const s="string"==typeof t?{deviceId:t}:t;if(!s.deviceId&&!s.fingerprintId)return{success:!0,status:200,data:{is_user_flagged:!1},failedOpen:{status:0,reason:"no_device_id"}};const i=new URLSearchParams;i.set("email_address",this.p(e)),s.deviceId&&i.set("device_id",this.p(s.deviceId)),s.fingerprintId&&i.set("fingerprint_id",this.p(s.fingerprintId));const r=await this.m(`${this.o}/v2/check-user?${i}`,{method:"GET"},{timeoutMs:s.timeoutMs,maxRetries:s.maxRetries});return r.success?r:{success:!0,status:200,data:{is_user_flagged:!1},failedOpen:{status:r.status}}}async triggerEmailVerification(e,t,s){const i={email_address:this.p(e),device_id:this.p(t)};s?.fingerprintId&&(i.fingerprint_id=this.p(s.fingerprintId));const r=await this.m(`${this.o}/v2/trigger-email-verification`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(i)});return!r.success&&(0===r.status||r.status>=500)?{success:!1,status:r.status,error:{code:"DELIVERY_FAILED",message:r.error?.message??"Delivery failed"}}:r}async verify(e,t,s,i){const r={email_address:this.p(e),device_id:this.p(t),code:this.p(s)};i?.fingerprintId&&(r.fingerprint_id=this.p(i.fingerprintId));const a=await this.m(`${this.o}/v2/verify`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(r)});return!a.success&&(0===a.status||a.status>=500)?{success:!1,status:a.status,error:{code:"DELIVERY_FAILED",message:a.error?.message??"Delivery failed"}}:a.success&&!1===a.data?.verified?{success:!1,status:a.status,error:{code:"VERIFICATION_FAILED",message:"Code is incorrect or expired",details:a.data.reason?{reason:a.data.reason}:void 0}}:a}async getInterstitialFlow(e){const t=new URLSearchParams;return t.set("flow_type",e?.flowType||"email_verification"),t.set("platform",e?.platform||"web"),this.m(`${this.o}/v2/interstitial-flow?${t}`,{method:"GET"})}}exports.UnsharedClient=UnsharedClient;
@@ -185,6 +185,8 @@ export declare class UnsharedClient {
185
185
  checkUser(emailAddress: string, opts: {
186
186
  deviceId?: string;
187
187
  fingerprintId?: string;
188
+ timeoutMs?: number;
189
+ maxRetries?: number;
188
190
  }): Promise<ApiResult<CheckUserResult>>;
189
191
  /**
190
192
  * Send a 6-digit verification code to the user's email address.
@@ -1 +1 @@
1
- import{createHash,randomUUID}from"crypto";import{encryptData}from"./util";const DEFAULT_BASE_URL="https://api.unshared.ai",DEFAULT_TIMEOUT_MS=1e4,DEFAULT_MAX_RETRIES=3,MAX_DELAY_MS=3e4,BASE_DELAY_MS=1e3;function sleep(e){return new Promise(t=>setTimeout(t,e))}function retryDelay(e){const t=Math.min(1e3*Math.pow(2,e-1),3e4),s=t*(.5*Math.random()-.25);return Math.max(0,t+s)}async function parseErrorBody(e){const t=await e.text().catch(()=>"");try{const s=JSON.parse(t);return s?.error?.code?{code:s.error.code,message:s.error.message??"Unknown error",details:s.error.details}:{code:"UNKNOWN_ERROR",message:t||e.statusText}}catch{return{code:"UNKNOWN_ERROR",message:t||e.statusText}}}export class UnsharedClient{constructor(e){if(!e.apiKey||""===e.apiKey.trim())throw new Error("apiKey is required");this.t=e.apiKey,this.i=e.baseUrl?e.baseUrl.replace(/\/$/,""):DEFAULT_BASE_URL,this.o=e.timeout??1e4,this.h=e.maxRetries??3,this.l=createHash("sha256").update(e.apiKey).digest(),this.u=e.fetch}_(e){return encryptData(e,this.l)}async p(e,t){const s=this.h+1;let r={success:!1,status:0,error:{code:"NETWORK_ERROR",message:"Request failed"}};for(let i=1;i<=s;i++){i>1&&await sleep(retryDelay(i-1));const s=new AbortController,a=setTimeout(()=>s.abort(),this.o);try{const i=this.u??globalThis.fetch,n=await i(e,{method:t.method,headers:{"X-API-Key":this.t,...t.headers},body:t.body,signal:s.signal});if(clearTimeout(a),n.ok){const e=await n.text().catch(()=>"{}");let t;try{t=JSON.parse(e)}catch{t={}}const s="data"in t?t.data:t;return{success:!0,status:n.status,data:s}}const o=await parseErrorBody(n);if(n.status>=400&&n.status<500){if(429===n.status){const e=n.headers.get("Retry-After");if(null!=e){const t=parseInt(e,10);isNaN(t)||(o.retryAfter=t)}}return{success:!1,status:n.status,error:o}}r={success:!1,status:n.status,error:o}}catch(e){clearTimeout(a),r={success:!1,status:0,error:{code:"NETWORK_ERROR",message:e instanceof Error?e.message:String(e)}}}}return r}async submitFingerprintEvent(e,t){const s={hash:e.full_hash,stable_hash:e.fingerprint_id,collected_at:e.timestamp,is_incognito:e.isIncognito,components:e.components,version:e.version};return null!=e.source&&(s.source=e.source),null!=t?.userId&&(s.user_id=this._(t.userId)),null!=t?.emailAddress&&(s.email_address=this._(t.emailAddress)),null!=t?.sessionHash&&(s.session_hash=t.sessionHash),null!=t?.eventType&&(s.event_type=t.eventType),null!=t?.ipAddress&&(s.ip_address=this._(t.ipAddress)),null!=t?.userAgent&&(s.user_agent=this._(t.userAgent)),this.p(`${this.i}/v2/submit-fingerprint-event`,{method:"POST",headers:{"Content-Type":"application/json","X-Idempotency-Key":t?.idempotencyKey??randomUUID()},body:JSON.stringify(s)})}async processUserEvent(e){const t={event_type:e.eventType,user_id:this._(e.userId),ip_address:this._(e.ipAddress),device_id:this._(e.deviceId),session_hash:e.sessionHash,user_agent:this._(e.userAgent),email_address:this._(e.emailAddress)};return null!=e.fingerprintId&&(t.fingerprint_id=this._(e.fingerprintId)),null!=e.subscriptionStatus&&(t.subscription_status=e.subscriptionStatus),null!=e.eventDetails&&(t.event_details=e.eventDetails),this.p(`${this.i}/v2/process-user-event`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})}async checkUser(e,t){const s="string"==typeof t?{deviceId:t}:t;if(!s.deviceId&&!s.fingerprintId)return{success:!0,status:200,data:{is_user_flagged:!1},failedOpen:{status:0,reason:"no_device_id"}};const r=new URLSearchParams;r.set("email_address",this._(e)),s.deviceId&&r.set("device_id",this._(s.deviceId)),s.fingerprintId&&r.set("fingerprint_id",this._(s.fingerprintId));const i=await this.p(`${this.i}/v2/check-user?${r}`,{method:"GET"});return i.success?i:{success:!0,status:200,data:{is_user_flagged:!1},failedOpen:{status:i.status}}}async triggerEmailVerification(e,t,s){const r={email_address:this._(e),device_id:this._(t)};s?.fingerprintId&&(r.fingerprint_id=this._(s.fingerprintId));const i=await this.p(`${this.i}/v2/trigger-email-verification`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(r)});return!i.success&&(0===i.status||i.status>=500)?{success:!1,status:i.status,error:{code:"DELIVERY_FAILED",message:i.error?.message??"Delivery failed"}}:i}async verify(e,t,s,r){const i={email_address:this._(e),device_id:this._(t),code:this._(s)};r?.fingerprintId&&(i.fingerprint_id=this._(r.fingerprintId));const a=await this.p(`${this.i}/v2/verify`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(i)});return!a.success&&(0===a.status||a.status>=500)?{success:!1,status:a.status,error:{code:"DELIVERY_FAILED",message:a.error?.message??"Delivery failed"}}:a.success&&!1===a.data?.verified?{success:!1,status:a.status,error:{code:"VERIFICATION_FAILED",message:"Code is incorrect or expired",details:a.data.reason?{reason:a.data.reason}:void 0}}:a}async getInterstitialFlow(e){const t=new URLSearchParams;return t.set("flow_type",e?.flowType||"email_verification"),t.set("platform",e?.platform||"web"),this.p(`${this.i}/v2/interstitial-flow?${t}`,{method:"GET"})}}
1
+ import{createHash,randomUUID}from"crypto";import{encryptData}from"./util";const DEFAULT_BASE_URL="https://api.unshared.ai",DEFAULT_TIMEOUT_MS=1e4,DEFAULT_MAX_RETRIES=3,MAX_DELAY_MS=3e4,BASE_DELAY_MS=1e3;function sleep(e){return new Promise(t=>setTimeout(t,e))}function retryDelay(e){const t=Math.min(1e3*Math.pow(2,e-1),3e4),s=t*(.5*Math.random()-.25);return Math.max(0,t+s)}async function parseErrorBody(e){const t=await e.text().catch(()=>"");try{const s=JSON.parse(t);return s?.error?.code?{code:s.error.code,message:s.error.message??"Unknown error",details:s.error.details}:{code:"UNKNOWN_ERROR",message:t||e.statusText}}catch{return{code:"UNKNOWN_ERROR",message:t||e.statusText}}}export class UnsharedClient{constructor(e){if(!e.apiKey||""===e.apiKey.trim())throw new Error("apiKey is required");this.t=e.apiKey,this.i=e.baseUrl?e.baseUrl.replace(/\/$/,""):DEFAULT_BASE_URL,this.o=e.timeout??1e4,this.h=e.maxRetries??3,this.l=createHash("sha256").update(e.apiKey).digest(),this.u=e.fetch}_(e){return encryptData(e,this.l)}async m(e,t,s){const r=(s?.maxRetries??this.h)+1,i=s?.timeoutMs??this.o;let a={success:!1,status:0,error:{code:"NETWORK_ERROR",message:"Request failed"}};for(let s=1;s<=r;s++){s>1&&await sleep(retryDelay(s-1));const r=new AbortController,n=setTimeout(()=>r.abort(),i);try{const s=this.u??globalThis.fetch,i=await s(e,{method:t.method,headers:{"X-API-Key":this.t,...t.headers},body:t.body,signal:r.signal});if(clearTimeout(n),i.ok){const e=await i.text().catch(()=>"{}");let t;try{t=JSON.parse(e)}catch{t={}}const s="data"in t?t.data:t;return{success:!0,status:i.status,data:s}}const o=await parseErrorBody(i);if(i.status>=400&&i.status<500){if(429===i.status){const e=i.headers.get("Retry-After");if(null!=e){const t=parseInt(e,10);isNaN(t)||(o.retryAfter=t)}}return{success:!1,status:i.status,error:o}}a={success:!1,status:i.status,error:o}}catch(e){clearTimeout(n),a={success:!1,status:0,error:{code:"NETWORK_ERROR",message:e instanceof Error?e.message:String(e)}}}}return a}async submitFingerprintEvent(e,t){const s={hash:e.full_hash,stable_hash:e.fingerprint_id,collected_at:e.timestamp,is_incognito:e.isIncognito,components:e.components,version:e.version};return null!=e.source&&(s.source=e.source),null!=t?.userId&&(s.user_id=this._(t.userId)),null!=t?.emailAddress&&(s.email_address=this._(t.emailAddress)),null!=t?.sessionHash&&(s.session_hash=t.sessionHash),null!=t?.eventType&&(s.event_type=t.eventType),null!=t?.ipAddress&&(s.ip_address=this._(t.ipAddress)),null!=t?.userAgent&&(s.user_agent=this._(t.userAgent)),this.m(`${this.i}/v2/submit-fingerprint-event`,{method:"POST",headers:{"Content-Type":"application/json","X-Idempotency-Key":t?.idempotencyKey??randomUUID()},body:JSON.stringify(s)})}async processUserEvent(e){const t={event_type:e.eventType,user_id:this._(e.userId),ip_address:this._(e.ipAddress),device_id:this._(e.deviceId),session_hash:e.sessionHash,user_agent:this._(e.userAgent),email_address:this._(e.emailAddress)};return null!=e.fingerprintId&&(t.fingerprint_id=this._(e.fingerprintId)),null!=e.subscriptionStatus&&(t.subscription_status=e.subscriptionStatus),null!=e.eventDetails&&(t.event_details=e.eventDetails),this.m(`${this.i}/v2/process-user-event`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})}async checkUser(e,t){const s="string"==typeof t?{deviceId:t}:t;if(!s.deviceId&&!s.fingerprintId)return{success:!0,status:200,data:{is_user_flagged:!1},failedOpen:{status:0,reason:"no_device_id"}};const r=new URLSearchParams;r.set("email_address",this._(e)),s.deviceId&&r.set("device_id",this._(s.deviceId)),s.fingerprintId&&r.set("fingerprint_id",this._(s.fingerprintId));const i=await this.m(`${this.i}/v2/check-user?${r}`,{method:"GET"},{timeoutMs:s.timeoutMs,maxRetries:s.maxRetries});return i.success?i:{success:!0,status:200,data:{is_user_flagged:!1},failedOpen:{status:i.status}}}async triggerEmailVerification(e,t,s){const r={email_address:this._(e),device_id:this._(t)};s?.fingerprintId&&(r.fingerprint_id=this._(s.fingerprintId));const i=await this.m(`${this.i}/v2/trigger-email-verification`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(r)});return!i.success&&(0===i.status||i.status>=500)?{success:!1,status:i.status,error:{code:"DELIVERY_FAILED",message:i.error?.message??"Delivery failed"}}:i}async verify(e,t,s,r){const i={email_address:this._(e),device_id:this._(t),code:this._(s)};r?.fingerprintId&&(i.fingerprint_id=this._(r.fingerprintId));const a=await this.m(`${this.i}/v2/verify`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(i)});return!a.success&&(0===a.status||a.status>=500)?{success:!1,status:a.status,error:{code:"DELIVERY_FAILED",message:a.error?.message??"Delivery failed"}}:a.success&&!1===a.data?.verified?{success:!1,status:a.status,error:{code:"VERIFICATION_FAILED",message:"Code is incorrect or expired",details:a.data.reason?{reason:a.data.reason}:void 0}}:a}async getInterstitialFlow(e){const t=new URLSearchParams;return t.set("flow_type",e?.flowType||"email_verification"),t.set("platform",e?.platform||"web"),this.m(`${this.i}/v2/interstitial-flow?${t}`,{method:"GET"})}}
@@ -1 +1 @@
1
- import{readFileSync}from"fs";import{VerdictCache}from"./verdict-cache";import{RateLimitBackoff}from"./rate-limit-backoff";import{DispatchDedupe}from"./dispatch-dedupe";import{interceptResponse}from"./response-interceptor";import{generateFingerprintScript}from"./injection/fingerprint-script";import{createFingerprintBundleSource}from"./injection/fp-bundle-source";import{handleSubmitFingerprint}from"./routes/submit-fp";import{handleVerifyTrigger,handleVerify}from"./routes/verify";import{handleGetInterstitialFlow}from"./routes/interstitial";import{generateGatePage}from"./injection/gate-page";import{sendJson,sendEmpty,sendBody,getRequestPath}from"./utils/http-helpers";import{isHtmlContentType,isHtmlNavigation}from"./utils/content-type";import{flaggedResponse}from"./utils/flagged-response";import{maskEmail}from"./utils/mask-email";import{shouldSkipPath}from"./utils/skip-paths";import{shouldIncludePath}from"./utils/include-path";import{isBot}from"./utils/is-bot";import{extractClientIp}from"./utils/client-ip";import{parseCookie}from"./utils/cookies";import{extractDeviceIdOrUndefined}from"./utils/device-id";import{isSecureRequest}from"./utils/secure";import{isSentinelUserId,SENTINEL_STICKINESS_TTL_MS}from"./utils/sentinel-user-id";export{VerdictCache};export{flaggedResponse,ACCOUNT_FLAGGED_ERROR}from"./utils/flagged-response";const CHECK_USER_TIMEOUT_MS=1500,FAIL_OPEN_TTL_MS=5e3,_lastFailOpenWarn={};function warnFailOpenThrottled(e){const t=Date.now();t-(_lastFailOpenWarn[e.reason]??0)<6e4||(_lastFailOpenWarn[e.reason]=t,console.warn(`[Unshared] Failing open (reason=${e.reason}${null!=e.status?`, status=${e.status}`:""}). Detection is not enforced for this request. Configure onFailOpen to observe this directly.`))}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:s,cacheTTL:o=6e4,skipPaths:a,includePathPrefix:d,disableBotFilter:c=!1,checkUserTimeoutMs:l=CHECK_USER_TIMEOUT_MS,cacheMissStrategy:u="block",sessionId:p,deviceId:f,onFlagged:m,onError:h,onFailOpen:g,blockFlagged:I=!1,flaggedMode:v,autoInterstitial:S=!1,interstitialFlowType:_="email_verification"}=t,C=v??(I?"gate":void 0),y=S||"overlay"===C,k=e=>{if(g)try{g(e)}catch{}else"async_miss"!==e.reason&&warnFailOpenThrottled(e)},A=new VerdictCache(o),T=new RateLimitBackoff,E=new DispatchDedupe,F=Date.now().toString(36),U=generateFingerprintScript(n,F,{autoInterstitial:y,interstitialFlowType:_});let x="";try{const e=require.resolve("unshared-frontend-sdk/dist/index.umd.js");x=readFileSync(e,"utf8")}catch{}const w=createFingerprintBundleSource(),O=void 0!==C||y,P="gate"===C?"flaggedMode 'gate' (blockFlagged)":"overlay"===C?"flaggedMode 'overlay'":"autoInterstitial";if(O&&!x)throw new Error(`[Unshared] ${P} requires unshared-frontend-sdk to be installed (its UMD bundle renders the interstitial).`);if(O&&"/__unshared"!==n)throw new Error(`[Unshared] ${P} requires the default routePrefix ("/__unshared"); the browser SDK proxy routes are fixed to that prefix.`);const b="gate"===C?generateGatePage(n,F):"",R=handleSubmitFingerprint({client:e,verdictCache:A,rateLimitBackoff:T,dispatchDedupe:E,resolveUserId:r,resolveEmailAddress:i,resolveSessionId:p,resolveDeviceId:f,disableBotFilter:c,onError:h}),D=handleVerifyTrigger({client:e,verdictCache:A,resolveEmailAddress:i,resolveDeviceId:f,onError:h}),M=handleVerify({client:e,verdictCache:A,resolveUserId:r,resolveEmailAddress:i,resolveDeviceId:f,onError:h}),N=handleGetInterstitialFlow({client:e}),$=s?Array.isArray(s)?s:[s]:null,V=`${n}/fp.js`,L=`${n}/fingerprint.js`,j=`${n}/submit-fp`,B=`${n}/verify-trigger`,q=`${n}/verify`,G=`${n}/status`,H=`${n}/interstitial-flow`,J=function(t,s,o){const g=getRequestPath(t.url),I=t.url||g;if(g.startsWith(n+"/")){if(function(e,t){if(!$)return;const r=e.headers.origin??"";$.includes("*")?(t.setHeader("Access-Control-Allow-Origin","*"),t.setHeader("Access-Control-Allow-Methods","GET, POST, OPTIONS"),t.setHeader("Access-Control-Allow-Headers","Content-Type, X-Idempotency-Key, X-Session-Id, X-Device-Id")):$.includes(r)&&(t.setHeader("Access-Control-Allow-Origin",r),t.setHeader("Vary","Origin"),t.setHeader("Access-Control-Allow-Methods","GET, 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,s),"OPTIONS"===t.method)return void sendEmpty(s,204);if("GET"===t.method&&g===V)return s.setHeader("Content-Type","application/javascript"),s.setHeader("Cache-Control","public, max-age=3600"),void sendBody(s,200,x);if("GET"===t.method&&g===L){const e=w.get();return e?(s.setHeader("Content-Type","application/javascript"),s.setHeader("Cache-Control","public, max-age=3600"),void sendBody(s,200,e)):void sendBody(s,503,"/* fingerprint agent unavailable */")}if("POST"===t.method&&(g===j||g===B||g===q))return void 0===t.body?void sendJson(s,400,{success:!1,error:{code:"BODY_PARSER_MISSING",message:"req.body is undefined. Mount a JSON body-parsing middleware (e.g., express.json()) before the Unshared middleware."}}):g===j?void R(t,s):g===B?void D(t,s):void M(t,s);if("GET"===t.method&&g===H)return void N(t,s);if("GET"===t.method&&g===G){let n;try{n=r(t)}catch{}if(!n)return void sendJson(s,200,{status:"anonymous"});const o=resolveEmail(t,i);return void(async()=>{let r=A.get(n);if((!r||A.isStale(n))&&o&&!T.isPaused()&&!A.isRefreshing(n)){A.markRefreshing(n);try{const i=extractDeviceIdOrUndefined(t,f),s=extractFingerprintId(t),a=extractSessionId(t,p),d=i??s??"unknown";await fetchAndCacheVerdict(e,A,n,o,d,s,a,l,(e,t)=>k({operation:"checkUser",reason:e,status:t,userId:n,emailAddress:o})),r=A.get(n)}catch(e){h&&h(e,{operation:"checkUser",userId:n,emailAddress:o}),k({operation:"checkUser",reason:"exception",userId:n,emailAddress:o})}finally{A.clearRefreshing(n)}}r&&r.isFlagged&&!r.isVerified&&(void 0!==C||m)&&o?sendJson(s,200,{status:"flagged",email:maskEmail(o)}):sendJson(s,200,{status:"ok"})})()}return void sendJson(s,404,{success:!1,error:{code:"NOT_FOUND",message:"Unknown route"}})}if(shouldSkipPath(g,a))return void o();if(!shouldIncludePath(g,d))return interceptForInjection(t,s,U),void o();let v;try{v=r(t)}catch{}if(isSentinelUserId(v)){const e=parseCookie(t,"__unshared_uid"),r=parseCookie(t,"__unshared_uid_at"),i=r?Number(r):NaN,n=Number.isFinite(i)&&Date.now()-i<=SENTINEL_STICKINESS_TTL_MS;v=e&&n?e:void 0}if(!v){const e=isSecureRequest(t)?"; Secure":"";return appendSetCookie(s,`__unshared_uid=; Path=/; SameSite=Lax; Max-Age=0${e}`),appendSetCookie(s,`__unshared_uid_at=; Path=/; SameSite=Lax; Max-Age=0${e}`),appendSetCookie(s,`__unshared_sid=; Path=/; SameSite=Lax; Max-Age=0${e}`),appendSetCookie(s,`__unshared_email=; Path=/; SameSite=Lax; Max-Age=0${e}`),interceptForInjection(t,s,U),void o()}const S=resolveEmail(t,i);if(setUserIdCookie(t,s,v),S&&setEmailCookie(t,s,S),!S)return interceptForInjection(t,s,U),void o();const _=extractSessionId(t,p),y=extractDeviceIdOrUndefined(t,f),F=extractFingerprintId(t),O=t.headers["user-agent"]??"",P=extractClientIp(t),J=y??F;if(!c&&isBot(O))return void o();const K=A.get(v);if(!K)return"async"===u?(A.isRefreshing(v)||(A.markRefreshing(v),fetchAndCacheVerdict(e,A,v,S,J??"unknown",F,_,l,(e,t)=>k({operation:"checkUser",reason:e,status:t,userId:v,emailAddress:S})).catch(()=>k({operation:"checkUser",reason:"exception",userId:v,emailAddress:S})).finally(()=>A.clearRefreshing(v))),k({operation:"checkUser",reason:"async_miss",userId:v,emailAddress:S}),X(),interceptForInjection(t,s,U),void o()):void fetchAndCacheVerdict(e,A,v,S,J??"unknown",F,_,l,(e,t)=>k({operation:"checkUser",reason:e,status:t,userId:v,emailAddress:S})).then(e=>{e.isFlagged||X(),applyVerdict(e,v,S,t,s,o,U,m,C,b)}).catch(()=>{k({operation:"checkUser",reason:"exception",userId:v,emailAddress:S}),X(),interceptForInjection(t,s,U),o()});function X(){"unknown"!==_&&J&&(T.isPaused()||dispatchUserEvent(e,A,T,E,{userId:v,emailAddress:S,sessionId:_,deviceId:J,fingerprintId:F,userAgent:O,ipAddress:P,eventType:I},h))}A.isStale(v)&&!A.isRefreshing(v)&&(A.markRefreshing(v),fetchAndCacheVerdict(e,A,v,S,J??"unknown",F,_,l,(e,t)=>k({operation:"checkUser",reason:e,status:t,userId:v,emailAddress:S})).catch(()=>k({operation:"checkUser",reason:"exception",userId:v,emailAddress:S})).finally(()=>A.clearRefreshing(v))),K.isFlagged||X(),applyVerdict(K,v,S,t,s,o,U,m,C,b)};return J.destroy=()=>{A.destroy(),w.stop()},J}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,s,o,a,d,c){const l=e.isFlagged&&!e.isVerified;if("gate"===d&&l)isHtmlNavigation(i.method,i.headers.accept)?(n.statusCode=200,n.setHeader("Content-Type","text/html; charset=utf-8"),n.setHeader("Cache-Control","no-store"),n.end(c)):sendJson(n,403,flaggedResponse(r));else if("overlay"===d&&l)isHtmlNavigation(i.method,i.headers.accept)?(interceptForInjection(i,n,o),s()):sendJson(n,403,flaggedResponse(r));else if(interceptForInjection(i,n,o),e.isFlagged&&!e.isVerified&&a)try{a({userId:t,emailAddress:r,verdict:e,req:i,res:n,next:s})}catch{s()}else s()}function interceptForInjection(e,t,r){delete e.headers["if-none-match"],delete e.headers["if-modified-since"],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)},{preventCaching:!0})}function dispatchUserEvent(e,t,r,i,n,s){i.mark(n.userId,n.eventType),e.processUserEvent({eventType:n.eventType,userId:n.userId,emailAddress:n.emailAddress,ipAddress:n.ipAddress,deviceId:n.deviceId,fingerprintId:n.fingerprintId,sessionHash:n.sessionId,userAgent:n.userAgent}).then(e=>{e.success&&e.data?.analysis&&t.update(n.userId,{isFlagged:e.data.analysis.is_user_flagged}),!e.success&&e.error?.retryAfter&&r.pause(1e3*e.error.retryAfter)}).catch(e=>{s&&s(e,{operation:"processUserEvent",userId:n.userId,emailAddress:n.emailAddress})})}async function fetchAndCacheVerdict(e,t,r,i,n,s,o,a=CHECK_USER_TIMEOUT_MS,d){const c={},l=n&&"unknown"!==n?n:void 0,u=l??s,p=s??l;let f;u&&(c.deviceId=u),p&&(c.fingerprintId=p);const m=await Promise.race([e.checkUser(i,c),new Promise(e=>{f=setTimeout(()=>e(null),a)})]);return clearTimeout(f),m?m.failedOpen?(d?.(m.failedOpen.reason??"http_error",m.failedOpen.status),cacheFailOpenVerdict(t,r,i,o)):(t.set(r,{isFlagged:m.data?.is_user_flagged??!1,isVerified:!1,emailAddress:i,sessionId:o}),t.get(r)):(d?.("timeout"),cacheFailOpenVerdict(t,r,i,o))}function cacheFailOpenVerdict(e,t,r,i){const n=e.get(t);return e.set(t,{isFlagged:n?.isFlagged??!1,isVerified:n?.isVerified??!1,emailAddress:r,sessionId:i},5e3),e.get(t)}function extractSessionId(e,t){if(t)try{const r=t(e);if(r)return r}catch{}return parseCookie(e,"__unshared_sid")??"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,r){const i=isSecureRequest(e)?"; Secure":"";appendSetCookie(t,`__unshared_uid=${encodeURIComponent(r)}; Path=/; SameSite=Lax${i}`),appendSetCookie(t,`__unshared_uid_at=${Date.now()}; Path=/; SameSite=Lax${i}`)}function setEmailCookie(e,t,r){const i=isSecureRequest(e)?"; Secure":"";appendSetCookie(t,`__unshared_email=${encodeURIComponent(r)}; HttpOnly; Path=/; SameSite=Lax${i}`)}
1
+ import{readFileSync}from"fs";import{VerdictCache}from"./verdict-cache";import{RateLimitBackoff}from"./rate-limit-backoff";import{DispatchDedupe}from"./dispatch-dedupe";import{interceptResponse}from"./response-interceptor";import{generateFingerprintScript}from"./injection/fingerprint-script";import{createFingerprintBundleSource}from"./injection/fp-bundle-source";import{handleSubmitFingerprint}from"./routes/submit-fp";import{handleVerifyTrigger,handleVerify}from"./routes/verify";import{handleGetInterstitialFlow}from"./routes/interstitial";import{generateGatePage}from"./injection/gate-page";import{sendJson,sendEmpty,sendBodyForMethod,getRequestPath}from"./utils/http-helpers";import{isHtmlContentType,isHtmlNavigation}from"./utils/content-type";import{flaggedResponse}from"./utils/flagged-response";import{maskEmail}from"./utils/mask-email";import{shouldSkipPath}from"./utils/skip-paths";import{shouldIncludePath}from"./utils/include-path";import{isBot}from"./utils/is-bot";import{extractClientIp}from"./utils/client-ip";import{parseCookie}from"./utils/cookies";import{extractDeviceIdOrUndefined}from"./utils/device-id";import{isSecureRequest}from"./utils/secure";import{isSentinelUserId,SENTINEL_STICKINESS_TTL_MS}from"./utils/sentinel-user-id";export{VerdictCache};export{flaggedResponse,ACCOUNT_FLAGGED_ERROR}from"./utils/flagged-response";const CHECK_USER_TIMEOUT_MS=1500,FAIL_OPEN_TTL_MS=5e3,_lastFailOpenWarn={};function warnFailOpenThrottled(e){const t=Date.now();t-(_lastFailOpenWarn[e.reason]??0)<6e4||(_lastFailOpenWarn[e.reason]=t,console.warn(`[Unshared] Failing open (reason=${e.reason}${null!=e.status?`, status=${e.status}`:""}). Detection is not enforced for this request. Configure onFailOpen to observe this directly.`))}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:a,includePathPrefix:d,disableBotFilter:c=!1,checkUserTimeoutMs:l=CHECK_USER_TIMEOUT_MS,cacheMissStrategy:u="block",sessionId:p,deviceId:f,onFlagged:m,onError:h,onFailOpen:g,blockFlagged:I=!1,flaggedMode:v,autoInterstitial:S=!1,interstitialFlowType:_="email_verification"}=t,C=v??(I?"gate":void 0),y=S||"overlay"===C,k=e=>{if(g)try{g(e)}catch{}else"async_miss"!==e.reason&&warnFailOpenThrottled(e)},A=new VerdictCache(s),T=new RateLimitBackoff,E=new Map,F=new DispatchDedupe,x=Date.now().toString(36),U=generateFingerprintScript(n,x,{autoInterstitial:y,interstitialFlowType:_});let w="";try{const e=require.resolve("unshared-frontend-sdk/dist/index.umd.js");w=readFileSync(e,"utf8")}catch{}const O=createFingerprintBundleSource(),P=void 0!==C||y,M="gate"===C?"flaggedMode 'gate' (blockFlagged)":"overlay"===C?"flaggedMode 'overlay'":"autoInterstitial";if(P&&!w)throw new Error(`[Unshared] ${M} requires unshared-frontend-sdk to be installed (its UMD bundle renders the interstitial).`);if(P&&"/__unshared"!==n)throw new Error(`[Unshared] ${M} requires the default routePrefix ("/__unshared"); the browser SDK proxy routes are fixed to that prefix.`);const b="gate"===C?generateGatePage(n,x):"",D=handleSubmitFingerprint({client:e,verdictCache:A,rateLimitBackoff:T,dispatchDedupe:F,resolveUserId:r,resolveEmailAddress:i,resolveSessionId:p,resolveDeviceId:f,disableBotFilter:c,onError:h}),R=handleVerifyTrigger({client:e,verdictCache:A,resolveEmailAddress:i,resolveDeviceId:f,onError:h}),N=handleVerify({client:e,verdictCache:A,resolveUserId:r,resolveEmailAddress:i,resolveDeviceId:f,onError:h}),$=handleGetInterstitialFlow({client:e}),V=o?Array.isArray(o)?o:[o]:null,L=`${n}/fp.js`,j=`${n}/fingerprint.js`,B=`${n}/submit-fp`,q=`${n}/verify-trigger`,H=`${n}/verify`,G=`${n}/status`,J=`${n}/interstitial-flow`,K=function(t,o,s){const g=getRequestPath(t.url),I=t.url||g;if(g.startsWith(n+"/")){if(function(e,t){if(!V)return;const r=e.headers.origin??"";V.includes("*")?(t.setHeader("Access-Control-Allow-Origin","*"),t.setHeader("Access-Control-Allow-Methods","GET, POST, OPTIONS"),t.setHeader("Access-Control-Allow-Headers","Content-Type, X-Idempotency-Key, X-Session-Id, X-Device-Id")):V.includes(r)&&(t.setHeader("Access-Control-Allow-Origin",r),t.setHeader("Vary","Origin"),t.setHeader("Access-Control-Allow-Methods","GET, 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)return void sendEmpty(o,204);if(("GET"===t.method||"HEAD"===t.method)&&g===L)return o.setHeader("Content-Type","application/javascript"),o.setHeader("Cache-Control","public, max-age=3600"),void sendBodyForMethod(o,t.method,200,w);if(("GET"===t.method||"HEAD"===t.method)&&g===j){const e=O.get();return e?(o.setHeader("Content-Type","application/javascript"),o.setHeader("Cache-Control","public, max-age=3600"),void sendBodyForMethod(o,t.method,200,e)):void sendBodyForMethod(o,t.method,503,"/* fingerprint agent unavailable */")}if("POST"===t.method&&(g===B||g===q||g===H))return void 0===t.body?void sendJson(o,400,{success:!1,error:{code:"BODY_PARSER_MISSING",message:"req.body is undefined. Mount a JSON body-parsing middleware (e.g., express.json()) before the Unshared middleware."}}):g===B?void D(t,o):g===q?void R(t,o):void N(t,o);if("GET"===t.method&&g===J)return void $(t,o);if("GET"===t.method&&g===G){let n;try{n=r(t)}catch{}if(!n)return void sendJson(o,200,{status:"anonymous"});const s=resolveEmail(t,i);return void(async()=>{let r=A.get(n);if((!r||A.isStale(n))&&s&&!T.isPaused()&&!A.isRefreshing(n)){A.markRefreshing(n);try{const i=extractDeviceIdOrUndefined(t,f),o=extractFingerprintId(t),a=extractSessionId(t,p),d=i??o??"unknown";await fetchAndCacheVerdict(e,A,n,s,d,o,a,l,(e,t)=>k({operation:"checkUser",reason:e,status:t,userId:n,emailAddress:s})),r=A.get(n)}catch(e){h&&h(e,{operation:"checkUser",userId:n,emailAddress:s}),k({operation:"checkUser",reason:"exception",userId:n,emailAddress:s})}finally{A.clearRefreshing(n)}}r&&r.isFlagged&&!r.isVerified&&(void 0!==C||m)&&s?sendJson(o,200,{status:"flagged",email:maskEmail(s)}):sendJson(o,200,{status:"ok"})})()}return void sendJson(o,404,{success:!1,error:{code:"NOT_FOUND",message:"Unknown route"}})}if(shouldSkipPath(g,a))return void s();if(!shouldIncludePath(g,d))return interceptForInjection(t,o,U),void s();let v;try{v=r(t)}catch{}if(isSentinelUserId(v)){const e=parseCookie(t,"__unshared_uid"),r=parseCookie(t,"__unshared_uid_at"),i=r?Number(r):NaN,n=Number.isFinite(i)&&Date.now()-i<=SENTINEL_STICKINESS_TTL_MS;v=e&&n?e:void 0}if(!v){const e=isSecureRequest(t)?"; Secure":"";return appendSetCookie(o,`__unshared_uid=; Path=/; SameSite=Lax; Max-Age=0${e}`),appendSetCookie(o,`__unshared_uid_at=; Path=/; SameSite=Lax; Max-Age=0${e}`),appendSetCookie(o,`__unshared_sid=; Path=/; SameSite=Lax; Max-Age=0${e}`),appendSetCookie(o,`__unshared_email=; Path=/; SameSite=Lax; Max-Age=0${e}`),interceptForInjection(t,o,U),void s()}const S=resolveEmail(t,i);if(setUserIdCookie(t,o,v),S&&setEmailCookie(t,o,S),!S)return interceptForInjection(t,o,U),void s();const _=extractSessionId(t,p),y=extractDeviceIdOrUndefined(t,f),x=extractFingerprintId(t),P=t.headers["user-agent"]??"",M=extractClientIp(t),K=y??x;if(!c&&isBot(P))return void s();const X=A.get(v);if(!X){if("async"===u)return A.isRefreshing(v)||(A.markRefreshing(v),fetchAndCacheVerdict(e,A,v,S,K??"unknown",x,_,l,(e,t)=>k({operation:"checkUser",reason:e,status:t,userId:v,emailAddress:S})).catch(()=>k({operation:"checkUser",reason:"exception",userId:v,emailAddress:S})).finally(()=>A.clearRefreshing(v))),k({operation:"checkUser",reason:"async_miss",userId:v,emailAddress:S}),W(),interceptForInjection(t,o,U),void s();let r=E.get(v);return r||(r=fetchAndCacheVerdict(e,A,v,S,K??"unknown",x,_,l,(e,t)=>k({operation:"checkUser",reason:e,status:t,userId:v,emailAddress:S})).finally(()=>E.delete(v)),E.set(v,r)),void r.then(e=>{e.isFlagged||W(),applyVerdict(e,v,S,t,o,s,U,m,C,b)}).catch(()=>{k({operation:"checkUser",reason:"exception",userId:v,emailAddress:S}),W(),interceptForInjection(t,o,U),s()})}function W(){"unknown"!==_&&K&&(T.isPaused()||dispatchUserEvent(e,A,T,F,{userId:v,emailAddress:S,sessionId:_,deviceId:K,fingerprintId:x,userAgent:P,ipAddress:M,eventType:I},h))}A.isStale(v)&&!A.isRefreshing(v)&&(A.markRefreshing(v),fetchAndCacheVerdict(e,A,v,S,K??"unknown",x,_,l,(e,t)=>k({operation:"checkUser",reason:e,status:t,userId:v,emailAddress:S})).catch(()=>k({operation:"checkUser",reason:"exception",userId:v,emailAddress:S})).finally(()=>A.clearRefreshing(v))),X.isFlagged||W(),applyVerdict(X,v,S,t,o,s,U,m,C,b)};return K.destroy=()=>{A.destroy(),O.stop()},K}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,a,d,c){const l=e.isFlagged&&!e.isVerified;if("gate"===d&&l)isHtmlNavigation(i.method,i.headers.accept)?(n.statusCode=200,n.setHeader("Content-Type","text/html; charset=utf-8"),n.setHeader("Cache-Control","no-store"),n.end(c)):sendJson(n,403,flaggedResponse(r));else if("overlay"===d&&l)isHtmlNavigation(i.method,i.headers.accept)?(interceptForInjection(i,n,s),o()):sendJson(n,403,flaggedResponse(r));else if(interceptForInjection(i,n,s),e.isFlagged&&!e.isVerified&&a)try{a({userId:t,emailAddress:r,verdict:e,req:i,res:n,next:o})}catch{o()}else o()}function interceptForInjection(e,t,r){delete e.headers["if-none-match"],delete e.headers["if-modified-since"],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)},{preventCaching:!0})}function dispatchUserEvent(e,t,r,i,n,o){i.mark(n.userId,n.eventType),e.processUserEvent({eventType:n.eventType,userId:n.userId,emailAddress:n.emailAddress,ipAddress:n.ipAddress,deviceId:n.deviceId,fingerprintId:n.fingerprintId,sessionHash:n.sessionId,userAgent:n.userAgent}).then(e=>{e.success&&e.data?.analysis&&t.update(n.userId,{isFlagged:e.data.analysis.is_user_flagged}),!e.success&&e.error?.retryAfter&&r.pause(1e3*e.error.retryAfter)}).catch(e=>{o&&o(e,{operation:"processUserEvent",userId:n.userId,emailAddress:n.emailAddress})})}async function fetchAndCacheVerdict(e,t,r,i,n,o,s,a=CHECK_USER_TIMEOUT_MS,d){const c={timeoutMs:a,maxRetries:0},l=n&&"unknown"!==n?n:void 0,u=l??o,p=o??l;let f;u&&(c.deviceId=u),p&&(c.fingerprintId=p);const m=await Promise.race([e.checkUser(i,c),new Promise(e=>{f=setTimeout(()=>e(null),a)})]);return clearTimeout(f),m?m.failedOpen?(d?.(m.failedOpen.reason??"http_error",m.failedOpen.status),cacheFailOpenVerdict(t,r,i,s)):(t.set(r,{isFlagged:m.data?.is_user_flagged??!1,isVerified:!1,emailAddress:i,sessionId:s}),t.get(r)):(d?.("timeout"),cacheFailOpenVerdict(t,r,i,s))}function cacheFailOpenVerdict(e,t,r,i){const n=e.get(t);return e.set(t,{isFlagged:n?.isFlagged??!1,isVerified:n?.isVerified??!1,emailAddress:r,sessionId:i},5e3),e.get(t)}function extractSessionId(e,t){if(t)try{const r=t(e);if(r)return r}catch{}return parseCookie(e,"__unshared_sid")??"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,r){const i=isSecureRequest(e)?"; Secure":"";appendSetCookie(t,`__unshared_uid=${encodeURIComponent(r)}; Path=/; SameSite=Lax${i}`),appendSetCookie(t,`__unshared_uid_at=${Date.now()}; Path=/; SameSite=Lax${i}`)}function setEmailCookie(e,t,r){const i=isSecureRequest(e)?"; Secure":"";appendSetCookie(t,`__unshared_email=${encodeURIComponent(r)}; HttpOnly; Path=/; SameSite=Lax${i}`)}
@@ -1 +1 @@
1
- const MAX_INJECT_BYTES=2e6;export function interceptResponse(n,t,e){const f=e?.preventCaching??!1,o=n.write.bind(n),u=n.end.bind(n),r=n.writeHead.bind(n),c=[];let i=0,l=!1,s=null;function p(){n.write=o,n.end=u;for(const n of c)o(n);c.length=0}function y(){if(null!==s)return;const t=n.getHeader("content-type");null!=t&&(s=String(t).includes("text/html"),s&&n.getHeader("content-encoding")&&(s=!1),s||p())}n.writeHead=function(t,...e){return y(),f&&s&&(n.setHeader("Cache-Control","no-store"),n.removeHeader("ETag"),n.removeHeader("Last-Modified")),r(t,...e)},n.write=function(n,t,e){if(y(),!1===s)return o(n,t,e);if(null!=n){const f=Buffer.isBuffer(n)?n:Buffer.from(n,"string"==typeof t?t:"utf8");if(i+=f.length,c.push(f),i>2e6)return p(),"function"==typeof t&&t(null),"function"==typeof e&&e(null),!0}return"function"==typeof t&&t(null),"function"==typeof e&&e(null),!0},n.end=function(e,f,r){if(l)return n;if(l=!0,y(),!1===s)return u(e,f,r);if(null!=e){const n=Buffer.isBuffer(e)?e:Buffer.from(e,"string"==typeof f?f:"utf8");i+=n.length,c.push(n)}if(i>2e6){p();const n="function"==typeof f?f:r;return n?u(n):u()}const B=Buffer.concat(c),g=n.getHeader("content-type");let C;try{C=t(B,g)}catch{C=null}if(null!=C){const t=Buffer.isBuffer(C)?C:Buffer.from(C,"utf8");n.setHeader("Content-Length",t.length),n.removeHeader("Content-Encoding"),o(t)}else B.length>0&&o(B);const a="function"==typeof f?f:r;return a?u(a):u(),n}}
1
+ const MAX_INJECT_BYTES=2e6;function findWriteHeadHeaders(e){return e.find(e=>e&&"object"==typeof e)}function readHeaderFromWriteHeadArgs(e,t){const n=findWriteHeadHeaders(e);if(!n)return;const r=t.toLowerCase();if(Array.isArray(n)){if(n.length&&Array.isArray(n[0])){for(const e of n)if(String(e[0]).toLowerCase()===r)return e[1]}else for(let e=0;e+1<n.length;e+=2)if(String(n[e]).toLowerCase()===r)return n[e+1]}else for(const e of Object.keys(n))if(e.toLowerCase()===r)return n[e]}function deleteHeaderFromWriteHeadArgs(e,t){const n=findWriteHeadHeaders(e);if(!n)return;const r=t.toLowerCase();if(Array.isArray(n))if(n.length&&Array.isArray(n[0]))for(let e=n.length-1;e>=0;e--)String(n[e][0]).toLowerCase()===r&&n.splice(e,1);else for(let e=n.length-2;e>=0;e-=2)String(n[e]).toLowerCase()===r&&n.splice(e,2);else for(const e of Object.keys(n))e.toLowerCase()===r&&delete n[e]}export function interceptResponse(e,t,n){const r=n?.preventCaching??!1,o=e.write.bind(e),f=e.end.bind(e),i=e.writeHead.bind(e),c=[];let u=0,l=!1,s=null;function d(){e.write=o,e.end=f;for(const e of c)o(e);c.length=0}function a(e,t){null===s&&null!=e&&(s=String(e).includes("text/html"),s&&t&&(s=!1),!1===s&&d())}function g(){a(e.getHeader("content-type"),e.getHeader("content-encoding"))}e.writeHead=function(t,...n){return a(readHeaderFromWriteHeadArgs(n,"content-type")??e.getHeader("content-type"),readHeaderFromWriteHeadArgs(n,"content-encoding")??e.getHeader("content-encoding")),s&&(r&&(e.setHeader("Cache-Control","no-store"),e.removeHeader("ETag"),e.removeHeader("Last-Modified")),e.removeHeader("Content-Length"),deleteHeaderFromWriteHeadArgs(n,"content-length")),i(t,...n)},e.write=function(e,t,n){if(g(),!1===s)return o(e,t,n);if(null!=e){const r=Buffer.isBuffer(e)?e:Buffer.from(e,"string"==typeof t?t:"utf8");if(u+=r.length,c.push(r),u>2e6)return d(),"function"==typeof t&&t(null),"function"==typeof n&&n(null),!0}return"function"==typeof t&&t(null),"function"==typeof n&&n(null),!0},e.end=function(n,r,i){if(l)return e;if(l=!0,g(),!1===s)return f(n,r,i);if(null!=n){const e=Buffer.isBuffer(n)?n:Buffer.from(n,"string"==typeof r?r:"utf8");u+=e.length,c.push(e)}if(u>2e6){d();const e="function"==typeof r?r:i;return e?f(e):f()}const a=Buffer.concat(c),y=e.getHeader("content-type");let p;try{p=t(a,y)}catch{p=null}if(null!=p){const t=Buffer.isBuffer(p)?p:Buffer.from(p,"utf8");e.headersSent||(e.setHeader("Content-Length",t.length),e.removeHeader("Content-Encoding")),o(t)}else a.length>0&&o(a);const H="function"==typeof r?r:i;return H?f(H):f(),e}}
@@ -14,6 +14,13 @@ export declare function sendEmpty(res: UnsharedResponse, statusCode: number): vo
14
14
  * `res.status(code).end(body)`.
15
15
  */
16
16
  export declare function sendBody(res: UnsharedResponse, statusCode: number, body: string | Buffer): void;
17
+ /**
18
+ * Send a body that honors HEAD. A HEAD response carries the same status and
19
+ * headers a GET would, but no body (RFC 9110 §9.3.2). Content-Length is set so a
20
+ * HEAD reports the byte size a GET would return. Use for GET routes that may also
21
+ * receive HEAD (e.g. a client probing a static asset with `curl -I`).
22
+ */
23
+ export declare function sendBodyForMethod(res: UnsharedResponse, method: string | undefined, statusCode: number, body: string | Buffer): void;
17
24
  /**
18
25
  * Extract the pathname from a request URL, stripping query string.
19
26
  * Framework-agnostic replacement for Express's `req.path`.
@@ -1 +1 @@
1
- export function sendJson(n,t,o){const e=JSON.stringify(o);n.statusCode=t,n.setHeader("Content-Type","application/json"),n.end(e)}export function sendEmpty(n,t){n.statusCode=t,n.end()}export function sendBody(n,t,o){n.statusCode=t,n.end(o)}export function getRequestPath(n){if(!n)return"/";const t=n.indexOf("?");return-1===t?n:n.slice(0,t)}
1
+ export function sendJson(n,t,o){const e=JSON.stringify(o);n.statusCode=t,n.setHeader("Content-Type","application/json"),n.end(e)}export function sendEmpty(n,t){n.statusCode=t,n.end()}export function sendBody(n,t,o){n.statusCode=t,n.end(o)}export function sendBodyForMethod(n,t,o,e){n.statusCode=o,n.setHeader("Content-Length",String(Buffer.byteLength(e))),"HEAD"===t?n.end():n.end(e)}export function getRequestPath(n){if(!n)return"/";const t=n.indexOf("?");return-1===t?n:n.slice(0,t)}
@@ -1 +1 @@
1
- import{sha256Hex}from"../util";import{VerdictCache}from"../middleware/verdict-cache";import{RateLimitBackoff}from"../middleware/rate-limit-backoff";import{DispatchDedupe}from"../middleware/dispatch-dedupe";import{generateFingerprintScript}from"../middleware/injection/fingerprint-script";import{generateGatePage}from"../middleware/injection/gate-page";import{flaggedResponse}from"../middleware/utils/flagged-response";import{maskEmail}from"../middleware/utils/mask-email";import{isHtmlContentType,isHtmlNavigation}from"../middleware/utils/content-type";import{shouldSkipPath}from"../middleware/utils/skip-paths";import{shouldIncludePath}from"../middleware/utils/include-path";import{isBot}from"../middleware/utils/is-bot";import{isSentinelUserId,SENTINEL_STICKINESS_TTL_MS}from"../middleware/utils/sentinel-user-id";import{parseCookieFromRequest,extractClientIpFromRequest,extractDeviceIdFromRequest,extractDeviceIdFromRequestOrUnknown,isSecureWebRequest,jsonResponse,emptyResponse,bodyResponse,mergeResponseHeaders}from"./web-helpers";const CHECK_USER_TIMEOUT_MS=500;export function createWebProtectionMiddleware(e,s){if(!s.userId)throw new Error("[Unshared] userId resolver is required");const{userId:t,emailAddress:r,routePrefix:n="/__unshared",corsOrigins:i,cacheTTL:o=6e4,skipPaths:a,includePathPrefix:d,sessionId:c,deviceId:u,fingerprintSdkBundle:l="",onFlagged:p,onError:m,onFailOpen:f,disableBotFilter:h=!1,checkUserTimeoutMs:_=CHECK_USER_TIMEOUT_MS,blockFlagged:g=!1,flaggedMode:R,autoInterstitial:I=!1,interstitialFlowType:v="email_verification"}=s,S=R??(g?"gate":void 0),y=I||"overlay"===S,w=e=>{if(f)try{f(e)}catch{}},C=Date.now().toString(36),A=void 0!==S||y,k="gate"===S?"flaggedMode 'gate' (blockFlagged)":"overlay"===S?"flaggedMode 'overlay'":"autoInterstitial";if(A&&!l)throw new Error(`[Unshared] ${k} requires fingerprintSdkBundle (the browser SDK UMD served at {routePrefix}/fp.js renders the interstitial).`);if(A&&"/__unshared"!==n)throw new Error(`[Unshared] ${k} requires the default routePrefix ("/__unshared"); the browser SDK proxy routes are fixed to that prefix.`);const E="gate"===S?generateGatePage(n,C):"",F=new VerdictCache(o),T=new RateLimitBackoff,x=new DispatchDedupe,U=generateFingerprintScript(n,C,{autoInterstitial:y,interstitialFlowType:v}),q=`${n}/fp.js`,j=`${n}/submit-fp`,O=`${n}/verify-trigger`,D=`${n}/verify`,b=`${n}/status`,P=i?Array.isArray(i)?i:[i]:null;return async function(s,i){let o,f,g;try{const e=new URL(s.url);o=e.pathname,f=e.search}catch{return i(s)}if(o.startsWith(n+"/")){const n=function(e){if(!P)return{};const s=e.headers.get("origin")??"",t=P.includes("*");return t||P.includes(s)?{"Access-Control-Allow-Origin":t?"*":s,"Access-Control-Allow-Methods":"POST, OPTIONS","Access-Control-Allow-Headers":"Content-Type, X-Idempotency-Key, X-Session-Id, X-Device-Id","Access-Control-Allow-Credentials":"true"}:{}}(s);if("OPTIONS"===s.method)return emptyResponse(204,n);if("GET"===s.method&&o===q)return l?bodyResponse(200,l,{...n,"Content-Type":"application/javascript","Cache-Control":"public, max-age=3600"}):jsonResponse(404,{success:!1,error:{code:"NOT_FOUND",message:"Fingerprint SDK bundle not configured. Pass fingerprintSdkBundle in config."}},n);if("POST"===s.method&&(o===j||o===O||o===D)){let i;try{i=await s.json()}catch{return jsonResponse(400,{success:!1,error:{code:"BODY_PARSER_MISSING",message:"Request body is not valid JSON."}},n)}return o===j?handleSubmitFp(s,i,{client:e,verdictCache:F,rateLimitBackoff:T,dispatchDedupe:x,resolveUserId:t,resolveEmailAddress:r,resolveSessionId:c,resolveDeviceId:u,disableBotFilter:h,onError:m},n):o===O?handleVerifyTriggerWeb(s,i,{client:e,verdictCache:F,resolveEmailAddress:r,resolveDeviceId:u,onError:m},n):handleVerifyWeb(s,i,{client:e,verdictCache:F,resolveEmailAddress:r,resolveDeviceId:u,onError:m},n)}if("GET"===s.method&&o===b){let i;try{i=t(s)}catch{}if(!i)return jsonResponse(200,{status:"anonymous"},n);const o=resolveEmail(s,r);let a=F.get(i);if((!a||F.isStale(i))&&o&&!T.isPaused()&&!F.isRefreshing(i)){F.markRefreshing(i);try{const t=extractDeviceIdFromRequest(s,u),r=parseCookieFromRequest(s,"__unshared_fingerprint_id")||void 0,n=extractSessionIdFromRequest(s,c),d=t??r??"unknown";await fetchAndCacheVerdict(e,F,i,o,d,r,n,_,(e,s)=>w({operation:"checkUser",reason:e,status:s,userId:i,emailAddress:o})),a=F.get(i)}catch(e){m&&m(e,{operation:"checkUser",userId:i,emailAddress:o}),w({operation:"checkUser",reason:"exception",userId:i,emailAddress:o})}finally{F.clearRefreshing(i)}}return a&&a.isFlagged&&!a.isVerified&&p&&o?jsonResponse(200,{status:"flagged",email:maskEmail(o)},n):jsonResponse(200,{status:"ok"},n)}return jsonResponse(404,{success:!1,error:{code:"NOT_FOUND",message:"Unknown route"}},n)}if(shouldSkipPath(o,a))return i(s);if(!shouldIncludePath(o,d))return injectIntoHtmlResponse(await i(s),U);try{g=t(s)}catch{}if(isSentinelUserId(g)){const e=parseCookieFromRequest(s,"__unshared_uid"),t=parseCookieFromRequest(s,"__unshared_uid_at"),r=t?Number(t):NaN,n=Number.isFinite(r)&&Date.now()-r<=SENTINEL_STICKINESS_TTL_MS;g=e&&n?e:void 0}if(!g){const e=isSecureWebRequest(s)?"; Secure":"",t=[`__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}`];return injectIntoHtmlResponse(await i(s),U,t)}const R=resolveEmail(s,r),I=[],v=isSecureWebRequest(s)?"; Secure":"";if(I.push(`__unshared_uid=${encodeURIComponent(g)}; Path=/; SameSite=Lax${v}`),I.push(`__unshared_uid_at=${Date.now()}; Path=/; SameSite=Lax${v}`),R&&I.push(`__unshared_email=${encodeURIComponent(R)}; HttpOnly; Path=/; SameSite=Lax${v}`),!R)return injectIntoHtmlResponse(await i(s),U,I);const y=extractSessionIdFromRequest(s,c),C=extractDeviceIdFromRequest(s,u),A=parseCookieFromRequest(s,"__unshared_fingerprint_id")||void 0,k=s.headers.get("user-agent")??"",$=extractClientIpFromRequest(s),N=C??A;if(!h&&isBot(k))return i(s);let H=F.get(g);if(H)F.isStale(g)&&!F.isRefreshing(g)&&(F.markRefreshing(g),fetchAndCacheVerdict(e,F,g,R,N??"unknown",A,y,_,(e,s)=>w({operation:"checkUser",reason:e,status:s,userId:g,emailAddress:R})).catch(()=>w({operation:"checkUser",reason:"exception",userId:g,emailAddress:R})).finally(()=>F.clearRefreshing(g)));else try{H=await fetchAndCacheVerdict(e,F,g,R,N??"unknown",A,y,_,(e,s)=>w({operation:"checkUser",reason:e,status:s,userId:g,emailAddress:R}))}catch{return w({operation:"checkUser",reason:"exception",userId:g,emailAddress:R}),injectIntoHtmlResponse(await i(s),U,I)}const L=H.isFlagged&&!H.isVerified;if("gate"===S&&L)return isHtmlNavigation(s.method,s.headers.get("accept")??void 0)?bodyResponse(200,E,{"Content-Type":"text/html; charset=utf-8","Cache-Control":"no-store"}):jsonResponse(403,flaggedResponse(R));if("overlay"===S&&L)return isHtmlNavigation(s.method,s.headers.get("accept")??void 0)?injectIntoHtmlResponse(await i(s),U,I):jsonResponse(403,flaggedResponse(R));if(H.isFlagged&&!H.isVerified&&p)try{const e=await p({userId:g,emailAddress:R,verdict:H,request:s});if(e)return injectIntoHtmlResponse(e,U,I)}catch(e){m&&m(e,{operation:"checkUser",userId:g,emailAddress:R})}return H.isFlagged||"unknown"===y||!N||T.isPaused()||dispatchUserEvent(e,F,T,x,{userId:g,emailAddress:R,sessionId:y,deviceId:N,fingerprintId:A,userAgent:k,ipAddress:$,eventType:o+f},m),injectIntoHtmlResponse(await i(s),U,I)}}async function injectIntoHtmlResponse(e,s,t){const r=e.headers.get("content-type");if(!isHtmlContentType(r??void 0)){if(!t||0===t.length)return e;const s=mergeResponseHeaders(e.headers,void 0,t);return new Response(e.body,{status:e.status,statusText:e.statusText,headers:s})}const n=await e.text(),i=n.lastIndexOf("</body>"),o=-1===i?n+s:n.slice(0,i)+s+n.slice(i),a=mergeResponseHeaders(e.headers,{"Cache-Control":"no-store","Content-Length":String((new TextEncoder).encode(o).length)},t);return a.delete("ETag"),a.delete("Last-Modified"),a.delete("Content-Encoding"),new Response(o,{status:e.status,statusText:e.statusText,headers:a})}function resolveEmail(e,s){if(s)try{const t=s(e);if(t)return t}catch{}const t=parseCookieFromRequest(e,"__unshared_email");if(t)return t}function resolveEmailWithBody(e,s,t){const r=resolveEmail(e,t);if(r)return r;const n=s.email;return"string"==typeof n&&n?n:void 0}function extractSessionIdFromRequest(e,s){if(s)try{const t=s(e);if(t)return t}catch{}return parseCookieFromRequest(e,"__unshared_sid")??"unknown"}function dispatchUserEvent(e,s,t,r,n,i){r.mark(n.userId,n.eventType),e.processUserEvent({eventType:n.eventType,userId:n.userId,emailAddress:n.emailAddress,ipAddress:n.ipAddress,deviceId:n.deviceId,fingerprintId:n.fingerprintId,sessionHash:n.sessionId,userAgent:n.userAgent}).then(e=>{e.success&&e.data?.analysis&&s.update(n.userId,{isFlagged:e.data.analysis.is_user_flagged}),!e.success&&e.error?.retryAfter&&t.pause(1e3*e.error.retryAfter)}).catch(e=>{i&&i(e,{operation:"processUserEvent",userId:n.userId,emailAddress:n.emailAddress})})}async function fetchAndCacheVerdict(e,s,t,r,n,i,o,a=CHECK_USER_TIMEOUT_MS,d){const c={};let u;n&&"unknown"!==n&&(c.deviceId=n),i&&(c.fingerprintId=i);const l=await Promise.race([e.checkUser(r,c),new Promise(e=>{u=setTimeout(()=>e(null),a)})]);if(clearTimeout(u),!l)return d?.("timeout"),{isFlagged:!1,isVerified:!1,emailAddress:r,sessionId:o,cachedAt:0,ttl:0};l.failedOpen&&d?.("http_error",l.failedOpen.status);const p=l.data?.is_user_flagged??!1;return s.set(t,{isFlagged:p,isVerified:!1,emailAddress:r,sessionId:o}),s.get(t)}async function handleSubmitFp(e,s,t,r){try{const n={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 i,o,a;try{const s=t.resolveUserId(e);s&&!isSentinelUserId(s)&&(i=s)}catch{}if(!i){const e="string"==typeof s.user_id?s.user_id:void 0;e&&!isSentinelUserId(e)&&(i=e)}if(!i){const s=parseCookieFromRequest(e,"__unshared_uid");s&&!isSentinelUserId(s)&&(i=s)}try{o=t.resolveEmailAddress?t.resolveEmailAddress(e):void 0}catch{}o=o??parseCookieFromRequest(e,"__unshared_email")??s.email??void 0;try{a=t.resolveSessionId?t.resolveSessionId(e):void 0}catch{}a=a??s.session_id??parseCookieFromRequest(e,"__unshared_sid");const d=extractClientIpFromRequest(e),c=e.headers.get("user-agent")??"";if(!t.disableBotFilter&&isBot(c))return jsonResponse(200,{success:!0},r);const u=(n.fingerprint_id&&n.fingerprint_id.length>0?n.fingerprint_id:void 0)??extractDeviceIdFromRequestOrUnknown(e,t.resolveDeviceId),l=n.fingerprint_id||void 0,p=n.full_hash||void 0,m=isSecureWebRequest(e)?"; Secure":"",f=[];if(p&&!parseCookieFromRequest(e,"__unshared_fingerprint_id")&&f.push(`__unshared_fingerprint_id=${encodeURIComponent(p)}; HttpOnly; Path=/; SameSite=Lax${m}`),l){const s=parseCookieFromRequest(e,"__unshared_fp_id");s&&s===l||f.push(`__unshared_fp_id=${encodeURIComponent(l)}; Path=/; SameSite=Lax; Max-Age=31536000${m}`)}let h;if(o&&!parseCookieFromRequest(e,"__unshared_email")&&f.push(`__unshared_email=${encodeURIComponent(o)}; HttpOnly; Path=/; SameSite=Lax${m}`),"string"==typeof s.event_type&&s.event_type)h=s.event_type;else{const s=e.headers.get("referer")??e.headers.get("referrer");let t="unknown";if(s)try{const e=new URL(s);t=(e.pathname||"/")+(e.search||"")}catch{}h=t}const _=e.headers.get("x-idempotency-key")||void 0,g=Date.now(),R=_?`${_}|${g}`:l&&i?`${sha256Hex(`${l}|${i}|${h}`)}|${g}`:void 0;i&&t.client.submitFingerprintEvent(n,{userId:i,emailAddress:o,sessionHash:a,eventType:h,ipAddress:d,userAgent:c,idempotencyKey:R}).catch(e=>{t.onError&&t.onError(e,{operation:"submitFingerprintEvent",userId:i,emailAddress:o})}),i&&o&&!t.rateLimitBackoff.isPaused()&&!t.dispatchDedupe.wasRecentlyDispatched(i,h)&&t.client.processUserEvent({eventType:h,userId:i,emailAddress:o,ipAddress:d,deviceId:u,fingerprintId:l,sessionHash:a??"unknown",userAgent:c}).then(e=>{e.success&&e.data?.analysis&&t.verdictCache.update(i,{isFlagged:e.data.analysis.is_user_flagged}),!e.success&&e.error?.retryAfter&&t.rateLimitBackoff.pause(1e3*e.error.retryAfter)}).catch(e=>{t.onError&&t.onError(e,{operation:"processUserEvent",userId:i,emailAddress:o})});const I={...r,"Content-Type":"application/json"},v=new Response(JSON.stringify({success:!0}),{status:200,headers:I});for(const e of f)v.headers.append("Set-Cookie",e);return v}catch{return jsonResponse(200,{success:!0},r)}}async function handleVerifyTriggerWeb(e,s,t,r){try{const n=resolveEmailWithBody(e,s??{},t.resolveEmailAddress);if(!n)return jsonResponse(400,{success:!1,error:{code:"VALIDATION_ERROR",message:"Email is required"}},r);const i=extractDeviceIdFromRequestOrUnknown(e,t.resolveDeviceId),o=parseCookieFromRequest(e,"__unshared_fingerprint_id")||void 0,a=await t.client.triggerEmailVerification(n,i,{fingerprintId:o});return a.success?jsonResponse(200,{success:!0,data:a.data},r):jsonResponse(200,{success:!1,error:a.error??{code:"TRIGGER_FAILED",message:"Failed to send verification email"}},r)}catch(e){return t.onError&&t.onError(e,{operation:"verifyTrigger"}),jsonResponse(200,{success:!1,error:{code:"INTERNAL_ERROR",message:"Failed to trigger verification"}},r)}}async function handleVerifyWeb(e,s,t,r){try{const n=resolveEmailWithBody(e,s??{},t.resolveEmailAddress),i=s?.code;if(!n||!i)return jsonResponse(400,{success:!1,error:{code:"VALIDATION_ERROR",message:"Email and code are required"}},r);const o=extractDeviceIdFromRequestOrUnknown(e,t.resolveDeviceId),a=parseCookieFromRequest(e,"__unshared_fingerprint_id")||void 0,d=await t.client.verify(n,o,i,{fingerprintId:a});if(d.success){const s=parseCookieFromRequest(e,"__unshared_uid");return s&&t.verdictCache.update(s,{isVerified:!0}),jsonResponse(200,{success:!0,data:{verified:!0}},r)}return jsonResponse(200,{success:!1,error:d.error??{code:"VERIFICATION_FAILED",message:"Verification failed"}},r)}catch(e){return t.onError&&t.onError(e,{operation:"verify"}),jsonResponse(200,{success:!1,error:{code:"INTERNAL_ERROR",message:"Verification failed"}},r)}}
1
+ import{sha256Hex}from"../util";import{VerdictCache}from"../middleware/verdict-cache";import{RateLimitBackoff}from"../middleware/rate-limit-backoff";import{DispatchDedupe}from"../middleware/dispatch-dedupe";import{generateFingerprintScript}from"../middleware/injection/fingerprint-script";import{generateGatePage}from"../middleware/injection/gate-page";import{flaggedResponse}from"../middleware/utils/flagged-response";import{maskEmail}from"../middleware/utils/mask-email";import{isHtmlContentType,isHtmlNavigation}from"../middleware/utils/content-type";import{shouldSkipPath}from"../middleware/utils/skip-paths";import{shouldIncludePath}from"../middleware/utils/include-path";import{isBot}from"../middleware/utils/is-bot";import{isSentinelUserId,SENTINEL_STICKINESS_TTL_MS}from"../middleware/utils/sentinel-user-id";import{parseCookieFromRequest,extractClientIpFromRequest,extractDeviceIdFromRequest,extractDeviceIdFromRequestOrUnknown,isSecureWebRequest,jsonResponse,emptyResponse,bodyResponse,mergeResponseHeaders}from"./web-helpers";const CHECK_USER_TIMEOUT_MS=1500,FAIL_OPEN_TTL_MS=5e3;export function createWebProtectionMiddleware(e,t){if(!t.userId)throw new Error("[Unshared] userId resolver is required");const{userId:s,emailAddress:r,routePrefix:n="/__unshared",corsOrigins:i,cacheTTL:o=6e4,skipPaths:a,includePathPrefix:d,sessionId:c,deviceId:u,fingerprintSdkBundle:l="",onFlagged:p,onError:m,onFailOpen:f,disableBotFilter:h=!1,checkUserTimeoutMs:_=CHECK_USER_TIMEOUT_MS,blockFlagged:g=!1,flaggedMode:R,autoInterstitial:I=!1,interstitialFlowType:v="email_verification"}=t,S=R??(g?"gate":void 0),y=I||"overlay"===S,w=e=>{if(f)try{f(e)}catch{}},E=Date.now().toString(36),C=void 0!==S||y,A="gate"===S?"flaggedMode 'gate' (blockFlagged)":"overlay"===S?"flaggedMode 'overlay'":"autoInterstitial";if(C&&!l)throw new Error(`[Unshared] ${A} requires fingerprintSdkBundle (the browser SDK UMD served at {routePrefix}/fp.js renders the interstitial).`);if(C&&"/__unshared"!==n)throw new Error(`[Unshared] ${A} requires the default routePrefix ("/__unshared"); the browser SDK proxy routes are fixed to that prefix.`);const k="gate"===S?generateGatePage(n,E):"",T=new VerdictCache(o),F=new RateLimitBackoff,x=new DispatchDedupe,U=generateFingerprintScript(n,E,{autoInterstitial:y,interstitialFlowType:v}),q=`${n}/fp.js`,D=`${n}/submit-fp`,O=`${n}/verify-trigger`,j=`${n}/verify`,P=`${n}/status`,b=i?Array.isArray(i)?i:[i]:null;return async function(t,i){let o,f,g;try{const e=new URL(t.url);o=e.pathname,f=e.search}catch{return i(t)}if(o.startsWith(n+"/")){const n=function(e){if(!b)return{};const t=e.headers.get("origin")??"",s=b.includes("*");if(!s&&!b.includes(t))return{};const r={"Access-Control-Allow-Origin":s?"*":t,"Access-Control-Allow-Methods":"POST, OPTIONS","Access-Control-Allow-Headers":"Content-Type, X-Idempotency-Key, X-Session-Id, X-Device-Id"};return s||(r["Access-Control-Allow-Credentials"]="true"),r}(t);if("OPTIONS"===t.method)return emptyResponse(204,n);if(("GET"===t.method||"HEAD"===t.method)&&o===q){if(!l)return 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)}):bodyResponse(200,l,e)}if("POST"===t.method&&(o===D||o===O||o===j)){let i;try{i=await t.json()}catch{return jsonResponse(400,{success:!1,error:{code:"BODY_PARSER_MISSING",message:"Request body is not valid JSON."}},n)}return o===D?handleSubmitFp(t,i,{client:e,verdictCache:T,rateLimitBackoff:F,dispatchDedupe:x,resolveUserId:s,resolveEmailAddress:r,resolveSessionId:c,resolveDeviceId:u,disableBotFilter:h,onError:m},n):o===O?handleVerifyTriggerWeb(t,i,{client:e,verdictCache:T,resolveUserId:s,resolveEmailAddress:r,resolveDeviceId:u,onError:m},n):handleVerifyWeb(t,i,{client:e,verdictCache:T,resolveUserId:s,resolveEmailAddress:r,resolveDeviceId:u,onError:m},n)}if("GET"===t.method&&o===P){let i;try{i=s(t)}catch{}if(!i)return jsonResponse(200,{status:"anonymous"},n);const o=resolveEmail(t,r);let a=T.get(i);if((!a||T.isStale(i))&&o&&!F.isPaused()&&!T.isRefreshing(i)){T.markRefreshing(i);try{const s=extractDeviceIdFromRequest(t,u),r=parseCookieFromRequest(t,"__unshared_fingerprint_id")||void 0,n=extractSessionIdFromRequest(t,c),d=s??r??"unknown";await fetchAndCacheVerdict(e,T,i,o,d,r,n,_,(e,t)=>w({operation:"checkUser",reason:e,status:t,userId:i,emailAddress:o})),a=T.get(i)}catch(e){m&&m(e,{operation:"checkUser",userId:i,emailAddress:o}),w({operation:"checkUser",reason:"exception",userId:i,emailAddress:o})}finally{T.clearRefreshing(i)}}return a&&a.isFlagged&&!a.isVerified&&(void 0!==S||p)&&o?jsonResponse(200,{status:"flagged",email:maskEmail(o)},n):jsonResponse(200,{status:"ok"},n)}return jsonResponse(404,{success:!1,error:{code:"NOT_FOUND",message:"Unknown route"}},n)}if(shouldSkipPath(o,a))return i(t);if(!shouldIncludePath(o,d))return injectIntoHtmlResponse(await i(t),U);try{g=s(t)}catch{}if(isSentinelUserId(g)){const e=parseCookieFromRequest(t,"__unshared_uid"),s=parseCookieFromRequest(t,"__unshared_uid_at"),r=s?Number(s):NaN,n=Number.isFinite(r)&&Date.now()-r<=SENTINEL_STICKINESS_TTL_MS;g=e&&n?e:void 0}if(!g){const e=isSecureWebRequest(t)?"; Secure":"",s=[`__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}`];return injectIntoHtmlResponse(await i(t),U,s)}const R=resolveEmail(t,r),I=[],v=isSecureWebRequest(t)?"; Secure":"";if(I.push(`__unshared_uid=${encodeURIComponent(g)}; Path=/; SameSite=Lax${v}`),I.push(`__unshared_uid_at=${Date.now()}; Path=/; SameSite=Lax${v}`),R&&I.push(`__unshared_email=${encodeURIComponent(R)}; HttpOnly; Path=/; SameSite=Lax${v}`),!R)return injectIntoHtmlResponse(await i(t),U,I);const y=extractSessionIdFromRequest(t,c),E=extractDeviceIdFromRequest(t,u),C=parseCookieFromRequest(t,"__unshared_fingerprint_id")||void 0,A=t.headers.get("user-agent")??"",H=extractClientIpFromRequest(t),L=E??C;if(!h&&isBot(A))return i(t);let N=T.get(g);if(N)T.isStale(g)&&!T.isRefreshing(g)&&(T.markRefreshing(g),fetchAndCacheVerdict(e,T,g,R,L??"unknown",C,y,_,(e,t)=>w({operation:"checkUser",reason:e,status:t,userId:g,emailAddress:R})).catch(()=>w({operation:"checkUser",reason:"exception",userId:g,emailAddress:R})).finally(()=>T.clearRefreshing(g)));else try{N=await fetchAndCacheVerdict(e,T,g,R,L??"unknown",C,y,_,(e,t)=>w({operation:"checkUser",reason:e,status:t,userId:g,emailAddress:R}))}catch{return w({operation:"checkUser",reason:"exception",userId:g,emailAddress:R}),injectIntoHtmlResponse(await i(t),U,I)}const $=N.isFlagged&&!N.isVerified;if("gate"===S&&$)return isHtmlNavigation(t.method,t.headers.get("accept")??void 0)?bodyResponse(200,k,{"Content-Type":"text/html; charset=utf-8","Cache-Control":"no-store"}):jsonResponse(403,flaggedResponse(R));if("overlay"===S&&$)return isHtmlNavigation(t.method,t.headers.get("accept")??void 0)?injectIntoHtmlResponse(await i(t),U,I):jsonResponse(403,flaggedResponse(R));if(N.isFlagged&&!N.isVerified&&p)try{const e=await p({userId:g,emailAddress:R,verdict:N,request:t});if(e)return injectIntoHtmlResponse(e,U,I)}catch(e){m&&m(e,{operation:"checkUser",userId:g,emailAddress:R})}return N.isFlagged||"unknown"===y||!L||F.isPaused()||dispatchUserEvent(e,T,F,x,{userId:g,emailAddress:R,sessionId:y,deviceId:L,fingerprintId:C,userAgent:A,ipAddress:H,eventType:o+f},m),injectIntoHtmlResponse(await i(t),U,I)}}async function injectIntoHtmlResponse(e,t,s){const r=e.headers.get("content-type");if(!isHtmlContentType(r??void 0)){if(!s||0===s.length)return e;const t=mergeResponseHeaders(e.headers,void 0,s);return new Response(e.body,{status:e.status,statusText:e.statusText,headers:t})}const n=await e.text(),i=n.lastIndexOf("</body>"),o=-1===i?n+t:n.slice(0,i)+t+n.slice(i),a=mergeResponseHeaders(e.headers,{"Cache-Control":"no-store","Content-Length":String((new TextEncoder).encode(o).length)},s);return a.delete("ETag"),a.delete("Last-Modified"),a.delete("Content-Encoding"),new Response(o,{status:e.status,statusText:e.statusText,headers:a})}function resolveEmail(e,t){if(t)try{const s=t(e);if(s)return s}catch{}const s=parseCookieFromRequest(e,"__unshared_email");if(s)return s}function resolveEmailWithBody(e,t,s){const r=resolveEmail(e,s);if(r)return r;const n=t.email;return"string"==typeof n&&n?n:void 0}function extractSessionIdFromRequest(e,t){if(t)try{const s=t(e);if(s)return s}catch{}return parseCookieFromRequest(e,"__unshared_sid")??"unknown"}function dispatchUserEvent(e,t,s,r,n,i){r.mark(n.userId,n.eventType),e.processUserEvent({eventType:n.eventType,userId:n.userId,emailAddress:n.emailAddress,ipAddress:n.ipAddress,deviceId:n.deviceId,fingerprintId:n.fingerprintId,sessionHash:n.sessionId,userAgent:n.userAgent}).then(e=>{e.success&&e.data?.analysis&&t.update(n.userId,{isFlagged:e.data.analysis.is_user_flagged}),!e.success&&e.error?.retryAfter&&s.pause(1e3*e.error.retryAfter)}).catch(e=>{i&&i(e,{operation:"processUserEvent",userId:n.userId,emailAddress:n.emailAddress})})}async function fetchAndCacheVerdict(e,t,s,r,n,i,o,a=CHECK_USER_TIMEOUT_MS,d){const c=()=>{const e=t.get(s);return t.set(s,{isFlagged:e?.isFlagged??!1,isVerified:e?.isVerified??!1,emailAddress:r,sessionId:o},5e3),t.get(s)},u={};let l;n&&"unknown"!==n&&(u.deviceId=n),i&&(u.fingerprintId=i);const p=await Promise.race([e.checkUser(r,u),new Promise(e=>{l=setTimeout(()=>e(null),a)})]);return clearTimeout(l),p?p.failedOpen?(d?.(p.failedOpen.reason??"http_error",p.failedOpen.status),c()):(t.set(s,{isFlagged:p.data?.is_user_flagged??!1,isVerified:!1,emailAddress:r,sessionId:o}),t.get(s)):(d?.("timeout"),c())}async function handleSubmitFp(e,t,s,r){try{const 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 i,o,a;try{const t=s.resolveUserId(e);t&&!isSentinelUserId(t)&&(i=t)}catch{}if(!i){const e="string"==typeof t.user_id?t.user_id:void 0;e&&!isSentinelUserId(e)&&(i=e)}if(!i){const t=parseCookieFromRequest(e,"__unshared_uid");t&&!isSentinelUserId(t)&&(i=t)}try{o=s.resolveEmailAddress?s.resolveEmailAddress(e):void 0}catch{}o=o??parseCookieFromRequest(e,"__unshared_email")??t.email??void 0;try{a=s.resolveSessionId?s.resolveSessionId(e):void 0}catch{}a=a??t.session_id??parseCookieFromRequest(e,"__unshared_sid");const d=extractClientIpFromRequest(e),c=e.headers.get("user-agent")??"";if(!s.disableBotFilter&&isBot(c))return jsonResponse(200,{success:!0},r);const u=(n.fingerprint_id&&n.fingerprint_id.length>0?n.fingerprint_id:void 0)??extractDeviceIdFromRequestOrUnknown(e,s.resolveDeviceId),l=n.fingerprint_id||void 0,p=n.full_hash||void 0,m=isSecureWebRequest(e)?"; Secure":"",f=[];if(p&&!parseCookieFromRequest(e,"__unshared_fingerprint_id")&&f.push(`__unshared_fingerprint_id=${encodeURIComponent(p)}; HttpOnly; Path=/; SameSite=Lax${m}`),l){const t=parseCookieFromRequest(e,"__unshared_fp_id");t&&t===l||f.push(`__unshared_fp_id=${encodeURIComponent(l)}; Path=/; SameSite=Lax; Max-Age=31536000${m}`)}let h;if(o&&!parseCookieFromRequest(e,"__unshared_email")&&f.push(`__unshared_email=${encodeURIComponent(o)}; HttpOnly; Path=/; SameSite=Lax${m}`),"string"==typeof t.event_type&&t.event_type)h=t.event_type;else{const t=e.headers.get("referer")??e.headers.get("referrer");let s="unknown";if(t)try{const e=new URL(t);s=(e.pathname||"/")+(e.search||"")}catch{}h=s}const _=e.headers.get("x-idempotency-key")||void 0,g=Date.now(),R=_?`${_}|${g}`:l&&i?`${sha256Hex(`${l}|${i}|${h}`)}|${g}`:void 0;i&&s.client.submitFingerprintEvent(n,{userId:i,emailAddress:o,sessionHash:a,eventType:h,ipAddress:d,userAgent:c,idempotencyKey:R}).catch(e=>{s.onError&&s.onError(e,{operation:"submitFingerprintEvent",userId:i,emailAddress:o})}),i&&o&&!s.rateLimitBackoff.isPaused()&&!s.dispatchDedupe.wasRecentlyDispatched(i,h)&&s.client.processUserEvent({eventType:h,userId:i,emailAddress:o,ipAddress:d,deviceId:u,fingerprintId:l,sessionHash:a??"unknown",userAgent:c}).then(e=>{e.success&&e.data?.analysis&&s.verdictCache.update(i,{isFlagged:e.data.analysis.is_user_flagged}),!e.success&&e.error?.retryAfter&&s.rateLimitBackoff.pause(1e3*e.error.retryAfter)}).catch(e=>{s.onError&&s.onError(e,{operation:"processUserEvent",userId:i,emailAddress:o})});const I={...r,"Content-Type":"application/json"},v=new Response(JSON.stringify({success:!0}),{status:200,headers:I});for(const e of f)v.headers.append("Set-Cookie",e);return v}catch{return jsonResponse(200,{success:!0},r)}}async function handleVerifyTriggerWeb(e,t,s,r){try{const n=resolveEmailWithBody(e,t??{},s.resolveEmailAddress);if(!n)return jsonResponse(400,{success:!1,error:{code:"VALIDATION_ERROR",message:"Email is required"}},r);const i=extractDeviceIdFromRequestOrUnknown(e,s.resolveDeviceId),o=parseCookieFromRequest(e,"__unshared_fingerprint_id")||void 0,a=await s.client.triggerEmailVerification(n,i,{fingerprintId:o});return a.success?jsonResponse(200,{success:!0,data:a.data},r):jsonResponse(200,{success:!1,error:a.error??{code:"TRIGGER_FAILED",message:"Failed to send verification email"}},r)}catch(e){return s.onError&&s.onError(e,{operation:"verifyTrigger"}),jsonResponse(200,{success:!1,error:{code:"INTERNAL_ERROR",message:"Failed to trigger verification"}},r)}}async function handleVerifyWeb(e,t,s,r){try{const n=resolveEmailWithBody(e,t??{},s.resolveEmailAddress),i=t?.code;if(!n||!i)return jsonResponse(400,{success:!1,error:{code:"VALIDATION_ERROR",message:"Email and code are required"}},r);const o=extractDeviceIdFromRequestOrUnknown(e,s.resolveDeviceId),a=parseCookieFromRequest(e,"__unshared_fingerprint_id")||void 0,d=await s.client.verify(n,o,i,{fingerprintId:a});if(d.success){let t;try{t=s.resolveUserId(e)}catch{}return t&&s.verdictCache.update(t,{isVerified:!0}),jsonResponse(200,{success:!0,data:{verified:!0}},r)}return jsonResponse(200,{success:!1,error:d.error??{code:"VERIFICATION_FAILED",message:"Verification failed"}},r)}catch(e){return s.onError&&s.onError(e,{operation:"verify"}),jsonResponse(200,{success:!1,error:{code:"INTERNAL_ERROR",message:"Verification failed"}},r)}}
@@ -59,7 +59,7 @@ export interface WebProtectionConfig {
59
59
  includePathPrefix?: string[];
60
60
  /** Skip the bot/crawler UA filter. Set to true in test environments so automated browsers (Playwright, Puppeteer, etc.) can observe verdicts. @default false */
61
61
  disableBotFilter?: boolean;
62
- /** Hard timeout (ms) for checkUser on cache miss. Fails open on timeout. @default 500 */
62
+ /** Hard timeout (ms) for checkUser on cache miss. Fails open on timeout. @default 1500 */
63
63
  checkUserTimeoutMs?: number;
64
64
  /** Resolves a custom session ID. Falls back to __unshared_sid cookie. */
65
65
  sessionId?: (req: Request) => string | undefined;
@@ -167,7 +167,7 @@ export interface WebProtectionConfig {
167
167
  }
168
168
  export interface WebFailOpenContext {
169
169
  operation: 'checkUser';
170
- reason: 'timeout' | 'http_error' | 'exception';
170
+ reason: 'timeout' | 'http_error' | 'exception' | 'no_device_id';
171
171
  status?: number;
172
172
  userId?: string;
173
173
  emailAddress?: string;
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"t",{value:!0}),exports.ACCOUNT_FLAGGED_ERROR=exports.flaggedResponse=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"),dispatch_dedupe_1=require("./dispatch-dedupe"),response_interceptor_1=require("./response-interceptor"),fingerprint_script_1=require("./injection/fingerprint-script"),fp_bundle_source_1=require("./injection/fp-bundle-source"),submit_fp_1=require("./routes/submit-fp"),verify_1=require("./routes/verify"),interstitial_1=require("./routes/interstitial"),gate_page_1=require("./injection/gate-page"),http_helpers_1=require("./utils/http-helpers"),content_type_1=require("./utils/content-type"),flagged_response_1=require("./utils/flagged-response"),mask_email_1=require("./utils/mask-email"),skip_paths_1=require("./utils/skip-paths"),include_path_1=require("./utils/include-path"),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"),sentinel_user_id_1=require("./utils/sentinel-user-id");var flagged_response_2=require("./utils/flagged-response");Object.defineProperty(exports,"flaggedResponse",{enumerable:!0,get:function(){return flagged_response_2.flaggedResponse}}),Object.defineProperty(exports,"ACCOUNT_FLAGGED_ERROR",{enumerable:!0,get:function(){return flagged_response_2.ACCOUNT_FLAGGED_ERROR}});const CHECK_USER_TIMEOUT_MS=1500,FAIL_OPEN_TTL_MS=5e3,_lastFailOpenWarn={};function warnFailOpenThrottled(e){const t=Date.now();t-(_lastFailOpenWarn[e.reason]??0)<6e4||(_lastFailOpenWarn[e.reason]=t,console.warn(`[Unshared] Failing open (reason=${e.reason}${null!=e.status?`, status=${e.status}`:""}). Detection is not enforced for this request. Configure onFailOpen to observe this directly.`))}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:s="/__unshared",corsOrigins:n,cacheTTL:o=6e4,skipPaths:a,includePathPrefix:d,disableBotFilter:c=!1,checkUserTimeoutMs:l=CHECK_USER_TIMEOUT_MS,cacheMissStrategy:u="block",sessionId:_,deviceId:p,onFlagged:h,onError:f,onFailOpen:g,blockFlagged:v=!1,flaggedMode:m,autoInterstitial:I=!1,interstitialFlowType:k="email_verification"}=t,y=m??(v?"gate":void 0),A=I||"overlay"===y,C=e=>{if(g)try{g(e)}catch{}else"async_miss"!==e.reason&&warnFailOpenThrottled(e)},S=new verdict_cache_1.VerdictCache(o),T=new rate_limit_backoff_1.RateLimitBackoff,x=new dispatch_dedupe_1.DispatchDedupe,b=Date.now().toString(36),E=(0,fingerprint_script_1.generateFingerprintScript)(s,b,{autoInterstitial:A,interstitialFlowType:k});let O="";try{const e=require.resolve("unshared-frontend-sdk/dist/index.umd.js");O=(0,fs_1.readFileSync)(e,"utf8")}catch{}const U=(0,fp_bundle_source_1.createFingerprintBundleSource)(),w=void 0!==y||A,F="gate"===y?"flaggedMode 'gate' (blockFlagged)":"overlay"===y?"flaggedMode 'overlay'":"autoInterstitial";if(w&&!O)throw new Error(`[Unshared] ${F} requires unshared-frontend-sdk to be installed (its UMD bundle renders the interstitial).`);if(w&&"/__unshared"!==s)throw new Error(`[Unshared] ${F} requires the default routePrefix ("/__unshared"); the browser SDK proxy routes are fixed to that prefix.`);const q="gate"===y?(0,gate_page_1.generateGatePage)(s,b):"",M=(0,submit_fp_1.handleSubmitFingerprint)({client:e,verdictCache:S,rateLimitBackoff:T,dispatchDedupe:x,resolveUserId:r,resolveEmailAddress:i,resolveSessionId:_,resolveDeviceId:p,disableBotFilter:c,onError:f}),P=(0,verify_1.handleVerifyTrigger)({client:e,verdictCache:S,resolveEmailAddress:i,resolveDeviceId:p,onError:f}),$=(0,verify_1.handleVerify)({client:e,verdictCache:S,resolveUserId:r,resolveEmailAddress:i,resolveDeviceId:p,onError:f}),j=(0,interstitial_1.handleGetInterstitialFlow)({client:e}),D=n?Array.isArray(n)?n:[n]:null,V=`${s}/fp.js`,N=`${s}/fingerprint.js`,L=`${s}/submit-fp`,R=`${s}/verify-trigger`,G=`${s}/verify`,H=`${s}/status`,B=`${s}/interstitial-flow`,K=function(t,n,o){const g=(0,http_helpers_1.getRequestPath)(t.url),v=t.url||g;if(g.startsWith(s+"/")){if(function(e,t){if(!D)return;const r=e.headers.origin??"";D.includes("*")?(t.setHeader("Access-Control-Allow-Origin","*"),t.setHeader("Access-Control-Allow-Methods","GET, POST, OPTIONS"),t.setHeader("Access-Control-Allow-Headers","Content-Type, X-Idempotency-Key, X-Session-Id, X-Device-Id")):D.includes(r)&&(t.setHeader("Access-Control-Allow-Origin",r),t.setHeader("Vary","Origin"),t.setHeader("Access-Control-Allow-Methods","GET, 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,n),"OPTIONS"===t.method)return void(0,http_helpers_1.sendEmpty)(n,204);if("GET"===t.method&&g===V)return n.setHeader("Content-Type","application/javascript"),n.setHeader("Cache-Control","public, max-age=3600"),void(0,http_helpers_1.sendBody)(n,200,O);if("GET"===t.method&&g===N){const e=U.get();return e?(n.setHeader("Content-Type","application/javascript"),n.setHeader("Cache-Control","public, max-age=3600"),void(0,http_helpers_1.sendBody)(n,200,e)):void(0,http_helpers_1.sendBody)(n,503,"/* fingerprint agent unavailable */")}if("POST"===t.method&&(g===L||g===R||g===G))return void 0===t.body?void(0,http_helpers_1.sendJson)(n,400,{success:!1,error:{code:"BODY_PARSER_MISSING",message:"req.body is undefined. Mount a JSON body-parsing middleware (e.g., express.json()) before the Unshared middleware."}}):g===L?void M(t,n):g===R?void P(t,n):void $(t,n);if("GET"===t.method&&g===B)return void j(t,n);if("GET"===t.method&&g===H){let s;try{s=r(t)}catch{}if(!s)return void(0,http_helpers_1.sendJson)(n,200,{status:"anonymous"});const o=resolveEmail(t,i);return void(async()=>{let r=S.get(s);if((!r||S.isStale(s))&&o&&!T.isPaused()&&!S.isRefreshing(s)){S.markRefreshing(s);try{const i=(0,device_id_1.extractDeviceIdOrUndefined)(t,p),n=extractFingerprintId(t),a=extractSessionId(t,_),d=i??n??"unknown";await fetchAndCacheVerdict(e,S,s,o,d,n,a,l,(e,t)=>C({operation:"checkUser",reason:e,status:t,userId:s,emailAddress:o})),r=S.get(s)}catch(e){f&&f(e,{operation:"checkUser",userId:s,emailAddress:o}),C({operation:"checkUser",reason:"exception",userId:s,emailAddress:o})}finally{S.clearRefreshing(s)}}r&&r.isFlagged&&!r.isVerified&&(void 0!==y||h)&&o?(0,http_helpers_1.sendJson)(n,200,{status:"flagged",email:(0,mask_email_1.maskEmail)(o)}):(0,http_helpers_1.sendJson)(n,200,{status:"ok"})})()}return void(0,http_helpers_1.sendJson)(n,404,{success:!1,error:{code:"NOT_FOUND",message:"Unknown route"}})}if((0,skip_paths_1.shouldSkipPath)(g,a))return void o();if(!(0,include_path_1.shouldIncludePath)(g,d))return interceptForInjection(t,n,E),void o();let m;try{m=r(t)}catch{}if((0,sentinel_user_id_1.isSentinelUserId)(m)){const e=(0,cookies_1.parseCookie)(t,"__unshared_uid"),r=(0,cookies_1.parseCookie)(t,"__unshared_uid_at"),i=r?Number(r):NaN,s=Number.isFinite(i)&&Date.now()-i<=sentinel_user_id_1.SENTINEL_STICKINESS_TTL_MS;m=e&&s?e:void 0}if(!m){const e=(0,secure_1.isSecureRequest)(t)?"; Secure":"";return appendSetCookie(n,`__unshared_uid=; Path=/; SameSite=Lax; Max-Age=0${e}`),appendSetCookie(n,`__unshared_uid_at=; Path=/; SameSite=Lax; Max-Age=0${e}`),appendSetCookie(n,`__unshared_sid=; Path=/; SameSite=Lax; Max-Age=0${e}`),appendSetCookie(n,`__unshared_email=; Path=/; SameSite=Lax; Max-Age=0${e}`),interceptForInjection(t,n,E),void o()}const I=resolveEmail(t,i);if(setUserIdCookie(t,n,m),I&&setEmailCookie(t,n,I),!I)return interceptForInjection(t,n,E),void o();const k=extractSessionId(t,_),A=(0,device_id_1.extractDeviceIdOrUndefined)(t,p),b=extractFingerprintId(t),w=t.headers["user-agent"]??"",F=(0,client_ip_1.extractClientIp)(t),K=A??b;if(!c&&(0,is_bot_1.isBot)(w))return void o();const X=S.get(m);if(!X)return"async"===u?(S.isRefreshing(m)||(S.markRefreshing(m),fetchAndCacheVerdict(e,S,m,I,K??"unknown",b,k,l,(e,t)=>C({operation:"checkUser",reason:e,status:t,userId:m,emailAddress:I})).catch(()=>C({operation:"checkUser",reason:"exception",userId:m,emailAddress:I})).finally(()=>S.clearRefreshing(m))),C({operation:"checkUser",reason:"async_miss",userId:m,emailAddress:I}),W(),interceptForInjection(t,n,E),void o()):void fetchAndCacheVerdict(e,S,m,I,K??"unknown",b,k,l,(e,t)=>C({operation:"checkUser",reason:e,status:t,userId:m,emailAddress:I})).then(e=>{e.isFlagged||W(),applyVerdict(e,m,I,t,n,o,E,h,y,q)}).catch(()=>{C({operation:"checkUser",reason:"exception",userId:m,emailAddress:I}),W(),interceptForInjection(t,n,E),o()});function W(){"unknown"!==k&&K&&(T.isPaused()||dispatchUserEvent(e,S,T,x,{userId:m,emailAddress:I,sessionId:k,deviceId:K,fingerprintId:b,userAgent:w,ipAddress:F,eventType:v},f))}S.isStale(m)&&!S.isRefreshing(m)&&(S.markRefreshing(m),fetchAndCacheVerdict(e,S,m,I,K??"unknown",b,k,l,(e,t)=>C({operation:"checkUser",reason:e,status:t,userId:m,emailAddress:I})).catch(()=>C({operation:"checkUser",reason:"exception",userId:m,emailAddress:I})).finally(()=>S.clearRefreshing(m))),X.isFlagged||W(),applyVerdict(X,m,I,t,n,o,E,h,y,q)};return K.destroy=()=>{S.destroy(),U.stop()},K}function resolveEmail(e,t){if(t)try{const r=t(e);if(r)return r}catch{}const r=(0,cookies_1.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,s,n,o,a,d,c){const l=e.isFlagged&&!e.isVerified;if("gate"===d&&l)(0,content_type_1.isHtmlNavigation)(i.method,i.headers.accept)?(s.statusCode=200,s.setHeader("Content-Type","text/html; charset=utf-8"),s.setHeader("Cache-Control","no-store"),s.end(c)):(0,http_helpers_1.sendJson)(s,403,(0,flagged_response_1.flaggedResponse)(r));else if("overlay"===d&&l)(0,content_type_1.isHtmlNavigation)(i.method,i.headers.accept)?(interceptForInjection(i,s,o),n()):(0,http_helpers_1.sendJson)(s,403,(0,flagged_response_1.flaggedResponse)(r));else if(interceptForInjection(i,s,o),e.isFlagged&&!e.isVerified&&a)try{a({userId:t,emailAddress:r,verdict:e,req:i,res:s,next:n})}catch{n()}else n()}function interceptForInjection(e,t,r){delete e.headers["if-none-match"],delete e.headers["if-modified-since"],(0,response_interceptor_1.interceptResponse)(t,(e,t)=>{if(!(0,content_type_1.isHtmlContentType)(t))return null;const i=e.toString("utf8"),s=i.lastIndexOf("</body>");return-1===s?i+r:i.slice(0,s)+r+i.slice(s)},{preventCaching:!0})}function dispatchUserEvent(e,t,r,i,s,n){i.mark(s.userId,s.eventType),e.processUserEvent({eventType:s.eventType,userId:s.userId,emailAddress:s.emailAddress,ipAddress:s.ipAddress,deviceId:s.deviceId,fingerprintId:s.fingerprintId,sessionHash:s.sessionId,userAgent:s.userAgent}).then(e=>{e.success&&e.data?.analysis&&t.update(s.userId,{isFlagged:e.data.analysis.is_user_flagged}),!e.success&&e.error?.retryAfter&&r.pause(1e3*e.error.retryAfter)}).catch(e=>{n&&n(e,{operation:"processUserEvent",userId:s.userId,emailAddress:s.emailAddress})})}async function fetchAndCacheVerdict(e,t,r,i,s,n,o,a=CHECK_USER_TIMEOUT_MS,d){const c={},l=s&&"unknown"!==s?s:void 0,u=l??n,_=n??l;let p;u&&(c.deviceId=u),_&&(c.fingerprintId=_);const h=await Promise.race([e.checkUser(i,c),new Promise(e=>{p=setTimeout(()=>e(null),a)})]);return clearTimeout(p),h?h.failedOpen?(d?.(h.failedOpen.reason??"http_error",h.failedOpen.status),cacheFailOpenVerdict(t,r,i,o)):(t.set(r,{isFlagged:h.data?.is_user_flagged??!1,isVerified:!1,emailAddress:i,sessionId:o}),t.get(r)):(d?.("timeout"),cacheFailOpenVerdict(t,r,i,o))}function cacheFailOpenVerdict(e,t,r,i){const s=e.get(t);return e.set(t,{isFlagged:s?.isFlagged??!1,isVerified:s?.isVerified??!1,emailAddress:r,sessionId:i},5e3),e.get(t)}function extractSessionId(e,t){if(t)try{const r=t(e);if(r)return r}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,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,r){const i=(0,secure_1.isSecureRequest)(e)?"; Secure":"";appendSetCookie(t,`__unshared_uid=${encodeURIComponent(r)}; Path=/; SameSite=Lax${i}`),appendSetCookie(t,`__unshared_uid_at=${Date.now()}; Path=/; SameSite=Lax${i}`)}function setEmailCookie(e,t,r){const i=(0,secure_1.isSecureRequest)(e)?"; Secure":"";appendSetCookie(t,`__unshared_email=${encodeURIComponent(r)}; HttpOnly; Path=/; SameSite=Lax${i}`)}
1
+ "use strict";Object.defineProperty(exports,"t",{value:!0}),exports.ACCOUNT_FLAGGED_ERROR=exports.flaggedResponse=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"),dispatch_dedupe_1=require("./dispatch-dedupe"),response_interceptor_1=require("./response-interceptor"),fingerprint_script_1=require("./injection/fingerprint-script"),fp_bundle_source_1=require("./injection/fp-bundle-source"),submit_fp_1=require("./routes/submit-fp"),verify_1=require("./routes/verify"),interstitial_1=require("./routes/interstitial"),gate_page_1=require("./injection/gate-page"),http_helpers_1=require("./utils/http-helpers"),content_type_1=require("./utils/content-type"),flagged_response_1=require("./utils/flagged-response"),mask_email_1=require("./utils/mask-email"),skip_paths_1=require("./utils/skip-paths"),include_path_1=require("./utils/include-path"),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"),sentinel_user_id_1=require("./utils/sentinel-user-id");var flagged_response_2=require("./utils/flagged-response");Object.defineProperty(exports,"flaggedResponse",{enumerable:!0,get:function(){return flagged_response_2.flaggedResponse}}),Object.defineProperty(exports,"ACCOUNT_FLAGGED_ERROR",{enumerable:!0,get:function(){return flagged_response_2.ACCOUNT_FLAGGED_ERROR}});const CHECK_USER_TIMEOUT_MS=1500,FAIL_OPEN_TTL_MS=5e3,_lastFailOpenWarn={};function warnFailOpenThrottled(e){const t=Date.now();t-(_lastFailOpenWarn[e.reason]??0)<6e4||(_lastFailOpenWarn[e.reason]=t,console.warn(`[Unshared] Failing open (reason=${e.reason}${null!=e.status?`, status=${e.status}`:""}). Detection is not enforced for this request. Configure onFailOpen to observe this directly.`))}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:s="/__unshared",corsOrigins:n,cacheTTL:o=6e4,skipPaths:a,includePathPrefix:d,disableBotFilter:c=!1,checkUserTimeoutMs:l=CHECK_USER_TIMEOUT_MS,cacheMissStrategy:u="block",sessionId:_,deviceId:p,onFlagged:h,onError:f,onFailOpen:g,blockFlagged:v=!1,flaggedMode:m,autoInterstitial:I=!1,interstitialFlowType:k="email_verification"}=t,y=m??(v?"gate":void 0),A=I||"overlay"===y,C=e=>{if(g)try{g(e)}catch{}else"async_miss"!==e.reason&&warnFailOpenThrottled(e)},S=new verdict_cache_1.VerdictCache(o),x=new rate_limit_backoff_1.RateLimitBackoff,T=new Map,b=new dispatch_dedupe_1.DispatchDedupe,E=Date.now().toString(36),w=(0,fingerprint_script_1.generateFingerprintScript)(s,E,{autoInterstitial:A,interstitialFlowType:k});let O="";try{const e=require.resolve("unshared-frontend-sdk/dist/index.umd.js");O=(0,fs_1.readFileSync)(e,"utf8")}catch{}const U=(0,fp_bundle_source_1.createFingerprintBundleSource)(),F=void 0!==y||A,q="gate"===y?"flaggedMode 'gate' (blockFlagged)":"overlay"===y?"flaggedMode 'overlay'":"autoInterstitial";if(F&&!O)throw new Error(`[Unshared] ${q} requires unshared-frontend-sdk to be installed (its UMD bundle renders the interstitial).`);if(F&&"/__unshared"!==s)throw new Error(`[Unshared] ${q} requires the default routePrefix ("/__unshared"); the browser SDK proxy routes are fixed to that prefix.`);const M="gate"===y?(0,gate_page_1.generateGatePage)(s,E):"",P=(0,submit_fp_1.handleSubmitFingerprint)({client:e,verdictCache:S,rateLimitBackoff:x,dispatchDedupe:b,resolveUserId:r,resolveEmailAddress:i,resolveSessionId:_,resolveDeviceId:p,disableBotFilter:c,onError:f}),$=(0,verify_1.handleVerifyTrigger)({client:e,verdictCache:S,resolveEmailAddress:i,resolveDeviceId:p,onError:f}),j=(0,verify_1.handleVerify)({client:e,verdictCache:S,resolveUserId:r,resolveEmailAddress:i,resolveDeviceId:p,onError:f}),D=(0,interstitial_1.handleGetInterstitialFlow)({client:e}),V=n?Array.isArray(n)?n:[n]:null,N=`${s}/fp.js`,L=`${s}/fingerprint.js`,R=`${s}/submit-fp`,G=`${s}/verify-trigger`,H=`${s}/verify`,B=`${s}/status`,K=`${s}/interstitial-flow`,X=function(t,n,o){const g=(0,http_helpers_1.getRequestPath)(t.url),v=t.url||g;if(g.startsWith(s+"/")){if(function(e,t){if(!V)return;const r=e.headers.origin??"";V.includes("*")?(t.setHeader("Access-Control-Allow-Origin","*"),t.setHeader("Access-Control-Allow-Methods","GET, POST, OPTIONS"),t.setHeader("Access-Control-Allow-Headers","Content-Type, X-Idempotency-Key, X-Session-Id, X-Device-Id")):V.includes(r)&&(t.setHeader("Access-Control-Allow-Origin",r),t.setHeader("Vary","Origin"),t.setHeader("Access-Control-Allow-Methods","GET, 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,n),"OPTIONS"===t.method)return void(0,http_helpers_1.sendEmpty)(n,204);if(("GET"===t.method||"HEAD"===t.method)&&g===N)return n.setHeader("Content-Type","application/javascript"),n.setHeader("Cache-Control","public, max-age=3600"),void(0,http_helpers_1.sendBodyForMethod)(n,t.method,200,O);if(("GET"===t.method||"HEAD"===t.method)&&g===L){const e=U.get();return e?(n.setHeader("Content-Type","application/javascript"),n.setHeader("Cache-Control","public, max-age=3600"),void(0,http_helpers_1.sendBodyForMethod)(n,t.method,200,e)):void(0,http_helpers_1.sendBodyForMethod)(n,t.method,503,"/* fingerprint agent unavailable */")}if("POST"===t.method&&(g===R||g===G||g===H))return void 0===t.body?void(0,http_helpers_1.sendJson)(n,400,{success:!1,error:{code:"BODY_PARSER_MISSING",message:"req.body is undefined. Mount a JSON body-parsing middleware (e.g., express.json()) before the Unshared middleware."}}):g===R?void P(t,n):g===G?void $(t,n):void j(t,n);if("GET"===t.method&&g===K)return void D(t,n);if("GET"===t.method&&g===B){let s;try{s=r(t)}catch{}if(!s)return void(0,http_helpers_1.sendJson)(n,200,{status:"anonymous"});const o=resolveEmail(t,i);return void(async()=>{let r=S.get(s);if((!r||S.isStale(s))&&o&&!x.isPaused()&&!S.isRefreshing(s)){S.markRefreshing(s);try{const i=(0,device_id_1.extractDeviceIdOrUndefined)(t,p),n=extractFingerprintId(t),a=extractSessionId(t,_),d=i??n??"unknown";await fetchAndCacheVerdict(e,S,s,o,d,n,a,l,(e,t)=>C({operation:"checkUser",reason:e,status:t,userId:s,emailAddress:o})),r=S.get(s)}catch(e){f&&f(e,{operation:"checkUser",userId:s,emailAddress:o}),C({operation:"checkUser",reason:"exception",userId:s,emailAddress:o})}finally{S.clearRefreshing(s)}}r&&r.isFlagged&&!r.isVerified&&(void 0!==y||h)&&o?(0,http_helpers_1.sendJson)(n,200,{status:"flagged",email:(0,mask_email_1.maskEmail)(o)}):(0,http_helpers_1.sendJson)(n,200,{status:"ok"})})()}return void(0,http_helpers_1.sendJson)(n,404,{success:!1,error:{code:"NOT_FOUND",message:"Unknown route"}})}if((0,skip_paths_1.shouldSkipPath)(g,a))return void o();if(!(0,include_path_1.shouldIncludePath)(g,d))return interceptForInjection(t,n,w),void o();let m;try{m=r(t)}catch{}if((0,sentinel_user_id_1.isSentinelUserId)(m)){const e=(0,cookies_1.parseCookie)(t,"__unshared_uid"),r=(0,cookies_1.parseCookie)(t,"__unshared_uid_at"),i=r?Number(r):NaN,s=Number.isFinite(i)&&Date.now()-i<=sentinel_user_id_1.SENTINEL_STICKINESS_TTL_MS;m=e&&s?e:void 0}if(!m){const e=(0,secure_1.isSecureRequest)(t)?"; Secure":"";return appendSetCookie(n,`__unshared_uid=; Path=/; SameSite=Lax; Max-Age=0${e}`),appendSetCookie(n,`__unshared_uid_at=; Path=/; SameSite=Lax; Max-Age=0${e}`),appendSetCookie(n,`__unshared_sid=; Path=/; SameSite=Lax; Max-Age=0${e}`),appendSetCookie(n,`__unshared_email=; Path=/; SameSite=Lax; Max-Age=0${e}`),interceptForInjection(t,n,w),void o()}const I=resolveEmail(t,i);if(setUserIdCookie(t,n,m),I&&setEmailCookie(t,n,I),!I)return interceptForInjection(t,n,w),void o();const k=extractSessionId(t,_),A=(0,device_id_1.extractDeviceIdOrUndefined)(t,p),E=extractFingerprintId(t),F=t.headers["user-agent"]??"",q=(0,client_ip_1.extractClientIp)(t),X=A??E;if(!c&&(0,is_bot_1.isBot)(F))return void o();const W=S.get(m);if(!W){if("async"===u)return S.isRefreshing(m)||(S.markRefreshing(m),fetchAndCacheVerdict(e,S,m,I,X??"unknown",E,k,l,(e,t)=>C({operation:"checkUser",reason:e,status:t,userId:m,emailAddress:I})).catch(()=>C({operation:"checkUser",reason:"exception",userId:m,emailAddress:I})).finally(()=>S.clearRefreshing(m))),C({operation:"checkUser",reason:"async_miss",userId:m,emailAddress:I}),J(),interceptForInjection(t,n,w),void o();let r=T.get(m);return r||(r=fetchAndCacheVerdict(e,S,m,I,X??"unknown",E,k,l,(e,t)=>C({operation:"checkUser",reason:e,status:t,userId:m,emailAddress:I})).finally(()=>T.delete(m)),T.set(m,r)),void r.then(e=>{e.isFlagged||J(),applyVerdict(e,m,I,t,n,o,w,h,y,M)}).catch(()=>{C({operation:"checkUser",reason:"exception",userId:m,emailAddress:I}),J(),interceptForInjection(t,n,w),o()})}function J(){"unknown"!==k&&X&&(x.isPaused()||dispatchUserEvent(e,S,x,b,{userId:m,emailAddress:I,sessionId:k,deviceId:X,fingerprintId:E,userAgent:F,ipAddress:q,eventType:v},f))}S.isStale(m)&&!S.isRefreshing(m)&&(S.markRefreshing(m),fetchAndCacheVerdict(e,S,m,I,X??"unknown",E,k,l,(e,t)=>C({operation:"checkUser",reason:e,status:t,userId:m,emailAddress:I})).catch(()=>C({operation:"checkUser",reason:"exception",userId:m,emailAddress:I})).finally(()=>S.clearRefreshing(m))),W.isFlagged||J(),applyVerdict(W,m,I,t,n,o,w,h,y,M)};return X.destroy=()=>{S.destroy(),U.stop()},X}function resolveEmail(e,t){if(t)try{const r=t(e);if(r)return r}catch{}const r=(0,cookies_1.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,s,n,o,a,d,c){const l=e.isFlagged&&!e.isVerified;if("gate"===d&&l)(0,content_type_1.isHtmlNavigation)(i.method,i.headers.accept)?(s.statusCode=200,s.setHeader("Content-Type","text/html; charset=utf-8"),s.setHeader("Cache-Control","no-store"),s.end(c)):(0,http_helpers_1.sendJson)(s,403,(0,flagged_response_1.flaggedResponse)(r));else if("overlay"===d&&l)(0,content_type_1.isHtmlNavigation)(i.method,i.headers.accept)?(interceptForInjection(i,s,o),n()):(0,http_helpers_1.sendJson)(s,403,(0,flagged_response_1.flaggedResponse)(r));else if(interceptForInjection(i,s,o),e.isFlagged&&!e.isVerified&&a)try{a({userId:t,emailAddress:r,verdict:e,req:i,res:s,next:n})}catch{n()}else n()}function interceptForInjection(e,t,r){delete e.headers["if-none-match"],delete e.headers["if-modified-since"],(0,response_interceptor_1.interceptResponse)(t,(e,t)=>{if(!(0,content_type_1.isHtmlContentType)(t))return null;const i=e.toString("utf8"),s=i.lastIndexOf("</body>");return-1===s?i+r:i.slice(0,s)+r+i.slice(s)},{preventCaching:!0})}function dispatchUserEvent(e,t,r,i,s,n){i.mark(s.userId,s.eventType),e.processUserEvent({eventType:s.eventType,userId:s.userId,emailAddress:s.emailAddress,ipAddress:s.ipAddress,deviceId:s.deviceId,fingerprintId:s.fingerprintId,sessionHash:s.sessionId,userAgent:s.userAgent}).then(e=>{e.success&&e.data?.analysis&&t.update(s.userId,{isFlagged:e.data.analysis.is_user_flagged}),!e.success&&e.error?.retryAfter&&r.pause(1e3*e.error.retryAfter)}).catch(e=>{n&&n(e,{operation:"processUserEvent",userId:s.userId,emailAddress:s.emailAddress})})}async function fetchAndCacheVerdict(e,t,r,i,s,n,o,a=CHECK_USER_TIMEOUT_MS,d){const c={timeoutMs:a,maxRetries:0},l=s&&"unknown"!==s?s:void 0,u=l??n,_=n??l;let p;u&&(c.deviceId=u),_&&(c.fingerprintId=_);const h=await Promise.race([e.checkUser(i,c),new Promise(e=>{p=setTimeout(()=>e(null),a)})]);return clearTimeout(p),h?h.failedOpen?(d?.(h.failedOpen.reason??"http_error",h.failedOpen.status),cacheFailOpenVerdict(t,r,i,o)):(t.set(r,{isFlagged:h.data?.is_user_flagged??!1,isVerified:!1,emailAddress:i,sessionId:o}),t.get(r)):(d?.("timeout"),cacheFailOpenVerdict(t,r,i,o))}function cacheFailOpenVerdict(e,t,r,i){const s=e.get(t);return e.set(t,{isFlagged:s?.isFlagged??!1,isVerified:s?.isVerified??!1,emailAddress:r,sessionId:i},5e3),e.get(t)}function extractSessionId(e,t){if(t)try{const r=t(e);if(r)return r}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,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,r){const i=(0,secure_1.isSecureRequest)(e)?"; Secure":"";appendSetCookie(t,`__unshared_uid=${encodeURIComponent(r)}; Path=/; SameSite=Lax${i}`),appendSetCookie(t,`__unshared_uid_at=${Date.now()}; Path=/; SameSite=Lax${i}`)}function setEmailCookie(e,t,r){const i=(0,secure_1.isSecureRequest)(e)?"; Secure":"";appendSetCookie(t,`__unshared_email=${encodeURIComponent(r)}; HttpOnly; Path=/; SameSite=Lax${i}`)}
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"t",{value:!0}),exports.interceptResponse=interceptResponse;const MAX_INJECT_BYTES=2e6;function interceptResponse(n,t,e){const f=e?.preventCaching??!1,o=n.write.bind(n),u=n.end.bind(n),r=n.writeHead.bind(n),c=[];let i=0,l=!1,s=null;function p(){n.write=o,n.end=u;for(const n of c)o(n);c.length=0}function y(){if(null!==s)return;const t=n.getHeader("content-type");null!=t&&(s=String(t).includes("text/html"),s&&n.getHeader("content-encoding")&&(s=!1),s||p())}n.writeHead=function(t,...e){return y(),f&&s&&(n.setHeader("Cache-Control","no-store"),n.removeHeader("ETag"),n.removeHeader("Last-Modified")),r(t,...e)},n.write=function(n,t,e){if(y(),!1===s)return o(n,t,e);if(null!=n){const f=Buffer.isBuffer(n)?n:Buffer.from(n,"string"==typeof t?t:"utf8");if(i+=f.length,c.push(f),i>MAX_INJECT_BYTES)return p(),"function"==typeof t&&t(null),"function"==typeof e&&e(null),!0}return"function"==typeof t&&t(null),"function"==typeof e&&e(null),!0},n.end=function(e,f,r){if(l)return n;if(l=!0,y(),!1===s)return u(e,f,r);if(null!=e){const n=Buffer.isBuffer(e)?e:Buffer.from(e,"string"==typeof f?f:"utf8");i+=n.length,c.push(n)}if(i>MAX_INJECT_BYTES){p();const n="function"==typeof f?f:r;return n?u(n):u()}const B=Buffer.concat(c),E=n.getHeader("content-type");let _;try{_=t(B,E)}catch{_=null}if(null!=_){const t=Buffer.isBuffer(_)?_:Buffer.from(_,"utf8");n.setHeader("Content-Length",t.length),n.removeHeader("Content-Encoding"),o(t)}else B.length>0&&o(B);const g="function"==typeof f?f:r;return g?u(g):u(),n}}
1
+ "use strict";Object.defineProperty(exports,"t",{value:!0}),exports.interceptResponse=interceptResponse;const MAX_INJECT_BYTES=2e6;function findWriteHeadHeaders(e){return e.find(e=>e&&"object"==typeof e)}function readHeaderFromWriteHeadArgs(e,t){const n=findWriteHeadHeaders(e);if(!n)return;const r=t.toLowerCase();if(Array.isArray(n)){if(n.length&&Array.isArray(n[0])){for(const e of n)if(String(e[0]).toLowerCase()===r)return e[1]}else for(let e=0;e+1<n.length;e+=2)if(String(n[e]).toLowerCase()===r)return n[e+1]}else for(const e of Object.keys(n))if(e.toLowerCase()===r)return n[e]}function deleteHeaderFromWriteHeadArgs(e,t){const n=findWriteHeadHeaders(e);if(!n)return;const r=t.toLowerCase();if(Array.isArray(n))if(n.length&&Array.isArray(n[0]))for(let e=n.length-1;e>=0;e--)String(n[e][0]).toLowerCase()===r&&n.splice(e,1);else for(let e=n.length-2;e>=0;e-=2)String(n[e]).toLowerCase()===r&&n.splice(e,2);else for(const e of Object.keys(n))e.toLowerCase()===r&&delete n[e]}function interceptResponse(e,t,n){const r=n?.preventCaching??!1,o=e.write.bind(e),f=e.end.bind(e),i=e.writeHead.bind(e),c=[];let u=0,s=!1,l=null;function d(){e.write=o,e.end=f;for(const e of c)o(e);c.length=0}function a(e,t){null===l&&null!=e&&(l=String(e).includes("text/html"),l&&t&&(l=!1),!1===l&&d())}function g(){a(e.getHeader("content-type"),e.getHeader("content-encoding"))}e.writeHead=function(t,...n){return a(readHeaderFromWriteHeadArgs(n,"content-type")??e.getHeader("content-type"),readHeaderFromWriteHeadArgs(n,"content-encoding")??e.getHeader("content-encoding")),l&&(r&&(e.setHeader("Cache-Control","no-store"),e.removeHeader("ETag"),e.removeHeader("Last-Modified")),e.removeHeader("Content-Length"),deleteHeaderFromWriteHeadArgs(n,"content-length")),i(t,...n)},e.write=function(e,t,n){if(g(),!1===l)return o(e,t,n);if(null!=e){const r=Buffer.isBuffer(e)?e:Buffer.from(e,"string"==typeof t?t:"utf8");if(u+=r.length,c.push(r),u>MAX_INJECT_BYTES)return d(),"function"==typeof t&&t(null),"function"==typeof n&&n(null),!0}return"function"==typeof t&&t(null),"function"==typeof n&&n(null),!0},e.end=function(n,r,i){if(s)return e;if(s=!0,g(),!1===l)return f(n,r,i);if(null!=n){const e=Buffer.isBuffer(n)?n:Buffer.from(n,"string"==typeof r?r:"utf8");u+=e.length,c.push(e)}if(u>MAX_INJECT_BYTES){d();const e="function"==typeof r?r:i;return e?f(e):f()}const a=Buffer.concat(c),p=e.getHeader("content-type");let y;try{y=t(a,p)}catch{y=null}if(null!=y){const t=Buffer.isBuffer(y)?y:Buffer.from(y,"utf8");e.headersSent||(e.setHeader("Content-Length",t.length),e.removeHeader("Content-Encoding")),o(t)}else a.length>0&&o(a);const H="function"==typeof r?r:i;return H?f(H):f(),e}}
@@ -14,6 +14,13 @@ export declare function sendEmpty(res: UnsharedResponse, statusCode: number): vo
14
14
  * `res.status(code).end(body)`.
15
15
  */
16
16
  export declare function sendBody(res: UnsharedResponse, statusCode: number, body: string | Buffer): void;
17
+ /**
18
+ * Send a body that honors HEAD. A HEAD response carries the same status and
19
+ * headers a GET would, but no body (RFC 9110 §9.3.2). Content-Length is set so a
20
+ * HEAD reports the byte size a GET would return. Use for GET routes that may also
21
+ * receive HEAD (e.g. a client probing a static asset with `curl -I`).
22
+ */
23
+ export declare function sendBodyForMethod(res: UnsharedResponse, method: string | undefined, statusCode: number, body: string | Buffer): void;
17
24
  /**
18
25
  * Extract the pathname from a request URL, stripping query string.
19
26
  * Framework-agnostic replacement for Express's `req.path`.
@@ -1 +1 @@
1
- "use strict";function sendJson(t,e,n){const s=JSON.stringify(n);t.statusCode=e,t.setHeader("Content-Type","application/json"),t.end(s)}function sendEmpty(t,e){t.statusCode=e,t.end()}function sendBody(t,e,n){t.statusCode=e,t.end(n)}function getRequestPath(t){if(!t)return"/";const e=t.indexOf("?");return-1===e?t:t.slice(0,e)}Object.defineProperty(exports,"t",{value:!0}),exports.sendJson=sendJson,exports.sendEmpty=sendEmpty,exports.sendBody=sendBody,exports.getRequestPath=getRequestPath;
1
+ "use strict";function sendJson(t,e,n){const o=JSON.stringify(n);t.statusCode=e,t.setHeader("Content-Type","application/json"),t.end(o)}function sendEmpty(t,e){t.statusCode=e,t.end()}function sendBody(t,e,n){t.statusCode=e,t.end(n)}function sendBodyForMethod(t,e,n,o){t.statusCode=n,t.setHeader("Content-Length",String(Buffer.byteLength(o))),"HEAD"===e?t.end():t.end(o)}function getRequestPath(t){if(!t)return"/";const e=t.indexOf("?");return-1===e?t:t.slice(0,e)}Object.defineProperty(exports,"t",{value:!0}),exports.sendJson=sendJson,exports.sendEmpty=sendEmpty,exports.sendBody=sendBody,exports.sendBodyForMethod=sendBodyForMethod,exports.getRequestPath=getRequestPath;
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"t",{value:!0}),exports.createWebProtectionMiddleware=createWebProtectionMiddleware;const util_1=require("../util"),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"),web_helpers_1=require("./web-helpers"),CHECK_USER_TIMEOUT_MS=500;function createWebProtectionMiddleware(e,r){if(!r.userId)throw new Error("[Unshared] userId resolver is required");const{userId:t,emailAddress:s,routePrefix:i="/__unshared",corsOrigins:n,cacheTTL:a=6e4,skipPaths:o,includePathPrefix:d,sessionId:_,deviceId:c,fingerprintSdkBundle:l="",onFlagged:u,onError:p,onFailOpen:h,disableBotFilter:f=!1,checkUserTimeoutMs:m=CHECK_USER_TIMEOUT_MS,blockFlagged:w=!1,flaggedMode:g,autoInterstitial:b=!1,interstitialFlowType:v="email_verification"}=r,I=g??(w?"gate":void 0),y=b||"overlay"===I,A=e=>{if(h)try{h(e)}catch{}},S=Date.now().toString(36),E=void 0!==I||y,R="gate"===I?"flaggedMode 'gate' (blockFlagged)":"overlay"===I?"flaggedMode 'overlay'":"autoInterstitial";if(E&&!l)throw new Error(`[Unshared] ${R} requires fingerprintSdkBundle (the browser SDK UMD served at {routePrefix}/fp.js renders the interstitial).`);if(E&&"/__unshared"!==i)throw new Error(`[Unshared] ${R} requires the default routePrefix ("/__unshared"); the browser SDK proxy routes are fixed to that prefix.`);const T="gate"===I?(0,gate_page_1.generateGatePage)(i,S):"",k=new verdict_cache_1.VerdictCache(a),C=new rate_limit_backoff_1.RateLimitBackoff,x=new dispatch_dedupe_1.DispatchDedupe,U=(0,fingerprint_script_1.generateFingerprintScript)(i,S,{autoInterstitial:y,interstitialFlowType:v}),O=`${i}/fp.js`,$=`${i}/submit-fp`,F=`${i}/verify-trigger`,P=`${i}/verify`,q=`${i}/status`,L=n?Array.isArray(n)?n:[n]:null;return async function(r,n){let a,h,w;try{const e=new URL(r.url);a=e.pathname,h=e.search}catch{return n(r)}if(a.startsWith(i+"/")){const i=function(e){if(!L)return{};const r=e.headers.get("origin")??"",t=L.includes("*");return t||L.includes(r)?{"Access-Control-Allow-Origin":t?"*":r,"Access-Control-Allow-Methods":"POST, OPTIONS","Access-Control-Allow-Headers":"Content-Type, X-Idempotency-Key, X-Session-Id, X-Device-Id","Access-Control-Allow-Credentials":"true"}:{}}(r);if("OPTIONS"===r.method)return(0,web_helpers_1.emptyResponse)(204,i);if("GET"===r.method&&a===O)return l?(0,web_helpers_1.bodyResponse)(200,l,{...i,"Content-Type":"application/javascript","Cache-Control":"public, max-age=3600"}):(0,web_helpers_1.jsonResponse)(404,{success:!1,error:{code:"NOT_FOUND",message:"Fingerprint SDK bundle not configured. Pass fingerprintSdkBundle in config."}},i);if("POST"===r.method&&(a===$||a===F||a===P)){let n;try{n=await r.json()}catch{return(0,web_helpers_1.jsonResponse)(400,{success:!1,error:{code:"BODY_PARSER_MISSING",message:"Request body is not valid JSON."}},i)}return a===$?handleSubmitFp(r,n,{client:e,verdictCache:k,rateLimitBackoff:C,dispatchDedupe:x,resolveUserId:t,resolveEmailAddress:s,resolveSessionId:_,resolveDeviceId:c,disableBotFilter:f,onError:p},i):a===F?handleVerifyTriggerWeb(r,n,{client:e,verdictCache:k,resolveEmailAddress:s,resolveDeviceId:c,onError:p},i):handleVerifyWeb(r,n,{client:e,verdictCache:k,resolveEmailAddress:s,resolveDeviceId:c,onError:p},i)}if("GET"===r.method&&a===q){let n;try{n=t(r)}catch{}if(!n)return(0,web_helpers_1.jsonResponse)(200,{status:"anonymous"},i);const a=resolveEmail(r,s);let o=k.get(n);if((!o||k.isStale(n))&&a&&!C.isPaused()&&!k.isRefreshing(n)){k.markRefreshing(n);try{const t=(0,web_helpers_1.extractDeviceIdFromRequest)(r,c),s=(0,web_helpers_1.parseCookieFromRequest)(r,"__unshared_fingerprint_id")||void 0,i=extractSessionIdFromRequest(r,_),d=t??s??"unknown";await fetchAndCacheVerdict(e,k,n,a,d,s,i,m,(e,r)=>A({operation:"checkUser",reason:e,status:r,userId:n,emailAddress:a})),o=k.get(n)}catch(e){p&&p(e,{operation:"checkUser",userId:n,emailAddress:a}),A({operation:"checkUser",reason:"exception",userId:n,emailAddress:a})}finally{k.clearRefreshing(n)}}return o&&o.isFlagged&&!o.isVerified&&u&&a?(0,web_helpers_1.jsonResponse)(200,{status:"flagged",email:(0,mask_email_1.maskEmail)(a)},i):(0,web_helpers_1.jsonResponse)(200,{status:"ok"},i)}return(0,web_helpers_1.jsonResponse)(404,{success:!1,error:{code:"NOT_FOUND",message:"Unknown route"}},i)}if((0,skip_paths_1.shouldSkipPath)(a,o))return n(r);if(!(0,include_path_1.shouldIncludePath)(a,d))return injectIntoHtmlResponse(await n(r),U);try{w=t(r)}catch{}if((0,sentinel_user_id_1.isSentinelUserId)(w)){const e=(0,web_helpers_1.parseCookieFromRequest)(r,"__unshared_uid"),t=(0,web_helpers_1.parseCookieFromRequest)(r,"__unshared_uid_at"),s=t?Number(t):NaN,i=Number.isFinite(s)&&Date.now()-s<=sentinel_user_id_1.SENTINEL_STICKINESS_TTL_MS;w=e&&i?e:void 0}if(!w){const e=(0,web_helpers_1.isSecureWebRequest)(r)?"; Secure":"",t=[`__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}`];return injectIntoHtmlResponse(await n(r),U,t)}const g=resolveEmail(r,s),b=[],v=(0,web_helpers_1.isSecureWebRequest)(r)?"; Secure":"";if(b.push(`__unshared_uid=${encodeURIComponent(w)}; Path=/; SameSite=Lax${v}`),b.push(`__unshared_uid_at=${Date.now()}; Path=/; SameSite=Lax${v}`),g&&b.push(`__unshared_email=${encodeURIComponent(g)}; HttpOnly; Path=/; SameSite=Lax${v}`),!g)return injectIntoHtmlResponse(await n(r),U,b);const y=extractSessionIdFromRequest(r,_),S=(0,web_helpers_1.extractDeviceIdFromRequest)(r,c),E=(0,web_helpers_1.parseCookieFromRequest)(r,"__unshared_fingerprint_id")||void 0,R=r.headers.get("user-agent")??"",M=(0,web_helpers_1.extractClientIpFromRequest)(r),D=S??E;if(!f&&(0,is_bot_1.isBot)(R))return n(r);let N=k.get(w);if(N)k.isStale(w)&&!k.isRefreshing(w)&&(k.markRefreshing(w),fetchAndCacheVerdict(e,k,w,g,D??"unknown",E,y,m,(e,r)=>A({operation:"checkUser",reason:e,status:r,userId:w,emailAddress:g})).catch(()=>A({operation:"checkUser",reason:"exception",userId:w,emailAddress:g})).finally(()=>k.clearRefreshing(w)));else try{N=await fetchAndCacheVerdict(e,k,w,g,D??"unknown",E,y,m,(e,r)=>A({operation:"checkUser",reason:e,status:r,userId:w,emailAddress:g}))}catch{return A({operation:"checkUser",reason:"exception",userId:w,emailAddress:g}),injectIntoHtmlResponse(await n(r),U,b)}const H=N.isFlagged&&!N.isVerified;if("gate"===I&&H)return(0,content_type_1.isHtmlNavigation)(r.method,r.headers.get("accept")??void 0)?(0,web_helpers_1.bodyResponse)(200,T,{"Content-Type":"text/html; charset=utf-8","Cache-Control":"no-store"}):(0,web_helpers_1.jsonResponse)(403,(0,flagged_response_1.flaggedResponse)(g));if("overlay"===I&&H)return(0,content_type_1.isHtmlNavigation)(r.method,r.headers.get("accept")??void 0)?injectIntoHtmlResponse(await n(r),U,b):(0,web_helpers_1.jsonResponse)(403,(0,flagged_response_1.flaggedResponse)(g));if(N.isFlagged&&!N.isVerified&&u)try{const e=await u({userId:w,emailAddress:g,verdict:N,request:r});if(e)return injectIntoHtmlResponse(e,U,b)}catch(e){p&&p(e,{operation:"checkUser",userId:w,emailAddress:g})}return N.isFlagged||"unknown"===y||!D||C.isPaused()||dispatchUserEvent(e,k,C,x,{userId:w,emailAddress:g,sessionId:y,deviceId:D,fingerprintId:E,userAgent:R,ipAddress:M,eventType:a+h},p),injectIntoHtmlResponse(await n(r),U,b)}}async function injectIntoHtmlResponse(e,r,t){const s=e.headers.get("content-type");if(!(0,content_type_1.isHtmlContentType)(s??void 0)){if(!t||0===t.length)return e;const r=(0,web_helpers_1.mergeResponseHeaders)(e.headers,void 0,t);return new Response(e.body,{status:e.status,statusText:e.statusText,headers:r})}const i=await e.text(),n=i.lastIndexOf("</body>"),a=-1===n?i+r:i.slice(0,n)+r+i.slice(n),o=(0,web_helpers_1.mergeResponseHeaders)(e.headers,{"Cache-Control":"no-store","Content-Length":String((new TextEncoder).encode(a).length)},t);return o.delete("ETag"),o.delete("Last-Modified"),o.delete("Content-Encoding"),new Response(a,{status:e.status,statusText:e.statusText,headers:o})}function resolveEmail(e,r){if(r)try{const t=r(e);if(t)return t}catch{}const t=(0,web_helpers_1.parseCookieFromRequest)(e,"__unshared_email");if(t)return t}function resolveEmailWithBody(e,r,t){const s=resolveEmail(e,t);if(s)return s;const i=r.email;return"string"==typeof i&&i?i:void 0}function extractSessionIdFromRequest(e,r){if(r)try{const t=r(e);if(t)return t}catch{}return(0,web_helpers_1.parseCookieFromRequest)(e,"__unshared_sid")??"unknown"}function dispatchUserEvent(e,r,t,s,i,n){s.mark(i.userId,i.eventType),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(e=>{n&&n(e,{operation:"processUserEvent",userId:i.userId,emailAddress:i.emailAddress})})}async function fetchAndCacheVerdict(e,r,t,s,i,n,a,o=CHECK_USER_TIMEOUT_MS,d){const _={};let c;i&&"unknown"!==i&&(_.deviceId=i),n&&(_.fingerprintId=n);const l=await Promise.race([e.checkUser(s,_),new Promise(e=>{c=setTimeout(()=>e(null),o)})]);if(clearTimeout(c),!l)return d?.("timeout"),{isFlagged:!1,isVerified:!1,emailAddress:s,sessionId:a,cachedAt:0,ttl:0};l.failedOpen&&d?.("http_error",l.failedOpen.status);const u=l.data?.is_user_flagged??!1;return r.set(t,{isFlagged:u,isVerified:!1,emailAddress:s,sessionId:a}),r.get(t)}async function handleSubmitFp(e,r,t,s){try{const i={full_hash:r.hash??"",fingerprint_id:r.stable_hash??"",timestamp:r.collected_at??(new Date).toISOString(),isIncognito:r.is_incognito??!1,components:r.components??{},version:r.version??"inline-1.0.0"};let n,a,o;try{const r=t.resolveUserId(e);r&&!(0,sentinel_user_id_1.isSentinelUserId)(r)&&(n=r)}catch{}if(!n){const e="string"==typeof r.user_id?r.user_id:void 0;e&&!(0,sentinel_user_id_1.isSentinelUserId)(e)&&(n=e)}if(!n){const r=(0,web_helpers_1.parseCookieFromRequest)(e,"__unshared_uid");r&&!(0,sentinel_user_id_1.isSentinelUserId)(r)&&(n=r)}try{a=t.resolveEmailAddress?t.resolveEmailAddress(e):void 0}catch{}a=a??(0,web_helpers_1.parseCookieFromRequest)(e,"__unshared_email")??r.email??void 0;try{o=t.resolveSessionId?t.resolveSessionId(e):void 0}catch{}o=o??r.session_id??(0,web_helpers_1.parseCookieFromRequest)(e,"__unshared_sid");const d=(0,web_helpers_1.extractClientIpFromRequest)(e),_=e.headers.get("user-agent")??"";if(!t.disableBotFilter&&(0,is_bot_1.isBot)(_))return(0,web_helpers_1.jsonResponse)(200,{success:!0},s);const c=(i.fingerprint_id&&i.fingerprint_id.length>0?i.fingerprint_id:void 0)??(0,web_helpers_1.extractDeviceIdFromRequestOrUnknown)(e,t.resolveDeviceId),l=i.fingerprint_id||void 0,u=i.full_hash||void 0,p=(0,web_helpers_1.isSecureWebRequest)(e)?"; Secure":"",h=[];if(u&&!(0,web_helpers_1.parseCookieFromRequest)(e,"__unshared_fingerprint_id")&&h.push(`__unshared_fingerprint_id=${encodeURIComponent(u)}; HttpOnly; Path=/; SameSite=Lax${p}`),l){const r=(0,web_helpers_1.parseCookieFromRequest)(e,"__unshared_fp_id");r&&r===l||h.push(`__unshared_fp_id=${encodeURIComponent(l)}; Path=/; SameSite=Lax; Max-Age=31536000${p}`)}let f;if(a&&!(0,web_helpers_1.parseCookieFromRequest)(e,"__unshared_email")&&h.push(`__unshared_email=${encodeURIComponent(a)}; HttpOnly; Path=/; SameSite=Lax${p}`),"string"==typeof r.event_type&&r.event_type)f=r.event_type;else{const r=e.headers.get("referer")??e.headers.get("referrer");let t="unknown";if(r)try{const e=new URL(r);t=(e.pathname||"/")+(e.search||"")}catch{}f=t}const m=e.headers.get("x-idempotency-key")||void 0,w=Date.now(),g=m?`${m}|${w}`:l&&n?`${(0,util_1.sha256Hex)(`${l}|${n}|${f}`)}|${w}`:void 0;n&&t.client.submitFingerprintEvent(i,{userId:n,emailAddress:a,sessionHash:o,eventType:f,ipAddress:d,userAgent:_,idempotencyKey:g}).catch(e=>{t.onError&&t.onError(e,{operation:"submitFingerprintEvent",userId:n,emailAddress:a})}),n&&a&&!t.rateLimitBackoff.isPaused()&&!t.dispatchDedupe.wasRecentlyDispatched(n,f)&&t.client.processUserEvent({eventType:f,userId:n,emailAddress:a,ipAddress:d,deviceId:c,fingerprintId:l,sessionHash:o??"unknown",userAgent:_}).then(e=>{e.success&&e.data?.analysis&&t.verdictCache.update(n,{isFlagged:e.data.analysis.is_user_flagged}),!e.success&&e.error?.retryAfter&&t.rateLimitBackoff.pause(1e3*e.error.retryAfter)}).catch(e=>{t.onError&&t.onError(e,{operation:"processUserEvent",userId:n,emailAddress:a})});const b={...s,"Content-Type":"application/json"},v=new Response(JSON.stringify({success:!0}),{status:200,headers:b});for(const e of h)v.headers.append("Set-Cookie",e);return v}catch{return(0,web_helpers_1.jsonResponse)(200,{success:!0},s)}}async function handleVerifyTriggerWeb(e,r,t,s){try{const i=resolveEmailWithBody(e,r??{},t.resolveEmailAddress);if(!i)return(0,web_helpers_1.jsonResponse)(400,{success:!1,error:{code:"VALIDATION_ERROR",message:"Email is required"}},s);const n=(0,web_helpers_1.extractDeviceIdFromRequestOrUnknown)(e,t.resolveDeviceId),a=(0,web_helpers_1.parseCookieFromRequest)(e,"__unshared_fingerprint_id")||void 0,o=await t.client.triggerEmailVerification(i,n,{fingerprintId:a});return o.success?(0,web_helpers_1.jsonResponse)(200,{success:!0,data:o.data},s):(0,web_helpers_1.jsonResponse)(200,{success:!1,error:o.error??{code:"TRIGGER_FAILED",message:"Failed to send verification email"}},s)}catch(e){return t.onError&&t.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,r,t,s){try{const i=resolveEmailWithBody(e,r??{},t.resolveEmailAddress),n=r?.code;if(!i||!n)return(0,web_helpers_1.jsonResponse)(400,{success:!1,error:{code:"VALIDATION_ERROR",message:"Email and code are required"}},s);const a=(0,web_helpers_1.extractDeviceIdFromRequestOrUnknown)(e,t.resolveDeviceId),o=(0,web_helpers_1.parseCookieFromRequest)(e,"__unshared_fingerprint_id")||void 0,d=await t.client.verify(i,a,n,{fingerprintId:o});if(d.success){const r=(0,web_helpers_1.parseCookieFromRequest)(e,"__unshared_uid");return r&&t.verdictCache.update(r,{isVerified:!0}),(0,web_helpers_1.jsonResponse)(200,{success:!0,data:{verified:!0}},s)}return(0,web_helpers_1.jsonResponse)(200,{success:!1,error:d.error??{code:"VERIFICATION_FAILED",message:"Verification failed"}},s)}catch(e){return t.onError&&t.onError(e,{operation:"verify"}),(0,web_helpers_1.jsonResponse)(200,{success:!1,error:{code:"INTERNAL_ERROR",message:"Verification failed"}},s)}}
1
+ "use strict";Object.defineProperty(exports,"t",{value:!0}),exports.createWebProtectionMiddleware=createWebProtectionMiddleware;const util_1=require("../util"),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"),web_helpers_1=require("./web-helpers"),CHECK_USER_TIMEOUT_MS=1500,FAIL_OPEN_TTL_MS=5e3;function createWebProtectionMiddleware(e,r){if(!r.userId)throw new Error("[Unshared] userId resolver is required");const{userId:t,emailAddress:s,routePrefix:i="/__unshared",corsOrigins:n,cacheTTL:a=6e4,skipPaths:o,includePathPrefix:d,sessionId:_,deviceId:c,fingerprintSdkBundle:l="",onFlagged:u,onError:p,onFailOpen:h,disableBotFilter:f=!1,checkUserTimeoutMs:m=CHECK_USER_TIMEOUT_MS,blockFlagged:w=!1,flaggedMode:g,autoInterstitial:v=!1,interstitialFlowType:I="email_verification"}=r,b=g??(w?"gate":void 0),y=v||"overlay"===b,A=e=>{if(h)try{h(e)}catch{}},S=Date.now().toString(36),E=void 0!==b||y,R="gate"===b?"flaggedMode 'gate' (blockFlagged)":"overlay"===b?"flaggedMode 'overlay'":"autoInterstitial";if(E&&!l)throw new Error(`[Unshared] ${R} requires fingerprintSdkBundle (the browser SDK UMD served at {routePrefix}/fp.js renders the interstitial).`);if(E&&"/__unshared"!==i)throw new Error(`[Unshared] ${R} requires the default routePrefix ("/__unshared"); the browser SDK proxy routes are fixed to that prefix.`);const T="gate"===b?(0,gate_page_1.generateGatePage)(i,S):"",k=new verdict_cache_1.VerdictCache(a),x=new rate_limit_backoff_1.RateLimitBackoff,C=new dispatch_dedupe_1.DispatchDedupe,U=(0,fingerprint_script_1.generateFingerprintScript)(i,S,{autoInterstitial:y,interstitialFlowType:I}),O=`${i}/fp.js`,$=`${i}/submit-fp`,F=`${i}/verify-trigger`,P=`${i}/verify`,L=`${i}/status`,q=n?Array.isArray(n)?n:[n]:null;return async function(r,n){let a,h,w;try{const e=new URL(r.url);a=e.pathname,h=e.search}catch{return n(r)}if(a.startsWith(i+"/")){const i=function(e){if(!q)return{};const r=e.headers.get("origin")??"",t=q.includes("*");if(!t&&!q.includes(r))return{};const s={"Access-Control-Allow-Origin":t?"*":r,"Access-Control-Allow-Methods":"POST, OPTIONS","Access-Control-Allow-Headers":"Content-Type, X-Idempotency-Key, X-Session-Id, X-Device-Id"};return t||(s["Access-Control-Allow-Credentials"]="true"),s}(r);if("OPTIONS"===r.method)return(0,web_helpers_1.emptyResponse)(204,i);if(("GET"===r.method||"HEAD"===r.method)&&a===O){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."}},i);const e={...i,"Content-Type":"application/javascript","Cache-Control":"public, max-age=3600","Content-Length":String((new TextEncoder).encode(l).byteLength)};return"HEAD"===r.method?new Response(null,{status:200,headers:new Headers(e)}):(0,web_helpers_1.bodyResponse)(200,l,e)}if("POST"===r.method&&(a===$||a===F||a===P)){let n;try{n=await r.json()}catch{return(0,web_helpers_1.jsonResponse)(400,{success:!1,error:{code:"BODY_PARSER_MISSING",message:"Request body is not valid JSON."}},i)}return a===$?handleSubmitFp(r,n,{client:e,verdictCache:k,rateLimitBackoff:x,dispatchDedupe:C,resolveUserId:t,resolveEmailAddress:s,resolveSessionId:_,resolveDeviceId:c,disableBotFilter:f,onError:p},i):a===F?handleVerifyTriggerWeb(r,n,{client:e,verdictCache:k,resolveUserId:t,resolveEmailAddress:s,resolveDeviceId:c,onError:p},i):handleVerifyWeb(r,n,{client:e,verdictCache:k,resolveUserId:t,resolveEmailAddress:s,resolveDeviceId:c,onError:p},i)}if("GET"===r.method&&a===L){let n;try{n=t(r)}catch{}if(!n)return(0,web_helpers_1.jsonResponse)(200,{status:"anonymous"},i);const a=resolveEmail(r,s);let o=k.get(n);if((!o||k.isStale(n))&&a&&!x.isPaused()&&!k.isRefreshing(n)){k.markRefreshing(n);try{const t=(0,web_helpers_1.extractDeviceIdFromRequest)(r,c),s=(0,web_helpers_1.parseCookieFromRequest)(r,"__unshared_fingerprint_id")||void 0,i=extractSessionIdFromRequest(r,_),d=t??s??"unknown";await fetchAndCacheVerdict(e,k,n,a,d,s,i,m,(e,r)=>A({operation:"checkUser",reason:e,status:r,userId:n,emailAddress:a})),o=k.get(n)}catch(e){p&&p(e,{operation:"checkUser",userId:n,emailAddress:a}),A({operation:"checkUser",reason:"exception",userId:n,emailAddress:a})}finally{k.clearRefreshing(n)}}return o&&o.isFlagged&&!o.isVerified&&(void 0!==b||u)&&a?(0,web_helpers_1.jsonResponse)(200,{status:"flagged",email:(0,mask_email_1.maskEmail)(a)},i):(0,web_helpers_1.jsonResponse)(200,{status:"ok"},i)}return(0,web_helpers_1.jsonResponse)(404,{success:!1,error:{code:"NOT_FOUND",message:"Unknown route"}},i)}if((0,skip_paths_1.shouldSkipPath)(a,o))return n(r);if(!(0,include_path_1.shouldIncludePath)(a,d))return injectIntoHtmlResponse(await n(r),U);try{w=t(r)}catch{}if((0,sentinel_user_id_1.isSentinelUserId)(w)){const e=(0,web_helpers_1.parseCookieFromRequest)(r,"__unshared_uid"),t=(0,web_helpers_1.parseCookieFromRequest)(r,"__unshared_uid_at"),s=t?Number(t):NaN,i=Number.isFinite(s)&&Date.now()-s<=sentinel_user_id_1.SENTINEL_STICKINESS_TTL_MS;w=e&&i?e:void 0}if(!w){const e=(0,web_helpers_1.isSecureWebRequest)(r)?"; Secure":"",t=[`__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}`];return injectIntoHtmlResponse(await n(r),U,t)}const g=resolveEmail(r,s),v=[],I=(0,web_helpers_1.isSecureWebRequest)(r)?"; Secure":"";if(v.push(`__unshared_uid=${encodeURIComponent(w)}; Path=/; SameSite=Lax${I}`),v.push(`__unshared_uid_at=${Date.now()}; Path=/; SameSite=Lax${I}`),g&&v.push(`__unshared_email=${encodeURIComponent(g)}; HttpOnly; Path=/; SameSite=Lax${I}`),!g)return injectIntoHtmlResponse(await n(r),U,v);const y=extractSessionIdFromRequest(r,_),S=(0,web_helpers_1.extractDeviceIdFromRequest)(r,c),E=(0,web_helpers_1.parseCookieFromRequest)(r,"__unshared_fingerprint_id")||void 0,R=r.headers.get("user-agent")??"",D=(0,web_helpers_1.extractClientIpFromRequest)(r),M=S??E;if(!f&&(0,is_bot_1.isBot)(R))return n(r);let H=k.get(w);if(H)k.isStale(w)&&!k.isRefreshing(w)&&(k.markRefreshing(w),fetchAndCacheVerdict(e,k,w,g,M??"unknown",E,y,m,(e,r)=>A({operation:"checkUser",reason:e,status:r,userId:w,emailAddress:g})).catch(()=>A({operation:"checkUser",reason:"exception",userId:w,emailAddress:g})).finally(()=>k.clearRefreshing(w)));else try{H=await fetchAndCacheVerdict(e,k,w,g,M??"unknown",E,y,m,(e,r)=>A({operation:"checkUser",reason:e,status:r,userId:w,emailAddress:g}))}catch{return A({operation:"checkUser",reason:"exception",userId:w,emailAddress:g}),injectIntoHtmlResponse(await n(r),U,v)}const N=H.isFlagged&&!H.isVerified;if("gate"===b&&N)return(0,content_type_1.isHtmlNavigation)(r.method,r.headers.get("accept")??void 0)?(0,web_helpers_1.bodyResponse)(200,T,{"Content-Type":"text/html; charset=utf-8","Cache-Control":"no-store"}):(0,web_helpers_1.jsonResponse)(403,(0,flagged_response_1.flaggedResponse)(g));if("overlay"===b&&N)return(0,content_type_1.isHtmlNavigation)(r.method,r.headers.get("accept")??void 0)?injectIntoHtmlResponse(await n(r),U,v):(0,web_helpers_1.jsonResponse)(403,(0,flagged_response_1.flaggedResponse)(g));if(H.isFlagged&&!H.isVerified&&u)try{const e=await u({userId:w,emailAddress:g,verdict:H,request:r});if(e)return injectIntoHtmlResponse(e,U,v)}catch(e){p&&p(e,{operation:"checkUser",userId:w,emailAddress:g})}return H.isFlagged||"unknown"===y||!M||x.isPaused()||dispatchUserEvent(e,k,x,C,{userId:w,emailAddress:g,sessionId:y,deviceId:M,fingerprintId:E,userAgent:R,ipAddress:D,eventType:a+h},p),injectIntoHtmlResponse(await n(r),U,v)}}async function injectIntoHtmlResponse(e,r,t){const s=e.headers.get("content-type");if(!(0,content_type_1.isHtmlContentType)(s??void 0)){if(!t||0===t.length)return e;const r=(0,web_helpers_1.mergeResponseHeaders)(e.headers,void 0,t);return new Response(e.body,{status:e.status,statusText:e.statusText,headers:r})}const i=await e.text(),n=i.lastIndexOf("</body>"),a=-1===n?i+r:i.slice(0,n)+r+i.slice(n),o=(0,web_helpers_1.mergeResponseHeaders)(e.headers,{"Cache-Control":"no-store","Content-Length":String((new TextEncoder).encode(a).length)},t);return o.delete("ETag"),o.delete("Last-Modified"),o.delete("Content-Encoding"),new Response(a,{status:e.status,statusText:e.statusText,headers:o})}function resolveEmail(e,r){if(r)try{const t=r(e);if(t)return t}catch{}const t=(0,web_helpers_1.parseCookieFromRequest)(e,"__unshared_email");if(t)return t}function resolveEmailWithBody(e,r,t){const s=resolveEmail(e,t);if(s)return s;const i=r.email;return"string"==typeof i&&i?i:void 0}function extractSessionIdFromRequest(e,r){if(r)try{const t=r(e);if(t)return t}catch{}return(0,web_helpers_1.parseCookieFromRequest)(e,"__unshared_sid")??"unknown"}function dispatchUserEvent(e,r,t,s,i,n){s.mark(i.userId,i.eventType),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(e=>{n&&n(e,{operation:"processUserEvent",userId:i.userId,emailAddress:i.emailAddress})})}async function fetchAndCacheVerdict(e,r,t,s,i,n,a,o=CHECK_USER_TIMEOUT_MS,d){const _=()=>{const e=r.get(t);return r.set(t,{isFlagged:e?.isFlagged??!1,isVerified:e?.isVerified??!1,emailAddress:s,sessionId:a},5e3),r.get(t)},c={};let l;i&&"unknown"!==i&&(c.deviceId=i),n&&(c.fingerprintId=n);const u=await Promise.race([e.checkUser(s,c),new Promise(e=>{l=setTimeout(()=>e(null),o)})]);return clearTimeout(l),u?u.failedOpen?(d?.(u.failedOpen.reason??"http_error",u.failedOpen.status),_()):(r.set(t,{isFlagged:u.data?.is_user_flagged??!1,isVerified:!1,emailAddress:s,sessionId:a}),r.get(t)):(d?.("timeout"),_())}async function handleSubmitFp(e,r,t,s){try{const i={full_hash:r.hash??"",fingerprint_id:r.stable_hash??"",timestamp:r.collected_at??(new Date).toISOString(),isIncognito:r.is_incognito??!1,components:r.components??{},version:r.version??"inline-1.0.0"};let n,a,o;try{const r=t.resolveUserId(e);r&&!(0,sentinel_user_id_1.isSentinelUserId)(r)&&(n=r)}catch{}if(!n){const e="string"==typeof r.user_id?r.user_id:void 0;e&&!(0,sentinel_user_id_1.isSentinelUserId)(e)&&(n=e)}if(!n){const r=(0,web_helpers_1.parseCookieFromRequest)(e,"__unshared_uid");r&&!(0,sentinel_user_id_1.isSentinelUserId)(r)&&(n=r)}try{a=t.resolveEmailAddress?t.resolveEmailAddress(e):void 0}catch{}a=a??(0,web_helpers_1.parseCookieFromRequest)(e,"__unshared_email")??r.email??void 0;try{o=t.resolveSessionId?t.resolveSessionId(e):void 0}catch{}o=o??r.session_id??(0,web_helpers_1.parseCookieFromRequest)(e,"__unshared_sid");const d=(0,web_helpers_1.extractClientIpFromRequest)(e),_=e.headers.get("user-agent")??"";if(!t.disableBotFilter&&(0,is_bot_1.isBot)(_))return(0,web_helpers_1.jsonResponse)(200,{success:!0},s);const c=(i.fingerprint_id&&i.fingerprint_id.length>0?i.fingerprint_id:void 0)??(0,web_helpers_1.extractDeviceIdFromRequestOrUnknown)(e,t.resolveDeviceId),l=i.fingerprint_id||void 0,u=i.full_hash||void 0,p=(0,web_helpers_1.isSecureWebRequest)(e)?"; Secure":"",h=[];if(u&&!(0,web_helpers_1.parseCookieFromRequest)(e,"__unshared_fingerprint_id")&&h.push(`__unshared_fingerprint_id=${encodeURIComponent(u)}; HttpOnly; Path=/; SameSite=Lax${p}`),l){const r=(0,web_helpers_1.parseCookieFromRequest)(e,"__unshared_fp_id");r&&r===l||h.push(`__unshared_fp_id=${encodeURIComponent(l)}; Path=/; SameSite=Lax; Max-Age=31536000${p}`)}let f;if(a&&!(0,web_helpers_1.parseCookieFromRequest)(e,"__unshared_email")&&h.push(`__unshared_email=${encodeURIComponent(a)}; HttpOnly; Path=/; SameSite=Lax${p}`),"string"==typeof r.event_type&&r.event_type)f=r.event_type;else{const r=e.headers.get("referer")??e.headers.get("referrer");let t="unknown";if(r)try{const e=new URL(r);t=(e.pathname||"/")+(e.search||"")}catch{}f=t}const m=e.headers.get("x-idempotency-key")||void 0,w=Date.now(),g=m?`${m}|${w}`:l&&n?`${(0,util_1.sha256Hex)(`${l}|${n}|${f}`)}|${w}`:void 0;n&&t.client.submitFingerprintEvent(i,{userId:n,emailAddress:a,sessionHash:o,eventType:f,ipAddress:d,userAgent:_,idempotencyKey:g}).catch(e=>{t.onError&&t.onError(e,{operation:"submitFingerprintEvent",userId:n,emailAddress:a})}),n&&a&&!t.rateLimitBackoff.isPaused()&&!t.dispatchDedupe.wasRecentlyDispatched(n,f)&&t.client.processUserEvent({eventType:f,userId:n,emailAddress:a,ipAddress:d,deviceId:c,fingerprintId:l,sessionHash:o??"unknown",userAgent:_}).then(e=>{e.success&&e.data?.analysis&&t.verdictCache.update(n,{isFlagged:e.data.analysis.is_user_flagged}),!e.success&&e.error?.retryAfter&&t.rateLimitBackoff.pause(1e3*e.error.retryAfter)}).catch(e=>{t.onError&&t.onError(e,{operation:"processUserEvent",userId:n,emailAddress:a})});const v={...s,"Content-Type":"application/json"},I=new Response(JSON.stringify({success:!0}),{status:200,headers:v});for(const e of h)I.headers.append("Set-Cookie",e);return I}catch{return(0,web_helpers_1.jsonResponse)(200,{success:!0},s)}}async function handleVerifyTriggerWeb(e,r,t,s){try{const i=resolveEmailWithBody(e,r??{},t.resolveEmailAddress);if(!i)return(0,web_helpers_1.jsonResponse)(400,{success:!1,error:{code:"VALIDATION_ERROR",message:"Email is required"}},s);const n=(0,web_helpers_1.extractDeviceIdFromRequestOrUnknown)(e,t.resolveDeviceId),a=(0,web_helpers_1.parseCookieFromRequest)(e,"__unshared_fingerprint_id")||void 0,o=await t.client.triggerEmailVerification(i,n,{fingerprintId:a});return o.success?(0,web_helpers_1.jsonResponse)(200,{success:!0,data:o.data},s):(0,web_helpers_1.jsonResponse)(200,{success:!1,error:o.error??{code:"TRIGGER_FAILED",message:"Failed to send verification email"}},s)}catch(e){return t.onError&&t.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,r,t,s){try{const i=resolveEmailWithBody(e,r??{},t.resolveEmailAddress),n=r?.code;if(!i||!n)return(0,web_helpers_1.jsonResponse)(400,{success:!1,error:{code:"VALIDATION_ERROR",message:"Email and code are required"}},s);const a=(0,web_helpers_1.extractDeviceIdFromRequestOrUnknown)(e,t.resolveDeviceId),o=(0,web_helpers_1.parseCookieFromRequest)(e,"__unshared_fingerprint_id")||void 0,d=await t.client.verify(i,a,n,{fingerprintId:o});if(d.success){let r;try{r=t.resolveUserId(e)}catch{}return r&&t.verdictCache.update(r,{isVerified:!0}),(0,web_helpers_1.jsonResponse)(200,{success:!0,data:{verified:!0}},s)}return(0,web_helpers_1.jsonResponse)(200,{success:!1,error:d.error??{code:"VERIFICATION_FAILED",message:"Verification failed"}},s)}catch(e){return t.onError&&t.onError(e,{operation:"verify"}),(0,web_helpers_1.jsonResponse)(200,{success:!1,error:{code:"INTERNAL_ERROR",message:"Verification failed"}},s)}}
@@ -59,7 +59,7 @@ export interface WebProtectionConfig {
59
59
  includePathPrefix?: string[];
60
60
  /** Skip the bot/crawler UA filter. Set to true in test environments so automated browsers (Playwright, Puppeteer, etc.) can observe verdicts. @default false */
61
61
  disableBotFilter?: boolean;
62
- /** Hard timeout (ms) for checkUser on cache miss. Fails open on timeout. @default 500 */
62
+ /** Hard timeout (ms) for checkUser on cache miss. Fails open on timeout. @default 1500 */
63
63
  checkUserTimeoutMs?: number;
64
64
  /** Resolves a custom session ID. Falls back to __unshared_sid cookie. */
65
65
  sessionId?: (req: Request) => string | undefined;
@@ -167,7 +167,7 @@ export interface WebProtectionConfig {
167
167
  }
168
168
  export interface WebFailOpenContext {
169
169
  operation: 'checkUser';
170
- reason: 'timeout' | 'http_error' | 'exception';
170
+ reason: 'timeout' | 'http_error' | 'exception' | 'no_device_id';
171
171
  status?: number;
172
172
  userId?: string;
173
173
  emailAddress?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "unshared-clientjs-sdk",
3
- "version": "2.2.0",
3
+ "version": "2.2.1",
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",
@@ -52,7 +52,7 @@
52
52
  "author": "",
53
53
  "license": "MIT",
54
54
  "dependencies": {
55
- "unshared-frontend-sdk": "2.2.0"
55
+ "unshared-frontend-sdk": "2.2.1"
56
56
  },
57
57
  "devDependencies": {
58
58
  "@unshared-labs/shared-types": "file:../../../shared/types",