@intenque/network-sdk 2.2.0-beta.1 → 2.2.0-beta.2
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/CHANGELOG.md +50 -1
- package/dist/capture/index.cjs +1 -1
- package/dist/capture/index.js +1 -1
- package/dist/cli/index.js +9 -6
- package/dist/cli/index.js.map +1 -1
- package/dist/{constants-LVhc0PNb.d.cts → constants-C_6Zxu_k.d.cts} +1 -1
- package/dist/{constants-LVhc0PNb.d.ts → constants-C_6Zxu_k.d.ts} +1 -1
- package/dist/react/index.cjs +2 -2
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +2 -2
- package/dist/react/index.d.ts +2 -2
- package/dist/react/index.js +2 -2
- package/dist/react/index.js.map +1 -1
- package/dist/server/index.cjs +1 -1
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.d.cts +14 -9
- package/dist/server/index.d.ts +14 -9
- package/dist/server/index.js +1 -1
- package/dist/server/index.js.map +1 -1
- package/package.json +1 -1
package/dist/server/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { a as SDK_VERSION } from '../constants-
|
|
1
|
+
export { a as SDK_VERSION } from '../constants-C_6Zxu_k.cjs';
|
|
2
2
|
|
|
3
3
|
type SignInput = {
|
|
4
4
|
secret: string;
|
|
@@ -54,8 +54,10 @@ type ReqLike = {
|
|
|
54
54
|
};
|
|
55
55
|
declare function referralFromRequest(req: ReqLike): ReferralRef | null;
|
|
56
56
|
/**
|
|
57
|
-
* Express middleware alternative: sets `req.inrRef` (`ReferralRef | null`), then removes `inrRef` from
|
|
58
|
-
* body (valid or not), so `new Model(req.body)` and strict
|
|
57
|
+
* Express middleware alternative: sets `req.inrRef` (`ReferralRef | null`), then removes an own `inrRef` from any
|
|
58
|
+
* object body (valid or not; plain objects, arrays and class instances alike), so `new Model(req.body)` and strict
|
|
59
|
+
* validators never see it (2.2 changelog). A body that cannot be changed (frozen, a throwing proxy) keeps it and never
|
|
60
|
+
* throws.
|
|
59
61
|
*/
|
|
60
62
|
declare function referralMiddleware(): (req: ReqLike & {
|
|
61
63
|
inrRef?: ReferralRef | null;
|
|
@@ -65,7 +67,7 @@ declare function referralMiddleware(): (req: ReqLike & {
|
|
|
65
67
|
* Mongo outbox for conversion reports — spec §6.5: collection `inrConversionOutbox` (TTL 14 d, matching the hub's
|
|
66
68
|
* `occurredAt` lower bound), one row per `(eventType, eventId)`, storing the UNSIGNED body (every attempt re-signs
|
|
67
69
|
* with a fresh timestamp and nonce). `nextAttemptAt` is the lease (addendum "Claim and lease"); `unauthorizedSince`
|
|
68
|
-
* carries the 24 h 401
|
|
70
|
+
* carries the 24 h rule of 401, missing config and misrouted answers (403, 404, a 3xx). The site passes its own `mongoose` (7 or 8; the model API used is identical on both); the
|
|
69
71
|
* SDK has no runtime dependency on it. The schema declares no index and has `autoIndex: false`: `ensureOutboxIndexes`
|
|
70
72
|
* creates the three helper indexes itself (addendum "Outbox row v2 and indexes"). The bounded in-memory spill holds
|
|
71
73
|
* reports whose enqueue failed or timed out (addendum "`report()`").
|
|
@@ -85,7 +87,7 @@ type OutboxEntry = {
|
|
|
85
87
|
nextAttemptAt: Date;
|
|
86
88
|
lastStatus: number | null;
|
|
87
89
|
lastError: OutboxError | null;
|
|
88
|
-
/** Set by the first 401
|
|
90
|
+
/** Set by the first 401, missing config or misrouted answer (403, 404, a 3xx); cleared by any other. */
|
|
89
91
|
unauthorizedSince: Date | null;
|
|
90
92
|
createdAt: Date;
|
|
91
93
|
};
|
|
@@ -142,8 +144,8 @@ type Outbox = {
|
|
|
142
144
|
remove: (key: OutboxKey) => Promise<void>;
|
|
143
145
|
/** A retried attempt. Never creates a row. */
|
|
144
146
|
reschedule: (key: OutboxKey, patch: ReschedulePatch) => Promise<void>;
|
|
145
|
-
/** An in-process chain renews its row's lease before each retry. */
|
|
146
|
-
renew: (key: OutboxKey, until: Date) => Promise<
|
|
147
|
+
/** An in-process chain renews its row's lease before each retry; false when no such row exists (purged). */
|
|
148
|
+
renew: (key: OutboxKey, until: Date) => Promise<boolean>;
|
|
147
149
|
/** The drain claim: the most overdue due row, its lease moved to `until` and `attempts + 1`; null when none. */
|
|
148
150
|
claim: (now: Date, until: Date) => Promise<OutboxEntry | null>;
|
|
149
151
|
/** `forget()`: deletes every row whose body names the click id (as `clickId` or `firstClickId`). */
|
|
@@ -170,10 +172,13 @@ type Logger = {
|
|
|
170
172
|
warn: (message: string, meta?: Record<string, unknown>) => void;
|
|
171
173
|
info?: (message: string, meta?: Record<string, unknown>) => void;
|
|
172
174
|
};
|
|
173
|
-
type Classification = 'success' | 'terminal' | 'retryable' | 'skew' | 'unauthorized';
|
|
175
|
+
type Classification = 'success' | 'terminal' | 'retryable' | 'skew' | 'unauthorized' | 'misrouted';
|
|
174
176
|
/**
|
|
175
177
|
* The §6.5 matrix (status first, then code; a missing body is classified by status). Status 0 is a transport failure
|
|
176
|
-
* (network error, timeout).
|
|
178
|
+
* (network error, timeout). 403, 404 and any 3xx that reaches it are `misrouted` (a proxy, WAF or maintenance page in
|
|
179
|
+
* the way, a missing route prefix): retried in process, then every 10 min, dropped 24 h after the first like a 401.
|
|
180
|
+
* A real redirect never gets here: fetch with `redirect: 'error'` fails 301/302/303/307/308 (with or without
|
|
181
|
+
* `Location`) as a transport error (status 0, retryable), so only 300, 304 and the like are classified.
|
|
177
182
|
*/
|
|
178
183
|
declare function classifyResponse(status: number, code: string | null): Classification;
|
|
179
184
|
|
package/dist/server/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { a as SDK_VERSION } from '../constants-
|
|
1
|
+
export { a as SDK_VERSION } from '../constants-C_6Zxu_k.js';
|
|
2
2
|
|
|
3
3
|
type SignInput = {
|
|
4
4
|
secret: string;
|
|
@@ -54,8 +54,10 @@ type ReqLike = {
|
|
|
54
54
|
};
|
|
55
55
|
declare function referralFromRequest(req: ReqLike): ReferralRef | null;
|
|
56
56
|
/**
|
|
57
|
-
* Express middleware alternative: sets `req.inrRef` (`ReferralRef | null`), then removes `inrRef` from
|
|
58
|
-
* body (valid or not), so `new Model(req.body)` and strict
|
|
57
|
+
* Express middleware alternative: sets `req.inrRef` (`ReferralRef | null`), then removes an own `inrRef` from any
|
|
58
|
+
* object body (valid or not; plain objects, arrays and class instances alike), so `new Model(req.body)` and strict
|
|
59
|
+
* validators never see it (2.2 changelog). A body that cannot be changed (frozen, a throwing proxy) keeps it and never
|
|
60
|
+
* throws.
|
|
59
61
|
*/
|
|
60
62
|
declare function referralMiddleware(): (req: ReqLike & {
|
|
61
63
|
inrRef?: ReferralRef | null;
|
|
@@ -65,7 +67,7 @@ declare function referralMiddleware(): (req: ReqLike & {
|
|
|
65
67
|
* Mongo outbox for conversion reports — spec §6.5: collection `inrConversionOutbox` (TTL 14 d, matching the hub's
|
|
66
68
|
* `occurredAt` lower bound), one row per `(eventType, eventId)`, storing the UNSIGNED body (every attempt re-signs
|
|
67
69
|
* with a fresh timestamp and nonce). `nextAttemptAt` is the lease (addendum "Claim and lease"); `unauthorizedSince`
|
|
68
|
-
* carries the 24 h 401
|
|
70
|
+
* carries the 24 h rule of 401, missing config and misrouted answers (403, 404, a 3xx). The site passes its own `mongoose` (7 or 8; the model API used is identical on both); the
|
|
69
71
|
* SDK has no runtime dependency on it. The schema declares no index and has `autoIndex: false`: `ensureOutboxIndexes`
|
|
70
72
|
* creates the three helper indexes itself (addendum "Outbox row v2 and indexes"). The bounded in-memory spill holds
|
|
71
73
|
* reports whose enqueue failed or timed out (addendum "`report()`").
|
|
@@ -85,7 +87,7 @@ type OutboxEntry = {
|
|
|
85
87
|
nextAttemptAt: Date;
|
|
86
88
|
lastStatus: number | null;
|
|
87
89
|
lastError: OutboxError | null;
|
|
88
|
-
/** Set by the first 401
|
|
90
|
+
/** Set by the first 401, missing config or misrouted answer (403, 404, a 3xx); cleared by any other. */
|
|
89
91
|
unauthorizedSince: Date | null;
|
|
90
92
|
createdAt: Date;
|
|
91
93
|
};
|
|
@@ -142,8 +144,8 @@ type Outbox = {
|
|
|
142
144
|
remove: (key: OutboxKey) => Promise<void>;
|
|
143
145
|
/** A retried attempt. Never creates a row. */
|
|
144
146
|
reschedule: (key: OutboxKey, patch: ReschedulePatch) => Promise<void>;
|
|
145
|
-
/** An in-process chain renews its row's lease before each retry. */
|
|
146
|
-
renew: (key: OutboxKey, until: Date) => Promise<
|
|
147
|
+
/** An in-process chain renews its row's lease before each retry; false when no such row exists (purged). */
|
|
148
|
+
renew: (key: OutboxKey, until: Date) => Promise<boolean>;
|
|
147
149
|
/** The drain claim: the most overdue due row, its lease moved to `until` and `attempts + 1`; null when none. */
|
|
148
150
|
claim: (now: Date, until: Date) => Promise<OutboxEntry | null>;
|
|
149
151
|
/** `forget()`: deletes every row whose body names the click id (as `clickId` or `firstClickId`). */
|
|
@@ -170,10 +172,13 @@ type Logger = {
|
|
|
170
172
|
warn: (message: string, meta?: Record<string, unknown>) => void;
|
|
171
173
|
info?: (message: string, meta?: Record<string, unknown>) => void;
|
|
172
174
|
};
|
|
173
|
-
type Classification = 'success' | 'terminal' | 'retryable' | 'skew' | 'unauthorized';
|
|
175
|
+
type Classification = 'success' | 'terminal' | 'retryable' | 'skew' | 'unauthorized' | 'misrouted';
|
|
174
176
|
/**
|
|
175
177
|
* The §6.5 matrix (status first, then code; a missing body is classified by status). Status 0 is a transport failure
|
|
176
|
-
* (network error, timeout).
|
|
178
|
+
* (network error, timeout). 403, 404 and any 3xx that reaches it are `misrouted` (a proxy, WAF or maintenance page in
|
|
179
|
+
* the way, a missing route prefix): retried in process, then every 10 min, dropped 24 h after the first like a 401.
|
|
180
|
+
* A real redirect never gets here: fetch with `redirect: 'error'` fails 301/302/303/307/308 (with or without
|
|
181
|
+
* `Location`) as a transport error (status 0, retryable), so only 300, 304 and the like are classified.
|
|
177
182
|
*/
|
|
178
183
|
declare function classifyResponse(status: number, code: string | null): Classification;
|
|
179
184
|
|
package/dist/server/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {createHash,randomUUID,createHmac,timingSafeEqual}from'crypto';import {isIP}from'net';function ye(e){return createHash("sha256").update(e).digest("hex")}function $(e,t,n,r,o,s,u){return `${e}.${t}.${n}.${r}.${o.toUpperCase()}.${s}.${ye(u)}`}function z(e){if(!e.secret)throw new Error("signRequest: secret is required");let t=e.timestamp??Math.floor(Date.now()/1e3),n=e.nonce??randomUUID(),o=`v1=${createHmac("sha256",e.secret).update($(t,n,e.siteKey,e.keyId,e.method,e.path,e.body),"utf8").digest("hex")}`;return {headers:{"Content-Type":"application/json","X-INR-Site":e.siteKey,"X-INR-Key-Id":e.keyId,"X-INR-Timestamp":String(t),"X-INR-Nonce":n,"X-INR-Signature":o},timestamp:t,nonce:n,signature:o}}function ze(e,t,n){if(!/^v1=[0-9a-f]{64}$/.test(n))return false;let{timestamp:r,nonce:o,siteKey:s,keyId:u,method:d,path:a,body:y}=t,g=createHmac("sha256",e).update($(r,o,s,u,d,a,y),"utf8").digest("hex");return timingSafeEqual(Buffer.from(g,"hex"),Buffer.from(n.slice(3),"hex"))}var k="2.2.0-beta.1";var w=/^[A-Za-z0-9_-]{32}$/;var U=e=>typeof e=="string"&&w.test(e)?e:null,me=e=>e==="session"||e==="persistent"?e:null;function Y(e,t){if(typeof e.get=="function")return e.get(t);let n=e.headers;if(!n)return;let r=n[t]??n[t.toLowerCase()];return Array.isArray(r)?r[0]:r}function ge(e){try{let t=e.body,n=t&&typeof t=="object"?t.inrRef:void 0;if(n&&typeof n=="object"){let s=n,u=U(s.clickId),d=me(s.tokenScope);if(u&&d){let a=s.firstClickId;return {clickId:u,firstClickId:a==null?null:U(a),tokenScope:d}}}let r=U(Y(e,"X-INR-Ref")),o=me(Y(e,"X-INR-Ref-Scope"));if(r&&o){let s=Y(e,"X-INR-Ref-First");return {clickId:r,firstClickId:s==null?null:U(s),tokenScope:o}}return null}catch{return null}}function Ye(){return (e,t,n)=>{e.inrRef=ge(e);let r=e.body;if(r&&typeof r=="object"){let o=Object.getPrototypeOf(r);(o===Object.prototype||o===null)&&Object.hasOwn(r,"inrRef")&&delete r.inrRef;}n();}}var K="inrConversionOutbox",qe=1209600;function Ee(){return {eventType:{type:String,required:true},eventId:{type:String,required:true},body:{type:Object,required:true},attempts:{type:Number,default:0},nextAttemptAt:{type:Date,default:Date.now},lastStatus:{type:Number,default:null},lastError:{type:Object,default:null},unauthorizedSince:{type:Date},createdAt:{type:Date,default:Date.now}}}var xe=[[{createdAt:1},{expireAfterSeconds:1209600}],[{nextAttemptAt:1},{}],[{eventType:1,eventId:1},{unique:true}]];function j(e,t=K){let n=`InrOutbox_${t}`,r=e.models[n];if(r)return r;let o=new e.Schema(Ee(),{versionKey:false,autoIndex:false});return e.model(n,o,t)}var _e=new WeakSet,be=new WeakSet,q=new WeakMap;function H(e,t){if(_e.has(e))return Promise.resolve();let n=q.get(e);return n||(n=(async()=>{let r=false;for(let[o,s]of xe)await Promise.resolve().then(()=>e.collection.createIndex(o,s)).catch(()=>r=true);r?be.has(e)||(be.add(e),t("INR_OUTBOX_INDEX_FAILED")):_e.add(e);})().finally(()=>q.delete(e)),q.set(e,n)),n}var M=e=>"exec"in e&&typeof e.exec=="function"?e.exec():e,V=e=>({eventType:e.eventType,eventId:e.eventId});function Ve(e){let t=e.lastError;return {...e,lastError:typeof t=="string"?{status:0,code:t}:t??null,unauthorizedSince:e.unauthorizedSince??null}}function G(e){return {async enqueue(t){let{eventType:n,eventId:r,unauthorizedSince:o,...s}=t,u=o?{...s,unauthorizedSince:o}:s,d=await M(e.updateOne({eventType:n,eventId:r},{$setOnInsert:u},{upsert:true}));return !(Number(d?.matchedCount)>0)},async remove(t){await M(e.deleteOne(V(t)));},async reschedule(t,n){let r={$set:{nextAttemptAt:n.nextAttemptAt,lastStatus:n.lastStatus,lastError:n.lastError},...n.attempts>0?{$inc:{attempts:n.attempts}}:{},...n.unauthorizedAt?{$min:{unauthorizedSince:n.unauthorizedAt}}:{$unset:{unauthorizedSince:1}}};await M(e.updateOne(V(t),r));},async renew(t,n){await M(e.updateOne(V(t),{$set:{nextAttemptAt:n}}));},async claim(t,n){let r=await M(e.findOneAndUpdate({nextAttemptAt:{$lte:t}},{$set:{nextAttemptAt:n},$inc:{attempts:1}},{sort:{nextAttemptAt:1},new:true,lean:true}));return r?Ve(r):null},async purge(t){await M(e.deleteMany({$or:[{"body.clickId":t},{"body.firstClickId":t}]}));},pending:()=>M(e.countDocuments({}))}}var W=(e,t)=>e.body.clickId===t||e.body.firstClickId===t,Re=(e,t)=>e.eventType===t.eventType&&e.eventId===t.eventId;function Z(e,t){e.nextAttemptAt=t.nextAttemptAt,e.lastStatus=t.lastStatus,e.lastError=t.lastError,e.attempts+=t.attempts,e.unauthorizedSince=t.unauthorizedAt?new Date(Math.min(e.unauthorizedSince?.getTime()??1/0,t.unauthorizedAt.getTime())):null;}function J(){let e=[],t=n=>e.find(r=>Re(r,n));return {entries:e,async enqueue(n){return t(n)?false:(e.push({...n}),true)},async remove(n){let r=e.findIndex(o=>Re(o,n));r>=0&&e.splice(r,1);},async reschedule(n,r){let o=t(n);o&&Z(o,r);},async renew(n,r){let o=t(n);o&&(o.nextAttemptAt=r);},async claim(n,r){let o;for(let s of e)s.nextAttemptAt<=n&&(!o||s.nextAttemptAt<o.nextAttemptAt)&&(o=s);return o?(o.nextAttemptAt=r,o.attempts+=1,{...o}):null},async purge(n){for(let r=e.length-1;r>=0;r--)W(e[r],n)&&e.splice(r,1);},pending:async()=>e.length}}function Se(e=1e3){let t=new Map,n=r=>`${r.eventType}\0${r.eventId}`;return {add(r){if(t.has(n(r))||(t.set(n(r),r),t.size<=e))return null;let[o,s]=t.entries().next().value;return t.delete(o),s},has:r=>t.has(n(r)),get:r=>t.get(n(r)),delete:r=>{t.delete(n(r));},entries:()=>[...t.values()],get size(){return t.size}}}async function Ie(e,t){let n=0;for(let r of e.entries())await t.enqueue(r),e.delete(r),n++;return n}var Oe=["booking_created","booking_paid","contact_submitted","quote_requested","call_clicked","signup","purchase","booking_cancelled","booking_refunded","booking_no_show","chargeback","fee_charged"],Te={booking_cancelled:["booking_created"],booking_no_show:["booking_created"],booking_refunded:["booking_paid"],chargeback:["booking_paid","purchase"]},B={...Te,fee_charged:["booking_no_show","booking_cancelled"]},je=Object.freeze(Object.keys(Te)),Q={booking_created:{value:"optional",fee:"null"},booking_paid:{value:"required",fee:"null"},contact_submitted:{value:"null",fee:"null"},quote_requested:{value:"optional",fee:"null"},call_clicked:{value:"null",fee:"null"},signup:{value:"optional",fee:"null"},purchase:{value:"optional",fee:"null"},booking_cancelled:{value:"null",fee:"optional"},booking_refunded:{value:"positive",fee:"null"},booking_no_show:{value:"null",fee:"optional"},chargeback:{value:"positive",fee:"null"},fee_charged:{value:"null",fee:"positive"}},He=1e9,ee=/^(?=.{1,64}$)(?!erased:)[a-z][a-z0-9_]{0,31}:[A-Za-z0-9_-]{1,62}$/,ve=/^[A-Z]{3}$/,te=/^inrs1_[A-Za-z0-9_-]{43}$/,ne=/^[0-9a-f]{8}$/,re=/^(?=.{1,48}$)[a-z0-9]+(-[a-z0-9]+)*$/;var N={conversion:"/v1/conversions",landing:"/v1/landings",forget:"/v1/referrals/"},he=e=>Object.hasOwn(B,e);function oe(e,t){return t===null?e==="null"||e==="optional":Number.isInteger(t)&&t>=(e==="positive"?1:0)&&t<=He?e!=="null":false}var E=1e3,F=[2*E,8*E,30*E],ie=30*E,ue=3600*E,Ae=60*E,Ge=600*E,We=24*3600*E,P=150*E,Ze=3e3,Je=500,Qe=3e4,et=8192,tt=2e3,nt=24*3600*E;function ae(e){if(!e)return null;let t=e.endsWith("/")?e.slice(0,-1):e;if(!/^[a-z][a-z0-9+.-]*:\/\/[^/?#]+$/i.test(t))return null;let n;try{n=new URL(t);}catch{return null}return n.pathname!=="/"||n.username||n.password?null:n.protocol==="https:"||n.protocol==="http:"&&["localhost","127.0.0.1","[::1]"].includes(n.hostname)?n.origin:null}function ke(e,t){let n=t?.trim(),r=e??(n?Number(n):void 0),o=r===void 0||!Number.isFinite(r)?Ze:r;return Math.min(Qe,Math.max(Je,o))}function Me(e,t){return e>=200&&e<300?"success":e===409?t==="NONCE_REUSED"?"success":"terminal":e===400?t==="TIMESTAMP_SKEW"?"skew":"terminal":e===401?"unauthorized":e===0||e===408||e===425||e===429||e>=500&&e<600?"retryable":"terminal"}function rt(e,t){if(!e)return null;let n=e.trim();if(/^\d+$/.test(n))return Number(n)*E;let r=Date.parse(n);return Number.isNaN(r)?null:Math.max(0,r-t)}var se=0;function ot(e,t,n){let r=e?Date.parse(e):NaN;if(Number.isNaN(r))return null;let o=Math.round(r+500-(t+n)/2);return Math.abs(o)<=nt?o:null}async function st(e,t){let n=e.body?.getReader();if(!n)return "";let r=new TextDecoder,o="";for(let s=0;s<t;){let{done:u,value:d}=await n.read();if(u)break;o+=r.decode(d.subarray(0,t-s),{stream:true}),s+=d.byteLength;}return n.cancel().catch(()=>{}),o}function it(e){try{let t=JSON.parse(e),n=t?.error?.code??t?.code??null;return typeof n=="string"?n:null}catch{return null}}async function we(e,t,n){let r=Math.floor((Date.now()+se)/1e3),{headers:o}=z({...e,...t,timestamp:r}),s=new AbortController,u=setTimeout(()=>s.abort(),n);u.unref?.();let d=Date.now();try{let a=await fetch(e.hubUrl+t.path,{method:t.method,headers:{...o,"User-Agent":`intenque-network-sdk/${k}`},body:t.body,redirect:"error",signal:s.signal}),y=Date.now(),g=await st(a,et).catch(()=>"");return {status:a.status,code:it(g),retryAfterMs:rt(a.headers.get("retry-after"),y),date:a.headers.get("date"),tSend:d,tRecv:y}}catch{let a=Date.now();return {status:0,code:s.signal.aborted?"TIMEOUT":"NETWORK",retryAfterMs:null,date:null,tSend:d,tRecv:a}}finally{clearTimeout(u);}}function le(e){let t=Math.min(60*E*2**Math.max(0,e-4),1800*E);return Math.round(t*(.8+Math.random()*.4))}var L=e=>new Promise(t=>{setTimeout(t,e).unref?.();});function Ne(e){let{outbox:t,spill:n,logger:r}=e,o=new Set,s=i=>{o.has(i)||(o.add(i),r.warn(i));},u=i=>{let l=Me(i.status,i.code);return l==="skew"?{kind:l,status:400,code:"TIMESTAMP_SKEW",offsetMs:ot(i.date,i.tSend,i.tRecv)}:{kind:l,status:i.status,code:i.code,retryAfterMs:i.retryAfterMs}};async function d(i){let l=e.config();if(!l)return {kind:"unauthorized",status:0,code:"INR_ENV_MISSING",retryAfterMs:null};if(typeof globalThis.fetch!="function")return s("INR_NO_FETCH"),{kind:"nofetch"};let f=u(await we(l,i,e.timeoutMs));return f.kind==="skew"&&f.offsetMs!==null&&(se=f.offsetMs,f=u(await we(l,i,e.timeoutMs)),f.kind==="skew"&&f.offsetMs!==null&&(se=f.offsetMs)),f}let a=i=>d({method:"POST",path:N.conversion,body:JSON.stringify(i.body)}),y=i=>{let l;return Promise.race([Promise.resolve().then(i),new Promise(f=>{l=setTimeout(f,tt);})]).then(()=>clearTimeout(l),()=>clearTimeout(l))},g=async i=>{n.delete(i),await y(()=>t.remove(i));},I=async(i,l,f)=>(await g(i),r.warn("inr_outbox_dropped_total",{eventType:i.eventType,eventId:i.eventId,status:l.status,code:l.code,reason:f}),"dropped"),T=async(i,l)=>{let f=n.get(i);f&&Z(f,l),await y(()=>t.reschedule(i,l));};async function b(i,l,f,x=Ae){if(l.kind==="nofetch")return null;let S=Date.now(),O={status:l.status,code:l.code},A=async c=>(await T(i,c),"retried");switch(l.kind){case "success":return await g(i),"sent";case "terminal":return I(i,O,"terminal");case "unauthorized":{let c=i.unauthorizedSince?.getTime();return c!==void 0&&S-c>=We?(r.warn("INR_UNAUTHORIZED",{eventType:i.eventType,eventId:i.eventId,...O}),I(i,O,"unauthorized_24h")):A({nextAttemptAt:new Date(S+Ge),lastStatus:l.status,lastError:O,attempts:f,unauthorizedAt:new Date(S)})}case "skew":return r.warn("INR_CLOCK_SKEW",{offsetMs:l.offsetMs}),A({nextAttemptAt:new Date(S+le(i.attempts+f)),lastStatus:400,lastError:O,attempts:f,unauthorizedAt:null});case "retryable":return A({nextAttemptAt:new Date(S+x),lastStatus:l.status,lastError:O,attempts:f,unauthorizedAt:null})}}async function v(i){let l=F;for(let f=0;;f++){let x=await a(i);if(x.kind!=="retryable")return b(i,x,f+1);let S=x.retryAfterMs===null?null:Math.min(x.retryAfterMs,ue);if(f===l.length)return b(i,x,f+1,Math.max(Ae,S??0));let O=Math.max(l[f],S??0);if(O>ie)return b(i,x,f+1,S);await L(O);let A=new Date(Date.now()+P),c=n.get(i);c&&(c.nextAttemptAt=A),await y(()=>t.renew(i,A));}}return {request:d,attempt:a,settle:b,drop:I,chain:v}}var D=1e3,ut=10*D,at=60*D,De=10*D,lt=50,ct=14*86400*D-3600*D,dt=5*D;function Ce(e){let{delivery:t,outbox:n,spill:r}=e,o,s=null,u=false;async function d(){let g={sent:0,retried:0,dropped:0};try{e.onTick?.(),await Ie(r,n);for(let I=0;I<lt;I++){let T=Date.now(),b=await n.claim(new Date(T),new Date(T+P));if(!b)break;if(T-new Date(b.createdAt).getTime()>=ct){await t.drop(b,b.lastError??{status:0,code:null},"expired"),g.dropped++;continue}let v=await t.attempt(b),i="retryAfterMs"in v?v.retryAfterMs??0:0,l=await t.settle(b,v,0,Math.max(le(b.attempts),Math.min(i,ue)));if(l&&g[l]++,!l||"status"in v&&(v.status===429||v.status===503))break}}catch{e.unavailable();}return g}function a(){return s??=d().finally(()=>{s=null;}),s}function y(g){u||(o=setTimeout(()=>{a().then(()=>y(at+(Math.random()*2-1)*De));},g),o.unref?.());}return {start:()=>y(ut+Math.random()*De),drainNow:a,async close(g){u=true,clearTimeout(o),await Promise.race([Promise.allSettled([s,g()]),L(dt)]);}}}var ft=6e4,X=1024,yt=10,mt=300,gt=30*864e5,_t=512,bt=/^https?:\/\/[^/\s]{1,253}$/;function Rt(e){let t=e.body;if(typeof t=="string"||t instanceof Uint8Array){let n=Buffer.from(t);return Promise.resolve(n.length>X?null:n.toString("utf8"))}return !e.readable||typeof e.on!="function"?Promise.resolve(null):new Promise(n=>{let r=[],o=0;e.on("data",s=>{o+=s.length,o<=X&&r.push(s);}),e.on("end",()=>n(o>X?null:Buffer.concat(r).toString("utf8"))),e.on("error",()=>n(null));})}async function Et(e){let t=e.body;if(t&&typeof t=="object"&&!(t instanceof Uint8Array)&&Object.keys(t).length>0)return JSON.stringify(t).length>X?null:t;let n=await Rt(e);try{let r=n===null?null:JSON.parse(n);return r&&typeof r=="object"?r:null}catch{return null}}var Pe=(e,t)=>{let n=e.headers?.[t];return typeof n=="string"?n:null};function xt(e,t,n){let{c:r,scope:o,ph:s,d:u}=e;if(typeof r!="string"||!w.test(r)||o!=="session"&&o!=="persistent"||s!==void 0&&!(typeof s=="string"&&/^[0-9a-f]{16}$/.test(s))||u!==void 0&&!(Number.isInteger(u)&&u>=0&&u<=gt))return null;let d=Pe(n,"origin");return {v:1,clickId:r,scope:o,pathHash:s,d:u,visitorIp:t,visitorUa:(Pe(n,"user-agent")??"").slice(0,_t),visitorOrigin:d!==null&&bt.test(d)?d:null,sdk:`server/${k}`}}var Le=-1,ce=0,de=new Map;function St(e){let t=Math.floor(Date.now()/ft);t!==Le&&(Le=t,ce=0,de.clear());let n=de.get(e)??0;return ce>=mt||n>=yt?false:(de.set(e,n+1),ce++,true)}function Ue(e){let t=false;return (n={})=>async(r,o)=>{let s=null;try{let u=await Et(r),d=!!r.app?.get("trust proxy");!d&&!t&&(t=!0,e.warn("INR_RELAY_TRUST_PROXY"));let a=d?n.visitorIp?.(r)??r.ip:void 0;e.enabled&&u&&typeof a=="string"&&isIP(a)&&(s=xt(u,a,r),s&&!St(a)&&(s=null));}catch{s=null;}try{o.status(204).end();}catch{}s&&e.forward({method:"POST",path:N.landing,body:JSON.stringify(s)});}}var It=/^\d{4}-\d\d-\d\dT\d\d:\d\d(:\d\d(\.\d+)?)?(Z|[+-]\d\d:\d\d)$/,Be=2e3,Ot=6e4;function pe(e){if(!e||typeof e!="object")return {error:"event"};let t=e.eventType;if(typeof t!="string"||!Object.hasOwn(Q,t))return {error:"eventType"};if(typeof e.eventId!="string"||!ee.test(e.eventId))return {error:"eventId"};if(typeof e.clickId!="string"||!w.test(e.clickId))return {error:"clickId"};let n=e.firstClickId??null;if(n!==null&&!(typeof n=="string"&&w.test(n)))return {error:"firstClickId"};if(e.tokenScope!=="session"&&e.tokenScope!=="persistent")return {error:"tokenScope"};let r=e.occurredAt instanceof Date?e.occurredAt:typeof e.occurredAt=="string"&&It.test(e.occurredAt)?new Date(e.occurredAt):new Date(NaN),o=Number.isNaN(r.getTime())?"":r.toISOString();if(!/^\d{4}-/.test(o))return {error:"occurredAt"};let s=e.valueCents??null,u=e.feeCents??null,d=Q[t];if(!oe(d.value,s))return {error:"valueCents"};if(!oe(d.fee,u))return {error:"feeCents"};let a=null;if(e.currency!=null&&(a=typeof e.currency=="string"?e.currency.toUpperCase():"",!ve.test(a)))return {error:"currency"};if(a===null&&(s!==null||u!==null))return {error:"currency"};let y=e.reverses??null;if(he(t)){let g=B[t];if(y===null||typeof y!="object"||!g.includes(y.eventType)||typeof y.eventId!="string"||!ee.test(y.eventId))return {error:"reverses"}}else if(y!==null)return {error:"reverses"};return {v:1,eventType:t,eventId:e.eventId,clickId:e.clickId,firstClickId:n===e.clickId?null:n,tokenScope:e.tokenScope,occurredAt:o,valueCents:s,feeCents:u,currency:a,reverses:y?{eventType:y.eventType,eventId:y.eventId}:null,sdk:`server/${k}`}}var Tt=["INR_HUB_URL","INR_SITE_KEY","INR_KEY_ID","INR_SITE_SECRET"],Ke={INR_HUB_URL:e=>ae(e)!==null,INR_SITE_KEY:e=>re.test(e),INR_KEY_ID:e=>ne.test(e),INR_SITE_SECRET:e=>te.test(e)};function vt(e=process.env,t={}){let n=Tt.flatMap(s=>{let u=e[s];return u?Ke[s]&&!Ke[s](u)?[`${s} (invalid)`]:[]:[s]});if(n.length===0)return [];let r=t.production??e.NODE_ENV==="production",o=`@intenque/network-sdk/server: missing or invalid env ${n.join(", ")}`;if(!r||e.INR_STRICT_ENV==="true")throw new Error(o);return (t.logger??console).warn(o),n}function ht(e={}){let t=e.logger??{warn:(c,p)=>console.warn(c,p??"")},n={warn:(c,p)=>{try{t.warn(c,p);}catch{}}},r=e.enabled!==false,o=()=>typeof process>"u"?{}:process.env,s=e.outbox?.mongoose?j(e.outbox.mongoose,e.outbox.collection??(o().INR_OUTBOX_COLLECTION||K)):null,u=()=>{s&&r&&H(s,n.warn);};u();let d=s?G(s):J(),a=Se(),y=false,I=Ne({config:()=>{let c=o(),p=e.hubUrl??c.INR_HUB_URL,R=ae(p);p&&!R&&!y&&(y=true,n.warn("INR_HUB_URL_INVALID"));let m=e.siteKey??c.INR_SITE_KEY??"",_=c.INR_KEY_ID||e.keyId||"",h=c.INR_SITE_SECRET||e.secret||"";return !R||!re.test(m)||!ne.test(_)||!te.test(h)?null:{hubUrl:R,siteKey:m,keyId:_,secret:h}},timeoutMs:ke(e.timeoutMs,o().INR_TIMEOUT_MS),outbox:d,spill:a,logger:n}),T=new Set,b=c=>{let p=c.catch(R=>{n.warn("inr.report attempt failed",{error:R instanceof Error?R.message:String(R)});}).finally(()=>T.delete(p));T.add(p);},v=c=>b(I.chain(c)),i=async()=>{for(;T.size>0;)await Promise.allSettled([...T]);},l=-1/0,f=()=>{Date.now()-l<Ot||(l=Date.now(),n.warn("INR_OUTBOX_UNAVAILABLE"));},x=Ce({delivery:I,outbox:d,spill:a,unavailable:f,onTick:u});r&&x.start();let S=async c=>{let p,R=new Promise(m=>{p=setTimeout(()=>m(void 0),Be);});try{return await Promise.race([Promise.resolve().then(c).catch(()=>{}),R])}finally{clearTimeout(p);}};async function O(c){if(typeof c!="string"||!w.test(c))return false;let p=(_,h)=>(n.warn("INR_FORGET_FAILED",{status:_,code:h}),false);for(let _ of a.entries())W(_,c)&&a.delete(_);if(!await S(()=>d.purge(c).then(()=>true)))return p(0,"INR_OUTBOX_UNAVAILABLE");let R={method:"DELETE",path:N.forget+c,body:""},m;for(let _=0;;_++){if(m=await I.request(R),"status"in m&&m.status>=200&&m.status<300)return true;if(m.kind!=="retryable"||_===F.length)break;let h=m.retryAfterMs??0;if(h>ie)break;await L(Math.max(F[_],h));}return m.kind==="nofetch"?p(0,"INR_NO_FETCH"):p(m.status,m.code)}let A=async c=>{let p=await S(()=>d.enqueue(c));return p===void 0?"failed":p?"inserted":"exists"};return {outbox:d,spill:a,bodyFor:pe,async report(c){try{let p=pe(c);if("error"in p)return n.warn("inr.report dropped: invalid event",{error:p.error}),{status:"invalid",error:p.error};if(!r)return {status:"disabled",body:p};let R=Date.now(),m={eventType:p.eventType,eventId:p.eventId,body:p,attempts:0,nextAttemptAt:new Date(R+P),lastStatus:null,lastError:null,unauthorizedSince:null,createdAt:new Date(R)},_=await A(m),h=_==="failed"?"spilled":"queued";if(_==="failed"&&f(),_==="exists"||a.has(m))return {status:h,body:p};if(_==="failed"){let C=a.add(m);C&&n.warn("inr_outbox_dropped_total",{eventType:C.eventType,eventId:C.eventId,status:C.lastError?.status??null,code:C.lastError?.code??null,reason:"memory_full"});}return v(m),{status:h,body:p}}catch(p){return n.warn("inr.report failed",{error:p instanceof Error?p.message:String(p)}),{status:"invalid",error:"internal"}}},idle:i,forget:O,drainNow:x.drainNow,close:()=>x.close(i),landingRelay:Ue({enabled:r,forward:c=>b(I.request(c)),warn:n.warn})}}export{Oe as CONVERSION_TYPES,Be as ENQUEUE_TIMEOUT_MS,B as LINK_TARGETS,K as OUTBOX_DEFAULT_COLLECTION,xe as OUTBOX_INDEXES,qe as OUTBOX_TTL_SECONDS,je as REVERSAL_TYPES,k as SDK_VERSION,pe as buildConversionBody,Me as classifyResponse,ht as createConversionReporter,J as createMemoryOutbox,G as createOutbox,H as ensureOutboxIndexes,$ as hmacBase,j as outboxModel,Ee as outboxSchemaDefinition,ge as referralFromRequest,Ye as referralMiddleware,ye as sha256Hex,z as signRequest,vt as verifyEnv,ze as verifySignature};//# sourceMappingURL=index.js.map
|
|
1
|
+
import {createHash,randomUUID,createHmac,timingSafeEqual}from'crypto';import {isIP}from'net';function me(e){return createHash("sha256").update(e).digest("hex")}function $(e,t,n,r,o,s,u){return `${e}.${t}.${n}.${r}.${o.toUpperCase()}.${s}.${me(u)}`}function Y(e){if(!e.secret)throw new Error("signRequest: secret is required");let t=e.timestamp??Math.floor(Date.now()/1e3),n=e.nonce??randomUUID(),o=`v1=${createHmac("sha256",e.secret).update($(t,n,e.siteKey,e.keyId,e.method,e.path,e.body),"utf8").digest("hex")}`;return {headers:{"Content-Type":"application/json","X-INR-Site":e.siteKey,"X-INR-Key-Id":e.keyId,"X-INR-Timestamp":String(t),"X-INR-Nonce":n,"X-INR-Signature":o},timestamp:t,nonce:n,signature:o}}function Ye(e,t,n){if(!/^v1=[0-9a-f]{64}$/.test(n))return false;let{timestamp:r,nonce:o,siteKey:s,keyId:u,method:c,path:l,body:y}=t,m=createHmac("sha256",e).update($(r,o,s,u,c,l,y),"utf8").digest("hex");return timingSafeEqual(Buffer.from(m,"hex"),Buffer.from(n.slice(3),"hex"))}var k="2.2.0-beta.2";var A=/^[A-Za-z0-9_-]{32}$/;var K=e=>typeof e=="string"&&A.test(e)?e:null,ge=e=>e==="session"||e==="persistent"?e:null;function z(e,t){if(typeof e.get=="function")return e.get(t);let n=e.headers;if(!n)return;let r=n[t]??n[t.toLowerCase()];return Array.isArray(r)?r[0]:r}function _e(e){try{let t=e.body,n=t&&typeof t=="object"?t.inrRef:void 0;if(n&&typeof n=="object"){let s=n,u=K(s.clickId),c=ge(s.tokenScope);if(u&&c){let l=s.firstClickId;return {clickId:u,firstClickId:l==null?null:K(l),tokenScope:c}}}let r=K(z(e,"X-INR-Ref")),o=ge(z(e,"X-INR-Ref-Scope"));if(r&&o){let s=z(e,"X-INR-Ref-First");return {clickId:r,firstClickId:s==null?null:K(s),tokenScope:o}}return null}catch{return null}}function ze(){return (e,t,n)=>{e.inrRef=_e(e);try{Reflect.deleteProperty(e.body,"inrRef");}catch{}n();}}var B="inrConversionOutbox",qe=1209600;function xe(){return {eventType:{type:String,required:true},eventId:{type:String,required:true},body:{type:Object,required:true},attempts:{type:Number,default:0},nextAttemptAt:{type:Date,default:Date.now},lastStatus:{type:Number,default:null},lastError:{type:Object,default:null},unauthorizedSince:{type:Date},createdAt:{type:Date,default:Date.now}}}var Se=[[{createdAt:1},{expireAfterSeconds:1209600}],[{nextAttemptAt:1},{}],[{eventType:1,eventId:1},{unique:true}]];function H(e,t=B){let n=`InrOutbox_${t}`,r=e.models[n];if(r)return r;let o=new e.Schema(xe(),{versionKey:false,autoIndex:false});return e.model(n,o,t)}var be=new WeakSet,Re=new WeakSet,q=new WeakMap;function j(e,t){if(be.has(e))return Promise.resolve();let n=q.get(e);return n||(n=(async()=>{let r=false;for(let[o,s]of Se)await Promise.resolve().then(()=>e.collection.createIndex(o,s)).catch(()=>r=true);r?Re.has(e)||(Re.add(e),t("INR_OUTBOX_INDEX_FAILED")):be.add(e);})().finally(()=>q.delete(e)),q.set(e,n)),n}var w=e=>"exec"in e&&typeof e.exec=="function"?e.exec():e,V=e=>({eventType:e.eventType,eventId:e.eventId});function Ve(e){let t=e.lastError;return {...e,lastError:typeof t=="string"?{status:0,code:t}:t??null,unauthorizedSince:e.unauthorizedSince??null}}function G(e){return {async enqueue(t){let{eventType:n,eventId:r,unauthorizedSince:o,...s}=t,u=o?{...s,unauthorizedSince:o}:s,c=await w(e.updateOne({eventType:n,eventId:r},{$setOnInsert:u},{upsert:true}));return !(Number(c?.matchedCount)>0)},async remove(t){await w(e.deleteOne(V(t)));},async reschedule(t,n){let r={$set:{nextAttemptAt:n.nextAttemptAt,lastStatus:n.lastStatus,lastError:n.lastError},...n.attempts>0?{$inc:{attempts:n.attempts}}:{},...n.unauthorizedAt?{$min:{unauthorizedSince:n.unauthorizedAt}}:{$unset:{unauthorizedSince:1}}};await w(e.updateOne(V(t),r));},async renew(t,n){return (await w(e.updateOne(V(t),{$set:{nextAttemptAt:n}})))?.matchedCount!==0},async claim(t,n){let r=await w(e.findOneAndUpdate({nextAttemptAt:{$lte:t}},{$set:{nextAttemptAt:n},$inc:{attempts:1}},{sort:{nextAttemptAt:1},new:true,lean:true}));return r?Ve(r):null},async purge(t){await w(e.deleteMany({$or:[{"body.clickId":t},{"body.firstClickId":t}]}));},pending:()=>w(e.countDocuments({}))}}var W=(e,t)=>e.body.clickId===t||e.body.firstClickId===t,Ee=(e,t)=>e.eventType===t.eventType&&e.eventId===t.eventId;function Z(e,t){e.nextAttemptAt=t.nextAttemptAt,e.lastStatus=t.lastStatus,e.lastError=t.lastError,e.attempts+=t.attempts,e.unauthorizedSince=t.unauthorizedAt?new Date(Math.min(e.unauthorizedSince?.getTime()??1/0,t.unauthorizedAt.getTime())):null;}function J(){let e=[],t=n=>e.find(r=>Ee(r,n));return {entries:e,async enqueue(n){return t(n)?false:(e.push({...n}),true)},async remove(n){let r=e.findIndex(o=>Ee(o,n));r>=0&&e.splice(r,1);},async reschedule(n,r){let o=t(n);o&&Z(o,r);},async renew(n,r){let o=t(n);return o&&(o.nextAttemptAt=r),!!o},async claim(n,r){let o;for(let s of e)s.nextAttemptAt<=n&&(!o||s.nextAttemptAt<o.nextAttemptAt)&&(o=s);return o?(o.nextAttemptAt=r,o.attempts+=1,{...o}):null},async purge(n){for(let r=e.length-1;r>=0;r--)W(e[r],n)&&e.splice(r,1);},pending:async()=>e.length}}function Ie(e=1e3){let t=new Map,n=r=>`${r.eventType}\0${r.eventId}`;return {add(r){if(t.has(n(r))||(t.set(n(r),r),t.size<=e))return null;let[o,s]=t.entries().next().value;return t.delete(o),s},has:r=>t.has(n(r)),get:r=>t.get(n(r)),delete:r=>{t.delete(n(r));},entries:()=>[...t.values()],get size(){return t.size}}}async function Oe(e,t){let n=0;for(let r of e.entries())if(e.get(r)===r){let o=await t.enqueue(r);e.get(r)===r?e.delete(r):o&&await t.remove(r),n++;}return n}var Te=["booking_created","booking_paid","contact_submitted","quote_requested","call_clicked","signup","purchase","booking_cancelled","booking_refunded","booking_no_show","chargeback","fee_charged"],ve={booking_cancelled:["booking_created"],booking_no_show:["booking_created"],booking_refunded:["booking_paid"],chargeback:["booking_paid","purchase"]},F={...ve,fee_charged:["booking_no_show","booking_cancelled"]},He=Object.freeze(Object.keys(ve)),Q={booking_created:{value:"optional",fee:"null"},booking_paid:{value:"required",fee:"null"},contact_submitted:{value:"null",fee:"null"},quote_requested:{value:"optional",fee:"null"},call_clicked:{value:"null",fee:"null"},signup:{value:"optional",fee:"null"},purchase:{value:"optional",fee:"null"},booking_cancelled:{value:"null",fee:"optional"},booking_refunded:{value:"positive",fee:"null"},booking_no_show:{value:"null",fee:"optional"},chargeback:{value:"positive",fee:"null"},fee_charged:{value:"null",fee:"positive"}},je=1e9,ee=/^(?=.{1,64}$)(?!erased:)[a-z][a-z0-9_]{0,31}:[A-Za-z0-9_-]{1,62}$/,he=/^[A-Z]{3}$/,te=/^inrs1_[A-Za-z0-9_-]{43}$/,ne=/^[0-9a-f]{8}$/,re=/^(?=.{1,48}$)[a-z0-9]+(-[a-z0-9]+)*$/;var M={conversion:"/v1/conversions",landing:"/v1/landings",forget:"/v1/referrals/"},Ae=e=>Object.hasOwn(F,e);function oe(e,t){return t===null?e==="null"||e==="optional":Number.isInteger(t)&&t>=(e==="positive"?1:0)&&t<=je?e!=="null":false}var R=1e3,P=[2*R,8*R,30*R],ie=30*R,ue=3600*R,ke=60*R,Ge=600*R,We=24*3600*R,L=150*R,Ze=3e3,Je=500,Qe=3e4,et=8192,ae=2e3,tt=24*3600*R;function le(e){if(!e)return null;let t=e.endsWith("/")?e.slice(0,-1):e;if(!/^[a-z][a-z0-9+.-]*:\/\/[^/?#]+$/i.test(t))return null;let n;try{n=new URL(t);}catch{return null}return n.pathname!=="/"||n.username||n.password?null:n.protocol==="https:"||n.protocol==="http:"&&["localhost","127.0.0.1","[::1]"].includes(n.hostname)?n.origin:null}function Me(e,t){let n=t?.trim(),r=e??(n?Number(n):void 0),o=r===void 0||!Number.isFinite(r)?Ze:r;return Math.min(Qe,Math.max(Je,o))}function Ne(e,t){return e>=200&&e<300?"success":e===409?t==="NONCE_REUSED"?"success":"terminal":e===400?t==="TIMESTAMP_SKEW"?"skew":"terminal":e===401?"unauthorized":e===0||e===408||e===425||e===429||e>=500&&e<600?"retryable":e<405&&e!==402?"misrouted":"terminal"}function nt(e,t){if(!e)return null;let n=e.trim();if(/^\d+$/.test(n))return Number(n)*R;let r=Date.parse(n);return Number.isNaN(r)?null:Math.max(0,r-t)}var se=0;function rt(e,t,n){let r=e?Date.parse(e):NaN;if(Number.isNaN(r))return null;let o=Math.round(r+500-(t+n)/2);return Math.abs(o)<=tt?o:null}async function ot(e,t){let n=e.body?.getReader();if(!n)return "";let r=new TextDecoder,o="";for(let s=0;s<t;){let{done:u,value:c}=await n.read();if(u)break;o+=r.decode(c.subarray(0,t-s),{stream:true}),s+=c.byteLength;}return n.cancel().catch(()=>{}),o}function st(e){try{let t=JSON.parse(e),n=t?.error?.code??t?.code;return typeof n=="string"&&/^[A-Z][A-Z0-9_]{0,63}$/.test(n)?n:null}catch{return null}}async function we(e,t,n){let r=Math.floor((Date.now()+se)/1e3),{headers:o}=Y({...e,...t,timestamp:r}),s=new AbortController,u=setTimeout(()=>s.abort(),n);u.unref?.();let c=Date.now();try{let l=await fetch(e.hubUrl+t.path,{method:t.method,headers:{...o,"User-Agent":`intenque-network-sdk/${k}`},body:t.body,redirect:"error",signal:s.signal}),y=Date.now(),m=await ot(l,et).catch(()=>"");return {status:l.status,code:st(m),retryAfterMs:nt(l.headers.get("retry-after"),y),date:l.headers.get("date"),tSend:c,tRecv:y}}catch{return {status:0,code:s.signal.aborted?"TIMEOUT":"NETWORK",retryAfterMs:null,date:null,tSend:c,tRecv:Date.now()}}finally{clearTimeout(u);}}function ce(e){let t=Math.min(60*R*2**Math.max(0,e-4),1800*R);return Math.round(t*(.8+Math.random()*.4))}var U=e=>new Promise(t=>{setTimeout(t,e).unref?.();});async function N(e){let t;try{return await Promise.race([Promise.resolve().then(e).catch(()=>{}),new Promise(n=>{t=setTimeout(()=>n(void 0),ae);})])}finally{clearTimeout(t);}}function De(e){let{outbox:t,spill:n,logger:r}=e,o=new Set,s=i=>{o.has(i)||(o.add(i),r.warn(i));},u=i=>{let a=Ne(i.status,i.code);return a==="skew"?{kind:a,status:400,code:"TIMESTAMP_SKEW",offsetMs:rt(i.date,i.tSend,i.tRecv)}:{kind:a,status:i.status,code:i.code,retryAfterMs:i.retryAfterMs}};async function c(i){let a=e.config();if(!a)return {kind:"unauthorized",status:0,code:"INR_ENV_MISSING",retryAfterMs:null};if(typeof globalThis.fetch!="function")return s("INR_NO_FETCH"),{kind:"nofetch"};let p=u(await we(a,i,e.timeoutMs));return p.kind==="skew"&&p.offsetMs!==null&&(se=p.offsetMs,p=u(await we(a,i,e.timeoutMs)),p.kind==="skew"&&p.offsetMs!==null&&(se=p.offsetMs)),p}let l=i=>c({method:"POST",path:M.conversion,body:JSON.stringify(i.body)}),y=async i=>{n.delete(i),await N(()=>t.remove(i));},m=async(i,a,p)=>(await y(i),r.warn("inr_outbox_dropped_total",{eventType:i.eventType,eventId:i.eventId,status:a.status,code:a.code,reason:p}),"dropped");async function E(i,a,p,x=ke){if(a.kind==="nofetch")return null;let T=Date.now(),S={status:a.status,code:a.code},v=async(h,f=null)=>{let d={nextAttemptAt:new Date(T+h),lastStatus:a.status,lastError:S,attempts:p,unauthorizedAt:f},b=n.get(i);return b&&Z(b,d),await N(()=>t.reschedule(i,d)),"retried"};switch(a.kind){case "success":return await y(i),"sent";case "terminal":return m(i,S,"terminal");case "unauthorized":case "misrouted":{let h=i.unauthorizedSince?.getTime();return h!==void 0&&T-h>=We?(a.kind==="unauthorized"&&r.warn("INR_UNAUTHORIZED",{eventType:i.eventType,eventId:i.eventId,...S}),m(i,S,`${a.kind}_24h`)):v(Ge,new Date(T))}case "skew":return r.warn("INR_CLOCK_SKEW",{offsetMs:a.offsetMs}),v(ce(i.attempts+p));case "retryable":return v(x)}}async function I(i){for(let a=0;;a++){let p=await l(i);if(p.kind!=="retryable"&&p.kind!=="misrouted")return E(i,p,a+1);let x=p.retryAfterMs===null?null:Math.min(p.retryAfterMs,ue);if(a===P.length)return E(i,p,a+1,Math.max(ke,x??0));let T=Math.max(P[a],x??0);if(T>ie)return E(i,p,a+1,x);await U(T);let S=new Date(Date.now()+L),v=n.get(i);v&&(v.nextAttemptAt=S);let h;if(await N(async()=>h=await t.renew(i,S)),h===false&&!v)return null}}return {request:c,attempt:l,settle:E,drop:m,chain:I}}var D=1e3,it=10*D,ut=60*D,Ce=10*D,at=50,lt=14*86400*D-3600*D,ct=5*D;function Pe(e){let{delivery:t,outbox:n,spill:r}=e,o,s=null,u=false;async function c(){let m={sent:0,retried:0,dropped:0};try{e.onTick?.(),await Oe(r,n);for(let E=0;E<at;E++){let I=Date.now(),i=await n.claim(new Date(I),new Date(I+L));if(!i)break;if(I-new Date(i.createdAt).getTime()>=lt){await t.drop(i,i.lastError??{status:0,code:null},"expired"),m.dropped++;continue}let a=await t.attempt(i),p="retryAfterMs"in a?a.retryAfterMs??0:0,x=await t.settle(i,a,0,Math.max(ce(i.attempts),Math.min(p,ue)));if(x&&m[x]++,!x||"status"in a&&(a.status===429||a.status===503))break}}catch{e.unavailable();}return m}function l(){return s??=c().finally(()=>{s=null;}),s}function y(m){u||(o=setTimeout(()=>{l().then(()=>y(ut+(Math.random()*2-1)*Ce));},m),o.unref?.());}return {start:()=>y(it+Math.random()*Ce),drainNow:l,async close(m){u=true,clearTimeout(o),await Promise.race([Promise.allSettled([s,m()]),U(ct)]);}}}var pt=6e4,X=1024,ft=10,yt=300,mt=30*864e5,gt=512,_t=/^https?:\/\/[^/\s]{1,253}$/;function bt(e){let t=e.body;if(typeof t=="string"||t instanceof Uint8Array){let n=Buffer.from(t);return Promise.resolve(n.length>X?null:n.toString("utf8"))}return !e.readable||typeof e.on!="function"?Promise.resolve(null):new Promise(n=>{let r=[],o=0;e.on("data",s=>{o+=s.length,o<=X&&r.push(s);}),e.on("end",()=>n(o>X?null:Buffer.concat(r).toString("utf8"))),e.on("error",()=>n(null));})}async function Rt(e){let t=e.body;if(t&&typeof t=="object"&&!(t instanceof Uint8Array)&&Object.keys(t).length>0)return JSON.stringify(t).length>X?null:t;let n=await bt(e);try{let r=n===null?null:JSON.parse(n);return r&&typeof r=="object"?r:null}catch{return null}}var Le=(e,t)=>{let n=e.headers?.[t];return typeof n=="string"?n:null};function Et(e,t,n){let{c:r,scope:o,ph:s,d:u}=e;if(typeof r!="string"||!A.test(r)||o!=="session"&&o!=="persistent"||s!==void 0&&!(typeof s=="string"&&/^[0-9a-f]{16}$/.test(s))||u!==void 0&&!(Number.isInteger(u)&&u>=0&&u<=mt))return null;let c=Le(n,"origin");return {v:1,clickId:r,scope:o,pathHash:s,d:u,visitorIp:t,visitorUa:(Le(n,"user-agent")??"").slice(0,gt),visitorOrigin:c!==null&&_t.test(c)?c:null,sdk:`server/${k}`}}var Ue=-1,de=0,pe=new Map;function xt(e){let t=Math.floor(Date.now()/pt);t!==Ue&&(Ue=t,de=0,pe.clear());let n=pe.get(e)??0;return de>=yt||n>=ft?false:(pe.set(e,n+1),de++,true)}function Ke(e){let t=false;return (n={})=>async(r,o)=>{let s=null;try{let u=await Rt(r),c=!!r.app?.get("trust proxy");!c&&!t&&(t=!0,e.warn("INR_RELAY_TRUST_PROXY"));let l=c?n.visitorIp?.(r)??r.ip:void 0;e.enabled&&u&&typeof l=="string"&&isIP(l)&&(s=Et(u,l,r),s&&!xt(l)&&(s=null));}catch{s=null;}try{o.status(204).end();}catch{}s&&e.forward({method:"POST",path:M.landing,body:JSON.stringify(s)});}}var St=/^\d{4}-\d\d-\d\dT\d\d:\d\d(:\d\d(\.\d+)?)?(Z|[+-]\d\d:\d\d)$/,It=ae,Ot=6e4;function fe(e){if(!e||typeof e!="object")return {error:"event"};let t=e.eventType;if(typeof t!="string"||!Object.hasOwn(Q,t))return {error:"eventType"};if(typeof e.eventId!="string"||!ee.test(e.eventId))return {error:"eventId"};if(typeof e.clickId!="string"||!A.test(e.clickId))return {error:"clickId"};let n=e.firstClickId??null;if(n!==null&&!(typeof n=="string"&&A.test(n)))return {error:"firstClickId"};if(e.tokenScope!=="session"&&e.tokenScope!=="persistent")return {error:"tokenScope"};let r=e.occurredAt instanceof Date?e.occurredAt:typeof e.occurredAt=="string"&&St.test(e.occurredAt)?new Date(e.occurredAt):new Date(NaN),o=Number.isNaN(r.getTime())?"":r.toISOString();if(!/^\d{4}-/.test(o))return {error:"occurredAt"};let s=e.valueCents??null,u=e.feeCents??null,c=Q[t];if(!oe(c.value,s))return {error:"valueCents"};if(!oe(c.fee,u))return {error:"feeCents"};let l=null;if(e.currency!=null&&(l=typeof e.currency=="string"?e.currency.toUpperCase():"",!he.test(l)))return {error:"currency"};if(l===null&&(s!==null||u!==null))return {error:"currency"};let y=e.reverses??null;if(Ae(t)){let m=F[t];if(y===null||typeof y!="object"||!m.includes(y.eventType)||typeof y.eventId!="string"||!ee.test(y.eventId))return {error:"reverses"}}else if(y!==null)return {error:"reverses"};return {v:1,eventType:t,eventId:e.eventId,clickId:e.clickId,firstClickId:n===e.clickId?null:n,tokenScope:e.tokenScope,occurredAt:o,valueCents:s,feeCents:u,currency:l,reverses:y?{eventType:y.eventType,eventId:y.eventId}:null,sdk:`server/${k}`}}var Tt=["INR_HUB_URL","INR_SITE_KEY","INR_KEY_ID","INR_SITE_SECRET"],Be={INR_HUB_URL:e=>le(e)!==null,INR_SITE_KEY:e=>re.test(e),INR_KEY_ID:e=>ne.test(e),INR_SITE_SECRET:e=>te.test(e)};function vt(e=process.env,t={}){let n=Tt.flatMap(s=>{let u=e[s];return u?Be[s]&&!Be[s](u)?[`${s} (invalid)`]:[]:[s]});if(n.length===0)return [];let r=t.production??e.NODE_ENV==="production",o=`@intenque/network-sdk/server: missing or invalid env ${n.join(", ")}`;if(!r||e.INR_STRICT_ENV==="true")throw new Error(o);return (t.logger??console).warn(o),n}function ht(e={}){let t=e.logger??{warn:(f,d)=>console.warn(f,d??"")},n={warn:(f,d)=>{try{t.warn(f,d);}catch{}}},r=e.enabled!==false,o=()=>typeof process>"u"?{}:process.env,s=e.outbox?.mongoose?H(e.outbox.mongoose,e.outbox.collection??(o().INR_OUTBOX_COLLECTION||B)):null,u=()=>{s&&r&&j(s,n.warn);};u();let c=s?G(s):J(),l=Ie(),y=false,E=De({config:()=>{let f=o(),d=e.hubUrl??f.INR_HUB_URL,b=le(d);d&&!b&&!y&&(y=true,n.warn("INR_HUB_URL_INVALID"));let g=e.siteKey??f.INR_SITE_KEY??"",_=f.INR_KEY_ID||e.keyId||"",O=f.INR_SITE_SECRET||e.secret||"";return !b||!re.test(g)||!ne.test(_)||!te.test(O)?null:{hubUrl:b,siteKey:g,keyId:_,secret:O}},timeoutMs:Me(e.timeoutMs,o().INR_TIMEOUT_MS),outbox:c,spill:l,logger:n}),I=new Set,i=f=>{let d=f.catch(b=>{n.warn("inr.report attempt failed",{error:b instanceof Error?b.message:String(b)});}).finally(()=>I.delete(d));I.add(d);},a=f=>i(E.chain(f)),p=async()=>{for(;I.size>0;)await Promise.allSettled([...I]);},x=-1/0,T=()=>{Date.now()-x<Ot||(x=Date.now(),n.warn("INR_OUTBOX_UNAVAILABLE"));},S=Pe({delivery:E,outbox:c,spill:l,unavailable:T,onTick:u});r&&S.start();async function v(f){if(typeof f!="string"||!A.test(f))return false;let d=(_,O)=>(n.warn("INR_FORGET_FAILED",{status:_,code:O}),false);for(let _ of l.entries())W(_,f)&&l.delete(_);if(!await N(()=>c.purge(f).then(()=>true)))return d(0,"INR_OUTBOX_UNAVAILABLE");let b={method:"DELETE",path:M.forget+f,body:""},g;for(let _=0;;_++){if(g=await E.request(b),"status"in g&&g.status>=200&&g.status<300)return true;if(g.kind!=="retryable"||_===P.length)break;let O=g.retryAfterMs??0;if(O>ie)break;await U(Math.max(P[_],O));}return g.kind==="nofetch"?d(0,"INR_NO_FETCH"):d(g.status,g.code)}let h=async f=>{let d=await N(()=>c.enqueue(f));return d===void 0?"failed":d?"inserted":"exists"};return {outbox:c,spill:l,bodyFor:fe,async report(f){try{let d=fe(f);if("error"in d)return n.warn("inr.report dropped: invalid event",{error:d.error}),{status:"invalid",error:d.error};if(!r)return {status:"disabled",body:d};let b=Date.now(),g={eventType:d.eventType,eventId:d.eventId,body:d,attempts:0,nextAttemptAt:new Date(b+L),lastStatus:null,lastError:null,unauthorizedSince:null,createdAt:new Date(b)},_=await h(g),O=_==="failed"?"spilled":"queued";if(_==="failed"&&T(),_==="exists"||l.has(g))return {status:O,body:d};if(_==="failed"){let C=l.add(g);C&&n.warn("inr_outbox_dropped_total",{eventType:C.eventType,eventId:C.eventId,status:C.lastError?.status??null,code:C.lastError?.code??null,reason:"memory_full"});}return a(g),{status:O,body:d}}catch(d){return n.warn("inr.report failed",{error:d instanceof Error?d.message:String(d)}),{status:"invalid",error:"internal"}}},idle:p,forget:v,drainNow:S.drainNow,close:()=>S.close(p),landingRelay:Ke({enabled:r,forward:f=>i(E.request(f)),warn:n.warn})}}export{Te as CONVERSION_TYPES,It as ENQUEUE_TIMEOUT_MS,F as LINK_TARGETS,B as OUTBOX_DEFAULT_COLLECTION,Se as OUTBOX_INDEXES,qe as OUTBOX_TTL_SECONDS,He as REVERSAL_TYPES,k as SDK_VERSION,fe as buildConversionBody,Ne as classifyResponse,ht as createConversionReporter,J as createMemoryOutbox,G as createOutbox,j as ensureOutboxIndexes,$ as hmacBase,H as outboxModel,xe as outboxSchemaDefinition,_e as referralFromRequest,ze as referralMiddleware,me as sha256Hex,Y as signRequest,vt as verifyEnv,Ye as verifySignature};//# sourceMappingURL=index.js.map
|
|
2
2
|
//# sourceMappingURL=index.js.map
|