unshared-clientjs-sdk 2.0.0-rc.4 → 2.0.0-rc.5

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
@@ -42,6 +42,7 @@ export interface SubmitFingerprintOptions {
42
42
  userId?: string;
43
43
  sessionHash?: string;
44
44
  eventType?: string;
45
+ ipAddress?: string;
45
46
  }
46
47
  export interface SubmitFingerprintResult {
47
48
  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,a=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(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._(s.userId)),null!=s?.sessionHash&&(t.session_hash=s.sessionHash),null!=s?.eventType&&(t.event_type=s.eventType),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:e.userId,ip_address:e.ipAddress,device_id:this._(e.deviceId),session_hash:e.sessionHash,user_agent:e.userAgent,email_address:this._(e.emailAddress)};return 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=new URLSearchParams({email_address:this._(e),device_id:this._(s)}),r=await this.p(`${this.o}/v2/check-user?${t}`,{method:"GET"});return r.success?r:{success:!0,status:200,data:{is_user_flagged:!1}}}async triggerEmailVerification(e,s){const t=await this.p(`${this.o}/v2/trigger-email-verification`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({email_address:this._(e),device_id:this._(s)})});return!t.success&&(0===t.status||t.status>=500)?{success:!1,status:t.status,error:{code:"DELIVERY_FAILED",message:t.error?.message??"Delivery failed"}}:t}async verify(e,s,t){const r=await this.p(`${this.o}/v2/verify`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({email_address:this._(e),device_id:this._(s),code:this._(t)})});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.success&&!1===r.data?.verified?{success:!1,status:r.status,error:{code:"VERIFICATION_FAILED",message:"Code is incorrect or expired",details:r.data.reason?{reason:r.data.reason}:void 0}}:r}}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,a=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(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._(s.userId)),null!=s?.sessionHash&&(t.session_hash=s.sessionHash),null!=s?.eventType&&(t.event_type=s.eventType),null!=s?.ipAddress&&(t.ip_address=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:e.userId,ip_address:e.ipAddress,device_id:this._(e.deviceId),session_hash:e.sessionHash,user_agent:e.userAgent,email_address:this._(e.emailAddress)};return 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=new URLSearchParams({email_address:this._(e),device_id:this._(s)}),r=await this.p(`${this.o}/v2/check-user?${t}`,{method:"GET"});return r.success?r:{success:!0,status:200,data:{is_user_flagged:!1}}}async triggerEmailVerification(e,s){const t=await this.p(`${this.o}/v2/trigger-email-verification`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({email_address:this._(e),device_id:this._(s)})});return!t.success&&(0===t.status||t.status>=500)?{success:!1,status:t.status,error:{code:"DELIVERY_FAILED",message:t.error?.message??"Delivery failed"}}:t}async verify(e,s,t){const r=await this.p(`${this.o}/v2/verify`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({email_address:this._(e),device_id:this._(s),code:this._(t)})});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.success&&!1===r.data?.verified?{success:!1,status:r.status,error:{code:"VERIFICATION_FAILED",message:"Code is incorrect or expired",details:r.data.reason?{reason:r.data.reason}:void 0}}:r}}exports.UnsharedLabsClient=UnsharedLabsClient;
@@ -42,6 +42,7 @@ export interface SubmitFingerprintOptions {
42
42
  userId?: string;
43
43
  sessionHash?: string;
44
44
  eventType?: string;
45
+ ipAddress?: string;
45
46
  }
46
47
  export interface SubmitFingerprintResult {
47
48
  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 a=1;a<=t;a++){a>1&&await sleep(retryDelay(a-1));const t=new AbortController,i=setTimeout(()=>t.abort(),this.o);try{const a=await fetch(e,{method:s.method,headers:{"X-API-Key":this.t,...s.headers},body:s.body,signal:t.signal});if(clearTimeout(i),a.ok){const e=await a.text().catch(()=>"{}");let s;try{s=JSON.parse(e)}catch{s={}}const t="data"in s?s.data:s;return{success:!0,status:a.status,data:t}}const n=await parseErrorBody(a);if(a.status>=400&&a.status<500){if(429===a.status){const e=a.headers.get("Retry-After");if(null!=e){const s=parseInt(e,10);isNaN(s)||(n.retryAfter=s)}}return{success:!1,status:a.status,error:n}}r={success:!1,status:a.status,error:n}}catch(e){clearTimeout(i),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),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:e.userId,ip_address:e.ipAddress,device_id:this.l(e.deviceId),session_hash:e.sessionHash,user_agent:e.userAgent,email_address:this.l(e.emailAddress)};return 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=new URLSearchParams({email_address:this.l(e),device_id:this.l(s)}),r=await this._(`${this.i}/v2/check-user?${t}`,{method:"GET"});return r.success?r:{success:!0,status:200,data:{is_user_flagged:!1}}}async triggerEmailVerification(e,s){const t=await this._(`${this.i}/v2/trigger-email-verification`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({email_address:this.l(e),device_id:this.l(s)})});return!t.success&&(0===t.status||t.status>=500)?{success:!1,status:t.status,error:{code:"DELIVERY_FAILED",message:t.error?.message??"Delivery failed"}}:t}async verify(e,s,t){const r=await this._(`${this.i}/v2/verify`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({email_address:this.l(e),device_id:this.l(s),code:this.l(t)})});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.success&&!1===r.data?.verified?{success:!1,status:r.status,error:{code:"VERIFICATION_FAILED",message:"Code is incorrect or expired",details:r.data.reason?{reason:r.data.reason}:void 0}}:r}}
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 a=1;a<=t;a++){a>1&&await sleep(retryDelay(a-1));const t=new AbortController,i=setTimeout(()=>t.abort(),this.o);try{const a=await fetch(e,{method:s.method,headers:{"X-API-Key":this.t,...s.headers},body:s.body,signal:t.signal});if(clearTimeout(i),a.ok){const e=await a.text().catch(()=>"{}");let s;try{s=JSON.parse(e)}catch{s={}}const t="data"in s?s.data:s;return{success:!0,status:a.status,data:t}}const n=await parseErrorBody(a);if(a.status>=400&&a.status<500){if(429===a.status){const e=a.headers.get("Retry-After");if(null!=e){const s=parseInt(e,10);isNaN(s)||(n.retryAfter=s)}}return{success:!1,status:a.status,error:n}}r={success:!1,status:a.status,error:n}}catch(e){clearTimeout(i),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=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:e.userId,ip_address:e.ipAddress,device_id:this.l(e.deviceId),session_hash:e.sessionHash,user_agent:e.userAgent,email_address:this.l(e.emailAddress)};return 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=new URLSearchParams({email_address:this.l(e),device_id:this.l(s)}),r=await this._(`${this.i}/v2/check-user?${t}`,{method:"GET"});return r.success?r:{success:!0,status:200,data:{is_user_flagged:!1}}}async triggerEmailVerification(e,s){const t=await this._(`${this.i}/v2/trigger-email-verification`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({email_address:this.l(e),device_id:this.l(s)})});return!t.success&&(0===t.status||t.status>=500)?{success:!1,status:t.status,error:{code:"DELIVERY_FAILED",message:t.error?.message??"Delivery failed"}}:t}async verify(e,s,t){const r=await this._(`${this.i}/v2/verify`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({email_address:this.l(e),device_id:this.l(s),code:this.l(t)})});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.success&&!1===r.data?.verified?{success:!1,status:r.status,error:{code:"VERIFICATION_FAILED",message:"Code is incorrect or expired",details:r.data.reason?{reason:r.data.reason}:void 0}}:r}}
@@ -7,6 +7,8 @@ export interface MiddlewareOptions {
7
7
  eventTypeExtractor?: (req: Request) => string | undefined;
8
8
  /** Override sessionId extractor. Falls back to X-Session-Id header, then req.body.session_id. */
9
9
  sessionIdExtractor?: (req: Request) => string | undefined;
10
+ /** Override IP address extractor. Falls back to req.ip. */
11
+ ipAddressExtractor?: (req: Request) => string | undefined;
10
12
  /** Default event type when none is extractable. @default "browser_event" */
11
13
  defaultEventType?: string;
12
14
  /**
@@ -1 +1 @@
1
- export function createUnsharedMiddleware(e,r){const{userIdExtractor:s,eventTypeExtractor:t,sessionIdExtractor:o,defaultEventType:n="browser_event",routePrefix:c="/unshared",corsOrigins:i}=r??{},a=`${c}/submit-fingerprint-event`,d=i?Array.isArray(i)?i:[i]:null;return async(r,c,i)=>{if(d&&r.path===a){const e=r.headers.origin??"",s=d.includes("*");if((s||d.includes(e))&&(c.setHeader("Access-Control-Allow-Origin",s?"*":e),c.setHeader("Access-Control-Allow-Methods","POST, OPTIONS"),c.setHeader("Access-Control-Allow-Headers","Content-Type, X-Idempotency-Key, X-Session-Id")),"OPTIONS"===r.method)return void c.status(204).end()}if("POST"===r.method&&r.path===a)try{const i=r.body??{};if(!i.hash||!i.stable_hash||!i.collected_at)return void c.status(400).json({success:!1,error:{code:"VALIDATION_ERROR",message:"Missing required fingerprint fields: hash, stable_hash, collected_at"}});const a={full_hash:i.hash,fingerprint_id:i.stable_hash,timestamp:i.collected_at,isIncognito:i.is_incognito??!1,components:i.components??{},version:i.version??"unknown"};let d,l,u;try{d=(s?s(r):void 0)??i.user_id}catch{d=i.user_id}r.body&&"object"==typeof r.body&&"user_id"in r.body&&delete r.body.user_id;try{l=(t?t(r):void 0)??i.event_type??n}catch{l=i.event_type??n}try{u=(o?o(r):void 0)??r.headers["x-session-id"]?.toString()??i.session_id}catch{u=r.headers["x-session-id"]?.toString()??i.session_id}const f=await e.submitFingerprintEvent(a,{userId:d,sessionHash:u,eventType:l});if(!f.success)return void c.status(200).json({success:!1,error:{code:"UPSTREAM_ERROR",message:f.error?.message??"Upstream request failed"}});c.status(202).json({success:!0,data:f.data})}catch(e){c.status(200).json({success:!1,error:{code:"MIDDLEWARE_ERROR",message:e instanceof Error?e.message:"Middleware error"}})}else i()}}
1
+ export function createUnsharedMiddleware(e,r){const{userIdExtractor:s,eventTypeExtractor:t,sessionIdExtractor:o,ipAddressExtractor:i,defaultEventType:n="browser_event",routePrefix:c="/unshared",corsOrigins:d}=r??{},a=`${c}/submit-fingerprint-event`,l=d?Array.isArray(d)?d:[d]:null;let u=!1;return async(r,c,d)=>{if(!u&&(u=!0,r.app&&!r.app.get("trust proxy")))throw new Error('[unshared-labs] Express "trust proxy" is not set. Add `app.set("trust proxy", 1)` before mounting this middleware, otherwise req.ip will reflect the proxy\'s IP instead of the real client IP.');if(l&&r.path===a){const e=r.headers.origin??"",s=l.includes("*");if((s||l.includes(e))&&(c.setHeader("Access-Control-Allow-Origin",s?"*":e),c.setHeader("Access-Control-Allow-Methods","POST, OPTIONS"),c.setHeader("Access-Control-Allow-Headers","Content-Type, X-Idempotency-Key, X-Session-Id")),"OPTIONS"===r.method)return void c.status(204).end()}if("POST"===r.method&&r.path===a)try{const d=r.body??{};if(!d.hash||!d.stable_hash||!d.collected_at)return void c.status(400).json({success:!1,error:{code:"VALIDATION_ERROR",message:"Missing required fingerprint fields: hash, stable_hash, collected_at"}});const a={full_hash:d.hash,fingerprint_id:d.stable_hash,timestamp:d.collected_at,isIncognito:d.is_incognito??!1,components:d.components??{},version:d.version??"unknown"};let l,u,p,h;try{l=(s?s(r):void 0)??d.user_id}catch{l=d.user_id}r.body&&"object"==typeof r.body&&"user_id"in r.body&&delete r.body.user_id;try{u=(t?t(r):void 0)??d.event_type??n}catch{u=d.event_type??n}try{p=(o?o(r):void 0)??r.headers["x-session-id"]?.toString()??d.session_id}catch{p=r.headers["x-session-id"]?.toString()??d.session_id}try{h=(i?i(r):void 0)??r.ip}catch{h=r.ip}const f=await e.submitFingerprintEvent(a,{userId:l,sessionHash:p,eventType:u,ipAddress:h});if(!f.success)return void c.status(200).json({success:!1,error:{code:"UPSTREAM_ERROR",message:f.error?.message??"Upstream request failed"}});c.status(202).json({success:!0,data:f.data})}catch(e){c.status(200).json({success:!1,error:{code:"MIDDLEWARE_ERROR",message:e instanceof Error?e.message:"Middleware error"}})}else d()}}
@@ -7,6 +7,8 @@ export interface MiddlewareOptions {
7
7
  eventTypeExtractor?: (req: Request) => string | undefined;
8
8
  /** Override sessionId extractor. Falls back to X-Session-Id header, then req.body.session_id. */
9
9
  sessionIdExtractor?: (req: Request) => string | undefined;
10
+ /** Override IP address extractor. Falls back to req.ip. */
11
+ ipAddressExtractor?: (req: Request) => string | undefined;
10
12
  /** Default event type when none is extractable. @default "browser_event" */
11
13
  defaultEventType?: string;
12
14
  /**
@@ -1 +1 @@
1
- "use strict";function createUnsharedMiddleware(e,r){const{userIdExtractor:s,eventTypeExtractor:t,sessionIdExtractor:o,defaultEventType:n="browser_event",routePrefix:c="/unshared",corsOrigins:i}=r??{},a=`${c}/submit-fingerprint-event`,d=i?Array.isArray(i)?i:[i]:null;return async(r,c,i)=>{if(d&&r.path===a){const e=r.headers.origin??"",s=d.includes("*");if((s||d.includes(e))&&(c.setHeader("Access-Control-Allow-Origin",s?"*":e),c.setHeader("Access-Control-Allow-Methods","POST, OPTIONS"),c.setHeader("Access-Control-Allow-Headers","Content-Type, X-Idempotency-Key, X-Session-Id")),"OPTIONS"===r.method)return void c.status(204).end()}if("POST"===r.method&&r.path===a)try{const i=r.body??{};if(!i.hash||!i.stable_hash||!i.collected_at)return void c.status(400).json({success:!1,error:{code:"VALIDATION_ERROR",message:"Missing required fingerprint fields: hash, stable_hash, collected_at"}});const a={full_hash:i.hash,fingerprint_id:i.stable_hash,timestamp:i.collected_at,isIncognito:i.is_incognito??!1,components:i.components??{},version:i.version??"unknown"};let d,l,u;try{d=(s?s(r):void 0)??i.user_id}catch{d=i.user_id}r.body&&"object"==typeof r.body&&"user_id"in r.body&&delete r.body.user_id;try{l=(t?t(r):void 0)??i.event_type??n}catch{l=i.event_type??n}try{u=(o?o(r):void 0)??r.headers["x-session-id"]?.toString()??i.session_id}catch{u=r.headers["x-session-id"]?.toString()??i.session_id}const f=await e.submitFingerprintEvent(a,{userId:d,sessionHash:u,eventType:l});if(!f.success)return void c.status(200).json({success:!1,error:{code:"UPSTREAM_ERROR",message:f.error?.message??"Upstream request failed"}});c.status(202).json({success:!0,data:f.data})}catch(e){c.status(200).json({success:!1,error:{code:"MIDDLEWARE_ERROR",message:e instanceof Error?e.message:"Middleware error"}})}else i()}}Object.defineProperty(exports,"t",{value:!0}),exports.createUnsharedMiddleware=createUnsharedMiddleware;
1
+ "use strict";function createUnsharedMiddleware(e,r){const{userIdExtractor:s,eventTypeExtractor:t,sessionIdExtractor:o,ipAddressExtractor:i,defaultEventType:n="browser_event",routePrefix:c="/unshared",corsOrigins:d}=r??{},a=`${c}/submit-fingerprint-event`,l=d?Array.isArray(d)?d:[d]:null;let u=!1;return async(r,c,d)=>{if(!u&&(u=!0,r.app&&!r.app.get("trust proxy")))throw new Error('[unshared-labs] Express "trust proxy" is not set. Add `app.set("trust proxy", 1)` before mounting this middleware, otherwise req.ip will reflect the proxy\'s IP instead of the real client IP.');if(l&&r.path===a){const e=r.headers.origin??"",s=l.includes("*");if((s||l.includes(e))&&(c.setHeader("Access-Control-Allow-Origin",s?"*":e),c.setHeader("Access-Control-Allow-Methods","POST, OPTIONS"),c.setHeader("Access-Control-Allow-Headers","Content-Type, X-Idempotency-Key, X-Session-Id")),"OPTIONS"===r.method)return void c.status(204).end()}if("POST"===r.method&&r.path===a)try{const d=r.body??{};if(!d.hash||!d.stable_hash||!d.collected_at)return void c.status(400).json({success:!1,error:{code:"VALIDATION_ERROR",message:"Missing required fingerprint fields: hash, stable_hash, collected_at"}});const a={full_hash:d.hash,fingerprint_id:d.stable_hash,timestamp:d.collected_at,isIncognito:d.is_incognito??!1,components:d.components??{},version:d.version??"unknown"};let l,u,p,h;try{l=(s?s(r):void 0)??d.user_id}catch{l=d.user_id}r.body&&"object"==typeof r.body&&"user_id"in r.body&&delete r.body.user_id;try{u=(t?t(r):void 0)??d.event_type??n}catch{u=d.event_type??n}try{p=(o?o(r):void 0)??r.headers["x-session-id"]?.toString()??d.session_id}catch{p=r.headers["x-session-id"]?.toString()??d.session_id}try{h=(i?i(r):void 0)??r.ip}catch{h=r.ip}const f=await e.submitFingerprintEvent(a,{userId:l,sessionHash:p,eventType:u,ipAddress:h});if(!f.success)return void c.status(200).json({success:!1,error:{code:"UPSTREAM_ERROR",message:f.error?.message??"Upstream request failed"}});c.status(202).json({success:!0,data:f.data})}catch(e){c.status(200).json({success:!1,error:{code:"MIDDLEWARE_ERROR",message:e instanceof Error?e.message:"Middleware error"}})}else d()}}Object.defineProperty(exports,"t",{value:!0}),exports.createUnsharedMiddleware=createUnsharedMiddleware;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "unshared-clientjs-sdk",
3
- "version": "2.0.0-rc.4",
3
+ "version": "2.0.0-rc.5",
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",