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

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
@@ -40,10 +40,14 @@ export interface ApiResult<T = unknown> {
40
40
  }
41
41
  export interface SubmitFingerprintOptions {
42
42
  userId?: string;
43
+ /** SDK encrypts before sending. */
44
+ emailAddress?: string;
43
45
  sessionHash?: string;
44
46
  eventType?: string;
45
47
  /** SDK encrypts before sending. */
46
48
  ipAddress?: string;
49
+ /** SDK encrypts before sending. */
50
+ userAgent?: string;
47
51
  }
48
52
  export interface SubmitFingerprintResult {
49
53
  hash: string;
package/dist/client.js CHANGED
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"t",{value:!0}),exports.UnsharedLabsClient=void 0;const crypto_1=require("crypto"),util_1=require("./util"),DEFAULT_BASE_URL="https://api-ingress.unsharedlabs.com",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 UnsharedLabsClient{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()}_(e){return(0,util_1.encryptData)(e,this.l)}async p(e,s){const t=this.u+1;let r={success:!1,status:0,error:{code:"NETWORK_ERROR",message:"Request failed"}};for(let i=1;i<=t;i++){i>1&&await sleep(retryDelay(i-1));const t=new AbortController,n=setTimeout(()=>t.abort(),this.h);try{const i=await fetch(e,{method:s.method,headers:{"X-API-Key":this.i,...s.headers},body:s.body,signal:t.signal});if(clearTimeout(n),i.ok){const e=await i.text().catch(()=>"{}");let s;try{s=JSON.parse(e)}catch{s={}}const t="data"in s?s.data:s;return{success:!0,status:i.status,data:t}}const a=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 s=parseInt(e,10);isNaN(s)||(a.retryAfter=s)}}return{success:!1,status:i.status,error:a}}r={success:!1,status:i.status,error:a}}catch(e){clearTimeout(n),r={success:!1,status:0,error:{code:"NETWORK_ERROR",message:e instanceof Error?e.message:String(e)}}}}return r}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!=s?.userId&&(t.user_id=this._(s.userId)),null!=s?.sessionHash&&(t.session_hash=s.sessionHash),null!=s?.eventType&&(t.event_type=s.eventType),null!=s?.ipAddress&&(t.ip_address=this._(s.ipAddress)),this.p(`${this.o}/v2/submit-fingerprint-event`,{method:"POST",headers:{"Content-Type":"application/json","X-Idempotency-Key":(0,crypto_1.randomUUID)()},body:JSON.stringify(t)})}async processUserEvent(e){const s={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&&(s.fingerprint_id=this._(e.fingerprintId)),null!=e.subscriptionStatus&&(s.subscription_status=e.subscriptionStatus),null!=e.eventDetails&&(s.event_details=e.eventDetails),this.p(`${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}};const r=new URLSearchParams;r.set("email_address",this._(e)),t.deviceId&&r.set("device_id",this._(t.deviceId)),t.fingerprintId&&r.set("fingerprint_id",this._(t.fingerprintId));const i=await this.p(`${this.o}/v2/check-user?${r}`,{method:"GET"});return i.success?i:{success:!0,status:200,data:{is_user_flagged:!1}}}async triggerEmailVerification(e,s,t){const r={email_address:this._(e),device_id:this._(s)};t?.fingerprintId&&(r.fingerprint_id=this._(t.fingerprintId));const i=await this.p(`${this.o}/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,s,t,r){const i={email_address:this._(e),device_id:this._(s),code:this._(t)};r?.fingerprintId&&(i.fingerprint_id=this._(r.fingerprintId));const n=await this.p(`${this.o}/v2/verify`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(i)});return!n.success&&(0===n.status||n.status>=500)?{success:!1,status:n.status,error:{code:"DELIVERY_FAILED",message:n.error?.message??"Delivery failed"}}:n.success&&!1===n.data?.verified?{success:!1,status:n.status,error:{code:"VERIFICATION_FAILED",message:"Code is incorrect or expired",details:n.data.reason?{reason:n.data.reason}:void 0}}:n}async getVerificationFlowConfig(){const e=await this.p(`${this.o}/v2/verification-flow-config`,{method:"GET"});return e.success&&e.data?e.data:null}}exports.UnsharedLabsClient=UnsharedLabsClient;
1
+ "use strict";Object.defineProperty(exports,"t",{value:!0}),exports.UnsharedLabsClient=void 0;const crypto_1=require("crypto"),util_1=require("./util"),DEFAULT_BASE_URL="https://api-ingress.unsharedlabs.com",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 UnsharedLabsClient{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()}_(e){return(0,util_1.encryptData)(e,this.l)}async p(e,s){const t=this.u+1;let r={success:!1,status:0,error:{code:"NETWORK_ERROR",message:"Request failed"}};for(let i=1;i<=t;i++){i>1&&await sleep(retryDelay(i-1));const t=new AbortController,n=setTimeout(()=>t.abort(),this.h);try{const i=await fetch(e,{method:s.method,headers:{"X-API-Key":this.i,...s.headers},body:s.body,signal:t.signal});if(clearTimeout(n),i.ok){const e=await i.text().catch(()=>"{}");let s;try{s=JSON.parse(e)}catch{s={}}const t="data"in s?s.data:s;return{success:!0,status:i.status,data:t}}const a=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 s=parseInt(e,10);isNaN(s)||(a.retryAfter=s)}}return{success:!1,status:i.status,error:a}}r={success:!1,status:i.status,error:a}}catch(e){clearTimeout(n),r={success:!1,status:0,error:{code:"NETWORK_ERROR",message:e instanceof Error?e.message:String(e)}}}}return r}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!=s?.userId&&(t.user_id=this._(s.userId)),null!=s?.emailAddress&&(t.email_address=this._(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._(s.ipAddress)),null!=s?.userAgent&&(t.user_agent=this._(s.userAgent)),this.p(`${this.o}/v2/submit-fingerprint-event`,{method:"POST",headers:{"Content-Type":"application/json","X-Idempotency-Key":(0,crypto_1.randomUUID)()},body:JSON.stringify(t)})}async processUserEvent(e){const s={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&&(s.fingerprint_id=this._(e.fingerprintId)),null!=e.subscriptionStatus&&(s.subscription_status=e.subscriptionStatus),null!=e.eventDetails&&(s.event_details=e.eventDetails),this.p(`${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}};const r=new URLSearchParams;r.set("email_address",this._(e)),t.deviceId&&r.set("device_id",this._(t.deviceId)),t.fingerprintId&&r.set("fingerprint_id",this._(t.fingerprintId));const i=await this.p(`${this.o}/v2/check-user?${r}`,{method:"GET"});return i.success?i:{success:!0,status:200,data:{is_user_flagged:!1}}}async triggerEmailVerification(e,s,t){const r={email_address:this._(e),device_id:this._(s)};t?.fingerprintId&&(r.fingerprint_id=this._(t.fingerprintId));const i=await this.p(`${this.o}/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,s,t,r){const i={email_address:this._(e),device_id:this._(s),code:this._(t)};r?.fingerprintId&&(i.fingerprint_id=this._(r.fingerprintId));const n=await this.p(`${this.o}/v2/verify`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(i)});return!n.success&&(0===n.status||n.status>=500)?{success:!1,status:n.status,error:{code:"DELIVERY_FAILED",message:n.error?.message??"Delivery failed"}}:n.success&&!1===n.data?.verified?{success:!1,status:n.status,error:{code:"VERIFICATION_FAILED",message:"Code is incorrect or expired",details:n.data.reason?{reason:n.data.reason}:void 0}}:n}async getVerificationFlowConfig(){const e=await this.p(`${this.o}/v2/verification-flow-config`,{method:"GET"});return e.success&&e.data?e.data:null}}exports.UnsharedLabsClient=UnsharedLabsClient;
@@ -40,10 +40,14 @@ export interface ApiResult<T = unknown> {
40
40
  }
41
41
  export interface SubmitFingerprintOptions {
42
42
  userId?: string;
43
+ /** SDK encrypts before sending. */
44
+ emailAddress?: string;
43
45
  sessionHash?: string;
44
46
  eventType?: string;
45
47
  /** SDK encrypts before sending. */
46
48
  ipAddress?: string;
49
+ /** SDK encrypts before sending. */
50
+ userAgent?: string;
47
51
  }
48
52
  export interface SubmitFingerprintResult {
49
53
  hash: string;
@@ -1 +1 @@
1
- import{createHash,randomUUID}from"crypto";import{encryptData}from"./util";const DEFAULT_BASE_URL="https://api-ingress.unsharedlabs.com",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}}}export class UnsharedLabsClient{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.u=createHash("sha256").update(e.apiKey).digest()}l(e){return encryptData(e,this.u)}async _(e,s){const t=this.h+1;let r={success:!1,status:0,error:{code:"NETWORK_ERROR",message:"Request failed"}};for(let i=1;i<=t;i++){i>1&&await sleep(retryDelay(i-1));const t=new AbortController,a=setTimeout(()=>t.abort(),this.o);try{const i=await fetch(e,{method:s.method,headers:{"X-API-Key":this.t,...s.headers},body:s.body,signal:t.signal});if(clearTimeout(a),i.ok){const e=await i.text().catch(()=>"{}");let s;try{s=JSON.parse(e)}catch{s={}}const t="data"in s?s.data:s;return{success:!0,status:i.status,data:t}}const n=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 s=parseInt(e,10);isNaN(s)||(n.retryAfter=s)}}return{success:!1,status:i.status,error:n}}r={success:!1,status:i.status,error:n}}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,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!=s?.userId&&(t.user_id=this.l(s.userId)),null!=s?.sessionHash&&(t.session_hash=s.sessionHash),null!=s?.eventType&&(t.event_type=s.eventType),null!=s?.ipAddress&&(t.ip_address=this.l(s.ipAddress)),this._(`${this.i}/v2/submit-fingerprint-event`,{method:"POST",headers:{"Content-Type":"application/json","X-Idempotency-Key":randomUUID()},body:JSON.stringify(t)})}async processUserEvent(e){const s={event_type:e.eventType,user_id:this.l(e.userId),ip_address:this.l(e.ipAddress),device_id:this.l(e.deviceId),session_hash:e.sessionHash,user_agent:this.l(e.userAgent),email_address:this.l(e.emailAddress)};return null!=e.fingerprintId&&(s.fingerprint_id=this.l(e.fingerprintId)),null!=e.subscriptionStatus&&(s.subscription_status=e.subscriptionStatus),null!=e.eventDetails&&(s.event_details=e.eventDetails),this._(`${this.i}/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}};const r=new URLSearchParams;r.set("email_address",this.l(e)),t.deviceId&&r.set("device_id",this.l(t.deviceId)),t.fingerprintId&&r.set("fingerprint_id",this.l(t.fingerprintId));const i=await this._(`${this.i}/v2/check-user?${r}`,{method:"GET"});return i.success?i:{success:!0,status:200,data:{is_user_flagged:!1}}}async triggerEmailVerification(e,s,t){const r={email_address:this.l(e),device_id:this.l(s)};t?.fingerprintId&&(r.fingerprint_id=this.l(t.fingerprintId));const i=await this._(`${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,s,t,r){const i={email_address:this.l(e),device_id:this.l(s),code:this.l(t)};r?.fingerprintId&&(i.fingerprint_id=this.l(r.fingerprintId));const a=await this._(`${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 getVerificationFlowConfig(){const e=await this._(`${this.i}/v2/verification-flow-config`,{method:"GET"});return e.success&&e.data?e.data:null}}
1
+ import{createHash,randomUUID}from"crypto";import{encryptData}from"./util";const DEFAULT_BASE_URL="https://api-ingress.unsharedlabs.com",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}}}export class UnsharedLabsClient{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.u=createHash("sha256").update(e.apiKey).digest()}l(e){return encryptData(e,this.u)}async _(e,s){const t=this.h+1;let r={success:!1,status:0,error:{code:"NETWORK_ERROR",message:"Request failed"}};for(let i=1;i<=t;i++){i>1&&await sleep(retryDelay(i-1));const t=new AbortController,a=setTimeout(()=>t.abort(),this.o);try{const i=await fetch(e,{method:s.method,headers:{"X-API-Key":this.t,...s.headers},body:s.body,signal:t.signal});if(clearTimeout(a),i.ok){const e=await i.text().catch(()=>"{}");let s;try{s=JSON.parse(e)}catch{s={}}const t="data"in s?s.data:s;return{success:!0,status:i.status,data:t}}const n=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 s=parseInt(e,10);isNaN(s)||(n.retryAfter=s)}}return{success:!1,status:i.status,error:n}}r={success:!1,status:i.status,error:n}}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,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!=s?.userId&&(t.user_id=this.l(s.userId)),null!=s?.emailAddress&&(t.email_address=this.l(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.l(s.ipAddress)),null!=s?.userAgent&&(t.user_agent=this.l(s.userAgent)),this._(`${this.i}/v2/submit-fingerprint-event`,{method:"POST",headers:{"Content-Type":"application/json","X-Idempotency-Key":randomUUID()},body:JSON.stringify(t)})}async processUserEvent(e){const s={event_type:e.eventType,user_id:this.l(e.userId),ip_address:this.l(e.ipAddress),device_id:this.l(e.deviceId),session_hash:e.sessionHash,user_agent:this.l(e.userAgent),email_address:this.l(e.emailAddress)};return null!=e.fingerprintId&&(s.fingerprint_id=this.l(e.fingerprintId)),null!=e.subscriptionStatus&&(s.subscription_status=e.subscriptionStatus),null!=e.eventDetails&&(s.event_details=e.eventDetails),this._(`${this.i}/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}};const r=new URLSearchParams;r.set("email_address",this.l(e)),t.deviceId&&r.set("device_id",this.l(t.deviceId)),t.fingerprintId&&r.set("fingerprint_id",this.l(t.fingerprintId));const i=await this._(`${this.i}/v2/check-user?${r}`,{method:"GET"});return i.success?i:{success:!0,status:200,data:{is_user_flagged:!1}}}async triggerEmailVerification(e,s,t){const r={email_address:this.l(e),device_id:this.l(s)};t?.fingerprintId&&(r.fingerprint_id=this.l(t.fingerprintId));const i=await this._(`${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,s,t,r){const i={email_address:this.l(e),device_id:this.l(s),code:this.l(t)};r?.fingerprintId&&(i.fingerprint_id=this.l(r.fingerprintId));const a=await this._(`${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 getVerificationFlowConfig(){const e=await this._(`${this.i}/v2/verification-flow-config`,{method:"GET"});return e.success&&e.data?e.data:null}}
@@ -1 +1 @@
1
- import{readFileSync}from"fs";import{VerdictCache}from"./verdict-cache";import{RateLimitBackoff}from"./rate-limit-backoff";import{interceptResponse}from"./response-interceptor";import{generateFingerprintScript}from"./injection/fingerprint-script";import{handleSubmitFingerprint}from"./routes/submit-fp";import{handleVerifyTrigger,handleVerify}from"./routes/verify";import{isHtmlContentType}from"./utils/content-type";import{shouldSkipPath}from"./utils/skip-paths";import{isBot}from"./utils/is-bot";export{VerdictCache};const CHECK_USER_TIMEOUT_MS=500;export function unsharedBoundToUser(e,t){if(!t.userId)throw new Error("[Unshared] userId resolver is required");if(!t.emailAddress){let e=!1;try{require.resolve("unshared-frontend-sdk"),e=!0}catch{}e||console.warn("[Unshared] Warning: emailAddress resolver is not configured and unshared-frontend-sdk is not installed.\nNo user events will be submitted. Either install unshared-frontend-sdk (Tier 1) or\nprovide emailAddress in your middleware config (Tier 2).")}const{userId:r,emailAddress:n,routePrefix:i="/__unshared",corsOrigins:o,cacheTTL:s=6e4,skipPaths:c,sessionId:d,deviceId:a,onFlagged:l}=t,u=new VerdictCache(s),p=new RateLimitBackoff,f=Date.now().toString(36),m=generateFingerprintScript(i,f);let h="";try{const e=require.resolve("unshared-frontend-sdk/dist/index.umd.js");h=readFileSync(e,"utf8")}catch{}const v=handleSubmitFingerprint({client:e,verdictCache:u,rateLimitBackoff:p,resolveUserId:r,resolveEmailAddress:n,resolveSessionId:d,resolveDeviceId:a}),C=handleVerifyTrigger({client:e,verdictCache:u,resolveEmailAddress:n,resolveDeviceId:a}),g=handleVerify({client:e,verdictCache:u,resolveEmailAddress:n,resolveDeviceId:a}),I=o?Array.isArray(o)?o:[o]:null,y=`${i}/fp.js`,k=`${i}/submit-fp`,S=`${i}/verify-trigger`,_=`${i}/verify`;return function(t,o,s){const f=t.path;if(f.startsWith(i+"/"))return function(e,t){if(!I)return;const r=e.headers.origin??"",n=I.includes("*");(n||I.includes(r))&&(t.setHeader("Access-Control-Allow-Origin",n?"*":r),t.setHeader("Access-Control-Allow-Methods","POST, OPTIONS"),t.setHeader("Access-Control-Allow-Headers","Content-Type, X-Idempotency-Key, X-Session-Id, X-Device-Id"),t.setHeader("Access-Control-Allow-Credentials","true"))}(t,o),"OPTIONS"===t.method?void o.status(204).end():"GET"===t.method&&f===y?(o.setHeader("Content-Type","application/javascript"),o.setHeader("Cache-Control","public, max-age=3600"),void o.status(200).end(h)):"POST"===t.method&&f===k?void v(t,o):"POST"===t.method&&f===S?void C(t,o):"POST"===t.method&&f===_?void g(t,o):void o.status(404).json({success:!1,error:{code:"NOT_FOUND",message:"Unknown route"}});if(shouldSkipPath(f,c))return void s();let A;try{A=r(t)}catch{}if(!A)return clearEmailCookieIfPresent(t,o),interceptForInjection(t,o,m),void s();const T=resolveEmail(t,n);if(setUserIdCookie(o,A),T&&setEmailCookie(o,T),!T)return interceptForInjection(t,o,m),void s();const x=extractSessionId(t,d),E=extractDeviceId(t,a),F=extractFingerprintId(t),w=t.headers["user-agent"]??"",P=extractClientIp(t);if(isBot(w))return void s();if("unknown"===x)return interceptForInjection(t,o,m),void s();p.isPaused()||dispatchUserEvent(e,u,p,{userId:A,emailAddress:T,sessionId:x,deviceId:E,fingerprintId:F,userAgent:w,ipAddress:P,eventType:`${t.method} ${t.path}`});const O=u.get(A);O?(u.isStale(A)&&!u.isRefreshing(A)&&(u.markRefreshing(A),fetchAndCacheVerdict(e,u,A,T,E,F,x).finally(()=>u.clearRefreshing(A))),applyVerdict(O,A,T,t,o,s,m,l)):fetchAndCacheVerdict(e,u,A,T,E,F,x).then(e=>{applyVerdict(e,A,T,t,o,s,m,l)}).catch(()=>{interceptForInjection(t,o,m),s()})}}function resolveEmail(e,t){if(t)try{const r=t(e);if(r)return r}catch{}const r=parseCookie(e,"__unshared_email");if(r)return r;const n=e.body?.email;return"string"==typeof n&&n?n:void 0}function applyVerdict(e,t,r,n,i,o,s,c){if(interceptForInjection(n,i,s),e.isFlagged&&!e.isVerified&&c)try{c({userId:t,emailAddress:r,verdict:e,req:n,res:i,next:o})}catch{o()}else o()}function preventHtmlCaching(e,t){delete e.headers["if-none-match"],delete e.headers["if-modified-since"];const r=t.writeHead.bind(t);t.writeHead=function(e,...n){const i=t.getHeader("content-type");return i&&String(i).includes("text/html")&&(t.setHeader("Cache-Control","no-store"),t.removeHeader("ETag"),t.removeHeader("Last-Modified")),r(e,...n)}}function interceptForInjection(e,t,r){preventHtmlCaching(e,t),interceptResponse(t,(e,t)=>{if(!isHtmlContentType(t))return null;const n=e.toString("utf8"),i=n.lastIndexOf("</body>");return-1===i?n+r:n.slice(0,i)+r+n.slice(i)})}function dispatchUserEvent(e,t,r,n){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(()=>{})}async function fetchAndCacheVerdict(e,t,r,n,i,o,s){const c={};i&&"unknown"!==i&&(c.deviceId=i),o&&(c.fingerprintId=o);const d=await Promise.race([e.checkUser(n,c),new Promise(e=>setTimeout(()=>e(null),500))]);if(!d)return{isFlagged:!1,isVerified:!1,emailAddress:n,sessionId:s,cachedAt:0,ttl:0};const a=d.data?.is_user_flagged??!1;return t.set(r,{isFlagged:a,isVerified:!1,emailAddress:n,sessionId:s}),t.get(r)}function parseCookie(e,t){const r=e.headers.cookie;if(!r)return;const n=r.match(new RegExp(`(?:^|; )${t}=([^;]*)`));return n?decodeURIComponent(n[1]):void 0}function extractSessionId(e,t){if(t)try{const r=t(e);if(r)return r}catch{}return parseCookie(e,"__unshared_sid")??"unknown"}function extractDeviceId(e,t){if(t)try{const r=t(e);if(r)return r}catch{}const r=parseCookie(e,"__unshared_fp_id");if(r)return r;const n=e.headers["x-device-id"];return"string"==typeof n&&n?n:"unknown"}function extractFingerprintId(e){return parseCookie(e,"__unshared_fingerprint_id")||void 0}function extractClientIp(e){const t=e.headers["cf-connecting-ip"];if("string"==typeof t&&t)return t;const r=e.headers["x-real-ip"];return"string"==typeof r&&r?r:e.ip??""}function appendSetCookie(e,t){const r=e.getHeader("Set-Cookie");if(r){const n=Array.isArray(r)?[...r]:[String(r)];n.push(t),e.setHeader("Set-Cookie",n)}else e.setHeader("Set-Cookie",t)}function setUserIdCookie(e,t){appendSetCookie(e,`__unshared_uid=${encodeURIComponent(t)}; Path=/; SameSite=Lax`)}function setEmailCookie(e,t){appendSetCookie(e,`__unshared_email=${encodeURIComponent(t)}; HttpOnly; Path=/; SameSite=Lax`)}function clearEmailCookieIfPresent(e,t){parseCookie(e,"__unshared_email")&&appendSetCookie(t,"__unshared_email=; HttpOnly; Path=/; SameSite=Lax; Max-Age=0")}
1
+ import{readFileSync}from"fs";import{VerdictCache}from"./verdict-cache";import{RateLimitBackoff}from"./rate-limit-backoff";import{interceptResponse}from"./response-interceptor";import{generateFingerprintScript}from"./injection/fingerprint-script";import{handleSubmitFingerprint}from"./routes/submit-fp";import{handleVerifyTrigger,handleVerify}from"./routes/verify";import{isHtmlContentType}from"./utils/content-type";import{shouldSkipPath}from"./utils/skip-paths";import{isBot}from"./utils/is-bot";import{extractClientIp}from"./utils/client-ip";export{VerdictCache};const CHECK_USER_TIMEOUT_MS=500;export function unsharedBoundToUser(e,t){if(!t.userId)throw new Error("[Unshared] userId resolver is required");if(!t.emailAddress){let e=!1;try{require.resolve("unshared-frontend-sdk"),e=!0}catch{}e||console.warn("[Unshared] Warning: emailAddress resolver is not configured and unshared-frontend-sdk is not installed.\nNo user events will be submitted. Either install unshared-frontend-sdk (Tier 1) or\nprovide emailAddress in your middleware config (Tier 2).")}const{userId:r,emailAddress:i,routePrefix:n="/__unshared",corsOrigins:o,cacheTTL:s=6e4,skipPaths:c,sessionId:d,deviceId:a,onFlagged:l}=t,u=new VerdictCache(s),p=new RateLimitBackoff,f=Date.now().toString(36),m=generateFingerprintScript(n,f);let h="";try{const e=require.resolve("unshared-frontend-sdk/dist/index.umd.js");h=readFileSync(e,"utf8")}catch{}const v=handleSubmitFingerprint({client:e,verdictCache:u,rateLimitBackoff:p,resolveUserId:r,resolveEmailAddress:i,resolveSessionId:d,resolveDeviceId:a}),C=handleVerifyTrigger({client:e,verdictCache:u,resolveEmailAddress:i,resolveDeviceId:a}),I=handleVerify({client:e,verdictCache:u,resolveEmailAddress:i,resolveDeviceId:a}),g=o?Array.isArray(o)?o:[o]:null,y=`${n}/fp.js`,k=`${n}/submit-fp`,S=`${n}/verify-trigger`,_=`${n}/verify`;return function(t,o,s){const f=t.path;if(f.startsWith(n+"/"))return function(e,t){if(!g)return;const r=e.headers.origin??"",i=g.includes("*");(i||g.includes(r))&&(t.setHeader("Access-Control-Allow-Origin",i?"*":r),t.setHeader("Access-Control-Allow-Methods","POST, OPTIONS"),t.setHeader("Access-Control-Allow-Headers","Content-Type, X-Idempotency-Key, X-Session-Id, X-Device-Id"),t.setHeader("Access-Control-Allow-Credentials","true"))}(t,o),"OPTIONS"===t.method?void o.status(204).end():"GET"===t.method&&f===y?(o.setHeader("Content-Type","application/javascript"),o.setHeader("Cache-Control","public, max-age=3600"),void o.status(200).end(h)):"POST"===t.method&&f===k?void v(t,o):"POST"===t.method&&f===S?void C(t,o):"POST"===t.method&&f===_?void I(t,o):void o.status(404).json({success:!1,error:{code:"NOT_FOUND",message:"Unknown route"}});if(shouldSkipPath(f,c))return void s();let A;try{A=r(t)}catch{}if(!A)return clearEmailCookieIfPresent(t,o),interceptForInjection(t,o,m),void s();const T=resolveEmail(t,i);if(setUserIdCookie(o,A),T&&setEmailCookie(o,T),!T)return interceptForInjection(t,o,m),void s();const x=extractSessionId(t,d),E=extractDeviceId(t,a),F=extractFingerprintId(t),w=t.headers["user-agent"]??"",P=extractClientIp(t);if(isBot(w))return void s();if("unknown"===x)return interceptForInjection(t,o,m),void s();p.isPaused()||dispatchUserEvent(e,u,p,{userId:A,emailAddress:T,sessionId:x,deviceId:E,fingerprintId:F,userAgent:w,ipAddress:P,eventType:`${t.method} ${t.path}`});const O=u.get(A);O?(u.isStale(A)&&!u.isRefreshing(A)&&(u.markRefreshing(A),fetchAndCacheVerdict(e,u,A,T,E,F,x).finally(()=>u.clearRefreshing(A))),applyVerdict(O,A,T,t,o,s,m,l)):fetchAndCacheVerdict(e,u,A,T,E,F,x).then(e=>{applyVerdict(e,A,T,t,o,s,m,l)}).catch(()=>{interceptForInjection(t,o,m),s()})}}function resolveEmail(e,t){if(t)try{const r=t(e);if(r)return r}catch{}const r=parseCookie(e,"__unshared_email");if(r)return r;const i=e.body?.email;return"string"==typeof i&&i?i:void 0}function applyVerdict(e,t,r,i,n,o,s,c){if(interceptForInjection(i,n,s),e.isFlagged&&!e.isVerified&&c)try{c({userId:t,emailAddress:r,verdict:e,req:i,res:n,next:o})}catch{o()}else o()}function preventHtmlCaching(e,t){delete e.headers["if-none-match"],delete e.headers["if-modified-since"];const r=t.writeHead.bind(t);t.writeHead=function(e,...i){const n=t.getHeader("content-type");return n&&String(n).includes("text/html")&&(t.setHeader("Cache-Control","no-store"),t.removeHeader("ETag"),t.removeHeader("Last-Modified")),r(e,...i)}}function interceptForInjection(e,t,r){preventHtmlCaching(e,t),interceptResponse(t,(e,t)=>{if(!isHtmlContentType(t))return null;const i=e.toString("utf8"),n=i.lastIndexOf("</body>");return-1===n?i+r:i.slice(0,n)+r+i.slice(n)})}function dispatchUserEvent(e,t,r,i){e.processUserEvent({eventType:i.eventType,userId:i.userId,emailAddress:i.emailAddress,ipAddress:i.ipAddress,deviceId:i.deviceId,fingerprintId:i.fingerprintId,sessionHash:i.sessionId,userAgent:i.userAgent}).then(e=>{e.success&&e.data?.analysis&&t.update(i.userId,{isFlagged:e.data.analysis.is_user_flagged}),!e.success&&e.error?.retryAfter&&r.pause(1e3*e.error.retryAfter)}).catch(()=>{})}async function fetchAndCacheVerdict(e,t,r,i,n,o,s){const c={};n&&"unknown"!==n&&(c.deviceId=n),o&&(c.fingerprintId=o);const d=await Promise.race([e.checkUser(i,c),new Promise(e=>setTimeout(()=>e(null),500))]);if(!d)return{isFlagged:!1,isVerified:!1,emailAddress:i,sessionId:s,cachedAt:0,ttl:0};const a=d.data?.is_user_flagged??!1;return t.set(r,{isFlagged:a,isVerified:!1,emailAddress:i,sessionId:s}),t.get(r)}function parseCookie(e,t){const r=e.headers.cookie;if(!r)return;const i=r.match(new RegExp(`(?:^|; )${t}=([^;]*)`));return i?decodeURIComponent(i[1]):void 0}function extractSessionId(e,t){if(t)try{const r=t(e);if(r)return r}catch{}return parseCookie(e,"__unshared_sid")??"unknown"}function extractDeviceId(e,t){if(t)try{const r=t(e);if(r)return r}catch{}const r=parseCookie(e,"__unshared_fp_id");if(r)return r;const i=e.headers["x-device-id"];return"string"==typeof i&&i?i:"unknown"}function extractFingerprintId(e){return parseCookie(e,"__unshared_fingerprint_id")||void 0}function appendSetCookie(e,t){const r=e.getHeader("Set-Cookie");if(r){const i=Array.isArray(r)?[...r]:[String(r)];i.push(t),e.setHeader("Set-Cookie",i)}else e.setHeader("Set-Cookie",t)}function setUserIdCookie(e,t){appendSetCookie(e,`__unshared_uid=${encodeURIComponent(t)}; Path=/; SameSite=Lax`)}function setEmailCookie(e,t){appendSetCookie(e,`__unshared_email=${encodeURIComponent(t)}; HttpOnly; Path=/; SameSite=Lax`)}function clearEmailCookieIfPresent(e,t){parseCookie(e,"__unshared_email")&&appendSetCookie(t,"__unshared_email=; HttpOnly; Path=/; SameSite=Lax; Max-Age=0")}
@@ -1 +1 @@
1
- import{isBot}from"../utils/is-bot";export function handleSubmitFingerprint(e){return async(t,n)=>{try{const i=t.body??{},o={full_hash:i.hash??"",fingerprint_id:i.stable_hash??"",timestamp:i.collected_at??(new Date).toISOString(),isIncognito:i.is_incognito??!1,components:i.components??{},version:i.version??"inline-1.0.0"};let s,r,c;try{s=e.resolveUserId?e.resolveUserId(t):void 0}catch{}s=s??i.user_id??void 0;try{r=e.resolveEmailAddress?e.resolveEmailAddress(t):void 0}catch{}r=r??parseCookie(t,"__unshared_email")??i.email??void 0;try{c=e.resolveSessionId?e.resolveSessionId(t):void 0}catch{}c=c??i.session_id??parseCookie(t,"__unshared_sid");const a=t.ip??"",d=t.headers["user-agent"]??"";if(isBot(d))return void n.status(200).json({success:!0});const u=extractDeviceId(t,e.resolveDeviceId),_=o.fingerprint_id||void 0,p=[];if(_&&!parseCookie(t,"__unshared_fingerprint_id")&&p.push(`__unshared_fingerprint_id=${encodeURIComponent(_)}; HttpOnly; Path=/; SameSite=Lax`),r&&!parseCookie(t,"__unshared_email")&&p.push(`__unshared_email=${encodeURIComponent(r)}; HttpOnly; Path=/; SameSite=Lax`),p.length>0){const e=n.getHeader("Set-Cookie");if(e){const t=Array.isArray(e)?[...e]:[String(e)];t.push(...p),n.setHeader("Set-Cookie",t)}else n.setHeader("Set-Cookie",p)}s&&e.client.submitFingerprintEvent(o,{userId:s,sessionHash:c,eventType:"auto_collect",ipAddress:a}).catch(()=>{}),s&&r&&!e.rateLimitBackoff.isPaused()&&e.client.processUserEvent({eventType:"auto_collect",userId:s,emailAddress:r,ipAddress:a,deviceId:u,fingerprintId:_,sessionHash:c??"unknown",userAgent:d}).then(t=>{t.success&&t.data?.analysis&&e.verdictCache.update(s,{isFlagged:t.data.analysis.is_user_flagged}),!t.success&&t.error?.retryAfter&&e.rateLimitBackoff.pause(1e3*t.error.retryAfter)}).catch(()=>{}),n.status(200).json({success:!0})}catch{n.status(200).json({success:!0})}}}function extractDeviceId(e,t){if(t)try{const n=t(e);if(n)return n}catch{}const n=parseCookie(e,"__unshared_fp_id");if(n)return n;const i=e.headers["x-device-id"];return"string"==typeof i&&i?i:"unknown"}function parseCookie(e,t){const n=e.headers.cookie;if(!n)return;const i=n.match(new RegExp(`(?:^|; )${t}=([^;]*)`));return i?decodeURIComponent(i[1]):void 0}
1
+ import{isBot}from"../utils/is-bot";import{extractClientIp}from"../utils/client-ip";export function handleSubmitFingerprint(e){return async(t,n)=>{try{const i=t.body??{},o={full_hash:i.hash??"",fingerprint_id:i.stable_hash??"",timestamp:i.collected_at??(new Date).toISOString(),isIncognito:i.is_incognito??!1,components:i.components??{},version:i.version??"inline-1.0.0"};let s,r,c;try{s=e.resolveUserId?e.resolveUserId(t):void 0}catch{}s=s??i.user_id??void 0;try{r=e.resolveEmailAddress?e.resolveEmailAddress(t):void 0}catch{}r=r??parseCookie(t,"__unshared_email")??i.email??void 0;try{c=e.resolveSessionId?e.resolveSessionId(t):void 0}catch{}c=c??i.session_id??parseCookie(t,"__unshared_sid");const a=extractClientIp(t),d=t.headers["user-agent"]??"";if(isBot(d))return void n.status(200).json({success:!0});const u=extractDeviceId(t,e.resolveDeviceId),p=o.fingerprint_id||void 0,_=[];if(p&&!parseCookie(t,"__unshared_fingerprint_id")&&_.push(`__unshared_fingerprint_id=${encodeURIComponent(p)}; HttpOnly; Path=/; SameSite=Lax`),r&&!parseCookie(t,"__unshared_email")&&_.push(`__unshared_email=${encodeURIComponent(r)}; HttpOnly; Path=/; SameSite=Lax`),_.length>0){const e=n.getHeader("Set-Cookie");if(e){const t=Array.isArray(e)?[...e]:[String(e)];t.push(..._),n.setHeader("Set-Cookie",t)}else n.setHeader("Set-Cookie",_)}s&&e.client.submitFingerprintEvent(o,{userId:s,emailAddress:r,sessionHash:c,eventType:"auto_collect",ipAddress:a,userAgent:d}).catch(()=>{}),s&&r&&!e.rateLimitBackoff.isPaused()&&e.client.processUserEvent({eventType:"auto_collect",userId:s,emailAddress:r,ipAddress:a,deviceId:u,fingerprintId:p,sessionHash:c??"unknown",userAgent:d}).then(t=>{t.success&&t.data?.analysis&&e.verdictCache.update(s,{isFlagged:t.data.analysis.is_user_flagged}),!t.success&&t.error?.retryAfter&&e.rateLimitBackoff.pause(1e3*t.error.retryAfter)}).catch(()=>{}),n.status(200).json({success:!0})}catch{n.status(200).json({success:!0})}}}function extractDeviceId(e,t){if(t)try{const n=t(e);if(n)return n}catch{}const n=parseCookie(e,"__unshared_fp_id");if(n)return n;const i=e.headers["x-device-id"];return"string"==typeof i&&i?i:"unknown"}function parseCookie(e,t){const n=e.headers.cookie;if(!n)return;const i=n.match(new RegExp(`(?:^|; )${t}=([^;]*)`));return i?decodeURIComponent(i[1]):void 0}
@@ -0,0 +1,6 @@
1
+ import type { Request } from 'express';
2
+ /**
3
+ * Extract the real client IP from proxy headers, falling back to req.ip.
4
+ * Checked in order: CF-Connecting-IP (Cloudflare) → X-Real-IP (nginx/ALB) → req.ip.
5
+ */
6
+ export declare function extractClientIp(req: Request): string;
@@ -0,0 +1 @@
1
+ export function extractClientIp(t){const n=t.headers["cf-connecting-ip"];if("string"==typeof n&&n)return n;const r=t.headers["x-real-ip"];return"string"==typeof r&&r?r:t.ip??""}
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"t",{value:!0}),exports.VerdictCache=void 0,exports.unsharedBoundToUser=unsharedBoundToUser;const fs_1=require("fs"),verdict_cache_1=require("./verdict-cache");Object.defineProperty(exports,"VerdictCache",{enumerable:!0,get:function(){return verdict_cache_1.VerdictCache}});const rate_limit_backoff_1=require("./rate-limit-backoff"),response_interceptor_1=require("./response-interceptor"),fingerprint_script_1=require("./injection/fingerprint-script"),submit_fp_1=require("./routes/submit-fp"),verify_1=require("./routes/verify"),content_type_1=require("./utils/content-type"),skip_paths_1=require("./utils/skip-paths"),is_bot_1=require("./utils/is-bot"),CHECK_USER_TIMEOUT_MS=500;function unsharedBoundToUser(e,t){if(!t.userId)throw new Error("[Unshared] userId resolver is required");if(!t.emailAddress){let e=!1;try{require.resolve("unshared-frontend-sdk"),e=!0}catch{}e||console.warn("[Unshared] Warning: emailAddress resolver is not configured and unshared-frontend-sdk is not installed.\nNo user events will be submitted. Either install unshared-frontend-sdk (Tier 1) or\nprovide emailAddress in your middleware config (Tier 2).")}const{userId:r,emailAddress:n,routePrefix:i="/__unshared",corsOrigins:o,cacheTTL:s=6e4,skipPaths:c,sessionId:d,deviceId:a,onFlagged:u}=t,l=new verdict_cache_1.VerdictCache(s),f=new rate_limit_backoff_1.RateLimitBackoff,p=Date.now().toString(36),_=(0,fingerprint_script_1.generateFingerprintScript)(i,p);let v="";try{const e=require.resolve("unshared-frontend-sdk/dist/index.umd.js");v=(0,fs_1.readFileSync)(e,"utf8")}catch{}const h=(0,submit_fp_1.handleSubmitFingerprint)({client:e,verdictCache:l,rateLimitBackoff:f,resolveUserId:r,resolveEmailAddress:n,resolveSessionId:d,resolveDeviceId:a}),m=(0,verify_1.handleVerifyTrigger)({client:e,verdictCache:l,resolveEmailAddress:n,resolveDeviceId:a}),C=(0,verify_1.handleVerify)({client:e,verdictCache:l,resolveEmailAddress:n,resolveDeviceId:a}),I=o?Array.isArray(o)?o:[o]:null,g=`${i}/fp.js`,y=`${i}/submit-fp`,k=`${i}/verify-trigger`,A=`${i}/verify`;return function(t,o,s){const p=t.path;if(p.startsWith(i+"/"))return function(e,t){if(!I)return;const r=e.headers.origin??"",n=I.includes("*");(n||I.includes(r))&&(t.setHeader("Access-Control-Allow-Origin",n?"*":r),t.setHeader("Access-Control-Allow-Methods","POST, OPTIONS"),t.setHeader("Access-Control-Allow-Headers","Content-Type, X-Idempotency-Key, X-Session-Id, X-Device-Id"),t.setHeader("Access-Control-Allow-Credentials","true"))}(t,o),"OPTIONS"===t.method?void o.status(204).end():"GET"===t.method&&p===g?(o.setHeader("Content-Type","application/javascript"),o.setHeader("Cache-Control","public, max-age=3600"),void o.status(200).end(v)):"POST"===t.method&&p===y?void h(t,o):"POST"===t.method&&p===k?void m(t,o):"POST"===t.method&&p===A?void C(t,o):void o.status(404).json({success:!1,error:{code:"NOT_FOUND",message:"Unknown route"}});if((0,skip_paths_1.shouldSkipPath)(p,c))return void s();let S;try{S=r(t)}catch{}if(!S)return clearEmailCookieIfPresent(t,o),interceptForInjection(t,o,_),void s();const x=resolveEmail(t,n);if(setUserIdCookie(o,S),x&&setEmailCookie(o,x),!x)return interceptForInjection(t,o,_),void s();const T=extractSessionId(t,d),E=extractDeviceId(t,a),w=extractFingerprintId(t),b=t.headers["user-agent"]??"",O=extractClientIp(t);if((0,is_bot_1.isBot)(b))return void s();if("unknown"===T)return interceptForInjection(t,o,_),void s();f.isPaused()||dispatchUserEvent(e,l,f,{userId:S,emailAddress:x,sessionId:T,deviceId:E,fingerprintId:w,userAgent:b,ipAddress:O,eventType:`${t.method} ${t.path}`});const U=l.get(S);U?(l.isStale(S)&&!l.isRefreshing(S)&&(l.markRefreshing(S),fetchAndCacheVerdict(e,l,S,x,E,w,T).finally(()=>l.clearRefreshing(S))),applyVerdict(U,S,x,t,o,s,_,u)):fetchAndCacheVerdict(e,l,S,x,E,w,T).then(e=>{applyVerdict(e,S,x,t,o,s,_,u)}).catch(()=>{interceptForInjection(t,o,_),s()})}}function resolveEmail(e,t){if(t)try{const r=t(e);if(r)return r}catch{}const r=parseCookie(e,"__unshared_email");if(r)return r;const n=e.body?.email;return"string"==typeof n&&n?n:void 0}function applyVerdict(e,t,r,n,i,o,s,c){if(interceptForInjection(n,i,s),e.isFlagged&&!e.isVerified&&c)try{c({userId:t,emailAddress:r,verdict:e,req:n,res:i,next:o})}catch{o()}else o()}function preventHtmlCaching(e,t){delete e.headers["if-none-match"],delete e.headers["if-modified-since"];const r=t.writeHead.bind(t);t.writeHead=function(e,...n){const i=t.getHeader("content-type");return i&&String(i).includes("text/html")&&(t.setHeader("Cache-Control","no-store"),t.removeHeader("ETag"),t.removeHeader("Last-Modified")),r(e,...n)}}function interceptForInjection(e,t,r){preventHtmlCaching(e,t),(0,response_interceptor_1.interceptResponse)(t,(e,t)=>{if(!(0,content_type_1.isHtmlContentType)(t))return null;const n=e.toString("utf8"),i=n.lastIndexOf("</body>");return-1===i?n+r:n.slice(0,i)+r+n.slice(i)})}function dispatchUserEvent(e,t,r,n){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(()=>{})}async function fetchAndCacheVerdict(e,t,r,n,i,o,s){const c={};i&&"unknown"!==i&&(c.deviceId=i),o&&(c.fingerprintId=o);const d=await Promise.race([e.checkUser(n,c),new Promise(e=>setTimeout(()=>e(null),500))]);if(!d)return{isFlagged:!1,isVerified:!1,emailAddress:n,sessionId:s,cachedAt:0,ttl:0};const a=d.data?.is_user_flagged??!1;return t.set(r,{isFlagged:a,isVerified:!1,emailAddress:n,sessionId:s}),t.get(r)}function parseCookie(e,t){const r=e.headers.cookie;if(!r)return;const n=r.match(new RegExp(`(?:^|; )${t}=([^;]*)`));return n?decodeURIComponent(n[1]):void 0}function extractSessionId(e,t){if(t)try{const r=t(e);if(r)return r}catch{}return parseCookie(e,"__unshared_sid")??"unknown"}function extractDeviceId(e,t){if(t)try{const r=t(e);if(r)return r}catch{}const r=parseCookie(e,"__unshared_fp_id");if(r)return r;const n=e.headers["x-device-id"];return"string"==typeof n&&n?n:"unknown"}function extractFingerprintId(e){return parseCookie(e,"__unshared_fingerprint_id")||void 0}function extractClientIp(e){const t=e.headers["cf-connecting-ip"];if("string"==typeof t&&t)return t;const r=e.headers["x-real-ip"];return"string"==typeof r&&r?r:e.ip??""}function appendSetCookie(e,t){const r=e.getHeader("Set-Cookie");if(r){const n=Array.isArray(r)?[...r]:[String(r)];n.push(t),e.setHeader("Set-Cookie",n)}else e.setHeader("Set-Cookie",t)}function setUserIdCookie(e,t){appendSetCookie(e,`__unshared_uid=${encodeURIComponent(t)}; Path=/; SameSite=Lax`)}function setEmailCookie(e,t){appendSetCookie(e,`__unshared_email=${encodeURIComponent(t)}; HttpOnly; Path=/; SameSite=Lax`)}function clearEmailCookieIfPresent(e,t){parseCookie(e,"__unshared_email")&&appendSetCookie(t,"__unshared_email=; HttpOnly; Path=/; SameSite=Lax; Max-Age=0")}
1
+ "use strict";Object.defineProperty(exports,"t",{value:!0}),exports.VerdictCache=void 0,exports.unsharedBoundToUser=unsharedBoundToUser;const fs_1=require("fs"),verdict_cache_1=require("./verdict-cache");Object.defineProperty(exports,"VerdictCache",{enumerable:!0,get:function(){return verdict_cache_1.VerdictCache}});const rate_limit_backoff_1=require("./rate-limit-backoff"),response_interceptor_1=require("./response-interceptor"),fingerprint_script_1=require("./injection/fingerprint-script"),submit_fp_1=require("./routes/submit-fp"),verify_1=require("./routes/verify"),content_type_1=require("./utils/content-type"),skip_paths_1=require("./utils/skip-paths"),is_bot_1=require("./utils/is-bot"),client_ip_1=require("./utils/client-ip"),CHECK_USER_TIMEOUT_MS=500;function unsharedBoundToUser(e,t){if(!t.userId)throw new Error("[Unshared] userId resolver is required");if(!t.emailAddress){let e=!1;try{require.resolve("unshared-frontend-sdk"),e=!0}catch{}e||console.warn("[Unshared] Warning: emailAddress resolver is not configured and unshared-frontend-sdk is not installed.\nNo user events will be submitted. Either install unshared-frontend-sdk (Tier 1) or\nprovide emailAddress in your middleware config (Tier 2).")}const{userId:r,emailAddress:i,routePrefix:n="/__unshared",corsOrigins:o,cacheTTL:s=6e4,skipPaths:c,sessionId:d,deviceId:a,onFlagged:u}=t,l=new verdict_cache_1.VerdictCache(s),p=new rate_limit_backoff_1.RateLimitBackoff,f=Date.now().toString(36),_=(0,fingerprint_script_1.generateFingerprintScript)(n,f);let v="";try{const e=require.resolve("unshared-frontend-sdk/dist/index.umd.js");v=(0,fs_1.readFileSync)(e,"utf8")}catch{}const h=(0,submit_fp_1.handleSubmitFingerprint)({client:e,verdictCache:l,rateLimitBackoff:p,resolveUserId:r,resolveEmailAddress:i,resolveSessionId:d,resolveDeviceId:a}),m=(0,verify_1.handleVerifyTrigger)({client:e,verdictCache:l,resolveEmailAddress:i,resolveDeviceId:a}),C=(0,verify_1.handleVerify)({client:e,verdictCache:l,resolveEmailAddress:i,resolveDeviceId:a}),I=o?Array.isArray(o)?o:[o]:null,g=`${n}/fp.js`,k=`${n}/submit-fp`,y=`${n}/verify-trigger`,A=`${n}/verify`;return function(t,o,s){const f=t.path;if(f.startsWith(n+"/"))return function(e,t){if(!I)return;const r=e.headers.origin??"",i=I.includes("*");(i||I.includes(r))&&(t.setHeader("Access-Control-Allow-Origin",i?"*":r),t.setHeader("Access-Control-Allow-Methods","POST, OPTIONS"),t.setHeader("Access-Control-Allow-Headers","Content-Type, X-Idempotency-Key, X-Session-Id, X-Device-Id"),t.setHeader("Access-Control-Allow-Credentials","true"))}(t,o),"OPTIONS"===t.method?void o.status(204).end():"GET"===t.method&&f===g?(o.setHeader("Content-Type","application/javascript"),o.setHeader("Cache-Control","public, max-age=3600"),void o.status(200).end(v)):"POST"===t.method&&f===k?void h(t,o):"POST"===t.method&&f===y?void m(t,o):"POST"===t.method&&f===A?void C(t,o):void o.status(404).json({success:!1,error:{code:"NOT_FOUND",message:"Unknown route"}});if((0,skip_paths_1.shouldSkipPath)(f,c))return void s();let S;try{S=r(t)}catch{}if(!S)return clearEmailCookieIfPresent(t,o),interceptForInjection(t,o,_),void s();const T=resolveEmail(t,i);if(setUserIdCookie(o,S),T&&setEmailCookie(o,T),!T)return interceptForInjection(t,o,_),void s();const x=extractSessionId(t,d),E=extractDeviceId(t,a),w=extractFingerprintId(t),b=t.headers["user-agent"]??"",O=(0,client_ip_1.extractClientIp)(t);if((0,is_bot_1.isBot)(b))return void s();if("unknown"===x)return interceptForInjection(t,o,_),void s();p.isPaused()||dispatchUserEvent(e,l,p,{userId:S,emailAddress:T,sessionId:x,deviceId:E,fingerprintId:w,userAgent:b,ipAddress:O,eventType:`${t.method} ${t.path}`});const U=l.get(S);U?(l.isStale(S)&&!l.isRefreshing(S)&&(l.markRefreshing(S),fetchAndCacheVerdict(e,l,S,T,E,w,x).finally(()=>l.clearRefreshing(S))),applyVerdict(U,S,T,t,o,s,_,u)):fetchAndCacheVerdict(e,l,S,T,E,w,x).then(e=>{applyVerdict(e,S,T,t,o,s,_,u)}).catch(()=>{interceptForInjection(t,o,_),s()})}}function resolveEmail(e,t){if(t)try{const r=t(e);if(r)return r}catch{}const r=parseCookie(e,"__unshared_email");if(r)return r;const i=e.body?.email;return"string"==typeof i&&i?i:void 0}function applyVerdict(e,t,r,i,n,o,s,c){if(interceptForInjection(i,n,s),e.isFlagged&&!e.isVerified&&c)try{c({userId:t,emailAddress:r,verdict:e,req:i,res:n,next:o})}catch{o()}else o()}function preventHtmlCaching(e,t){delete e.headers["if-none-match"],delete e.headers["if-modified-since"];const r=t.writeHead.bind(t);t.writeHead=function(e,...i){const n=t.getHeader("content-type");return n&&String(n).includes("text/html")&&(t.setHeader("Cache-Control","no-store"),t.removeHeader("ETag"),t.removeHeader("Last-Modified")),r(e,...i)}}function interceptForInjection(e,t,r){preventHtmlCaching(e,t),(0,response_interceptor_1.interceptResponse)(t,(e,t)=>{if(!(0,content_type_1.isHtmlContentType)(t))return null;const i=e.toString("utf8"),n=i.lastIndexOf("</body>");return-1===n?i+r:i.slice(0,n)+r+i.slice(n)})}function dispatchUserEvent(e,t,r,i){e.processUserEvent({eventType:i.eventType,userId:i.userId,emailAddress:i.emailAddress,ipAddress:i.ipAddress,deviceId:i.deviceId,fingerprintId:i.fingerprintId,sessionHash:i.sessionId,userAgent:i.userAgent}).then(e=>{e.success&&e.data?.analysis&&t.update(i.userId,{isFlagged:e.data.analysis.is_user_flagged}),!e.success&&e.error?.retryAfter&&r.pause(1e3*e.error.retryAfter)}).catch(()=>{})}async function fetchAndCacheVerdict(e,t,r,i,n,o,s){const c={};n&&"unknown"!==n&&(c.deviceId=n),o&&(c.fingerprintId=o);const d=await Promise.race([e.checkUser(i,c),new Promise(e=>setTimeout(()=>e(null),500))]);if(!d)return{isFlagged:!1,isVerified:!1,emailAddress:i,sessionId:s,cachedAt:0,ttl:0};const a=d.data?.is_user_flagged??!1;return t.set(r,{isFlagged:a,isVerified:!1,emailAddress:i,sessionId:s}),t.get(r)}function parseCookie(e,t){const r=e.headers.cookie;if(!r)return;const i=r.match(new RegExp(`(?:^|; )${t}=([^;]*)`));return i?decodeURIComponent(i[1]):void 0}function extractSessionId(e,t){if(t)try{const r=t(e);if(r)return r}catch{}return parseCookie(e,"__unshared_sid")??"unknown"}function extractDeviceId(e,t){if(t)try{const r=t(e);if(r)return r}catch{}const r=parseCookie(e,"__unshared_fp_id");if(r)return r;const i=e.headers["x-device-id"];return"string"==typeof i&&i?i:"unknown"}function extractFingerprintId(e){return parseCookie(e,"__unshared_fingerprint_id")||void 0}function appendSetCookie(e,t){const r=e.getHeader("Set-Cookie");if(r){const i=Array.isArray(r)?[...r]:[String(r)];i.push(t),e.setHeader("Set-Cookie",i)}else e.setHeader("Set-Cookie",t)}function setUserIdCookie(e,t){appendSetCookie(e,`__unshared_uid=${encodeURIComponent(t)}; Path=/; SameSite=Lax`)}function setEmailCookie(e,t){appendSetCookie(e,`__unshared_email=${encodeURIComponent(t)}; HttpOnly; Path=/; SameSite=Lax`)}function clearEmailCookieIfPresent(e,t){parseCookie(e,"__unshared_email")&&appendSetCookie(t,"__unshared_email=; HttpOnly; Path=/; SameSite=Lax; Max-Age=0")}
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"t",{value:!0}),exports.handleSubmitFingerprint=handleSubmitFingerprint;const is_bot_1=require("../utils/is-bot");function handleSubmitFingerprint(e){return async(t,n)=>{try{const i=t.body??{},o={full_hash:i.hash??"",fingerprint_id:i.stable_hash??"",timestamp:i.collected_at??(new Date).toISOString(),isIncognito:i.is_incognito??!1,components:i.components??{},version:i.version??"inline-1.0.0"};let s,r,c;try{s=e.resolveUserId?e.resolveUserId(t):void 0}catch{}s=s??i.user_id??void 0;try{r=e.resolveEmailAddress?e.resolveEmailAddress(t):void 0}catch{}r=r??parseCookie(t,"__unshared_email")??i.email??void 0;try{c=e.resolveSessionId?e.resolveSessionId(t):void 0}catch{}c=c??i.session_id??parseCookie(t,"__unshared_sid");const a=t.ip??"",d=t.headers["user-agent"]??"";if((0,is_bot_1.isBot)(d))return void n.status(200).json({success:!0});const u=extractDeviceId(t,e.resolveDeviceId),_=o.fingerprint_id||void 0,p=[];if(_&&!parseCookie(t,"__unshared_fingerprint_id")&&p.push(`__unshared_fingerprint_id=${encodeURIComponent(_)}; HttpOnly; Path=/; SameSite=Lax`),r&&!parseCookie(t,"__unshared_email")&&p.push(`__unshared_email=${encodeURIComponent(r)}; HttpOnly; Path=/; SameSite=Lax`),p.length>0){const e=n.getHeader("Set-Cookie");if(e){const t=Array.isArray(e)?[...e]:[String(e)];t.push(...p),n.setHeader("Set-Cookie",t)}else n.setHeader("Set-Cookie",p)}s&&e.client.submitFingerprintEvent(o,{userId:s,sessionHash:c,eventType:"auto_collect",ipAddress:a}).catch(()=>{}),s&&r&&!e.rateLimitBackoff.isPaused()&&e.client.processUserEvent({eventType:"auto_collect",userId:s,emailAddress:r,ipAddress:a,deviceId:u,fingerprintId:_,sessionHash:c??"unknown",userAgent:d}).then(t=>{t.success&&t.data?.analysis&&e.verdictCache.update(s,{isFlagged:t.data.analysis.is_user_flagged}),!t.success&&t.error?.retryAfter&&e.rateLimitBackoff.pause(1e3*t.error.retryAfter)}).catch(()=>{}),n.status(200).json({success:!0})}catch{n.status(200).json({success:!0})}}}function extractDeviceId(e,t){if(t)try{const n=t(e);if(n)return n}catch{}const n=parseCookie(e,"__unshared_fp_id");if(n)return n;const i=e.headers["x-device-id"];return"string"==typeof i&&i?i:"unknown"}function parseCookie(e,t){const n=e.headers.cookie;if(!n)return;const i=n.match(new RegExp(`(?:^|; )${t}=([^;]*)`));return i?decodeURIComponent(i[1]):void 0}
1
+ "use strict";Object.defineProperty(exports,"t",{value:!0}),exports.handleSubmitFingerprint=handleSubmitFingerprint;const is_bot_1=require("../utils/is-bot"),client_ip_1=require("../utils/client-ip");function handleSubmitFingerprint(e){return async(t,i)=>{try{const n=t.body??{},s={full_hash:n.hash??"",fingerprint_id:n.stable_hash??"",timestamp:n.collected_at??(new Date).toISOString(),isIncognito:n.is_incognito??!1,components:n.components??{},version:n.version??"inline-1.0.0"};let o,r,c;try{o=e.resolveUserId?e.resolveUserId(t):void 0}catch{}o=o??n.user_id??void 0;try{r=e.resolveEmailAddress?e.resolveEmailAddress(t):void 0}catch{}r=r??parseCookie(t,"__unshared_email")??n.email??void 0;try{c=e.resolveSessionId?e.resolveSessionId(t):void 0}catch{}c=c??n.session_id??parseCookie(t,"__unshared_sid");const a=(0,client_ip_1.extractClientIp)(t),d=t.headers["user-agent"]??"";if((0,is_bot_1.isBot)(d))return void i.status(200).json({success:!0});const u=extractDeviceId(t,e.resolveDeviceId),_=s.fingerprint_id||void 0,p=[];if(_&&!parseCookie(t,"__unshared_fingerprint_id")&&p.push(`__unshared_fingerprint_id=${encodeURIComponent(_)}; HttpOnly; Path=/; SameSite=Lax`),r&&!parseCookie(t,"__unshared_email")&&p.push(`__unshared_email=${encodeURIComponent(r)}; HttpOnly; Path=/; SameSite=Lax`),p.length>0){const e=i.getHeader("Set-Cookie");if(e){const t=Array.isArray(e)?[...e]:[String(e)];t.push(...p),i.setHeader("Set-Cookie",t)}else i.setHeader("Set-Cookie",p)}o&&e.client.submitFingerprintEvent(s,{userId:o,emailAddress:r,sessionHash:c,eventType:"auto_collect",ipAddress:a,userAgent:d}).catch(()=>{}),o&&r&&!e.rateLimitBackoff.isPaused()&&e.client.processUserEvent({eventType:"auto_collect",userId:o,emailAddress:r,ipAddress:a,deviceId:u,fingerprintId:_,sessionHash:c??"unknown",userAgent:d}).then(t=>{t.success&&t.data?.analysis&&e.verdictCache.update(o,{isFlagged:t.data.analysis.is_user_flagged}),!t.success&&t.error?.retryAfter&&e.rateLimitBackoff.pause(1e3*t.error.retryAfter)}).catch(()=>{}),i.status(200).json({success:!0})}catch{i.status(200).json({success:!0})}}}function extractDeviceId(e,t){if(t)try{const i=t(e);if(i)return i}catch{}const i=parseCookie(e,"__unshared_fp_id");if(i)return i;const n=e.headers["x-device-id"];return"string"==typeof n&&n?n:"unknown"}function parseCookie(e,t){const i=e.headers.cookie;if(!i)return;const n=i.match(new RegExp(`(?:^|; )${t}=([^;]*)`));return n?decodeURIComponent(n[1]):void 0}
@@ -0,0 +1,6 @@
1
+ import type { Request } from 'express';
2
+ /**
3
+ * Extract the real client IP from proxy headers, falling back to req.ip.
4
+ * Checked in order: CF-Connecting-IP (Cloudflare) → X-Real-IP (nginx/ALB) → req.ip.
5
+ */
6
+ export declare function extractClientIp(req: Request): string;
@@ -0,0 +1 @@
1
+ "use strict";function extractClientIp(t){const e=t.headers["cf-connecting-ip"];if("string"==typeof e&&e)return e;const r=t.headers["x-real-ip"];return"string"==typeof r&&r?r:t.ip??""}Object.defineProperty(exports,"t",{value:!0}),exports.extractClientIp=extractClientIp;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "unshared-clientjs-sdk",
3
- "version": "2.0.0-rc.12",
3
+ "version": "2.0.0-rc.13",
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",