unshared-clientjs-sdk 2.0.0-rc.4 → 2.0.0-rc.6
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 +1 -0
- package/dist/client.js +1 -1
- package/dist/esm/client.d.mts +1 -0
- package/dist/esm/client.mjs +1 -1
- package/dist/esm/index.d.mts +2 -0
- package/dist/esm/index.mjs +1 -1
- package/dist/esm/middleware.d.mts +18 -1
- package/dist/esm/middleware.mjs +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -1
- package/dist/middleware.d.ts +18 -1
- package/dist/middleware.js +1 -1
- package/package.json +1 -1
package/dist/client.d.ts
CHANGED
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;
|
package/dist/esm/client.d.mts
CHANGED
package/dist/esm/client.mjs
CHANGED
|
@@ -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}}
|
package/dist/esm/index.d.mts
CHANGED
|
@@ -1,2 +1,4 @@
|
|
|
1
1
|
export { UnsharedLabsClient } from './client';
|
|
2
|
+
export { createUnsharedMiddleware, assertTrustProxy } from './middleware';
|
|
3
|
+
export type { MiddlewareOptions } from './middleware';
|
|
2
4
|
export type { UnsharedLabsClientConfig, ApiResult, UnsharedLabsError, SubmitFingerprintOptions, SubmitFingerprintResult, ProcessUserEventParams, ProcessUserEventResult, CheckUserResult, TriggerEmailVerificationResult, VerifyResult, } from './client';
|
package/dist/esm/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export{UnsharedLabsClient}from"./client";
|
|
1
|
+
export{UnsharedLabsClient}from"./client";export{createUnsharedMiddleware,assertTrustProxy}from"./middleware";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Request, Response, NextFunction } from 'express';
|
|
1
|
+
import type { Request, Response, NextFunction, Application } from 'express';
|
|
2
2
|
import type { UnsharedLabsClient } from './client';
|
|
3
3
|
export interface MiddlewareOptions {
|
|
4
4
|
/** Override userId extractor. Falls back to req.body.user_id. */
|
|
@@ -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
|
/**
|
|
@@ -23,6 +25,21 @@ export interface MiddlewareOptions {
|
|
|
23
25
|
*/
|
|
24
26
|
corsOrigins?: string | string[];
|
|
25
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Asserts that Express `trust proxy` is configured on the app.
|
|
30
|
+
* Call this once during application startup, before mounting any middleware.
|
|
31
|
+
*
|
|
32
|
+
* Throws synchronously if the setting is missing, killing the process before
|
|
33
|
+
* any requests are served.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```typescript
|
|
37
|
+
* assertTrustProxy(app); // throws at startup if not set
|
|
38
|
+
* app.use(express.json());
|
|
39
|
+
* app.use(createUnsharedMiddleware(client, options));
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export declare function assertTrustProxy(app: Application): void;
|
|
26
43
|
/**
|
|
27
44
|
* Creates an Express middleware that proxies browser fingerprint events to
|
|
28
45
|
* Unshared Labs. Mount this to handle the browser fingerprint route contract (§4 of spec).
|
package/dist/esm/middleware.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export function createUnsharedMiddleware(e,r){const{userIdExtractor:
|
|
1
|
+
export function assertTrustProxy(e){if(!e.get("trust proxy"))throw new Error('[unshared-labs] Express "trust proxy" is not set. Add `app.set("trust proxy", 1)` before calling assertTrustProxy, otherwise req.ip will reflect the proxy\'s IP instead of the real client IP.')}export function createUnsharedMiddleware(e,r){const{userIdExtractor:t,eventTypeExtractor:s,sessionIdExtractor:o,ipAddressExtractor:i,defaultEventType:n="browser_event",routePrefix:c="/unshared",corsOrigins:a}=r??{},d=`${c}/submit-fingerprint-event`,l=a?Array.isArray(a)?a:[a]:null;let u=!1;return async(r,c,a)=>{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===d){const e=r.headers.origin??"",t=l.includes("*");if((t||l.includes(e))&&(c.setHeader("Access-Control-Allow-Origin",t?"*":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===d)try{const a=r.body??{};if(!a.hash||!a.stable_hash||!a.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 d={full_hash:a.hash,fingerprint_id:a.stable_hash,timestamp:a.collected_at,isIncognito:a.is_incognito??!1,components:a.components??{},version:a.version??"unknown"};let l,u,p,h;try{l=(t?t(r):void 0)??a.user_id}catch{l=a.user_id}r.body&&"object"==typeof r.body&&"user_id"in r.body&&delete r.body.user_id;try{u=(s?s(r):void 0)??a.event_type??n}catch{u=a.event_type??n}try{p=(o?o(r):void 0)??r.headers["x-session-id"]?.toString()??a.session_id}catch{p=r.headers["x-session-id"]?.toString()??a.session_id}try{h=(i?i(r):void 0)??r.ip}catch{h=r.ip}const f=await e.submitFingerprintEvent(d,{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 a()}}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,4 @@
|
|
|
1
1
|
export { UnsharedLabsClient } from './client';
|
|
2
|
+
export { createUnsharedMiddleware, assertTrustProxy } from './middleware';
|
|
3
|
+
export type { MiddlewareOptions } from './middleware';
|
|
2
4
|
export type { UnsharedLabsClientConfig, ApiResult, UnsharedLabsError, SubmitFingerprintOptions, SubmitFingerprintResult, ProcessUserEventParams, ProcessUserEventResult, CheckUserResult, TriggerEmailVerificationResult, VerifyResult, } from './client';
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"t",{value:!0}),exports.UnsharedLabsClient=void 0;var client_1=require("./client");Object.defineProperty(exports,"UnsharedLabsClient",{enumerable:!0,get:function(){return client_1.UnsharedLabsClient}});
|
|
1
|
+
"use strict";Object.defineProperty(exports,"t",{value:!0}),exports.assertTrustProxy=exports.createUnsharedMiddleware=exports.UnsharedLabsClient=void 0;var client_1=require("./client");Object.defineProperty(exports,"UnsharedLabsClient",{enumerable:!0,get:function(){return client_1.UnsharedLabsClient}});var middleware_1=require("./middleware");Object.defineProperty(exports,"createUnsharedMiddleware",{enumerable:!0,get:function(){return middleware_1.createUnsharedMiddleware}}),Object.defineProperty(exports,"assertTrustProxy",{enumerable:!0,get:function(){return middleware_1.assertTrustProxy}});
|
package/dist/middleware.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Request, Response, NextFunction } from 'express';
|
|
1
|
+
import type { Request, Response, NextFunction, Application } from 'express';
|
|
2
2
|
import type { UnsharedLabsClient } from './client';
|
|
3
3
|
export interface MiddlewareOptions {
|
|
4
4
|
/** Override userId extractor. Falls back to req.body.user_id. */
|
|
@@ -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
|
/**
|
|
@@ -23,6 +25,21 @@ export interface MiddlewareOptions {
|
|
|
23
25
|
*/
|
|
24
26
|
corsOrigins?: string | string[];
|
|
25
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Asserts that Express `trust proxy` is configured on the app.
|
|
30
|
+
* Call this once during application startup, before mounting any middleware.
|
|
31
|
+
*
|
|
32
|
+
* Throws synchronously if the setting is missing, killing the process before
|
|
33
|
+
* any requests are served.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```typescript
|
|
37
|
+
* assertTrustProxy(app); // throws at startup if not set
|
|
38
|
+
* app.use(express.json());
|
|
39
|
+
* app.use(createUnsharedMiddleware(client, options));
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export declare function assertTrustProxy(app: Application): void;
|
|
26
43
|
/**
|
|
27
44
|
* Creates an Express middleware that proxies browser fingerprint events to
|
|
28
45
|
* Unshared Labs. Mount this to handle the browser fingerprint route contract (§4 of spec).
|
package/dist/middleware.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";function createUnsharedMiddleware(e,r){const{userIdExtractor:s,eventTypeExtractor:t,sessionIdExtractor:o,defaultEventType:n="browser_event",routePrefix:c="/unshared",corsOrigins:
|
|
1
|
+
"use strict";function assertTrustProxy(e){if(!e.get("trust proxy"))throw new Error('[unshared-labs] Express "trust proxy" is not set. Add `app.set("trust proxy", 1)` before calling assertTrustProxy, otherwise req.ip will reflect the proxy\'s IP instead of the real client IP.')}function createUnsharedMiddleware(e,r){const{userIdExtractor:s,eventTypeExtractor:t,sessionIdExtractor:o,ipAddressExtractor:i,defaultEventType:n="browser_event",routePrefix:c="/unshared",corsOrigins:a}=r??{},d=`${c}/submit-fingerprint-event`,l=a?Array.isArray(a)?a:[a]:null;let u=!1;return async(r,c,a)=>{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===d){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===d)try{const a=r.body??{};if(!a.hash||!a.stable_hash||!a.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 d={full_hash:a.hash,fingerprint_id:a.stable_hash,timestamp:a.collected_at,isIncognito:a.is_incognito??!1,components:a.components??{},version:a.version??"unknown"};let l,u,p,h;try{l=(s?s(r):void 0)??a.user_id}catch{l=a.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)??a.event_type??n}catch{u=a.event_type??n}try{p=(o?o(r):void 0)??r.headers["x-session-id"]?.toString()??a.session_id}catch{p=r.headers["x-session-id"]?.toString()??a.session_id}try{h=(i?i(r):void 0)??r.ip}catch{h=r.ip}const f=await e.submitFingerprintEvent(d,{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 a()}}Object.defineProperty(exports,"t",{value:!0}),exports.assertTrustProxy=assertTrustProxy,exports.createUnsharedMiddleware=createUnsharedMiddleware;
|