@crelora/mark 0.2.0 → 0.2.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/dist/node.cjs CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const p="https://ingest.onelence.com";class f extends Error{status;retryAfterMs;constructor(t,e={}){super(t),this.name="TransportError",this.status=e.status,this.retryAfterMs=e.retryAfterMs}}function w(a){return!(typeof a!="number"||a<400||a>=500||a===408||a===429)}function b(a){if(!a)return;const t=a.trim();if(!t)return;const e=Number(t);if(Number.isFinite(e)&&e>=0)return Math.floor(e*1e3);const s=Date.parse(t);if(Number.isFinite(s)){const i=s-Date.now();return i>0?i:0}}const I=5,k=300,v=15e3,M=2880*60*1e3;class q{constructor(t,e={}){this.transport=t,this.maxAttempts=e.maxAttempts??I,this.baseBackoffMs=e.baseBackoffMs??k,this.maxBackoffMs=e.maxBackoffMs??v,this.maxItemAgeMs=e.maxItemAgeMs??M,this.debug=e.debug??!1,this.loadPersisted=e.loadPersisted,this.savePersisted=e.savePersisted,this.onError=e.onError;const s=this.loadPersisted?.()??[];if(s.length>0){const i=Date.now();for(const o of s){const n=o.enqueuedAt??i;if(i-n>this.maxItemAgeMs){this.dropped+=1;continue}this.queue.push({...o,enqueuedAt:n})}this.persist()}}queue=[];flushing=!1;sent=0;failed=0;dropped=0;maxAttempts;baseBackoffMs;maxBackoffMs;maxItemAgeMs;debug;loadPersisted;savePersisted;onError;enqueue(t,e){this.queue.push({path:t,data:e,attempts:0,nextAttemptAt:Date.now(),enqueuedAt:Date.now()}),this.persist(),this.process()}async flush(){await this.process(!0),await this.transport.flush?.()}drainViaBeacon(){if(this.queue.length===0)return;const t=this.queue.splice(0,this.queue.length);this.persist();for(const e of t){const s={...e.data};delete s.__prefer_beacon;try{this.transport.send(e.path,s,{preferBeacon:!0})}catch{}}}getStats(){return{queued:this.queue.length,sent:this.sent,failed:this.failed,dropped:this.dropped}}clear(){this.queue.splice(0,this.queue.length),this.persist()}persist(){this.savePersisted?.(this.queue)}evictExpired(){if(this.queue.length===0)return;const e=Date.now()-this.maxItemAgeMs;let s=0;for(let i=this.queue.length-1;i>=0;i-=1)this.queue[i].enqueuedAt<=e&&(this.queue.splice(i,1),this.dropped+=1,s+=1);s>0&&this.persist()}async process(t=!1){if(!this.flushing){this.flushing=!0;try{for(this.evictExpired();this.queue.length>0;){const e=this.queue[0];if(!t&&e.nextAttemptAt>Date.now())break;try{const s={...e.data},i=s.__prefer_beacon===!0;delete s.__prefer_beacon,await this.transport.send(e.path,s,{preferBeacon:i}),this.queue.shift(),this.sent+=1,this.persist()}catch(s){this.failed+=1,this.onError?.(s,e.data);const i=s instanceof f?s.status:void 0;if(w(i)){this.queue.shift(),this.dropped+=1,this.persist(),this.debug&&console.error("[Mark] Dropping event after non-retriable status",i,e.path);continue}if(e.attempts+=1,e.attempts>=this.maxAttempts){this.queue.shift(),this.dropped+=1,this.persist(),this.debug&&console.error("[Mark] Dropping event after max retries",e.path,s);continue}const o=s instanceof f?s.retryAfterMs:void 0;let n;if(typeof o=="number")n=Math.min(this.maxBackoffMs,Math.max(0,o));else{const r=Math.random()*this.baseBackoffMs;n=Math.min(this.maxBackoffMs,this.baseBackoffMs*2**(e.attempts-1)+r)}e.nextAttemptAt=Date.now()+n,this.persist();break}}}finally{this.flushing=!1}}}}const A=new Set(["event_name","user_id","consent_state","source","is_conversion"]),S=new Set(["user_id","visitor_id","click_id","campaign_id","query","consent_state","source"]);class D{constructor(t,e){this.deps=e,this.validateConfig(t),this.config={endpoint:t.endpoint??p,...t,include_page_context:t.include_page_context??!0},this.consentRequirement=t.require_consent??!1,this.siteId=t.site_id,this.siteHost=t.site_host,this.sessionTimeoutMs=t.session_timeout_ms??1800*1e3,this.queue=new q(this.deps.transport,{debug:this.config.debug,loadPersisted:()=>this.deps.storage.getOutbox?.()??[],savePersisted:s=>this.deps.storage.setOutbox?.(s),onError:(s,i)=>this.config.on_error?.(s,i)}),this.warnMisconfiguredSiteHost(),this.subscribeTcf()}config;consentRequirement;siteId;siteHost;queue;sessionTimeoutMs;batchTimer=null;batchedEvents=[];tcfCachedAllowed=!1;drainViaBeacon(){this.flushBatch(),this.queue.drainViaBeacon()}kickQueue(){this.queue.flush()}track(t,e={}){return this.trackInternal(t,e,!1)}trackInternal(t,e={},s=!1,i){if(!t)return this.config.debug&&console.warn("[Mark] track called without event name"),!1;if(!this.hasConsent()||this.isDntBlocked())return this.config.debug&&console.warn("[Mark] Tracking blocked due to consent requirement."),!1;if(!s&&!this.shouldSampleTrack())return!0;const o=this.sanitizeTrackData(e),n={...o};"query"in n&&delete n.query,"site_id"in n&&delete n.site_id,"site_host"in n&&delete n.site_host;const r={event_name:t,message_id:this.createMessageId(),...this.getIdentityFields(o),...n};s&&(r.is_conversion=!0);const h=o.site_id??this.siteId,d=o.site_host??this.siteHost;h&&(r.site_id=h),d&&(r.site_host=d),this.config.include_page_context&&typeof window<"u"&&(this.applyPageContext(r),!d&&r.site&&(r.site_host=r.site)),this.applyInternalFlag(r,o.is_internal);const u=this.config.before_send?this.config.before_send(r):r;return u?(this.ensureSession(),this.config.batching?.enabled&&!s&&!i?.preferBeacon?(this.enqueueBatch(u),!0):(this.queue.enqueue("/event",{...u,__prefer_beacon:i?.preferBeacon===!0}),!0)):!0}identify(t,e={}){if(!t){this.config.debug&&console.warn("[Mark] identify called without userId");return}if(!this.hasConsent()||this.isDntBlocked()){this.config.debug&&console.warn("[Mark] Identify blocked due to consent requirement.");return}this.deps.storage.update({user_id:t});const s={user_id:t,message_id:this.createMessageId(),...this.sanitizeIdentifyTraits(e),...this.getIdentityFields()};this.siteId&&(s.site_id=this.siteId),this.siteHost&&(s.site_host=this.siteHost),this.applyInternalFlag(s);const i=this.config.before_send?this.config.before_send(s):s;i&&this.queue.enqueue("/identify",i)}conversion(t,e={}){return this.trackInternal(t,e,!0)}trackWithOptions(t,e={},s){return this.trackInternal(t,e,!1,s)}getVisitorId(){return this.deps.storage.getVisitorId()}setConsent(t){const e=this.deps.storage.getConsentStatus();this.deps.storage.setConsentStatus(t),t==="denied"?(this.deps.storage.clearAttribution?.(),this.deps.storage.clearCookieVisitorId?.(),this.deps.storage.setInternal?.(!1)):t==="granted"&&e==="denied"&&this.config.rotate_visitor_on_consent_change&&this.deps.storage.rotateVisitorId?.();const s={visitor_id:this.deps.storage.getVisitorId(),consent_state:t,source:"sdk",message_id:this.createMessageId()};this.siteId&&(s.site_id=this.siteId),this.siteHost&&(s.site_host=this.siteHost),this.applyInternalFlag(s);const i=this.config.before_send?this.config.before_send(s):s;i&&this.queue.enqueue("/consent",i)}reset(){this.deps.storage.update({user_id:void 0,last_click_id:void 0,campaign_id:void 0,query_params:void 0,session_id:void 0,session_started_at:void 0,last_activity_at:void 0,is_internal:void 0}),this.deps.storage.rotateVisitorId?.()}setInternal(t){this.deps.storage.setInternal?.(!!t)}getInternal(){return!!this.deps.storage.getInternal?.()}flush(){return this.flushBatch(),this.queue.flush()}getStats(){return this.queue.getStats()}applyInternalFlag(t,e){if(e===!1){delete t.is_internal;return}const s=this.deps.storage.getInternal?.()===!0;e===!0||s?t.is_internal=!0:delete t.is_internal}getIdentityFields(t){const e=t?.visitor_id??this.deps.storage.getVisitorId(),s=t?.user_id??this.deps.storage.getUserId?.(),i=t?.click_id??this.deps.storage.getLastClickId(),o=t?.campaign_id??this.deps.storage.getCampaignId(),n=t?.session_id??this.deps.storage.getSessionId?.(),r=this.deps.storage.getQueryParams()??{},h=t?.query??{},d={...r,...h},u={};return e&&(u.visitor_id=e),s&&(u.user_id=s),i&&(u.click_id=i),o&&(u.campaign_id=o),n&&(u.session_id=n),Object.keys(d).length>0&&(u.query=d),u}hasConsent(){if(this.config.consent_source?.type==="tcf"&&typeof window<"u"&&!this.tcfCachedAllowed)return!1;if(!this.consentRequirement)return!0;const e=this.deps.storage.getConsentStatus();return this.consentRequirement==="auto",e==="granted"}sanitizeTrackData(t){const e={};for(const[s,i]of Object.entries(t))A.has(s)||(e[s]=i);return e}sanitizeIdentifyTraits(t){const e={};for(const[s,i]of Object.entries(t))S.has(s)||(e[s]=i);return e}validateConfig(t){if(!t.key||!t.key.trim())throw new Error("[Mark] `key` must be a non-empty string.");if(t.endpoint)try{new URL(t.endpoint)}catch{throw new Error("[Mark] `endpoint` must be a valid absolute URL.")}if(typeof t.site_id=="string"&&!t.site_id.trim())throw new Error("[Mark] `site_id` cannot be an empty string.");if(typeof t.site_host=="string"&&!t.site_host.trim())throw new Error("[Mark] `site_host` cannot be an empty string.")}applyPageContext(t){typeof document>"u"||(t.site||(t.site=window.location.host),t.page||(t.page=window.location.pathname),t.title||(t.title=document.title),!t.referrer&&document.referrer&&(t.referrer=this.scrubReferrer(document.referrer)))}enqueueBatch(t){this.batchedEvents.push(t);const e=this.config.batching?.max_size??20;if(this.batchedEvents.length>=e){this.flushBatch();return}if(!this.batchTimer){const s=this.config.batching?.flush_interval_ms??2e3;this.batchTimer=setTimeout(()=>{this.batchTimer=null,this.flushBatch()},s)}}flushBatch(){if(this.batchedEvents.length===0)return;const t=this.config.batching?.endpoint_path??"/events",e=this.batchedEvents.splice(0,this.batchedEvents.length);this.queue.enqueue(t,{events:e,message_id:this.createMessageId()})}createMessageId(){return typeof crypto<"u"&&typeof crypto.randomUUID=="function"?crypto.randomUUID():`msg_${Date.now()}_${Math.random().toString(16).slice(2)}`}ensureSession(){const t=Date.now(),e=this.deps.storage.getSessionId?.(),s=this.deps.storage.getLastActivityAt?.(),i=s?Date.parse(s):0;if(!e||!i||t-i>=this.sessionTimeoutMs||this.crossedUtcDay(i,t)){const n=this.createMessageId(),r=new Date(t).toISOString();this.deps.storage.update({session_id:n,session_started_at:r,last_activity_at:r});return}this.deps.storage.update({last_activity_at:new Date(t).toISOString()})}crossedUtcDay(t,e){const s=new Date(t),i=new Date(e);return s.getUTCFullYear()!==i.getUTCFullYear()||s.getUTCMonth()!==i.getUTCMonth()||s.getUTCDate()!==i.getUTCDate()}shouldSampleTrack(){return typeof this.config.sample_rate!="number"?!0:this.config.sample_rate<=0?!1:this.config.sample_rate>=1?!0:Math.random()<=this.config.sample_rate}isDntBlocked(){if(!this.config.honor_dnt||typeof navigator>"u")return!1;const t=navigator.doNotTrack,e=navigator.globalPrivacyControl;return t==="1"||e===!0}scrubReferrer(t){try{const e=new URL(t);if(typeof window>"u")return t;const s=new URL(window.location.href);return e.origin!==s.origin?(e.search="",e.toString()):t}catch{return t}}warnMisconfiguredSiteHost(){!this.config.debug||!this.siteHost||typeof window>"u"||(window.location.host!==this.siteHost&&console.warn("[Mark] config.site_host does not match current host",{expected:this.siteHost,actual:window.location.host}),this.siteId&&!/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(this.siteId)&&console.warn("[Mark] config.site_id does not look like UUID v4",this.siteId))}subscribeTcf(){const t=this.config.consent_source;if(t?.type!=="tcf"||typeof window>"u")return;const e=t.purposes,s=i=>{try{const o=window;if(typeof o.__tcfapi!="function"){i>0&&setTimeout(()=>s(i-1),200);return}o.__tcfapi("addEventListener",2,(n,r)=>{if(!r||!n){this.tcfCachedAllowed=!1;return}if(n.gdprApplies===!1){this.tcfCachedAllowed=!0;return}const h=n.purpose?.consents??{};this.tcfCachedAllowed=e.every(d=>h[String(d)]===!0)})}catch{this.tcfCachedAllowed=!1}};s(10)}}class T{config;endpoint;pending=new Set;constructor(t){this.config=t,this.endpoint=t.endpoint??p}async send(t,e,s){const i=this.sendInternal(t,e,s);this.pending.add(i);try{await i}finally{this.pending.delete(i)}}async flush(){this.pending.size!==0&&await Promise.allSettled(Array.from(this.pending))}async sendInternal(t,e,s){const i=this.joinUrl(this.endpoint,t),o=this.config.key,n={"Content-Type":"application/json",[o.startsWith("sk_")?"x-secret-key":"x-publishable-key"]:o};this.config.debug&&console.log("[Mark] Sending",i,e);const r=JSON.stringify(e);if(s?.preferBeacon&&typeof navigator<"u"&&typeof navigator.sendBeacon=="function"){const c=new Blob([r],{type:"application/json"});if(navigator.sendBeacon(i,c))return}if(typeof fetch!="function")throw this.config.debug&&console.error("[Mark] Global fetch is not available in this runtime."),new f("[Mark] Global fetch is not available in this runtime.");const h=this.config.request_timeout_ms??1e4,d=new AbortController;let u=!1;const y=setTimeout(()=>{u=!0,d.abort()},h);try{const c=await fetch(i,{method:"POST",headers:n,body:r,keepalive:!0,signal:d.signal});if(!c.ok){const l=await this.readErrorSnippet(c),g=b(c.headers.get("Retry-After"));throw this.config.debug&&console.error("[Mark] Request rejected",{url:i,status:c.status,statusText:c.statusText,body:l,retryAfterMs:g}),new f(`[Mark] Request rejected with status ${c.status}: ${l}`,{status:c.status,retryAfterMs:g})}}catch(c){if(this.config.debug&&console.error("[Mark] Failed to send",i,c),c instanceof f)throw c;if(u)throw new f(`[Mark] Request timed out after ${h}ms`,{status:408});const l=c instanceof Error?c.message:String(c);throw new f(`[Mark] Network error: ${l}`)}finally{clearTimeout(y)}}joinUrl(t,e){const s=t.replace(/\/+$/,""),i=e.replace(/^\/+/,"");return`${s}/${i}`}async readErrorSnippet(t){try{return(await t.text()).slice(0,300)}catch{return""}}}class _{constructor(t={}){this.defaults=t}getVisitorId(){return this.defaults.visitor_id}getLastClickId(){return this.defaults.last_click_id}getCampaignId(){return this.defaults.campaign_id}getQueryParams(){return this.defaults.query_params}getUserId(){return this.defaults.user_id}getSessionId(){return this.defaults.session_id}getSessionStartedAt(){return this.defaults.session_started_at}getLastActivityAt(){return this.defaults.last_activity_at}getConsentStatus(){return this.defaults.consent_status}update(){}setConsentStatus(){}clearAttribution(){}clearCookieVisitorId(){}rotateVisitorId(){}getInternal(){return this.defaults.is_internal}setInternal(t){this.defaults.is_internal=t?!0:void 0}}class m{constructor(t){this.client=t}track(t,e={}){this.client.track(t,e)}conversion(t,e={}){this.client.conversion(t,e)}identify(t,e={}){this.client.identify(t,e)}setConsent(t){this.client.setConsent(t)}flush(){return this.client.flush()}reset(){this.client.reset()}getStats(){return this.client.getStats()}getVisitorId(){return this.client.getVisitorId()}setInternal(t){this.client.setInternal(t)}getInternal(){return this.client.getInternal()}}const E=(a,t={})=>{const e=t.storage??new _({visitor_id:t.storageDefaults?.visitor_id,user_id:t.storageDefaults?.user_id,session_id:t.storageDefaults?.session_id,session_started_at:t.storageDefaults?.session_started_at,last_activity_at:t.storageDefaults?.last_activity_at,last_click_id:t.storageDefaults?.last_click_id,campaign_id:t.storageDefaults?.campaign_id,query_params:t.storageDefaults?.query_params,consent_status:t.storageDefaults?.consent_status,is_internal:t.storageDefaults?.is_internal}),s=t.transport??new T(a),i=new D(a,{storage:e,transport:s});return new m(i)};exports.NodeMark=m;exports.StatelessStorage=_;exports.createNodeMark=E;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const _="https://ingest.onelence.com";class f extends Error{status;retryAfterMs;constructor(t,e={}){super(t),this.name="TransportError",this.status=e.status,this.retryAfterMs=e.retryAfterMs}}function w(a){return!(typeof a!="number"||a<400||a>=500||a===408||a===429)}function b(a){if(!a)return;const t=a.trim();if(!t)return;const e=Number(t);if(Number.isFinite(e)&&e>=0)return Math.floor(e*1e3);const s=Date.parse(t);if(Number.isFinite(s)){const i=s-Date.now();return i>0?i:0}}const I=5,k=300,v=15e3,M=2880*60*1e3;class S{constructor(t,e={}){this.transport=t,this.maxAttempts=e.maxAttempts??I,this.baseBackoffMs=e.baseBackoffMs??k,this.maxBackoffMs=e.maxBackoffMs??v,this.maxItemAgeMs=e.maxItemAgeMs??M,this.debug=e.debug??!1,this.loadPersisted=e.loadPersisted,this.savePersisted=e.savePersisted,this.onError=e.onError;const s=this.loadPersisted?.()??[];if(s.length>0){const i=Date.now();for(const o of s){const n=o.enqueuedAt??i;if(i-n>this.maxItemAgeMs){this.dropped+=1;continue}this.queue.push({...o,enqueuedAt:n})}this.persist()}}queue=[];flushing=!1;sent=0;failed=0;dropped=0;maxAttempts;baseBackoffMs;maxBackoffMs;maxItemAgeMs;debug;loadPersisted;savePersisted;onError;enqueue(t,e){this.queue.push({path:t,data:e,attempts:0,nextAttemptAt:Date.now(),enqueuedAt:Date.now()}),this.persist(),this.process()}async flush(){await this.process(!0),await this.transport.flush?.()}drainViaBeacon(){if(this.queue.length===0)return;const t=this.queue.splice(0,this.queue.length);this.persist();for(const e of t){const s={...e.data};delete s.__prefer_beacon;try{this.transport.send(e.path,s,{preferBeacon:!0})}catch{}}}getStats(){return{queued:this.queue.length,sent:this.sent,failed:this.failed,dropped:this.dropped}}clear(){this.queue.splice(0,this.queue.length),this.persist()}persist(){this.savePersisted?.(this.queue)}evictExpired(){if(this.queue.length===0)return;const e=Date.now()-this.maxItemAgeMs;let s=0;for(let i=this.queue.length-1;i>=0;i-=1)this.queue[i].enqueuedAt<=e&&(this.queue.splice(i,1),this.dropped+=1,s+=1);s>0&&this.persist()}async process(t=!1){if(!this.flushing){this.flushing=!0;try{for(this.evictExpired();this.queue.length>0;){const e=this.queue[0];if(!t&&e.nextAttemptAt>Date.now())break;try{const s={...e.data},i=s.__prefer_beacon===!0;delete s.__prefer_beacon,await this.transport.send(e.path,s,{preferBeacon:i}),this.queue.shift(),this.sent+=1,this.persist()}catch(s){this.failed+=1,this.onError?.(s,e.data);const i=s instanceof f?s.status:void 0;if(w(i)){this.queue.shift(),this.dropped+=1,this.persist(),this.debug&&console.error("[Mark] Dropping event after non-retriable status",i,e.path);continue}if(e.attempts+=1,e.attempts>=this.maxAttempts){this.queue.shift(),this.dropped+=1,this.persist(),this.debug&&console.error("[Mark] Dropping event after max retries",e.path,s);continue}const o=s instanceof f?s.retryAfterMs:void 0;let n;if(typeof o=="number")n=Math.min(this.maxBackoffMs,Math.max(0,o));else{const r=Math.random()*this.baseBackoffMs;n=Math.min(this.maxBackoffMs,this.baseBackoffMs*2**(e.attempts-1)+r)}e.nextAttemptAt=Date.now()+n,this.persist();break}}}finally{this.flushing=!1}}}}const A=new Set(["event_name","user_id","consent_state","source","is_conversion"]),q=new Set(["user_id","visitor_id","click_id","campaign_id","query","consent_state","source"]);class D{constructor(t,e){this.deps=e,this.validateConfig(t),this.config={endpoint:t.endpoint??_,...t,include_page_context:t.include_page_context??!0},this.consentRequirement=t.require_consent??!1,this.siteId=t.site_id,this.siteHost=t.site_host,this.sessionTimeoutMs=t.session_timeout_ms??1800*1e3,this.queue=new S(this.deps.transport,{debug:this.config.debug,loadPersisted:()=>this.deps.storage.getOutbox?.()??[],savePersisted:s=>this.deps.storage.setOutbox?.(s),onError:(s,i)=>this.config.on_error?.(s,i)}),this.warnMisconfiguredSiteHost(),this.subscribeTcf()}config;consentRequirement;siteId;siteHost;queue;sessionTimeoutMs;batchTimer=null;batchedEvents=[];tcfCachedAllowed=!1;drainViaBeacon(){this.flushBatch(),this.queue.drainViaBeacon()}kickQueue(){this.queue.flush()}track(t,e={}){return this.trackInternal(t,e,!1)}trackInternal(t,e={},s=!1,i){if(!t)return this.config.debug&&console.warn("[Mark] track called without event name"),!1;if(!this.hasConsent()||this.isDntBlocked())return this.config.debug&&console.warn("[Mark] Tracking blocked due to consent requirement."),!1;if(!s&&!this.shouldSampleTrack())return!0;const o=this.sanitizeTrackData(e),n={...o};"query"in n&&delete n.query,"site_id"in n&&delete n.site_id,"site_host"in n&&delete n.site_host;const r={event_name:t,message_id:this.createMessageId(),...this.getIdentityFields(o),...n};s&&(r.is_conversion=!0);const h=o.site_id??this.siteId,u=o.site_host??this.siteHost;h&&(r.site_id=h),u&&(r.site_host=u),this.config.include_page_context&&typeof window<"u"&&(this.applyPageContext(r),!u&&r.site&&(r.site_host=r.site)),this.applyInternalFlag(r,o.is_internal);const c=this.config.before_send?this.config.before_send(r):r;return c?(this.ensureSession(),this.applySessionFields(c),this.config.batching?.enabled&&!s&&!i?.preferBeacon?(this.enqueueBatch(c),!0):(this.queue.enqueue("/event",{...c,__prefer_beacon:i?.preferBeacon===!0}),!0)):!0}identify(t,e={}){if(!t){this.config.debug&&console.warn("[Mark] identify called without userId");return}if(!this.hasConsent()||this.isDntBlocked()){this.config.debug&&console.warn("[Mark] Identify blocked due to consent requirement.");return}this.deps.storage.update({user_id:t});const s={user_id:t,message_id:this.createMessageId(),...this.sanitizeIdentifyTraits(e),...this.getIdentityFields()};this.siteId&&(s.site_id=this.siteId),this.siteHost&&(s.site_host=this.siteHost),this.applyInternalFlag(s);const i=this.config.before_send?this.config.before_send(s):s;i&&(this.ensureSession(),this.applySessionFields(i),this.queue.enqueue("/identify",i))}conversion(t,e={}){return this.trackInternal(t,e,!0)}trackWithOptions(t,e={},s){return this.trackInternal(t,e,!1,s)}getVisitorId(){return this.deps.storage.getVisitorId()}setConsent(t){const e=this.deps.storage.getConsentStatus();this.deps.storage.setConsentStatus(t),t==="denied"?(this.deps.storage.clearAttribution?.(),this.deps.storage.clearCookieVisitorId?.(),this.deps.storage.setInternal?.(!1)):t==="granted"&&e==="denied"&&this.config.rotate_visitor_on_consent_change&&this.deps.storage.rotateVisitorId?.();const s={visitor_id:this.deps.storage.getVisitorId(),consent_state:t,source:"sdk",message_id:this.createMessageId()};this.siteId&&(s.site_id=this.siteId),this.siteHost&&(s.site_host=this.siteHost),this.applyInternalFlag(s);const i=this.config.before_send?this.config.before_send(s):s;i&&this.queue.enqueue("/consent",i)}reset(){this.deps.storage.update({user_id:void 0,last_click_id:void 0,campaign_id:void 0,query_params:void 0,session_id:void 0,session_started_at:void 0,last_activity_at:void 0,is_internal:void 0}),this.deps.storage.rotateVisitorId?.()}setInternal(t){this.deps.storage.setInternal?.(!!t)}getInternal(){return!!this.deps.storage.getInternal?.()}flush(){return this.flushBatch(),this.queue.flush()}getStats(){return this.queue.getStats()}applyInternalFlag(t,e){if(e===!1){delete t.is_internal;return}const s=this.deps.storage.getInternal?.()===!0;e===!0||s?t.is_internal=!0:delete t.is_internal}getIdentityFields(t){const e=t?.visitor_id??this.deps.storage.getVisitorId(),s=t?.user_id??this.deps.storage.getUserId?.(),i=t?.click_id??this.deps.storage.getLastClickId(),o=t?.campaign_id??this.deps.storage.getCampaignId(),n=t?.session_id??this.deps.storage.getSessionId?.(),r=this.deps.storage.getQueryParams()??{},h=t?.query??{},u={...r,...h},c={};e&&(c.visitor_id=e),s&&(c.user_id=s),i&&(c.click_id=i),o&&(c.campaign_id=o),n&&(c.session_id=n);const l=this.deps.storage.getSessionStartedAt?.();return n&&l&&(c.session_started_at=l,c.session_elapsed_ms=Date.now()-Date.parse(l)),Object.keys(u).length>0&&(c.query=u),c}applySessionFields(t){if(!t.session_id){const s=this.deps.storage.getSessionId?.(),i=this.deps.storage.getSessionStartedAt?.();s&&(t.session_id=s),i&&(t.session_started_at=i)}const e=t.session_started_at;t.session_id&&typeof e=="string"&&(t.session_elapsed_ms=Date.now()-Date.parse(e))}hasConsent(){if(this.config.consent_source?.type==="tcf"&&typeof window<"u"&&!this.tcfCachedAllowed)return!1;if(!this.consentRequirement)return!0;const e=this.deps.storage.getConsentStatus();return this.consentRequirement==="auto",e==="granted"}sanitizeTrackData(t){const e={};for(const[s,i]of Object.entries(t))A.has(s)||(e[s]=i);return e}sanitizeIdentifyTraits(t){const e={};for(const[s,i]of Object.entries(t))q.has(s)||(e[s]=i);return e}validateConfig(t){if(!t.key||!t.key.trim())throw new Error("[Mark] `key` must be a non-empty string.");if(t.endpoint)try{new URL(t.endpoint)}catch{throw new Error("[Mark] `endpoint` must be a valid absolute URL.")}if(typeof t.site_id=="string"&&!t.site_id.trim())throw new Error("[Mark] `site_id` cannot be an empty string.");if(typeof t.site_host=="string"&&!t.site_host.trim())throw new Error("[Mark] `site_host` cannot be an empty string.")}applyPageContext(t){typeof document>"u"||(t.site||(t.site=window.location.host),t.page||(t.page=window.location.pathname),t.title||(t.title=document.title),!t.referrer&&document.referrer&&(t.referrer=this.scrubReferrer(document.referrer)))}enqueueBatch(t){this.batchedEvents.push(t);const e=this.config.batching?.max_size??20;if(this.batchedEvents.length>=e){this.flushBatch();return}if(!this.batchTimer){const s=this.config.batching?.flush_interval_ms??2e3;this.batchTimer=setTimeout(()=>{this.batchTimer=null,this.flushBatch()},s)}}flushBatch(){if(this.batchedEvents.length===0)return;const t=this.config.batching?.endpoint_path??"/events",e=this.batchedEvents.splice(0,this.batchedEvents.length);this.queue.enqueue(t,{events:e,message_id:this.createMessageId()})}createMessageId(){return typeof crypto<"u"&&typeof crypto.randomUUID=="function"?crypto.randomUUID():`msg_${Date.now()}_${Math.random().toString(16).slice(2)}`}ensureSession(){const t=Date.now(),e=this.deps.storage.getSessionId?.(),s=this.deps.storage.getLastActivityAt?.(),i=s?Date.parse(s):0;if(!e||!i||t-i>=this.sessionTimeoutMs||this.crossedUtcDay(i,t)){const n=this.createMessageId(),r=new Date(t).toISOString();this.deps.storage.update({session_id:n,session_started_at:r,last_activity_at:r});return}this.deps.storage.update({last_activity_at:new Date(t).toISOString()})}crossedUtcDay(t,e){const s=new Date(t),i=new Date(e);return s.getUTCFullYear()!==i.getUTCFullYear()||s.getUTCMonth()!==i.getUTCMonth()||s.getUTCDate()!==i.getUTCDate()}shouldSampleTrack(){return typeof this.config.sample_rate!="number"?!0:this.config.sample_rate<=0?!1:this.config.sample_rate>=1?!0:Math.random()<=this.config.sample_rate}isDntBlocked(){if(!this.config.honor_dnt||typeof navigator>"u")return!1;const t=navigator.doNotTrack,e=navigator.globalPrivacyControl;return t==="1"||e===!0}scrubReferrer(t){try{const e=new URL(t);if(typeof window>"u")return t;const s=new URL(window.location.href);return e.origin!==s.origin?(e.search="",e.toString()):t}catch{return t}}warnMisconfiguredSiteHost(){!this.config.debug||!this.siteHost||typeof window>"u"||(window.location.host!==this.siteHost&&console.warn("[Mark] config.site_host does not match current host",{expected:this.siteHost,actual:window.location.host}),this.siteId&&!/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(this.siteId)&&console.warn("[Mark] config.site_id does not look like UUID v4",this.siteId))}subscribeTcf(){const t=this.config.consent_source;if(t?.type!=="tcf"||typeof window>"u")return;const e=t.purposes,s=i=>{try{const o=window;if(typeof o.__tcfapi!="function"){i>0&&setTimeout(()=>s(i-1),200);return}o.__tcfapi("addEventListener",2,(n,r)=>{if(!r||!n){this.tcfCachedAllowed=!1;return}if(n.gdprApplies===!1){this.tcfCachedAllowed=!0;return}const h=n.purpose?.consents??{};this.tcfCachedAllowed=e.every(u=>h[String(u)]===!0)})}catch{this.tcfCachedAllowed=!1}};s(10)}}class T{config;endpoint;pending=new Set;constructor(t){this.config=t,this.endpoint=t.endpoint??_}async send(t,e,s){const i=this.sendInternal(t,e,s);this.pending.add(i);try{await i}finally{this.pending.delete(i)}}async flush(){this.pending.size!==0&&await Promise.allSettled(Array.from(this.pending))}async sendInternal(t,e,s){const i=this.joinUrl(this.endpoint,t),o=this.config.key,n={"Content-Type":"application/json",[o.startsWith("sk_")?"x-secret-key":"x-publishable-key"]:o};this.config.debug&&console.log("[Mark] Sending",i,e);const r=JSON.stringify(e);if(s?.preferBeacon&&typeof navigator<"u"&&typeof navigator.sendBeacon=="function"){const d=new Blob([r],{type:"application/json"});if(navigator.sendBeacon(i,d))return}if(typeof fetch!="function")throw this.config.debug&&console.error("[Mark] Global fetch is not available in this runtime."),new f("[Mark] Global fetch is not available in this runtime.");const h=this.config.request_timeout_ms??1e4,u=new AbortController;let c=!1;const l=setTimeout(()=>{c=!0,u.abort()},h);try{const d=await fetch(i,{method:"POST",headers:n,body:r,keepalive:!0,signal:u.signal});if(!d.ok){const g=await this.readErrorSnippet(d),p=b(d.headers.get("Retry-After"));throw this.config.debug&&console.error("[Mark] Request rejected",{url:i,status:d.status,statusText:d.statusText,body:g,retryAfterMs:p}),new f(`[Mark] Request rejected with status ${d.status}: ${g}`,{status:d.status,retryAfterMs:p})}}catch(d){if(this.config.debug&&console.error("[Mark] Failed to send",i,d),d instanceof f)throw d;if(c)throw new f(`[Mark] Request timed out after ${h}ms`,{status:408});const g=d instanceof Error?d.message:String(d);throw new f(`[Mark] Network error: ${g}`)}finally{clearTimeout(l)}}joinUrl(t,e){const s=t.replace(/\/+$/,""),i=e.replace(/^\/+/,"");return`${s}/${i}`}async readErrorSnippet(t){try{return(await t.text()).slice(0,300)}catch{return""}}}class m{constructor(t={}){this.defaults=t}getVisitorId(){return this.defaults.visitor_id}getLastClickId(){return this.defaults.last_click_id}getCampaignId(){return this.defaults.campaign_id}getQueryParams(){return this.defaults.query_params}getUserId(){return this.defaults.user_id}getSessionId(){return this.defaults.session_id}getSessionStartedAt(){return this.defaults.session_started_at}getLastActivityAt(){return this.defaults.last_activity_at}getConsentStatus(){return this.defaults.consent_status}update(){}setConsentStatus(){}clearAttribution(){}clearCookieVisitorId(){}rotateVisitorId(){}getInternal(){return this.defaults.is_internal}setInternal(t){this.defaults.is_internal=t?!0:void 0}}class y{constructor(t){this.client=t}track(t,e={}){this.client.track(t,e)}conversion(t,e={}){this.client.conversion(t,e)}identify(t,e={}){this.client.identify(t,e)}setConsent(t){this.client.setConsent(t)}flush(){return this.client.flush()}reset(){this.client.reset()}getStats(){return this.client.getStats()}getVisitorId(){return this.client.getVisitorId()}setInternal(t){this.client.setInternal(t)}getInternal(){return this.client.getInternal()}}const E=(a,t={})=>{const e=t.storage??new m({visitor_id:t.storageDefaults?.visitor_id,user_id:t.storageDefaults?.user_id,session_id:t.storageDefaults?.session_id,session_started_at:t.storageDefaults?.session_started_at,last_activity_at:t.storageDefaults?.last_activity_at,last_click_id:t.storageDefaults?.last_click_id,campaign_id:t.storageDefaults?.campaign_id,query_params:t.storageDefaults?.query_params,consent_status:t.storageDefaults?.consent_status,is_internal:t.storageDefaults?.is_internal}),s=t.transport??new T(a),i=new D(a,{storage:e,transport:s});return new y(i)};exports.NodeMark=y;exports.StatelessStorage=m;exports.createNodeMark=E;
2
2
  //# sourceMappingURL=node.cjs.map
package/dist/node.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"node.cjs","sources":["../src/core/constants.ts","../src/core/TransportError.ts","../src/core/DeliveryQueue.ts","../src/core/MarkCore.ts","../src/core/HttpTransport.ts","../src/node/StatelessStorage.ts","../src/node/index.ts"],"sourcesContent":["export const DEFAULT_ENDPOINT = 'https://ingest.onelence.com';\n\n","export class TransportError extends Error {\r\n public readonly status?: number;\r\n public readonly retryAfterMs?: number;\r\n\r\n constructor(message: string, options: { status?: number; retryAfterMs?: number } = {}) {\r\n super(message);\r\n this.name = 'TransportError';\r\n this.status = options.status;\r\n this.retryAfterMs = options.retryAfterMs;\r\n }\r\n}\r\n\r\n/**\r\n * Returns true when a TransportError status should NOT be retried by the queue.\r\n * 4xx client errors (other than 408 Request Timeout and 429 Too Many Requests)\r\n * indicate a request problem that re-sending will not fix.\r\n */\r\nexport function isNonRetriableStatus(status: number | undefined): boolean {\r\n if (typeof status !== 'number') return false;\r\n if (status < 400 || status >= 500) return false;\r\n if (status === 408 || status === 429) return false;\r\n return true;\r\n}\r\n\r\nexport function parseRetryAfter(headerValue: string | null | undefined): number | undefined {\r\n if (!headerValue) return undefined;\r\n const trimmed = headerValue.trim();\r\n if (!trimmed) return undefined;\r\n const numeric = Number(trimmed);\r\n if (Number.isFinite(numeric) && numeric >= 0) {\r\n return Math.floor(numeric * 1000);\r\n }\r\n const dateMs = Date.parse(trimmed);\r\n if (Number.isFinite(dateMs)) {\r\n const diff = dateMs - Date.now();\r\n return diff > 0 ? diff : 0;\r\n }\r\n return undefined;\r\n}\r\n","import type { TransportAdapter } from './adapters';\r\nimport { TransportError, isNonRetriableStatus } from './TransportError';\r\n\r\ninterface QueueItem {\r\n path: string;\r\n data: Record<string, unknown>;\r\n attempts: number;\r\n nextAttemptAt: number;\r\n enqueuedAt: number;\r\n}\r\n\r\nexport interface DeliveryQueueOptions {\r\n debug?: boolean;\r\n maxAttempts?: number;\r\n baseBackoffMs?: number;\r\n maxBackoffMs?: number;\r\n /**\r\n * Maximum age (ms) for persisted queue items before they are dropped on load\r\n * or next process tick. Defaults to 48 hours.\r\n */\r\n maxItemAgeMs?: number;\r\n loadPersisted?: () => QueueItem[];\r\n savePersisted?: (items: QueueItem[]) => void;\r\n onError?: (error: unknown, event?: Record<string, unknown>) => void;\r\n}\r\n\r\nconst DEFAULT_MAX_ATTEMPTS = 5;\r\nconst DEFAULT_BASE_BACKOFF_MS = 300;\r\nconst DEFAULT_MAX_BACKOFF_MS = 15000;\r\nconst DEFAULT_MAX_ITEM_AGE_MS = 48 * 60 * 60 * 1000;\r\n\r\nexport class DeliveryQueue {\r\n private readonly queue: QueueItem[] = [];\r\n private flushing = false;\r\n private sent = 0;\r\n private failed = 0;\r\n private dropped = 0;\r\n private readonly maxAttempts: number;\r\n private readonly baseBackoffMs: number;\r\n private readonly maxBackoffMs: number;\r\n private readonly maxItemAgeMs: number;\r\n private readonly debug: boolean;\r\n private readonly loadPersisted?: () => QueueItem[];\r\n private readonly savePersisted?: (items: QueueItem[]) => void;\r\n private readonly onError?: (error: unknown, event?: Record<string, unknown>) => void;\r\n\r\n constructor(private readonly transport: TransportAdapter, options: DeliveryQueueOptions = {}) {\r\n this.maxAttempts = options.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;\r\n this.baseBackoffMs = options.baseBackoffMs ?? DEFAULT_BASE_BACKOFF_MS;\r\n this.maxBackoffMs = options.maxBackoffMs ?? DEFAULT_MAX_BACKOFF_MS;\r\n this.maxItemAgeMs = options.maxItemAgeMs ?? DEFAULT_MAX_ITEM_AGE_MS;\r\n this.debug = options.debug ?? false;\r\n this.loadPersisted = options.loadPersisted;\r\n this.savePersisted = options.savePersisted;\r\n this.onError = options.onError;\r\n const persisted = this.loadPersisted?.() ?? [];\r\n if (persisted.length > 0) {\r\n const now = Date.now();\r\n for (const item of persisted) {\r\n const enqueuedAt = item.enqueuedAt ?? now;\r\n if (now - enqueuedAt > this.maxItemAgeMs) {\r\n this.dropped += 1;\r\n continue;\r\n }\r\n this.queue.push({ ...item, enqueuedAt });\r\n }\r\n this.persist();\r\n }\r\n }\r\n\r\n public enqueue(path: string, data: Record<string, unknown>) {\r\n this.queue.push({\r\n path,\r\n data,\r\n attempts: 0,\r\n nextAttemptAt: Date.now(),\r\n enqueuedAt: Date.now(),\r\n });\r\n this.persist();\r\n void this.process();\r\n }\r\n\r\n public async flush() {\r\n await this.process(true);\r\n await this.transport.flush?.();\r\n }\r\n\r\n /**\r\n * Best-effort synchronous drain using sendBeacon. Intended for page unload;\r\n * errors are swallowed because the tab is going away.\r\n */\r\n public drainViaBeacon() {\r\n if (this.queue.length === 0) return;\r\n const items = this.queue.splice(0, this.queue.length);\r\n this.persist();\r\n for (const item of items) {\r\n const eventData = { ...item.data };\r\n delete eventData.__prefer_beacon;\r\n try {\r\n void this.transport.send(item.path, eventData, { preferBeacon: true });\r\n } catch {\r\n // ignore: browser is unloading\r\n }\r\n }\r\n }\r\n\r\n public getStats() {\r\n return {\r\n queued: this.queue.length,\r\n sent: this.sent,\r\n failed: this.failed,\r\n dropped: this.dropped,\r\n };\r\n }\r\n\r\n public clear() {\r\n this.queue.splice(0, this.queue.length);\r\n this.persist();\r\n }\r\n\r\n private persist() {\r\n this.savePersisted?.(this.queue);\r\n }\r\n\r\n private evictExpired() {\r\n if (this.queue.length === 0) return;\r\n const now = Date.now();\r\n const cutoff = now - this.maxItemAgeMs;\r\n let removed = 0;\r\n for (let i = this.queue.length - 1; i >= 0; i -= 1) {\r\n if (this.queue[i].enqueuedAt <= cutoff) {\r\n this.queue.splice(i, 1);\r\n this.dropped += 1;\r\n removed += 1;\r\n }\r\n }\r\n if (removed > 0) {\r\n this.persist();\r\n }\r\n }\r\n\r\n private async process(force = false): Promise<void> {\r\n if (this.flushing) {\r\n return;\r\n }\r\n this.flushing = true;\r\n try {\r\n this.evictExpired();\r\n while (this.queue.length > 0) {\r\n const item = this.queue[0];\r\n if (!force && item.nextAttemptAt > Date.now()) {\r\n break;\r\n }\r\n try {\r\n const eventData = { ...item.data };\r\n const preferBeacon = eventData.__prefer_beacon === true;\r\n delete eventData.__prefer_beacon;\r\n await this.transport.send(item.path, eventData, { preferBeacon });\r\n this.queue.shift();\r\n this.sent += 1;\r\n this.persist();\r\n } catch (error) {\r\n this.failed += 1;\r\n this.onError?.(error, item.data);\r\n\r\n const status = error instanceof TransportError ? error.status : undefined;\r\n if (isNonRetriableStatus(status)) {\r\n this.queue.shift();\r\n this.dropped += 1;\r\n this.persist();\r\n if (this.debug) {\r\n console.error('[Mark] Dropping event after non-retriable status', status, item.path);\r\n }\r\n continue;\r\n }\r\n\r\n item.attempts += 1;\r\n if (item.attempts >= this.maxAttempts) {\r\n this.queue.shift();\r\n this.dropped += 1;\r\n this.persist();\r\n if (this.debug) {\r\n console.error('[Mark] Dropping event after max retries', item.path, error);\r\n }\r\n continue;\r\n }\r\n const retryAfterMs = error instanceof TransportError ? error.retryAfterMs : undefined;\r\n let delay: number;\r\n if (typeof retryAfterMs === 'number') {\r\n delay = Math.min(this.maxBackoffMs, Math.max(0, retryAfterMs));\r\n } else {\r\n const jitter = Math.random() * this.baseBackoffMs;\r\n delay = Math.min(\r\n this.maxBackoffMs,\r\n this.baseBackoffMs * 2 ** (item.attempts - 1) + jitter\r\n );\r\n }\r\n item.nextAttemptAt = Date.now() + delay;\r\n this.persist();\r\n break;\r\n }\r\n }\r\n } finally {\r\n this.flushing = false;\r\n }\r\n }\r\n}\r\n","import type {\r\n MarkConfig,\r\n TrackEventData,\r\n ConversionProperties,\r\n IdentifyTraits,\r\n ConsentStatus,\r\n JsonValue,\r\n} from '../types';\r\nimport type { MarkDependencies } from './adapters';\r\nimport { DEFAULT_ENDPOINT } from './constants';\r\nimport { DeliveryQueue } from './DeliveryQueue';\r\n\r\ntype ResolvedConfig = MarkConfig & { endpoint: string };\r\nconst TRACK_RESERVED_FIELDS = new Set(['event_name', 'user_id', 'consent_state', 'source', 'is_conversion']);\r\nconst IDENTIFY_RESERVED_FIELDS = new Set([\r\n 'user_id',\r\n 'visitor_id',\r\n 'click_id',\r\n 'campaign_id',\r\n 'query',\r\n 'consent_state',\r\n 'source',\r\n]);\r\n\r\nexport class MarkCore {\r\n private config: ResolvedConfig;\r\n private consentRequirement: boolean | 'auto';\r\n private siteId?: string;\r\n private siteHost?: string;\r\n private readonly queue: DeliveryQueue;\r\n private readonly sessionTimeoutMs: number;\r\n private batchTimer: ReturnType<typeof setTimeout> | null = null;\r\n private readonly batchedEvents: Array<Record<string, unknown>> = [];\r\n private tcfCachedAllowed = false;\r\n\r\n constructor(config: MarkConfig, private readonly deps: MarkDependencies) {\r\n this.validateConfig(config);\r\n this.config = {\r\n endpoint: config.endpoint ?? DEFAULT_ENDPOINT,\r\n ...config,\r\n include_page_context: config.include_page_context ?? true,\r\n };\r\n this.consentRequirement = config.require_consent ?? false;\r\n this.siteId = config.site_id;\r\n this.siteHost = config.site_host;\r\n this.sessionTimeoutMs = config.session_timeout_ms ?? 30 * 60 * 1000;\r\n this.queue = new DeliveryQueue(this.deps.transport, {\r\n debug: this.config.debug,\r\n loadPersisted: () => (this.deps.storage.getOutbox?.() as any[]) ?? [],\r\n savePersisted: (items) => this.deps.storage.setOutbox?.(items as unknown[]),\r\n onError: (error, event) => this.config.on_error?.(error, event),\r\n });\r\n this.warnMisconfiguredSiteHost();\r\n this.subscribeTcf();\r\n }\r\n\r\n /**\r\n * Best-effort synchronous drain that dispatches all queued events via\r\n * sendBeacon. Intended for use on page unload (visibilitychange=hidden,\r\n * pagehide) where async fetch may be cancelled by the browser.\r\n */\r\n public drainViaBeacon() {\r\n this.flushBatch();\r\n this.queue.drainViaBeacon();\r\n }\r\n\r\n /**\r\n * Kicks the queue to retry any pending items now. Safe to call repeatedly;\r\n * used by the browser wrapper in response to `online` events or periodic\r\n * timers.\r\n */\r\n public kickQueue() {\r\n void this.queue.flush();\r\n }\r\n\r\n public track(eventName: string, data: TrackEventData = {}) {\r\n return this.trackInternal(eventName, data, false);\r\n }\r\n\r\n private trackInternal(\r\n eventName: string,\r\n data: TrackEventData = {},\r\n isConversion = false,\r\n options?: { preferBeacon?: boolean }\r\n ) {\r\n if (!eventName) {\r\n if (this.config.debug) {\r\n console.warn('[Mark] track called without event name');\r\n }\r\n return false;\r\n }\r\n\r\n if (!this.hasConsent() || this.isDntBlocked()) {\r\n if (this.config.debug) {\r\n console.warn('[Mark] Tracking blocked due to consent requirement.');\r\n }\r\n return false;\r\n }\r\n\r\n if (!isConversion && !this.shouldSampleTrack()) {\r\n return true;\r\n }\r\n\r\n const sanitizedData = this.sanitizeTrackData(data);\r\n const restData: TrackEventData = { ...sanitizedData };\r\n if ('query' in restData) {\r\n delete restData.query;\r\n }\r\n if ('site_id' in restData) {\r\n delete restData.site_id;\r\n }\r\n if ('site_host' in restData) {\r\n delete restData.site_host;\r\n }\r\n const payload: Record<string, unknown> = {\r\n event_name: eventName,\r\n message_id: this.createMessageId(),\r\n ...this.getIdentityFields(sanitizedData),\r\n ...restData,\r\n };\r\n if (isConversion) {\r\n payload.is_conversion = true;\r\n }\r\n\r\n // Add site_id and site_host if configured (event-level overrides init-level)\r\n const siteId = sanitizedData.site_id ?? this.siteId;\r\n const siteHost = sanitizedData.site_host ?? this.siteHost;\r\n\r\n if (siteId) {\r\n payload.site_id = siteId;\r\n }\r\n if (siteHost) {\r\n payload.site_host = siteHost;\r\n }\r\n\r\n if (this.config.include_page_context && typeof window !== 'undefined') {\r\n this.applyPageContext(payload);\r\n // If site_host wasn't explicitly set, use the host from page context\r\n if (!siteHost && payload.site) {\r\n payload.site_host = payload.site;\r\n }\r\n }\r\n\r\n this.applyInternalFlag(payload, sanitizedData.is_internal);\r\n\r\n const transformed = this.config.before_send ? this.config.before_send(payload) : payload;\r\n if (!transformed) {\r\n return true;\r\n }\r\n\r\n this.ensureSession();\r\n if (this.config.batching?.enabled && !isConversion && !options?.preferBeacon) {\r\n this.enqueueBatch(transformed);\r\n return true;\r\n }\r\n this.queue.enqueue('/event', { ...transformed, __prefer_beacon: options?.preferBeacon === true });\r\n return true;\r\n }\r\n\r\n public identify(userId: string, traits: IdentifyTraits = {}) {\r\n if (!userId) {\r\n if (this.config.debug) {\r\n console.warn('[Mark] identify called without userId');\r\n }\r\n return;\r\n }\r\n\r\n if (!this.hasConsent() || this.isDntBlocked()) {\r\n if (this.config.debug) {\r\n console.warn('[Mark] Identify blocked due to consent requirement.');\r\n }\r\n return;\r\n }\r\n\r\n this.deps.storage.update({ user_id: userId });\r\n\r\n const payload: Record<string, unknown> = {\r\n user_id: userId,\r\n message_id: this.createMessageId(),\r\n ...this.sanitizeIdentifyTraits(traits),\r\n ...this.getIdentityFields(),\r\n };\r\n\r\n // Add site_id and site_host if configured\r\n if (this.siteId) {\r\n payload.site_id = this.siteId;\r\n }\r\n if (this.siteHost) {\r\n payload.site_host = this.siteHost;\r\n }\r\n\r\n this.applyInternalFlag(payload);\r\n\r\n const transformed = this.config.before_send ? this.config.before_send(payload) : payload;\r\n if (!transformed) return;\r\n this.queue.enqueue('/identify', transformed);\r\n }\r\n\r\n public conversion(eventName: string, data: ConversionProperties = {}) {\r\n return this.trackInternal(eventName, data, true);\r\n }\r\n\r\n public trackWithOptions(eventName: string, data: TrackEventData = {}, options?: { preferBeacon?: boolean }) {\r\n return this.trackInternal(eventName, data, false, options);\r\n }\r\n\r\n /**\r\n * Returns the current visitor ID from storage, if any.\r\n * Used by browser/Node wrappers to expose a stable pseudonymous ID for server-side attribution.\r\n */\r\n public getVisitorId(): string | undefined {\r\n return this.deps.storage.getVisitorId();\r\n }\r\n\r\n public setConsent(status: ConsentStatus) {\r\n const previous = this.deps.storage.getConsentStatus();\r\n this.deps.storage.setConsentStatus(status);\r\n if (status === 'denied') {\r\n this.deps.storage.clearAttribution?.();\r\n this.deps.storage.clearCookieVisitorId?.();\r\n this.deps.storage.setInternal?.(false);\r\n } else if (\r\n status === 'granted' &&\r\n previous === 'denied' &&\r\n this.config.rotate_visitor_on_consent_change\r\n ) {\r\n this.deps.storage.rotateVisitorId?.();\r\n }\r\n const payload: Record<string, unknown> = {\r\n visitor_id: this.deps.storage.getVisitorId(),\r\n consent_state: status,\r\n source: 'sdk',\r\n message_id: this.createMessageId(),\r\n };\r\n\r\n // Add site_id and site_host if configured\r\n if (this.siteId) {\r\n payload.site_id = this.siteId;\r\n }\r\n if (this.siteHost) {\r\n payload.site_host = this.siteHost;\r\n }\r\n\r\n this.applyInternalFlag(payload);\r\n\r\n const transformed = this.config.before_send ? this.config.before_send(payload) : payload;\r\n if (!transformed) return;\r\n this.queue.enqueue('/consent', transformed);\r\n }\r\n\r\n public reset() {\r\n this.deps.storage.update({\r\n user_id: undefined,\r\n last_click_id: undefined,\r\n campaign_id: undefined,\r\n query_params: undefined,\r\n session_id: undefined,\r\n session_started_at: undefined,\r\n last_activity_at: undefined,\r\n is_internal: undefined,\r\n });\r\n this.deps.storage.rotateVisitorId?.();\r\n }\r\n\r\n /**\r\n * Marks or unmarks the current visitor as internal traffic. While set, every\r\n * subsequent event (track/identify/conversion/consent) is stamped with\r\n * `is_internal: true`, so the backend can exclude it from customer-facing\r\n * reports by default.\r\n *\r\n * The flag is persisted via the storage adapter (browser: localStorage) so\r\n * it survives reloads, and is cleared by `reset()` and by\r\n * `setConsent('denied')`.\r\n */\r\n public setInternal(value: boolean) {\r\n this.deps.storage.setInternal?.(Boolean(value));\r\n }\r\n\r\n /**\r\n * Returns the currently persisted internal-traffic flag, if any.\r\n */\r\n public getInternal(): boolean {\r\n return Boolean(this.deps.storage.getInternal?.());\r\n }\r\n\r\n public flush() {\r\n this.flushBatch();\r\n return this.queue.flush();\r\n }\r\n\r\n public getStats() {\r\n return this.queue.getStats();\r\n }\r\n\r\n /**\r\n * Stamps `is_internal: true` on the payload when either:\r\n * - the persistent visitor flag is set (via setInternal), or\r\n * - the caller passed `is_internal: true` on this specific event.\r\n *\r\n * Explicit `is_internal: false` on a single event wins over the visitor flag\r\n * so individual calls can opt out.\r\n */\r\n private applyInternalFlag(payload: Record<string, unknown>, eventValue?: boolean) {\r\n if (eventValue === false) {\r\n delete payload.is_internal;\r\n return;\r\n }\r\n const stored = this.deps.storage.getInternal?.() === true;\r\n if (eventValue === true || stored) {\r\n payload.is_internal = true;\r\n } else {\r\n delete payload.is_internal;\r\n }\r\n }\r\n\r\n private getIdentityFields(data?: TrackEventData) {\r\n const visitorId = data?.visitor_id ?? this.deps.storage.getVisitorId();\r\n const userId = data?.user_id ?? this.deps.storage.getUserId?.();\r\n const clickId = data?.click_id ?? this.deps.storage.getLastClickId();\r\n const campaignId = data?.campaign_id ?? this.deps.storage.getCampaignId();\r\n const sessionId = data?.session_id ?? this.deps.storage.getSessionId?.();\r\n\r\n // Auto-capture query params from storage or event data\r\n const storedQuery = this.deps.storage.getQueryParams() ?? {};\r\n const inputQuery = (data?.query as Record<string, string> | undefined) ?? {};\r\n\r\n // Merge: stored values serve as defaults, input values override/append\r\n const query = { ...storedQuery, ...inputQuery };\r\n\r\n const identity: Record<string, unknown> = {};\r\n\r\n if (visitorId) identity.visitor_id = visitorId;\r\n if (userId) identity.user_id = userId;\r\n if (clickId) identity.click_id = clickId;\r\n if (campaignId) identity.campaign_id = campaignId;\r\n if (sessionId) identity.session_id = sessionId;\r\n\r\n if (Object.keys(query).length > 0) {\r\n identity.query = query;\r\n }\r\n\r\n return identity;\r\n }\r\n\r\n private hasConsent() {\r\n if (this.config.consent_source?.type === 'tcf' && typeof window !== 'undefined') {\r\n if (!this.tcfCachedAllowed) {\r\n return false;\r\n }\r\n }\r\n if (!this.consentRequirement) {\r\n return true;\r\n }\r\n\r\n const stored = this.deps.storage.getConsentStatus();\r\n const state = stored;\r\n\r\n if (this.consentRequirement === 'auto') {\r\n // In auto mode, default to deny until consent is explicitly granted.\r\n return state === 'granted';\r\n }\r\n\r\n return state === 'granted';\r\n }\r\n\r\n private sanitizeTrackData(data: TrackEventData) {\r\n const sanitized: Record<string, JsonValue | undefined> = {};\r\n for (const [key, value] of Object.entries(data)) {\r\n if (TRACK_RESERVED_FIELDS.has(key)) {\r\n continue;\r\n }\r\n sanitized[key] = value;\r\n }\r\n return sanitized as TrackEventData;\r\n }\r\n\r\n private sanitizeIdentifyTraits(traits: IdentifyTraits) {\r\n const sanitized: Record<string, JsonValue | undefined> = {};\r\n for (const [key, value] of Object.entries(traits)) {\r\n if (IDENTIFY_RESERVED_FIELDS.has(key)) {\r\n continue;\r\n }\r\n sanitized[key] = value;\r\n }\r\n return sanitized as IdentifyTraits;\r\n }\r\n\r\n private validateConfig(config: MarkConfig) {\r\n if (!config.key || !config.key.trim()) {\r\n throw new Error('[Mark] `key` must be a non-empty string.');\r\n }\r\n if (config.endpoint) {\r\n try {\r\n // eslint-disable-next-line no-new\r\n new URL(config.endpoint);\r\n } catch {\r\n throw new Error('[Mark] `endpoint` must be a valid absolute URL.');\r\n }\r\n }\r\n if (typeof config.site_id === 'string' && !config.site_id.trim()) {\r\n throw new Error('[Mark] `site_id` cannot be an empty string.');\r\n }\r\n if (typeof config.site_host === 'string' && !config.site_host.trim()) {\r\n throw new Error('[Mark] `site_host` cannot be an empty string.');\r\n }\r\n }\r\n\r\n private applyPageContext(payload: Record<string, unknown>) {\r\n if (typeof document === 'undefined') {\r\n return;\r\n }\r\n if (!payload.site) payload.site = window.location.host;\r\n if (!payload.page) payload.page = window.location.pathname;\r\n if (!payload.title) payload.title = document.title;\r\n if (!payload.referrer && document.referrer) {\r\n payload.referrer = this.scrubReferrer(document.referrer);\r\n }\r\n // full URL omitted for privacy; include only if explicitly provided by the caller\r\n }\r\n\r\n private enqueueBatch(event: Record<string, unknown>) {\r\n this.batchedEvents.push(event);\r\n const maxSize = this.config.batching?.max_size ?? 20;\r\n if (this.batchedEvents.length >= maxSize) {\r\n this.flushBatch();\r\n return;\r\n }\r\n if (!this.batchTimer) {\r\n const intervalMs = this.config.batching?.flush_interval_ms ?? 2000;\r\n this.batchTimer = setTimeout(() => {\r\n this.batchTimer = null;\r\n this.flushBatch();\r\n }, intervalMs);\r\n }\r\n }\r\n\r\n private flushBatch() {\r\n if (this.batchedEvents.length === 0) return;\r\n const path = this.config.batching?.endpoint_path ?? '/events';\r\n const payload = this.batchedEvents.splice(0, this.batchedEvents.length);\r\n this.queue.enqueue(path, { events: payload, message_id: this.createMessageId() });\r\n }\r\n\r\n private createMessageId() {\r\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\r\n return crypto.randomUUID();\r\n }\r\n return `msg_${Date.now()}_${Math.random().toString(16).slice(2)}`;\r\n }\r\n\r\n private ensureSession() {\r\n const now = Date.now();\r\n const currentId = this.deps.storage.getSessionId?.();\r\n const lastActivityIso = this.deps.storage.getLastActivityAt?.();\r\n const lastActivity = lastActivityIso ? Date.parse(lastActivityIso) : 0;\r\n const shouldRotate =\r\n !currentId ||\r\n !lastActivity ||\r\n now - lastActivity >= this.sessionTimeoutMs ||\r\n this.crossedUtcDay(lastActivity, now);\r\n if (shouldRotate) {\r\n const newSessionId = this.createMessageId();\r\n const nowIso = new Date(now).toISOString();\r\n this.deps.storage.update({\r\n session_id: newSessionId,\r\n session_started_at: nowIso,\r\n last_activity_at: nowIso,\r\n });\r\n return;\r\n }\r\n this.deps.storage.update({ last_activity_at: new Date(now).toISOString() });\r\n }\r\n\r\n private crossedUtcDay(previousTs: number, currentTs: number) {\r\n const prev = new Date(previousTs);\r\n const next = new Date(currentTs);\r\n return (\r\n prev.getUTCFullYear() !== next.getUTCFullYear() ||\r\n prev.getUTCMonth() !== next.getUTCMonth() ||\r\n prev.getUTCDate() !== next.getUTCDate()\r\n );\r\n }\r\n\r\n private shouldSampleTrack() {\r\n if (typeof this.config.sample_rate !== 'number') return true;\r\n if (this.config.sample_rate <= 0) return false;\r\n if (this.config.sample_rate >= 1) return true;\r\n return Math.random() <= this.config.sample_rate;\r\n }\r\n\r\n private isDntBlocked() {\r\n if (!this.config.honor_dnt || typeof navigator === 'undefined') {\r\n return false;\r\n }\r\n const dnt = navigator.doNotTrack;\r\n const gpc = (navigator as Navigator & { globalPrivacyControl?: boolean }).globalPrivacyControl;\r\n return dnt === '1' || gpc === true;\r\n }\r\n\r\n private scrubReferrer(referrer: string) {\r\n try {\r\n const refUrl = new URL(referrer);\r\n if (typeof window === 'undefined') return referrer;\r\n const currentUrl = new URL(window.location.href);\r\n if (refUrl.origin !== currentUrl.origin) {\r\n refUrl.search = '';\r\n return refUrl.toString();\r\n }\r\n return referrer;\r\n } catch {\r\n return referrer;\r\n }\r\n }\r\n\r\n private warnMisconfiguredSiteHost() {\r\n if (!this.config.debug || !this.siteHost || typeof window === 'undefined') {\r\n return;\r\n }\r\n if (window.location.host !== this.siteHost) {\r\n console.warn('[Mark] config.site_host does not match current host', {\r\n expected: this.siteHost,\r\n actual: window.location.host,\r\n });\r\n }\r\n if (this.siteId && !/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(this.siteId)) {\r\n console.warn('[Mark] config.site_id does not look like UUID v4', this.siteId);\r\n }\r\n }\r\n\r\n /**\r\n * Subscribes to the IAB TCF v2 CMP via `__tcfapi('addEventListener', ...)`.\r\n * Result is cached in `tcfCachedAllowed` so the synchronous `hasConsent()`\r\n * path does not need to await the CMP. If the CMP is not yet present, we\r\n * poll briefly (CMPs commonly load asynchronously) and give up after ~2s.\r\n */\r\n private subscribeTcf() {\r\n const source = this.config.consent_source;\r\n if (source?.type !== 'tcf' || typeof window === 'undefined') return;\r\n const purposes = source.purposes;\r\n\r\n const attempt = (retries: number) => {\r\n try {\r\n const w = window as Window & {\r\n __tcfapi?: (\r\n command: string,\r\n version: number,\r\n callback: (\r\n tcData?: {\r\n eventStatus?: string;\r\n gdprApplies?: boolean;\r\n listenerId?: number;\r\n purpose?: { consents?: Record<string, boolean> };\r\n },\r\n ok?: boolean\r\n ) => void,\r\n parameter?: number\r\n ) => void;\r\n };\r\n if (typeof w.__tcfapi !== 'function') {\r\n if (retries > 0) {\r\n setTimeout(() => attempt(retries - 1), 200);\r\n }\r\n return;\r\n }\r\n w.__tcfapi('addEventListener', 2, (tcData, ok) => {\r\n if (!ok || !tcData) {\r\n this.tcfCachedAllowed = false;\r\n return;\r\n }\r\n if (tcData.gdprApplies === false) {\r\n this.tcfCachedAllowed = true;\r\n return;\r\n }\r\n const consents = tcData.purpose?.consents ?? {};\r\n this.tcfCachedAllowed = purposes.every((p) => consents[String(p)] === true);\r\n });\r\n } catch {\r\n this.tcfCachedAllowed = false;\r\n }\r\n };\r\n\r\n attempt(10);\r\n }\r\n}\r\n\r\n","import type { MarkConfig } from '../types';\r\nimport { DEFAULT_ENDPOINT } from './constants';\r\nimport type { TransportAdapter } from './adapters';\r\nimport { TransportError, parseRetryAfter } from './TransportError';\r\n\r\nexport class HttpTransport implements TransportAdapter {\r\n private readonly config: MarkConfig;\r\n private readonly endpoint: string;\r\n private readonly pending = new Set<Promise<void>>();\r\n\r\n constructor(config: MarkConfig) {\r\n this.config = config;\r\n this.endpoint = config.endpoint ?? DEFAULT_ENDPOINT;\r\n }\r\n\r\n public async send(path: string, data: unknown, options?: { preferBeacon?: boolean }) {\r\n const request = this.sendInternal(path, data, options);\r\n this.pending.add(request);\r\n try {\r\n await request;\r\n } finally {\r\n this.pending.delete(request);\r\n }\r\n }\r\n\r\n public async flush() {\r\n if (this.pending.size === 0) {\r\n return;\r\n }\r\n await Promise.allSettled(Array.from(this.pending));\r\n }\r\n\r\n private async sendInternal(path: string, data: unknown, options?: { preferBeacon?: boolean }) {\r\n const url = this.joinUrl(this.endpoint, path);\r\n const key = this.config.key;\r\n\r\n const headers: Record<string, string> = {\r\n 'Content-Type': 'application/json',\r\n [key.startsWith('sk_') ? 'x-secret-key' : 'x-publishable-key']: key,\r\n };\r\n\r\n if (this.config.debug) {\r\n console.log('[Mark] Sending', url, data);\r\n }\r\n\r\n const body = JSON.stringify(data);\r\n if (options?.preferBeacon && typeof navigator !== 'undefined' && typeof navigator.sendBeacon === 'function') {\r\n const blob = new Blob([body], { type: 'application/json' });\r\n const sent = navigator.sendBeacon(url, blob);\r\n if (sent) {\r\n return;\r\n }\r\n }\r\n\r\n if (typeof fetch !== 'function') {\r\n if (this.config.debug) {\r\n console.error('[Mark] Global fetch is not available in this runtime.');\r\n }\r\n throw new TransportError('[Mark] Global fetch is not available in this runtime.');\r\n }\r\n\r\n const timeoutMs = this.config.request_timeout_ms ?? 10000;\r\n const controller = new AbortController();\r\n let timedOut = false;\r\n const timer = setTimeout(() => {\r\n timedOut = true;\r\n controller.abort();\r\n }, timeoutMs);\r\n\r\n try {\r\n const response = await fetch(url, {\r\n method: 'POST',\r\n headers,\r\n body,\r\n keepalive: true,\r\n signal: controller.signal,\r\n });\r\n if (!response.ok) {\r\n const snippet = await this.readErrorSnippet(response);\r\n const retryAfterMs = parseRetryAfter(response.headers.get('Retry-After'));\r\n if (this.config.debug) {\r\n console.error('[Mark] Request rejected', {\r\n url,\r\n status: response.status,\r\n statusText: response.statusText,\r\n body: snippet,\r\n retryAfterMs,\r\n });\r\n }\r\n throw new TransportError(\r\n `[Mark] Request rejected with status ${response.status}: ${snippet}`,\r\n { status: response.status, retryAfterMs }\r\n );\r\n }\r\n } catch (error) {\r\n if (this.config.debug) {\r\n console.error('[Mark] Failed to send', url, error);\r\n }\r\n if (error instanceof TransportError) {\r\n throw error;\r\n }\r\n if (timedOut) {\r\n throw new TransportError(`[Mark] Request timed out after ${timeoutMs}ms`, { status: 408 });\r\n }\r\n const message = error instanceof Error ? error.message : String(error);\r\n throw new TransportError(`[Mark] Network error: ${message}`);\r\n } finally {\r\n clearTimeout(timer);\r\n }\r\n }\r\n\r\n private joinUrl(endpoint: string, path: string) {\r\n const normalizedEndpoint = endpoint.replace(/\\/+$/, '');\r\n const normalizedPath = path.replace(/^\\/+/, '');\r\n return `${normalizedEndpoint}/${normalizedPath}`;\r\n }\r\n\r\n private async readErrorSnippet(response: Response) {\r\n try {\r\n const text = await response.text();\r\n return text.slice(0, 300);\r\n } catch {\r\n return '';\r\n }\r\n }\r\n}\r\n","import type { ConsentStatus, StorageData } from '../types';\nimport type { StorageAdapter } from '../core/adapters';\n\n/**\n * Stateless storage adapter used by default in server runtimes.\n * It simply exposes optional defaults and ignores updates.\n */\nexport class StatelessStorage implements StorageAdapter {\n constructor(private defaults: StorageData = {}) { }\n\n public getVisitorId() {\n return this.defaults.visitor_id;\n }\n\n public getLastClickId() {\n return this.defaults.last_click_id;\n }\n\n public getCampaignId() {\n return this.defaults.campaign_id;\n }\n\n public getQueryParams() {\n return this.defaults.query_params;\n }\n\n public getUserId() {\n return this.defaults.user_id;\n }\n\n public getSessionId() {\n return this.defaults.session_id;\n }\n\n public getSessionStartedAt() {\n return this.defaults.session_started_at;\n }\n\n public getLastActivityAt() {\n return this.defaults.last_activity_at;\n }\n\n public getConsentStatus(): ConsentStatus | undefined {\n return this.defaults.consent_status as ConsentStatus | undefined;\n }\n\n public update(): void {\n // Server runtimes should provide identifiers explicitly.\n }\n\n public setConsentStatus(): void {\n // Stateless; no-op.\n }\n\n public clearAttribution(): void {\n // Stateless; no-op.\n }\n\n public clearCookieVisitorId(): void {\n // Stateless; no-op.\n }\n\n public rotateVisitorId(): void {\n // Stateless; no-op.\n }\n\n public getInternal(): boolean | undefined {\n return this.defaults.is_internal;\n }\n\n public setInternal(value: boolean): void {\n this.defaults.is_internal = value ? true : undefined;\n }\n}\n\n","import type {\n MarkConfig,\n TrackEventData,\n IdentifyTraits,\n StorageData,\n ConsentStatus,\n} from '../types';\nimport { MarkCore } from '../core/MarkCore';\nimport { HttpTransport } from '../core/HttpTransport';\nimport type { StorageAdapter, TransportAdapter } from '../core/adapters';\nimport { StatelessStorage } from './StatelessStorage';\n\nexport interface NodeMarkOptions {\n storage?: StorageAdapter;\n transport?: TransportAdapter;\n storageDefaults?: StorageData;\n}\n\nexport class NodeMark {\n constructor(private readonly client: MarkCore) {}\n\n public track(eventName: string, data: TrackEventData = {}) {\n this.client.track(eventName, data);\n }\n\n public conversion(eventName: string, data: TrackEventData = {}) {\n this.client.conversion(eventName, data);\n }\n\n public identify(userId: string, traits: IdentifyTraits = {}) {\n this.client.identify(userId, traits);\n }\n\n public setConsent(status: ConsentStatus) {\n this.client.setConsent(status);\n }\n\n public flush() {\n return this.client.flush();\n }\n\n public reset() {\n this.client.reset();\n }\n\n public getStats() {\n return this.client.getStats();\n }\n\n /**\n * Returns the visitor ID from the configured storage, if any.\n * With default StatelessStorage, this is the value passed via `storageDefaults.visitor_id` when creating the client.\n * Use it to associate server-side events with the same visitor dimension as browser events.\n */\n public getVisitorId(): string | undefined {\n return this.client.getVisitorId();\n }\n\n /**\n * Marks the current request/visitor as internal traffic. When using the\n * default StatelessStorage, the flag is scoped to this NodeMark instance.\n */\n public setInternal(value: boolean) {\n this.client.setInternal(value);\n }\n\n public getInternal(): boolean {\n return this.client.getInternal();\n }\n}\n\nexport const createNodeMark = (config: MarkConfig, options: NodeMarkOptions = {}) => {\n const storage =\n options.storage ??\n new StatelessStorage({\n visitor_id: options.storageDefaults?.visitor_id,\n user_id: options.storageDefaults?.user_id,\n session_id: options.storageDefaults?.session_id,\n session_started_at: options.storageDefaults?.session_started_at,\n last_activity_at: options.storageDefaults?.last_activity_at,\n last_click_id: options.storageDefaults?.last_click_id,\n campaign_id: options.storageDefaults?.campaign_id,\n query_params: options.storageDefaults?.query_params,\n consent_status: options.storageDefaults?.consent_status,\n is_internal: options.storageDefaults?.is_internal,\n });\n\n const transport = options.transport ?? new HttpTransport(config);\n const client = new MarkCore(config, { storage, transport });\n\n return new NodeMark(client);\n};\n\nexport { StatelessStorage };\n\n"],"names":["DEFAULT_ENDPOINT","TransportError","message","options","isNonRetriableStatus","status","parseRetryAfter","headerValue","trimmed","numeric","dateMs","diff","DEFAULT_MAX_ATTEMPTS","DEFAULT_BASE_BACKOFF_MS","DEFAULT_MAX_BACKOFF_MS","DEFAULT_MAX_ITEM_AGE_MS","DeliveryQueue","transport","persisted","now","item","enqueuedAt","path","data","items","eventData","cutoff","removed","force","preferBeacon","error","retryAfterMs","delay","jitter","TRACK_RESERVED_FIELDS","IDENTIFY_RESERVED_FIELDS","MarkCore","config","deps","event","eventName","isConversion","sanitizedData","restData","payload","siteId","siteHost","transformed","userId","traits","previous","value","eventValue","stored","visitorId","clickId","campaignId","sessionId","storedQuery","inputQuery","query","identity","state","sanitized","key","maxSize","intervalMs","currentId","lastActivityIso","lastActivity","newSessionId","nowIso","previousTs","currentTs","prev","next","dnt","gpc","referrer","refUrl","currentUrl","source","purposes","attempt","retries","w","tcData","ok","consents","p","HttpTransport","request","url","headers","body","blob","timeoutMs","controller","timedOut","timer","response","snippet","endpoint","normalizedEndpoint","normalizedPath","StatelessStorage","defaults","NodeMark","client","createNodeMark","storage"],"mappings":"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"}
1
+ {"version":3,"file":"node.cjs","sources":["../src/core/constants.ts","../src/core/TransportError.ts","../src/core/DeliveryQueue.ts","../src/core/MarkCore.ts","../src/core/HttpTransport.ts","../src/node/StatelessStorage.ts","../src/node/index.ts"],"sourcesContent":["export const DEFAULT_ENDPOINT = 'https://ingest.onelence.com';\n\n","export class TransportError extends Error {\r\n public readonly status?: number;\r\n public readonly retryAfterMs?: number;\r\n\r\n constructor(message: string, options: { status?: number; retryAfterMs?: number } = {}) {\r\n super(message);\r\n this.name = 'TransportError';\r\n this.status = options.status;\r\n this.retryAfterMs = options.retryAfterMs;\r\n }\r\n}\r\n\r\n/**\r\n * Returns true when a TransportError status should NOT be retried by the queue.\r\n * 4xx client errors (other than 408 Request Timeout and 429 Too Many Requests)\r\n * indicate a request problem that re-sending will not fix.\r\n */\r\nexport function isNonRetriableStatus(status: number | undefined): boolean {\r\n if (typeof status !== 'number') return false;\r\n if (status < 400 || status >= 500) return false;\r\n if (status === 408 || status === 429) return false;\r\n return true;\r\n}\r\n\r\nexport function parseRetryAfter(headerValue: string | null | undefined): number | undefined {\r\n if (!headerValue) return undefined;\r\n const trimmed = headerValue.trim();\r\n if (!trimmed) return undefined;\r\n const numeric = Number(trimmed);\r\n if (Number.isFinite(numeric) && numeric >= 0) {\r\n return Math.floor(numeric * 1000);\r\n }\r\n const dateMs = Date.parse(trimmed);\r\n if (Number.isFinite(dateMs)) {\r\n const diff = dateMs - Date.now();\r\n return diff > 0 ? diff : 0;\r\n }\r\n return undefined;\r\n}\r\n","import type { TransportAdapter } from './adapters';\r\nimport { TransportError, isNonRetriableStatus } from './TransportError';\r\n\r\ninterface QueueItem {\r\n path: string;\r\n data: Record<string, unknown>;\r\n attempts: number;\r\n nextAttemptAt: number;\r\n enqueuedAt: number;\r\n}\r\n\r\nexport interface DeliveryQueueOptions {\r\n debug?: boolean;\r\n maxAttempts?: number;\r\n baseBackoffMs?: number;\r\n maxBackoffMs?: number;\r\n /**\r\n * Maximum age (ms) for persisted queue items before they are dropped on load\r\n * or next process tick. Defaults to 48 hours.\r\n */\r\n maxItemAgeMs?: number;\r\n loadPersisted?: () => QueueItem[];\r\n savePersisted?: (items: QueueItem[]) => void;\r\n onError?: (error: unknown, event?: Record<string, unknown>) => void;\r\n}\r\n\r\nconst DEFAULT_MAX_ATTEMPTS = 5;\r\nconst DEFAULT_BASE_BACKOFF_MS = 300;\r\nconst DEFAULT_MAX_BACKOFF_MS = 15000;\r\nconst DEFAULT_MAX_ITEM_AGE_MS = 48 * 60 * 60 * 1000;\r\n\r\nexport class DeliveryQueue {\r\n private readonly queue: QueueItem[] = [];\r\n private flushing = false;\r\n private sent = 0;\r\n private failed = 0;\r\n private dropped = 0;\r\n private readonly maxAttempts: number;\r\n private readonly baseBackoffMs: number;\r\n private readonly maxBackoffMs: number;\r\n private readonly maxItemAgeMs: number;\r\n private readonly debug: boolean;\r\n private readonly loadPersisted?: () => QueueItem[];\r\n private readonly savePersisted?: (items: QueueItem[]) => void;\r\n private readonly onError?: (error: unknown, event?: Record<string, unknown>) => void;\r\n\r\n constructor(private readonly transport: TransportAdapter, options: DeliveryQueueOptions = {}) {\r\n this.maxAttempts = options.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;\r\n this.baseBackoffMs = options.baseBackoffMs ?? DEFAULT_BASE_BACKOFF_MS;\r\n this.maxBackoffMs = options.maxBackoffMs ?? DEFAULT_MAX_BACKOFF_MS;\r\n this.maxItemAgeMs = options.maxItemAgeMs ?? DEFAULT_MAX_ITEM_AGE_MS;\r\n this.debug = options.debug ?? false;\r\n this.loadPersisted = options.loadPersisted;\r\n this.savePersisted = options.savePersisted;\r\n this.onError = options.onError;\r\n const persisted = this.loadPersisted?.() ?? [];\r\n if (persisted.length > 0) {\r\n const now = Date.now();\r\n for (const item of persisted) {\r\n const enqueuedAt = item.enqueuedAt ?? now;\r\n if (now - enqueuedAt > this.maxItemAgeMs) {\r\n this.dropped += 1;\r\n continue;\r\n }\r\n this.queue.push({ ...item, enqueuedAt });\r\n }\r\n this.persist();\r\n }\r\n }\r\n\r\n public enqueue(path: string, data: Record<string, unknown>) {\r\n this.queue.push({\r\n path,\r\n data,\r\n attempts: 0,\r\n nextAttemptAt: Date.now(),\r\n enqueuedAt: Date.now(),\r\n });\r\n this.persist();\r\n void this.process();\r\n }\r\n\r\n public async flush() {\r\n await this.process(true);\r\n await this.transport.flush?.();\r\n }\r\n\r\n /**\r\n * Best-effort synchronous drain using sendBeacon. Intended for page unload;\r\n * errors are swallowed because the tab is going away.\r\n */\r\n public drainViaBeacon() {\r\n if (this.queue.length === 0) return;\r\n const items = this.queue.splice(0, this.queue.length);\r\n this.persist();\r\n for (const item of items) {\r\n const eventData = { ...item.data };\r\n delete eventData.__prefer_beacon;\r\n try {\r\n void this.transport.send(item.path, eventData, { preferBeacon: true });\r\n } catch {\r\n // ignore: browser is unloading\r\n }\r\n }\r\n }\r\n\r\n public getStats() {\r\n return {\r\n queued: this.queue.length,\r\n sent: this.sent,\r\n failed: this.failed,\r\n dropped: this.dropped,\r\n };\r\n }\r\n\r\n public clear() {\r\n this.queue.splice(0, this.queue.length);\r\n this.persist();\r\n }\r\n\r\n private persist() {\r\n this.savePersisted?.(this.queue);\r\n }\r\n\r\n private evictExpired() {\r\n if (this.queue.length === 0) return;\r\n const now = Date.now();\r\n const cutoff = now - this.maxItemAgeMs;\r\n let removed = 0;\r\n for (let i = this.queue.length - 1; i >= 0; i -= 1) {\r\n if (this.queue[i].enqueuedAt <= cutoff) {\r\n this.queue.splice(i, 1);\r\n this.dropped += 1;\r\n removed += 1;\r\n }\r\n }\r\n if (removed > 0) {\r\n this.persist();\r\n }\r\n }\r\n\r\n private async process(force = false): Promise<void> {\r\n if (this.flushing) {\r\n return;\r\n }\r\n this.flushing = true;\r\n try {\r\n this.evictExpired();\r\n while (this.queue.length > 0) {\r\n const item = this.queue[0];\r\n if (!force && item.nextAttemptAt > Date.now()) {\r\n break;\r\n }\r\n try {\r\n const eventData = { ...item.data };\r\n const preferBeacon = eventData.__prefer_beacon === true;\r\n delete eventData.__prefer_beacon;\r\n await this.transport.send(item.path, eventData, { preferBeacon });\r\n this.queue.shift();\r\n this.sent += 1;\r\n this.persist();\r\n } catch (error) {\r\n this.failed += 1;\r\n this.onError?.(error, item.data);\r\n\r\n const status = error instanceof TransportError ? error.status : undefined;\r\n if (isNonRetriableStatus(status)) {\r\n this.queue.shift();\r\n this.dropped += 1;\r\n this.persist();\r\n if (this.debug) {\r\n console.error('[Mark] Dropping event after non-retriable status', status, item.path);\r\n }\r\n continue;\r\n }\r\n\r\n item.attempts += 1;\r\n if (item.attempts >= this.maxAttempts) {\r\n this.queue.shift();\r\n this.dropped += 1;\r\n this.persist();\r\n if (this.debug) {\r\n console.error('[Mark] Dropping event after max retries', item.path, error);\r\n }\r\n continue;\r\n }\r\n const retryAfterMs = error instanceof TransportError ? error.retryAfterMs : undefined;\r\n let delay: number;\r\n if (typeof retryAfterMs === 'number') {\r\n delay = Math.min(this.maxBackoffMs, Math.max(0, retryAfterMs));\r\n } else {\r\n const jitter = Math.random() * this.baseBackoffMs;\r\n delay = Math.min(\r\n this.maxBackoffMs,\r\n this.baseBackoffMs * 2 ** (item.attempts - 1) + jitter\r\n );\r\n }\r\n item.nextAttemptAt = Date.now() + delay;\r\n this.persist();\r\n break;\r\n }\r\n }\r\n } finally {\r\n this.flushing = false;\r\n }\r\n }\r\n}\r\n","import type {\r\n MarkConfig,\r\n TrackEventData,\r\n ConversionProperties,\r\n IdentifyTraits,\r\n ConsentStatus,\r\n JsonValue,\r\n} from '../types';\r\nimport type { MarkDependencies } from './adapters';\r\nimport { DEFAULT_ENDPOINT } from './constants';\r\nimport { DeliveryQueue } from './DeliveryQueue';\r\n\r\ntype ResolvedConfig = MarkConfig & { endpoint: string };\r\nconst TRACK_RESERVED_FIELDS = new Set(['event_name', 'user_id', 'consent_state', 'source', 'is_conversion']);\r\nconst IDENTIFY_RESERVED_FIELDS = new Set([\r\n 'user_id',\r\n 'visitor_id',\r\n 'click_id',\r\n 'campaign_id',\r\n 'query',\r\n 'consent_state',\r\n 'source',\r\n]);\r\n\r\nexport class MarkCore {\r\n private config: ResolvedConfig;\r\n private consentRequirement: boolean | 'auto';\r\n private siteId?: string;\r\n private siteHost?: string;\r\n private readonly queue: DeliveryQueue;\r\n private readonly sessionTimeoutMs: number;\r\n private batchTimer: ReturnType<typeof setTimeout> | null = null;\r\n private readonly batchedEvents: Array<Record<string, unknown>> = [];\r\n private tcfCachedAllowed = false;\r\n\r\n constructor(config: MarkConfig, private readonly deps: MarkDependencies) {\r\n this.validateConfig(config);\r\n this.config = {\r\n endpoint: config.endpoint ?? DEFAULT_ENDPOINT,\r\n ...config,\r\n include_page_context: config.include_page_context ?? true,\r\n };\r\n this.consentRequirement = config.require_consent ?? false;\r\n this.siteId = config.site_id;\r\n this.siteHost = config.site_host;\r\n this.sessionTimeoutMs = config.session_timeout_ms ?? 30 * 60 * 1000;\r\n this.queue = new DeliveryQueue(this.deps.transport, {\r\n debug: this.config.debug,\r\n loadPersisted: () => (this.deps.storage.getOutbox?.() as any[]) ?? [],\r\n savePersisted: (items) => this.deps.storage.setOutbox?.(items as unknown[]),\r\n onError: (error, event) => this.config.on_error?.(error, event),\r\n });\r\n this.warnMisconfiguredSiteHost();\r\n this.subscribeTcf();\r\n }\r\n\r\n /**\r\n * Best-effort synchronous drain that dispatches all queued events via\r\n * sendBeacon. Intended for use on page unload (visibilitychange=hidden,\r\n * pagehide) where async fetch may be cancelled by the browser.\r\n */\r\n public drainViaBeacon() {\r\n this.flushBatch();\r\n this.queue.drainViaBeacon();\r\n }\r\n\r\n /**\r\n * Kicks the queue to retry any pending items now. Safe to call repeatedly;\r\n * used by the browser wrapper in response to `online` events or periodic\r\n * timers.\r\n */\r\n public kickQueue() {\r\n void this.queue.flush();\r\n }\r\n\r\n public track(eventName: string, data: TrackEventData = {}) {\r\n return this.trackInternal(eventName, data, false);\r\n }\r\n\r\n private trackInternal(\r\n eventName: string,\r\n data: TrackEventData = {},\r\n isConversion = false,\r\n options?: { preferBeacon?: boolean }\r\n ) {\r\n if (!eventName) {\r\n if (this.config.debug) {\r\n console.warn('[Mark] track called without event name');\r\n }\r\n return false;\r\n }\r\n\r\n if (!this.hasConsent() || this.isDntBlocked()) {\r\n if (this.config.debug) {\r\n console.warn('[Mark] Tracking blocked due to consent requirement.');\r\n }\r\n return false;\r\n }\r\n\r\n if (!isConversion && !this.shouldSampleTrack()) {\r\n return true;\r\n }\r\n\r\n const sanitizedData = this.sanitizeTrackData(data);\r\n const restData: TrackEventData = { ...sanitizedData };\r\n if ('query' in restData) {\r\n delete restData.query;\r\n }\r\n if ('site_id' in restData) {\r\n delete restData.site_id;\r\n }\r\n if ('site_host' in restData) {\r\n delete restData.site_host;\r\n }\r\n const payload: Record<string, unknown> = {\r\n event_name: eventName,\r\n message_id: this.createMessageId(),\r\n ...this.getIdentityFields(sanitizedData),\r\n ...restData,\r\n };\r\n if (isConversion) {\r\n payload.is_conversion = true;\r\n }\r\n\r\n // Add site_id and site_host if configured (event-level overrides init-level)\r\n const siteId = sanitizedData.site_id ?? this.siteId;\r\n const siteHost = sanitizedData.site_host ?? this.siteHost;\r\n\r\n if (siteId) {\r\n payload.site_id = siteId;\r\n }\r\n if (siteHost) {\r\n payload.site_host = siteHost;\r\n }\r\n\r\n if (this.config.include_page_context && typeof window !== 'undefined') {\r\n this.applyPageContext(payload);\r\n // If site_host wasn't explicitly set, use the host from page context\r\n if (!siteHost && payload.site) {\r\n payload.site_host = payload.site;\r\n }\r\n }\r\n\r\n this.applyInternalFlag(payload, sanitizedData.is_internal);\r\n\r\n const transformed = this.config.before_send ? this.config.before_send(payload) : payload;\r\n if (!transformed) {\r\n return true;\r\n }\r\n\r\n this.ensureSession();\r\n this.applySessionFields(transformed);\r\n if (this.config.batching?.enabled && !isConversion && !options?.preferBeacon) {\r\n this.enqueueBatch(transformed);\r\n return true;\r\n }\r\n this.queue.enqueue('/event', { ...transformed, __prefer_beacon: options?.preferBeacon === true });\r\n return true;\r\n }\r\n\r\n public identify(userId: string, traits: IdentifyTraits = {}) {\r\n if (!userId) {\r\n if (this.config.debug) {\r\n console.warn('[Mark] identify called without userId');\r\n }\r\n return;\r\n }\r\n\r\n if (!this.hasConsent() || this.isDntBlocked()) {\r\n if (this.config.debug) {\r\n console.warn('[Mark] Identify blocked due to consent requirement.');\r\n }\r\n return;\r\n }\r\n\r\n this.deps.storage.update({ user_id: userId });\r\n\r\n const payload: Record<string, unknown> = {\r\n user_id: userId,\r\n message_id: this.createMessageId(),\r\n ...this.sanitizeIdentifyTraits(traits),\r\n ...this.getIdentityFields(),\r\n };\r\n\r\n // Add site_id and site_host if configured\r\n if (this.siteId) {\r\n payload.site_id = this.siteId;\r\n }\r\n if (this.siteHost) {\r\n payload.site_host = this.siteHost;\r\n }\r\n\r\n this.applyInternalFlag(payload);\r\n\r\n const transformed = this.config.before_send ? this.config.before_send(payload) : payload;\r\n if (!transformed) return;\r\n\r\n this.ensureSession();\r\n this.applySessionFields(transformed);\r\n this.queue.enqueue('/identify', transformed);\r\n }\r\n\r\n public conversion(eventName: string, data: ConversionProperties = {}) {\r\n return this.trackInternal(eventName, data, true);\r\n }\r\n\r\n public trackWithOptions(eventName: string, data: TrackEventData = {}, options?: { preferBeacon?: boolean }) {\r\n return this.trackInternal(eventName, data, false, options);\r\n }\r\n\r\n /**\r\n * Returns the current visitor ID from storage, if any.\r\n * Used by browser/Node wrappers to expose a stable pseudonymous ID for server-side attribution.\r\n */\r\n public getVisitorId(): string | undefined {\r\n return this.deps.storage.getVisitorId();\r\n }\r\n\r\n public setConsent(status: ConsentStatus) {\r\n const previous = this.deps.storage.getConsentStatus();\r\n this.deps.storage.setConsentStatus(status);\r\n if (status === 'denied') {\r\n this.deps.storage.clearAttribution?.();\r\n this.deps.storage.clearCookieVisitorId?.();\r\n this.deps.storage.setInternal?.(false);\r\n } else if (\r\n status === 'granted' &&\r\n previous === 'denied' &&\r\n this.config.rotate_visitor_on_consent_change\r\n ) {\r\n this.deps.storage.rotateVisitorId?.();\r\n }\r\n const payload: Record<string, unknown> = {\r\n visitor_id: this.deps.storage.getVisitorId(),\r\n consent_state: status,\r\n source: 'sdk',\r\n message_id: this.createMessageId(),\r\n };\r\n\r\n // Add site_id and site_host if configured\r\n if (this.siteId) {\r\n payload.site_id = this.siteId;\r\n }\r\n if (this.siteHost) {\r\n payload.site_host = this.siteHost;\r\n }\r\n\r\n this.applyInternalFlag(payload);\r\n\r\n const transformed = this.config.before_send ? this.config.before_send(payload) : payload;\r\n if (!transformed) return;\r\n this.queue.enqueue('/consent', transformed);\r\n }\r\n\r\n public reset() {\r\n this.deps.storage.update({\r\n user_id: undefined,\r\n last_click_id: undefined,\r\n campaign_id: undefined,\r\n query_params: undefined,\r\n session_id: undefined,\r\n session_started_at: undefined,\r\n last_activity_at: undefined,\r\n is_internal: undefined,\r\n });\r\n this.deps.storage.rotateVisitorId?.();\r\n }\r\n\r\n /**\r\n * Marks or unmarks the current visitor as internal traffic. While set, every\r\n * subsequent event (track/identify/conversion/consent) is stamped with\r\n * `is_internal: true`, so the backend can exclude it from customer-facing\r\n * reports by default.\r\n *\r\n * The flag is persisted via the storage adapter (browser: localStorage) so\r\n * it survives reloads, and is cleared by `reset()` and by\r\n * `setConsent('denied')`.\r\n */\r\n public setInternal(value: boolean) {\r\n this.deps.storage.setInternal?.(Boolean(value));\r\n }\r\n\r\n /**\r\n * Returns the currently persisted internal-traffic flag, if any.\r\n */\r\n public getInternal(): boolean {\r\n return Boolean(this.deps.storage.getInternal?.());\r\n }\r\n\r\n public flush() {\r\n this.flushBatch();\r\n return this.queue.flush();\r\n }\r\n\r\n public getStats() {\r\n return this.queue.getStats();\r\n }\r\n\r\n /**\r\n * Stamps `is_internal: true` on the payload when either:\r\n * - the persistent visitor flag is set (via setInternal), or\r\n * - the caller passed `is_internal: true` on this specific event.\r\n *\r\n * Explicit `is_internal: false` on a single event wins over the visitor flag\r\n * so individual calls can opt out.\r\n */\r\n private applyInternalFlag(payload: Record<string, unknown>, eventValue?: boolean) {\r\n if (eventValue === false) {\r\n delete payload.is_internal;\r\n return;\r\n }\r\n const stored = this.deps.storage.getInternal?.() === true;\r\n if (eventValue === true || stored) {\r\n payload.is_internal = true;\r\n } else {\r\n delete payload.is_internal;\r\n }\r\n }\r\n\r\n private getIdentityFields(data?: TrackEventData) {\r\n const visitorId = data?.visitor_id ?? this.deps.storage.getVisitorId();\r\n const userId = data?.user_id ?? this.deps.storage.getUserId?.();\r\n const clickId = data?.click_id ?? this.deps.storage.getLastClickId();\r\n const campaignId = data?.campaign_id ?? this.deps.storage.getCampaignId();\r\n const sessionId = data?.session_id ?? this.deps.storage.getSessionId?.();\r\n\r\n // Auto-capture query params from storage or event data\r\n const storedQuery = this.deps.storage.getQueryParams() ?? {};\r\n const inputQuery = (data?.query as Record<string, string> | undefined) ?? {};\r\n\r\n // Merge: stored values serve as defaults, input values override/append\r\n const query = { ...storedQuery, ...inputQuery };\r\n\r\n const identity: Record<string, unknown> = {};\r\n\r\n if (visitorId) identity.visitor_id = visitorId;\r\n if (userId) identity.user_id = userId;\r\n if (clickId) identity.click_id = clickId;\r\n if (campaignId) identity.campaign_id = campaignId;\r\n if (sessionId) identity.session_id = sessionId;\r\n\r\n const sessionStartedAt = this.deps.storage.getSessionStartedAt?.();\r\n if (sessionId && sessionStartedAt) {\r\n identity.session_started_at = sessionStartedAt;\r\n identity.session_elapsed_ms = Date.now() - Date.parse(sessionStartedAt);\r\n }\r\n\r\n if (Object.keys(query).length > 0) {\r\n identity.query = query;\r\n }\r\n\r\n return identity;\r\n }\r\n\r\n /**\r\n * Patches session fields after ensureSession when the first event in a session was built\r\n * before rotation created an id. Rotation events keep the previous session_id from identity.\r\n */\r\n private applySessionFields(payload: Record<string, unknown>) {\r\n if (!payload.session_id) {\r\n const sessionId = this.deps.storage.getSessionId?.();\r\n const sessionStartedAt = this.deps.storage.getSessionStartedAt?.();\r\n if (sessionId) payload.session_id = sessionId;\r\n if (sessionStartedAt) payload.session_started_at = sessionStartedAt;\r\n }\r\n const startedAt = payload.session_started_at;\r\n if (payload.session_id && typeof startedAt === 'string') {\r\n payload.session_elapsed_ms = Date.now() - Date.parse(startedAt);\r\n }\r\n }\r\n\r\n private hasConsent() {\r\n if (this.config.consent_source?.type === 'tcf' && typeof window !== 'undefined') {\r\n if (!this.tcfCachedAllowed) {\r\n return false;\r\n }\r\n }\r\n if (!this.consentRequirement) {\r\n return true;\r\n }\r\n\r\n const stored = this.deps.storage.getConsentStatus();\r\n const state = stored;\r\n\r\n if (this.consentRequirement === 'auto') {\r\n // In auto mode, default to deny until consent is explicitly granted.\r\n return state === 'granted';\r\n }\r\n\r\n return state === 'granted';\r\n }\r\n\r\n private sanitizeTrackData(data: TrackEventData) {\r\n const sanitized: Record<string, JsonValue | undefined> = {};\r\n for (const [key, value] of Object.entries(data)) {\r\n if (TRACK_RESERVED_FIELDS.has(key)) {\r\n continue;\r\n }\r\n sanitized[key] = value;\r\n }\r\n return sanitized as TrackEventData;\r\n }\r\n\r\n private sanitizeIdentifyTraits(traits: IdentifyTraits) {\r\n const sanitized: Record<string, JsonValue | undefined> = {};\r\n for (const [key, value] of Object.entries(traits)) {\r\n if (IDENTIFY_RESERVED_FIELDS.has(key)) {\r\n continue;\r\n }\r\n sanitized[key] = value;\r\n }\r\n return sanitized as IdentifyTraits;\r\n }\r\n\r\n private validateConfig(config: MarkConfig) {\r\n if (!config.key || !config.key.trim()) {\r\n throw new Error('[Mark] `key` must be a non-empty string.');\r\n }\r\n if (config.endpoint) {\r\n try {\r\n // eslint-disable-next-line no-new\r\n new URL(config.endpoint);\r\n } catch {\r\n throw new Error('[Mark] `endpoint` must be a valid absolute URL.');\r\n }\r\n }\r\n if (typeof config.site_id === 'string' && !config.site_id.trim()) {\r\n throw new Error('[Mark] `site_id` cannot be an empty string.');\r\n }\r\n if (typeof config.site_host === 'string' && !config.site_host.trim()) {\r\n throw new Error('[Mark] `site_host` cannot be an empty string.');\r\n }\r\n }\r\n\r\n private applyPageContext(payload: Record<string, unknown>) {\r\n if (typeof document === 'undefined') {\r\n return;\r\n }\r\n if (!payload.site) payload.site = window.location.host;\r\n if (!payload.page) payload.page = window.location.pathname;\r\n if (!payload.title) payload.title = document.title;\r\n if (!payload.referrer && document.referrer) {\r\n payload.referrer = this.scrubReferrer(document.referrer);\r\n }\r\n // full URL omitted for privacy; include only if explicitly provided by the caller\r\n }\r\n\r\n private enqueueBatch(event: Record<string, unknown>) {\r\n this.batchedEvents.push(event);\r\n const maxSize = this.config.batching?.max_size ?? 20;\r\n if (this.batchedEvents.length >= maxSize) {\r\n this.flushBatch();\r\n return;\r\n }\r\n if (!this.batchTimer) {\r\n const intervalMs = this.config.batching?.flush_interval_ms ?? 2000;\r\n this.batchTimer = setTimeout(() => {\r\n this.batchTimer = null;\r\n this.flushBatch();\r\n }, intervalMs);\r\n }\r\n }\r\n\r\n private flushBatch() {\r\n if (this.batchedEvents.length === 0) return;\r\n const path = this.config.batching?.endpoint_path ?? '/events';\r\n const payload = this.batchedEvents.splice(0, this.batchedEvents.length);\r\n this.queue.enqueue(path, { events: payload, message_id: this.createMessageId() });\r\n }\r\n\r\n private createMessageId() {\r\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\r\n return crypto.randomUUID();\r\n }\r\n return `msg_${Date.now()}_${Math.random().toString(16).slice(2)}`;\r\n }\r\n\r\n private ensureSession() {\r\n const now = Date.now();\r\n const currentId = this.deps.storage.getSessionId?.();\r\n const lastActivityIso = this.deps.storage.getLastActivityAt?.();\r\n const lastActivity = lastActivityIso ? Date.parse(lastActivityIso) : 0;\r\n const shouldRotate =\r\n !currentId ||\r\n !lastActivity ||\r\n now - lastActivity >= this.sessionTimeoutMs ||\r\n this.crossedUtcDay(lastActivity, now);\r\n if (shouldRotate) {\r\n const newSessionId = this.createMessageId();\r\n const nowIso = new Date(now).toISOString();\r\n this.deps.storage.update({\r\n session_id: newSessionId,\r\n session_started_at: nowIso,\r\n last_activity_at: nowIso,\r\n });\r\n return;\r\n }\r\n this.deps.storage.update({ last_activity_at: new Date(now).toISOString() });\r\n }\r\n\r\n private crossedUtcDay(previousTs: number, currentTs: number) {\r\n const prev = new Date(previousTs);\r\n const next = new Date(currentTs);\r\n return (\r\n prev.getUTCFullYear() !== next.getUTCFullYear() ||\r\n prev.getUTCMonth() !== next.getUTCMonth() ||\r\n prev.getUTCDate() !== next.getUTCDate()\r\n );\r\n }\r\n\r\n private shouldSampleTrack() {\r\n if (typeof this.config.sample_rate !== 'number') return true;\r\n if (this.config.sample_rate <= 0) return false;\r\n if (this.config.sample_rate >= 1) return true;\r\n return Math.random() <= this.config.sample_rate;\r\n }\r\n\r\n private isDntBlocked() {\r\n if (!this.config.honor_dnt || typeof navigator === 'undefined') {\r\n return false;\r\n }\r\n const dnt = navigator.doNotTrack;\r\n const gpc = (navigator as Navigator & { globalPrivacyControl?: boolean }).globalPrivacyControl;\r\n return dnt === '1' || gpc === true;\r\n }\r\n\r\n private scrubReferrer(referrer: string) {\r\n try {\r\n const refUrl = new URL(referrer);\r\n if (typeof window === 'undefined') return referrer;\r\n const currentUrl = new URL(window.location.href);\r\n if (refUrl.origin !== currentUrl.origin) {\r\n refUrl.search = '';\r\n return refUrl.toString();\r\n }\r\n return referrer;\r\n } catch {\r\n return referrer;\r\n }\r\n }\r\n\r\n private warnMisconfiguredSiteHost() {\r\n if (!this.config.debug || !this.siteHost || typeof window === 'undefined') {\r\n return;\r\n }\r\n if (window.location.host !== this.siteHost) {\r\n console.warn('[Mark] config.site_host does not match current host', {\r\n expected: this.siteHost,\r\n actual: window.location.host,\r\n });\r\n }\r\n if (this.siteId && !/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(this.siteId)) {\r\n console.warn('[Mark] config.site_id does not look like UUID v4', this.siteId);\r\n }\r\n }\r\n\r\n /**\r\n * Subscribes to the IAB TCF v2 CMP via `__tcfapi('addEventListener', ...)`.\r\n * Result is cached in `tcfCachedAllowed` so the synchronous `hasConsent()`\r\n * path does not need to await the CMP. If the CMP is not yet present, we\r\n * poll briefly (CMPs commonly load asynchronously) and give up after ~2s.\r\n */\r\n private subscribeTcf() {\r\n const source = this.config.consent_source;\r\n if (source?.type !== 'tcf' || typeof window === 'undefined') return;\r\n const purposes = source.purposes;\r\n\r\n const attempt = (retries: number) => {\r\n try {\r\n const w = window as Window & {\r\n __tcfapi?: (\r\n command: string,\r\n version: number,\r\n callback: (\r\n tcData?: {\r\n eventStatus?: string;\r\n gdprApplies?: boolean;\r\n listenerId?: number;\r\n purpose?: { consents?: Record<string, boolean> };\r\n },\r\n ok?: boolean\r\n ) => void,\r\n parameter?: number\r\n ) => void;\r\n };\r\n if (typeof w.__tcfapi !== 'function') {\r\n if (retries > 0) {\r\n setTimeout(() => attempt(retries - 1), 200);\r\n }\r\n return;\r\n }\r\n w.__tcfapi('addEventListener', 2, (tcData, ok) => {\r\n if (!ok || !tcData) {\r\n this.tcfCachedAllowed = false;\r\n return;\r\n }\r\n if (tcData.gdprApplies === false) {\r\n this.tcfCachedAllowed = true;\r\n return;\r\n }\r\n const consents = tcData.purpose?.consents ?? {};\r\n this.tcfCachedAllowed = purposes.every((p) => consents[String(p)] === true);\r\n });\r\n } catch {\r\n this.tcfCachedAllowed = false;\r\n }\r\n };\r\n\r\n attempt(10);\r\n }\r\n}\r\n\r\n","import type { MarkConfig } from '../types';\r\nimport { DEFAULT_ENDPOINT } from './constants';\r\nimport type { TransportAdapter } from './adapters';\r\nimport { TransportError, parseRetryAfter } from './TransportError';\r\n\r\nexport class HttpTransport implements TransportAdapter {\r\n private readonly config: MarkConfig;\r\n private readonly endpoint: string;\r\n private readonly pending = new Set<Promise<void>>();\r\n\r\n constructor(config: MarkConfig) {\r\n this.config = config;\r\n this.endpoint = config.endpoint ?? DEFAULT_ENDPOINT;\r\n }\r\n\r\n public async send(path: string, data: unknown, options?: { preferBeacon?: boolean }) {\r\n const request = this.sendInternal(path, data, options);\r\n this.pending.add(request);\r\n try {\r\n await request;\r\n } finally {\r\n this.pending.delete(request);\r\n }\r\n }\r\n\r\n public async flush() {\r\n if (this.pending.size === 0) {\r\n return;\r\n }\r\n await Promise.allSettled(Array.from(this.pending));\r\n }\r\n\r\n private async sendInternal(path: string, data: unknown, options?: { preferBeacon?: boolean }) {\r\n const url = this.joinUrl(this.endpoint, path);\r\n const key = this.config.key;\r\n\r\n const headers: Record<string, string> = {\r\n 'Content-Type': 'application/json',\r\n [key.startsWith('sk_') ? 'x-secret-key' : 'x-publishable-key']: key,\r\n };\r\n\r\n if (this.config.debug) {\r\n console.log('[Mark] Sending', url, data);\r\n }\r\n\r\n const body = JSON.stringify(data);\r\n if (options?.preferBeacon && typeof navigator !== 'undefined' && typeof navigator.sendBeacon === 'function') {\r\n const blob = new Blob([body], { type: 'application/json' });\r\n const sent = navigator.sendBeacon(url, blob);\r\n if (sent) {\r\n return;\r\n }\r\n }\r\n\r\n if (typeof fetch !== 'function') {\r\n if (this.config.debug) {\r\n console.error('[Mark] Global fetch is not available in this runtime.');\r\n }\r\n throw new TransportError('[Mark] Global fetch is not available in this runtime.');\r\n }\r\n\r\n const timeoutMs = this.config.request_timeout_ms ?? 10000;\r\n const controller = new AbortController();\r\n let timedOut = false;\r\n const timer = setTimeout(() => {\r\n timedOut = true;\r\n controller.abort();\r\n }, timeoutMs);\r\n\r\n try {\r\n const response = await fetch(url, {\r\n method: 'POST',\r\n headers,\r\n body,\r\n keepalive: true,\r\n signal: controller.signal,\r\n });\r\n if (!response.ok) {\r\n const snippet = await this.readErrorSnippet(response);\r\n const retryAfterMs = parseRetryAfter(response.headers.get('Retry-After'));\r\n if (this.config.debug) {\r\n console.error('[Mark] Request rejected', {\r\n url,\r\n status: response.status,\r\n statusText: response.statusText,\r\n body: snippet,\r\n retryAfterMs,\r\n });\r\n }\r\n throw new TransportError(\r\n `[Mark] Request rejected with status ${response.status}: ${snippet}`,\r\n { status: response.status, retryAfterMs }\r\n );\r\n }\r\n } catch (error) {\r\n if (this.config.debug) {\r\n console.error('[Mark] Failed to send', url, error);\r\n }\r\n if (error instanceof TransportError) {\r\n throw error;\r\n }\r\n if (timedOut) {\r\n throw new TransportError(`[Mark] Request timed out after ${timeoutMs}ms`, { status: 408 });\r\n }\r\n const message = error instanceof Error ? error.message : String(error);\r\n throw new TransportError(`[Mark] Network error: ${message}`);\r\n } finally {\r\n clearTimeout(timer);\r\n }\r\n }\r\n\r\n private joinUrl(endpoint: string, path: string) {\r\n const normalizedEndpoint = endpoint.replace(/\\/+$/, '');\r\n const normalizedPath = path.replace(/^\\/+/, '');\r\n return `${normalizedEndpoint}/${normalizedPath}`;\r\n }\r\n\r\n private async readErrorSnippet(response: Response) {\r\n try {\r\n const text = await response.text();\r\n return text.slice(0, 300);\r\n } catch {\r\n return '';\r\n }\r\n }\r\n}\r\n","import type { ConsentStatus, StorageData } from '../types';\nimport type { StorageAdapter } from '../core/adapters';\n\n/**\n * Stateless storage adapter used by default in server runtimes.\n * It simply exposes optional defaults and ignores updates.\n */\nexport class StatelessStorage implements StorageAdapter {\n constructor(private defaults: StorageData = {}) { }\n\n public getVisitorId() {\n return this.defaults.visitor_id;\n }\n\n public getLastClickId() {\n return this.defaults.last_click_id;\n }\n\n public getCampaignId() {\n return this.defaults.campaign_id;\n }\n\n public getQueryParams() {\n return this.defaults.query_params;\n }\n\n public getUserId() {\n return this.defaults.user_id;\n }\n\n public getSessionId() {\n return this.defaults.session_id;\n }\n\n public getSessionStartedAt() {\n return this.defaults.session_started_at;\n }\n\n public getLastActivityAt() {\n return this.defaults.last_activity_at;\n }\n\n public getConsentStatus(): ConsentStatus | undefined {\n return this.defaults.consent_status as ConsentStatus | undefined;\n }\n\n public update(): void {\n // Server runtimes should provide identifiers explicitly.\n }\n\n public setConsentStatus(): void {\n // Stateless; no-op.\n }\n\n public clearAttribution(): void {\n // Stateless; no-op.\n }\n\n public clearCookieVisitorId(): void {\n // Stateless; no-op.\n }\n\n public rotateVisitorId(): void {\n // Stateless; no-op.\n }\n\n public getInternal(): boolean | undefined {\n return this.defaults.is_internal;\n }\n\n public setInternal(value: boolean): void {\n this.defaults.is_internal = value ? true : undefined;\n }\n}\n\n","import type {\n MarkConfig,\n TrackEventData,\n IdentifyTraits,\n StorageData,\n ConsentStatus,\n} from '../types';\nimport { MarkCore } from '../core/MarkCore';\nimport { HttpTransport } from '../core/HttpTransport';\nimport type { StorageAdapter, TransportAdapter } from '../core/adapters';\nimport { StatelessStorage } from './StatelessStorage';\n\nexport interface NodeMarkOptions {\n storage?: StorageAdapter;\n transport?: TransportAdapter;\n storageDefaults?: StorageData;\n}\n\nexport class NodeMark {\n constructor(private readonly client: MarkCore) {}\n\n public track(eventName: string, data: TrackEventData = {}) {\n this.client.track(eventName, data);\n }\n\n public conversion(eventName: string, data: TrackEventData = {}) {\n this.client.conversion(eventName, data);\n }\n\n public identify(userId: string, traits: IdentifyTraits = {}) {\n this.client.identify(userId, traits);\n }\n\n public setConsent(status: ConsentStatus) {\n this.client.setConsent(status);\n }\n\n public flush() {\n return this.client.flush();\n }\n\n public reset() {\n this.client.reset();\n }\n\n public getStats() {\n return this.client.getStats();\n }\n\n /**\n * Returns the visitor ID from the configured storage, if any.\n * With default StatelessStorage, this is the value passed via `storageDefaults.visitor_id` when creating the client.\n * Use it to associate server-side events with the same visitor dimension as browser events.\n */\n public getVisitorId(): string | undefined {\n return this.client.getVisitorId();\n }\n\n /**\n * Marks the current request/visitor as internal traffic. When using the\n * default StatelessStorage, the flag is scoped to this NodeMark instance.\n */\n public setInternal(value: boolean) {\n this.client.setInternal(value);\n }\n\n public getInternal(): boolean {\n return this.client.getInternal();\n }\n}\n\nexport const createNodeMark = (config: MarkConfig, options: NodeMarkOptions = {}) => {\n const storage =\n options.storage ??\n new StatelessStorage({\n visitor_id: options.storageDefaults?.visitor_id,\n user_id: options.storageDefaults?.user_id,\n session_id: options.storageDefaults?.session_id,\n session_started_at: options.storageDefaults?.session_started_at,\n last_activity_at: options.storageDefaults?.last_activity_at,\n last_click_id: options.storageDefaults?.last_click_id,\n campaign_id: options.storageDefaults?.campaign_id,\n query_params: options.storageDefaults?.query_params,\n consent_status: options.storageDefaults?.consent_status,\n is_internal: options.storageDefaults?.is_internal,\n });\n\n const transport = options.transport ?? new HttpTransport(config);\n const client = new MarkCore(config, { storage, transport });\n\n return new NodeMark(client);\n};\n\nexport { StatelessStorage };\n\n"],"names":["DEFAULT_ENDPOINT","TransportError","message","options","isNonRetriableStatus","status","parseRetryAfter","headerValue","trimmed","numeric","dateMs","diff","DEFAULT_MAX_ATTEMPTS","DEFAULT_BASE_BACKOFF_MS","DEFAULT_MAX_BACKOFF_MS","DEFAULT_MAX_ITEM_AGE_MS","DeliveryQueue","transport","persisted","now","item","enqueuedAt","path","data","items","eventData","cutoff","removed","force","preferBeacon","error","retryAfterMs","delay","jitter","TRACK_RESERVED_FIELDS","IDENTIFY_RESERVED_FIELDS","MarkCore","config","deps","event","eventName","isConversion","sanitizedData","restData","payload","siteId","siteHost","transformed","userId","traits","previous","value","eventValue","stored","visitorId","clickId","campaignId","sessionId","storedQuery","inputQuery","query","identity","sessionStartedAt","startedAt","state","sanitized","key","maxSize","intervalMs","currentId","lastActivityIso","lastActivity","newSessionId","nowIso","previousTs","currentTs","prev","next","dnt","gpc","referrer","refUrl","currentUrl","source","purposes","attempt","retries","w","tcData","ok","consents","p","HttpTransport","request","url","headers","body","blob","timeoutMs","controller","timedOut","timer","response","snippet","endpoint","normalizedEndpoint","normalizedPath","StatelessStorage","defaults","NodeMark","client","createNodeMark","storage"],"mappings":"gFAAO,MAAMA,EAAmB,8BCAzB,MAAMC,UAAuB,KAAM,CACxB,OACA,aAEhB,YAAYC,EAAiBC,EAAsD,GAAI,CACrF,MAAMD,CAAO,EACb,KAAK,KAAO,iBACZ,KAAK,OAASC,EAAQ,OACtB,KAAK,aAAeA,EAAQ,YAC9B,CACF,CAOO,SAASC,EAAqBC,EAAqC,CAGxE,MAFI,SAAOA,GAAW,UAClBA,EAAS,KAAOA,GAAU,KAC1BA,IAAW,KAAOA,IAAW,IAEnC,CAEO,SAASC,EAAgBC,EAA4D,CAC1F,GAAI,CAACA,EAAa,OAClB,MAAMC,EAAUD,EAAY,KAAA,EAC5B,GAAI,CAACC,EAAS,OACd,MAAMC,EAAU,OAAOD,CAAO,EAC9B,GAAI,OAAO,SAASC,CAAO,GAAKA,GAAW,EACzC,OAAO,KAAK,MAAMA,EAAU,GAAI,EAElC,MAAMC,EAAS,KAAK,MAAMF,CAAO,EACjC,GAAI,OAAO,SAASE,CAAM,EAAG,CAC3B,MAAMC,EAAOD,EAAS,KAAK,IAAA,EAC3B,OAAOC,EAAO,EAAIA,EAAO,CAC3B,CAEF,CCZA,MAAMC,EAAuB,EACvBC,EAA0B,IAC1BC,EAAyB,KACzBC,EAA0B,KAAU,GAAK,IAExC,MAAMC,CAAc,CAezB,YAA6BC,EAA6Bd,EAAgC,GAAI,CAAjE,KAAA,UAAAc,EAC3B,KAAK,YAAcd,EAAQ,aAAeS,EAC1C,KAAK,cAAgBT,EAAQ,eAAiBU,EAC9C,KAAK,aAAeV,EAAQ,cAAgBW,EAC5C,KAAK,aAAeX,EAAQ,cAAgBY,EAC5C,KAAK,MAAQZ,EAAQ,OAAS,GAC9B,KAAK,cAAgBA,EAAQ,cAC7B,KAAK,cAAgBA,EAAQ,cAC7B,KAAK,QAAUA,EAAQ,QACvB,MAAMe,EAAY,KAAK,gBAAA,GAAqB,CAAA,EAC5C,GAAIA,EAAU,OAAS,EAAG,CACxB,MAAMC,EAAM,KAAK,IAAA,EACjB,UAAWC,KAAQF,EAAW,CAC5B,MAAMG,EAAaD,EAAK,YAAcD,EACtC,GAAIA,EAAME,EAAa,KAAK,aAAc,CACxC,KAAK,SAAW,EAChB,QACF,CACA,KAAK,MAAM,KAAK,CAAE,GAAGD,EAAM,WAAAC,EAAY,CACzC,CACA,KAAK,QAAA,CACP,CACF,CApCiB,MAAqB,CAAA,EAC9B,SAAW,GACX,KAAO,EACP,OAAS,EACT,QAAU,EACD,YACA,cACA,aACA,aACA,MACA,cACA,cACA,QA0BV,QAAQC,EAAcC,EAA+B,CAC1D,KAAK,MAAM,KAAK,CACd,KAAAD,EACA,KAAAC,EACA,SAAU,EACV,cAAe,KAAK,IAAA,EACpB,WAAY,KAAK,IAAA,CAAI,CACtB,EACD,KAAK,QAAA,EACA,KAAK,QAAA,CACZ,CAEA,MAAa,OAAQ,CACnB,MAAM,KAAK,QAAQ,EAAI,EACvB,MAAM,KAAK,UAAU,QAAA,CACvB,CAMO,gBAAiB,CACtB,GAAI,KAAK,MAAM,SAAW,EAAG,OAC7B,MAAMC,EAAQ,KAAK,MAAM,OAAO,EAAG,KAAK,MAAM,MAAM,EACpD,KAAK,QAAA,EACL,UAAWJ,KAAQI,EAAO,CACxB,MAAMC,EAAY,CAAE,GAAGL,EAAK,IAAA,EAC5B,OAAOK,EAAU,gBACjB,GAAI,CACG,KAAK,UAAU,KAAKL,EAAK,KAAMK,EAAW,CAAE,aAAc,GAAM,CACvE,MAAQ,CAER,CACF,CACF,CAEO,UAAW,CAChB,MAAO,CACL,OAAQ,KAAK,MAAM,OACnB,KAAM,KAAK,KACX,OAAQ,KAAK,OACb,QAAS,KAAK,OAAA,CAElB,CAEO,OAAQ,CACb,KAAK,MAAM,OAAO,EAAG,KAAK,MAAM,MAAM,EACtC,KAAK,QAAA,CACP,CAEQ,SAAU,CAChB,KAAK,gBAAgB,KAAK,KAAK,CACjC,CAEQ,cAAe,CACrB,GAAI,KAAK,MAAM,SAAW,EAAG,OAE7B,MAAMC,EADM,KAAK,IAAA,EACI,KAAK,aAC1B,IAAIC,EAAU,EACd,QAAS,EAAI,KAAK,MAAM,OAAS,EAAG,GAAK,EAAG,GAAK,EAC3C,KAAK,MAAM,CAAC,EAAE,YAAcD,IAC9B,KAAK,MAAM,OAAO,EAAG,CAAC,EACtB,KAAK,SAAW,EAChBC,GAAW,GAGXA,EAAU,GACZ,KAAK,QAAA,CAET,CAEA,MAAc,QAAQC,EAAQ,GAAsB,CAClD,GAAI,MAAK,SAGT,MAAK,SAAW,GAChB,GAAI,CAEF,IADA,KAAK,aAAA,EACE,KAAK,MAAM,OAAS,GAAG,CAC5B,MAAMR,EAAO,KAAK,MAAM,CAAC,EACzB,GAAI,CAACQ,GAASR,EAAK,cAAgB,KAAK,MACtC,MAEF,GAAI,CACF,MAAMK,EAAY,CAAE,GAAGL,EAAK,IAAA,EACtBS,EAAeJ,EAAU,kBAAoB,GACnD,OAAOA,EAAU,gBACjB,MAAM,KAAK,UAAU,KAAKL,EAAK,KAAMK,EAAW,CAAE,aAAAI,EAAc,EAChE,KAAK,MAAM,MAAA,EACX,KAAK,MAAQ,EACb,KAAK,QAAA,CACP,OAASC,EAAO,CACd,KAAK,QAAU,EACf,KAAK,UAAUA,EAAOV,EAAK,IAAI,EAE/B,MAAMf,EAASyB,aAAiB7B,EAAiB6B,EAAM,OAAS,OAChE,GAAI1B,EAAqBC,CAAM,EAAG,CAChC,KAAK,MAAM,MAAA,EACX,KAAK,SAAW,EAChB,KAAK,QAAA,EACD,KAAK,OACP,QAAQ,MAAM,mDAAoDA,EAAQe,EAAK,IAAI,EAErF,QACF,CAGA,GADAA,EAAK,UAAY,EACbA,EAAK,UAAY,KAAK,YAAa,CACrC,KAAK,MAAM,MAAA,EACX,KAAK,SAAW,EAChB,KAAK,QAAA,EACD,KAAK,OACP,QAAQ,MAAM,0CAA2CA,EAAK,KAAMU,CAAK,EAE3E,QACF,CACA,MAAMC,EAAeD,aAAiB7B,EAAiB6B,EAAM,aAAe,OAC5E,IAAIE,EACJ,GAAI,OAAOD,GAAiB,SAC1BC,EAAQ,KAAK,IAAI,KAAK,aAAc,KAAK,IAAI,EAAGD,CAAY,CAAC,MACxD,CACL,MAAME,EAAS,KAAK,OAAA,EAAW,KAAK,cACpCD,EAAQ,KAAK,IACX,KAAK,aACL,KAAK,cAAgB,IAAMZ,EAAK,SAAW,GAAKa,CAAA,CAEpD,CACAb,EAAK,cAAgB,KAAK,IAAA,EAAQY,EAClC,KAAK,QAAA,EACL,KACF,CACF,CACF,QAAA,CACE,KAAK,SAAW,EAClB,EACF,CACF,CCjMA,MAAME,MAA4B,IAAI,CAAC,aAAc,UAAW,gBAAiB,SAAU,eAAe,CAAC,EACrGC,MAA+B,IAAI,CACvC,UACA,aACA,WACA,cACA,QACA,gBACA,QACF,CAAC,EAEM,MAAMC,CAAS,CAWpB,YAAYC,EAAqCC,EAAwB,CAAxB,KAAA,KAAAA,EAC/C,KAAK,eAAeD,CAAM,EAC1B,KAAK,OAAS,CACZ,SAAUA,EAAO,UAAYrC,EAC7B,GAAGqC,EACH,qBAAsBA,EAAO,sBAAwB,EAAA,EAEvD,KAAK,mBAAqBA,EAAO,iBAAmB,GACpD,KAAK,OAASA,EAAO,QACrB,KAAK,SAAWA,EAAO,UACvB,KAAK,iBAAmBA,EAAO,oBAAsB,KAAU,IAC/D,KAAK,MAAQ,IAAIrB,EAAc,KAAK,KAAK,UAAW,CAClD,MAAO,KAAK,OAAO,MACnB,cAAe,IAAO,KAAK,KAAK,QAAQ,YAAA,GAA2B,CAAA,EACnE,cAAgBQ,GAAU,KAAK,KAAK,QAAQ,YAAYA,CAAkB,EAC1E,QAAS,CAACM,EAAOS,IAAU,KAAK,OAAO,WAAWT,EAAOS,CAAK,CAAA,CAC/D,EACD,KAAK,0BAAA,EACL,KAAK,aAAA,CACP,CA7BQ,OACA,mBACA,OACA,SACS,MACA,iBACT,WAAmD,KAC1C,cAAgD,CAAA,EACzD,iBAAmB,GA4BpB,gBAAiB,CACtB,KAAK,WAAA,EACL,KAAK,MAAM,eAAA,CACb,CAOO,WAAY,CACZ,KAAK,MAAM,MAAA,CAClB,CAEO,MAAMC,EAAmBjB,EAAuB,GAAI,CACzD,OAAO,KAAK,cAAciB,EAAWjB,EAAM,EAAK,CAClD,CAEQ,cACNiB,EACAjB,EAAuB,CAAA,EACvBkB,EAAe,GACftC,EACA,CACA,GAAI,CAACqC,EACH,OAAI,KAAK,OAAO,OACd,QAAQ,KAAK,wCAAwC,EAEhD,GAGT,GAAI,CAAC,KAAK,WAAA,GAAgB,KAAK,eAC7B,OAAI,KAAK,OAAO,OACd,QAAQ,KAAK,qDAAqD,EAE7D,GAGT,GAAI,CAACC,GAAgB,CAAC,KAAK,oBACzB,MAAO,GAGT,MAAMC,EAAgB,KAAK,kBAAkBnB,CAAI,EAC3CoB,EAA2B,CAAE,GAAGD,CAAA,EAClC,UAAWC,GACb,OAAOA,EAAS,MAEd,YAAaA,GACf,OAAOA,EAAS,QAEd,cAAeA,GACjB,OAAOA,EAAS,UAElB,MAAMC,EAAmC,CACvC,WAAYJ,EACZ,WAAY,KAAK,gBAAA,EACjB,GAAG,KAAK,kBAAkBE,CAAa,EACvC,GAAGC,CAAA,EAEDF,IACFG,EAAQ,cAAgB,IAI1B,MAAMC,EAASH,EAAc,SAAW,KAAK,OACvCI,EAAWJ,EAAc,WAAa,KAAK,SAE7CG,IACFD,EAAQ,QAAUC,GAEhBC,IACFF,EAAQ,UAAYE,GAGlB,KAAK,OAAO,sBAAwB,OAAO,OAAW,MACxD,KAAK,iBAAiBF,CAAO,EAEzB,CAACE,GAAYF,EAAQ,OACvBA,EAAQ,UAAYA,EAAQ,OAIhC,KAAK,kBAAkBA,EAASF,EAAc,WAAW,EAEzD,MAAMK,EAAc,KAAK,OAAO,YAAc,KAAK,OAAO,YAAYH,CAAO,EAAIA,EACjF,OAAKG,GAIL,KAAK,cAAA,EACL,KAAK,mBAAmBA,CAAW,EAC/B,KAAK,OAAO,UAAU,SAAW,CAACN,GAAgB,CAACtC,GAAS,cAC9D,KAAK,aAAa4C,CAAW,EACtB,KAET,KAAK,MAAM,QAAQ,SAAU,CAAE,GAAGA,EAAa,gBAAiB5C,GAAS,eAAiB,GAAM,EACzF,KAVE,EAWX,CAEO,SAAS6C,EAAgBC,EAAyB,GAAI,CAC3D,GAAI,CAACD,EAAQ,CACP,KAAK,OAAO,OACd,QAAQ,KAAK,uCAAuC,EAEtD,MACF,CAEA,GAAI,CAAC,KAAK,WAAA,GAAgB,KAAK,eAAgB,CACzC,KAAK,OAAO,OACd,QAAQ,KAAK,qDAAqD,EAEpE,MACF,CAEA,KAAK,KAAK,QAAQ,OAAO,CAAE,QAASA,EAAQ,EAE5C,MAAMJ,EAAmC,CACvC,QAASI,EACT,WAAY,KAAK,gBAAA,EACjB,GAAG,KAAK,uBAAuBC,CAAM,EACrC,GAAG,KAAK,kBAAA,CAAkB,EAIxB,KAAK,SACPL,EAAQ,QAAU,KAAK,QAErB,KAAK,WACPA,EAAQ,UAAY,KAAK,UAG3B,KAAK,kBAAkBA,CAAO,EAE9B,MAAMG,EAAc,KAAK,OAAO,YAAc,KAAK,OAAO,YAAYH,CAAO,EAAIA,EAC5EG,IAEL,KAAK,cAAA,EACL,KAAK,mBAAmBA,CAAW,EACnC,KAAK,MAAM,QAAQ,YAAaA,CAAW,EAC7C,CAEO,WAAWP,EAAmBjB,EAA6B,GAAI,CACpE,OAAO,KAAK,cAAciB,EAAWjB,EAAM,EAAI,CACjD,CAEO,iBAAiBiB,EAAmBjB,EAAuB,CAAA,EAAIpB,EAAsC,CAC1G,OAAO,KAAK,cAAcqC,EAAWjB,EAAM,GAAOpB,CAAO,CAC3D,CAMO,cAAmC,CACxC,OAAO,KAAK,KAAK,QAAQ,aAAA,CAC3B,CAEO,WAAWE,EAAuB,CACvC,MAAM6C,EAAW,KAAK,KAAK,QAAQ,iBAAA,EACnC,KAAK,KAAK,QAAQ,iBAAiB7C,CAAM,EACrCA,IAAW,UACb,KAAK,KAAK,QAAQ,mBAAA,EAClB,KAAK,KAAK,QAAQ,uBAAA,EAClB,KAAK,KAAK,QAAQ,cAAc,EAAK,GAErCA,IAAW,WACX6C,IAAa,UACb,KAAK,OAAO,kCAEZ,KAAK,KAAK,QAAQ,kBAAA,EAEpB,MAAMN,EAAmC,CACvC,WAAY,KAAK,KAAK,QAAQ,aAAA,EAC9B,cAAevC,EACf,OAAQ,MACR,WAAY,KAAK,gBAAA,CAAgB,EAI/B,KAAK,SACPuC,EAAQ,QAAU,KAAK,QAErB,KAAK,WACPA,EAAQ,UAAY,KAAK,UAG3B,KAAK,kBAAkBA,CAAO,EAE9B,MAAMG,EAAc,KAAK,OAAO,YAAc,KAAK,OAAO,YAAYH,CAAO,EAAIA,EAC5EG,GACL,KAAK,MAAM,QAAQ,WAAYA,CAAW,CAC5C,CAEO,OAAQ,CACb,KAAK,KAAK,QAAQ,OAAO,CACvB,QAAS,OACT,cAAe,OACf,YAAa,OACb,aAAc,OACd,WAAY,OACZ,mBAAoB,OACpB,iBAAkB,OAClB,YAAa,MAAA,CACd,EACD,KAAK,KAAK,QAAQ,kBAAA,CACpB,CAYO,YAAYI,EAAgB,CACjC,KAAK,KAAK,QAAQ,cAAc,EAAQA,CAAM,CAChD,CAKO,aAAuB,CAC5B,MAAO,EAAQ,KAAK,KAAK,QAAQ,eACnC,CAEO,OAAQ,CACb,YAAK,WAAA,EACE,KAAK,MAAM,MAAA,CACpB,CAEO,UAAW,CAChB,OAAO,KAAK,MAAM,SAAA,CACpB,CAUQ,kBAAkBP,EAAkCQ,EAAsB,CAChF,GAAIA,IAAe,GAAO,CACxB,OAAOR,EAAQ,YACf,MACF,CACA,MAAMS,EAAS,KAAK,KAAK,QAAQ,kBAAoB,GACjDD,IAAe,IAAQC,EACzBT,EAAQ,YAAc,GAEtB,OAAOA,EAAQ,WAEnB,CAEQ,kBAAkBrB,EAAuB,CAC/C,MAAM+B,EAAY/B,GAAM,YAAc,KAAK,KAAK,QAAQ,aAAA,EAClDyB,EAASzB,GAAM,SAAW,KAAK,KAAK,QAAQ,YAAA,EAC5CgC,EAAUhC,GAAM,UAAY,KAAK,KAAK,QAAQ,eAAA,EAC9CiC,EAAajC,GAAM,aAAe,KAAK,KAAK,QAAQ,cAAA,EACpDkC,EAAYlC,GAAM,YAAc,KAAK,KAAK,QAAQ,eAAA,EAGlDmC,EAAc,KAAK,KAAK,QAAQ,eAAA,GAAoB,CAAA,EACpDC,EAAcpC,GAAM,OAAgD,CAAA,EAGpEqC,EAAQ,CAAE,GAAGF,EAAa,GAAGC,CAAA,EAE7BE,EAAoC,CAAA,EAEtCP,MAAoB,WAAaA,GACjCN,MAAiB,QAAUA,GAC3BO,MAAkB,SAAWA,GAC7BC,MAAqB,YAAcA,GACnCC,MAAoB,WAAaA,GAErC,MAAMK,EAAmB,KAAK,KAAK,QAAQ,sBAAA,EAC3C,OAAIL,GAAaK,IACfD,EAAS,mBAAqBC,EAC9BD,EAAS,mBAAqB,KAAK,IAAA,EAAQ,KAAK,MAAMC,CAAgB,GAGpE,OAAO,KAAKF,CAAK,EAAE,OAAS,IAC9BC,EAAS,MAAQD,GAGZC,CACT,CAMQ,mBAAmBjB,EAAkC,CAC3D,GAAI,CAACA,EAAQ,WAAY,CACvB,MAAMa,EAAY,KAAK,KAAK,QAAQ,eAAA,EAC9BK,EAAmB,KAAK,KAAK,QAAQ,sBAAA,EACvCL,MAAmB,WAAaA,GAChCK,MAA0B,mBAAqBA,EACrD,CACA,MAAMC,EAAYnB,EAAQ,mBACtBA,EAAQ,YAAc,OAAOmB,GAAc,WAC7CnB,EAAQ,mBAAqB,KAAK,IAAA,EAAQ,KAAK,MAAMmB,CAAS,EAElE,CAEQ,YAAa,CACnB,GAAI,KAAK,OAAO,gBAAgB,OAAS,OAAS,OAAO,OAAW,KAC9D,CAAC,KAAK,iBACR,MAAO,GAGX,GAAI,CAAC,KAAK,mBACR,MAAO,GAIT,MAAMC,EADS,KAAK,KAAK,QAAQ,iBAAA,EAGjC,OAAI,KAAK,qBAAuB,OAEvBA,IAAU,SAIrB,CAEQ,kBAAkBzC,EAAsB,CAC9C,MAAM0C,EAAmD,CAAA,EACzD,SAAW,CAACC,EAAKf,CAAK,IAAK,OAAO,QAAQ5B,CAAI,EACxCW,EAAsB,IAAIgC,CAAG,IAGjCD,EAAUC,CAAG,EAAIf,GAEnB,OAAOc,CACT,CAEQ,uBAAuBhB,EAAwB,CACrD,MAAMgB,EAAmD,CAAA,EACzD,SAAW,CAACC,EAAKf,CAAK,IAAK,OAAO,QAAQF,CAAM,EAC1Cd,EAAyB,IAAI+B,CAAG,IAGpCD,EAAUC,CAAG,EAAIf,GAEnB,OAAOc,CACT,CAEQ,eAAe5B,EAAoB,CACzC,GAAI,CAACA,EAAO,KAAO,CAACA,EAAO,IAAI,OAC7B,MAAM,IAAI,MAAM,0CAA0C,EAE5D,GAAIA,EAAO,SACT,GAAI,CAEF,IAAI,IAAIA,EAAO,QAAQ,CACzB,MAAQ,CACN,MAAM,IAAI,MAAM,iDAAiD,CACnE,CAEF,GAAI,OAAOA,EAAO,SAAY,UAAY,CAACA,EAAO,QAAQ,OACxD,MAAM,IAAI,MAAM,6CAA6C,EAE/D,GAAI,OAAOA,EAAO,WAAc,UAAY,CAACA,EAAO,UAAU,OAC5D,MAAM,IAAI,MAAM,+CAA+C,CAEnE,CAEQ,iBAAiBO,EAAkC,CACrD,OAAO,SAAa,MAGnBA,EAAQ,OAAMA,EAAQ,KAAO,OAAO,SAAS,MAC7CA,EAAQ,OAAMA,EAAQ,KAAO,OAAO,SAAS,UAC7CA,EAAQ,QAAOA,EAAQ,MAAQ,SAAS,OACzC,CAACA,EAAQ,UAAY,SAAS,WAChCA,EAAQ,SAAW,KAAK,cAAc,SAAS,QAAQ,GAG3D,CAEQ,aAAaL,EAAgC,CACnD,KAAK,cAAc,KAAKA,CAAK,EAC7B,MAAM4B,EAAU,KAAK,OAAO,UAAU,UAAY,GAClD,GAAI,KAAK,cAAc,QAAUA,EAAS,CACxC,KAAK,WAAA,EACL,MACF,CACA,GAAI,CAAC,KAAK,WAAY,CACpB,MAAMC,EAAa,KAAK,OAAO,UAAU,mBAAqB,IAC9D,KAAK,WAAa,WAAW,IAAM,CACjC,KAAK,WAAa,KAClB,KAAK,WAAA,CACP,EAAGA,CAAU,CACf,CACF,CAEQ,YAAa,CACnB,GAAI,KAAK,cAAc,SAAW,EAAG,OACrC,MAAM9C,EAAO,KAAK,OAAO,UAAU,eAAiB,UAC9CsB,EAAU,KAAK,cAAc,OAAO,EAAG,KAAK,cAAc,MAAM,EACtE,KAAK,MAAM,QAAQtB,EAAM,CAAE,OAAQsB,EAAS,WAAY,KAAK,gBAAA,EAAmB,CAClF,CAEQ,iBAAkB,CACxB,OAAI,OAAO,OAAW,KAAe,OAAO,OAAO,YAAe,WACzD,OAAO,WAAA,EAET,OAAO,KAAK,IAAA,CAAK,IAAI,KAAK,OAAA,EAAS,SAAS,EAAE,EAAE,MAAM,CAAC,CAAC,EACjE,CAEQ,eAAgB,CACtB,MAAMzB,EAAM,KAAK,IAAA,EACXkD,EAAY,KAAK,KAAK,QAAQ,eAAA,EAC9BC,EAAkB,KAAK,KAAK,QAAQ,oBAAA,EACpCC,EAAeD,EAAkB,KAAK,MAAMA,CAAe,EAAI,EAMrE,GAJE,CAACD,GACD,CAACE,GACDpD,EAAMoD,GAAgB,KAAK,kBAC3B,KAAK,cAAcA,EAAcpD,CAAG,EACpB,CAChB,MAAMqD,EAAe,KAAK,gBAAA,EACpBC,EAAS,IAAI,KAAKtD,CAAG,EAAE,YAAA,EAC7B,KAAK,KAAK,QAAQ,OAAO,CACvB,WAAYqD,EACZ,mBAAoBC,EACpB,iBAAkBA,CAAA,CACnB,EACD,MACF,CACA,KAAK,KAAK,QAAQ,OAAO,CAAE,iBAAkB,IAAI,KAAKtD,CAAG,EAAE,YAAA,EAAe,CAC5E,CAEQ,cAAcuD,EAAoBC,EAAmB,CAC3D,MAAMC,EAAO,IAAI,KAAKF,CAAU,EAC1BG,EAAO,IAAI,KAAKF,CAAS,EAC/B,OACEC,EAAK,eAAA,IAAqBC,EAAK,eAAA,GAC/BD,EAAK,YAAA,IAAkBC,EAAK,eAC5BD,EAAK,WAAA,IAAiBC,EAAK,WAAA,CAE/B,CAEQ,mBAAoB,CAC1B,OAAI,OAAO,KAAK,OAAO,aAAgB,SAAiB,GACpD,KAAK,OAAO,aAAe,EAAU,GACrC,KAAK,OAAO,aAAe,EAAU,GAClC,KAAK,OAAA,GAAY,KAAK,OAAO,WACtC,CAEQ,cAAe,CACrB,GAAI,CAAC,KAAK,OAAO,WAAa,OAAO,UAAc,IACjD,MAAO,GAET,MAAMC,EAAM,UAAU,WAChBC,EAAO,UAA6D,qBAC1E,OAAOD,IAAQ,KAAOC,IAAQ,EAChC,CAEQ,cAAcC,EAAkB,CACtC,GAAI,CACF,MAAMC,EAAS,IAAI,IAAID,CAAQ,EAC/B,GAAI,OAAO,OAAW,IAAa,OAAOA,EAC1C,MAAME,EAAa,IAAI,IAAI,OAAO,SAAS,IAAI,EAC/C,OAAID,EAAO,SAAWC,EAAW,QAC/BD,EAAO,OAAS,GACTA,EAAO,SAAA,GAETD,CACT,MAAQ,CACN,OAAOA,CACT,CACF,CAEQ,2BAA4B,CAC9B,CAAC,KAAK,OAAO,OAAS,CAAC,KAAK,UAAY,OAAO,OAAW,MAG1D,OAAO,SAAS,OAAS,KAAK,UAChC,QAAQ,KAAK,sDAAuD,CAClE,SAAU,KAAK,SACf,OAAQ,OAAO,SAAS,IAAA,CACzB,EAEC,KAAK,QAAU,CAAC,6EAA6E,KAAK,KAAK,MAAM,GAC/G,QAAQ,KAAK,mDAAoD,KAAK,MAAM,EAEhF,CAQQ,cAAe,CACrB,MAAMG,EAAS,KAAK,OAAO,eAC3B,GAAIA,GAAQ,OAAS,OAAS,OAAO,OAAW,IAAa,OAC7D,MAAMC,EAAWD,EAAO,SAElBE,EAAWC,GAAoB,CACnC,GAAI,CACF,MAAMC,EAAI,OAgBV,GAAI,OAAOA,EAAE,UAAa,WAAY,CAChCD,EAAU,GACZ,WAAW,IAAMD,EAAQC,EAAU,CAAC,EAAG,GAAG,EAE5C,MACF,CACAC,EAAE,SAAS,mBAAoB,EAAG,CAACC,EAAQC,IAAO,CAChD,GAAI,CAACA,GAAM,CAACD,EAAQ,CAClB,KAAK,iBAAmB,GACxB,MACF,CACA,GAAIA,EAAO,cAAgB,GAAO,CAChC,KAAK,iBAAmB,GACxB,MACF,CACA,MAAME,EAAWF,EAAO,SAAS,UAAY,CAAA,EAC7C,KAAK,iBAAmBJ,EAAS,MAAOO,GAAMD,EAAS,OAAOC,CAAC,CAAC,IAAM,EAAI,CAC5E,CAAC,CACH,MAAQ,CACN,KAAK,iBAAmB,EAC1B,CACF,EAEAN,EAAQ,EAAE,CACZ,CACF,CC7lBO,MAAMO,CAA0C,CACpC,OACA,SACA,YAAc,IAE/B,YAAYvD,EAAoB,CAC9B,KAAK,OAASA,EACd,KAAK,SAAWA,EAAO,UAAYrC,CACrC,CAEA,MAAa,KAAKsB,EAAcC,EAAepB,EAAsC,CACnF,MAAM0F,EAAU,KAAK,aAAavE,EAAMC,EAAMpB,CAAO,EACrD,KAAK,QAAQ,IAAI0F,CAAO,EACxB,GAAI,CACF,MAAMA,CACR,QAAA,CACE,KAAK,QAAQ,OAAOA,CAAO,CAC7B,CACF,CAEA,MAAa,OAAQ,CACf,KAAK,QAAQ,OAAS,GAG1B,MAAM,QAAQ,WAAW,MAAM,KAAK,KAAK,OAAO,CAAC,CACnD,CAEA,MAAc,aAAavE,EAAcC,EAAepB,EAAsC,CAC5F,MAAM2F,EAAM,KAAK,QAAQ,KAAK,SAAUxE,CAAI,EACtC4C,EAAM,KAAK,OAAO,IAElB6B,EAAkC,CACtC,eAAgB,mBAChB,CAAC7B,EAAI,WAAW,KAAK,EAAI,eAAiB,mBAAmB,EAAGA,CAAA,EAG9D,KAAK,OAAO,OACd,QAAQ,IAAI,iBAAkB4B,EAAKvE,CAAI,EAGzC,MAAMyE,EAAO,KAAK,UAAUzE,CAAI,EAChC,GAAIpB,GAAS,cAAgB,OAAO,UAAc,KAAe,OAAO,UAAU,YAAe,WAAY,CAC3G,MAAM8F,EAAO,IAAI,KAAK,CAACD,CAAI,EAAG,CAAE,KAAM,mBAAoB,EAE1D,GADa,UAAU,WAAWF,EAAKG,CAAI,EAEzC,MAEJ,CAEA,GAAI,OAAO,OAAU,WACnB,MAAI,KAAK,OAAO,OACd,QAAQ,MAAM,uDAAuD,EAEjE,IAAIhG,EAAe,uDAAuD,EAGlF,MAAMiG,EAAY,KAAK,OAAO,oBAAsB,IAC9CC,EAAa,IAAI,gBACvB,IAAIC,EAAW,GACf,MAAMC,EAAQ,WAAW,IAAM,CAC7BD,EAAW,GACXD,EAAW,MAAA,CACb,EAAGD,CAAS,EAEZ,GAAI,CACF,MAAMI,EAAW,MAAM,MAAMR,EAAK,CAChC,OAAQ,OACR,QAAAC,EACA,KAAAC,EACA,UAAW,GACX,OAAQG,EAAW,MAAA,CACpB,EACD,GAAI,CAACG,EAAS,GAAI,CAChB,MAAMC,EAAU,MAAM,KAAK,iBAAiBD,CAAQ,EAC9CvE,EAAezB,EAAgBgG,EAAS,QAAQ,IAAI,aAAa,CAAC,EACxE,MAAI,KAAK,OAAO,OACd,QAAQ,MAAM,0BAA2B,CACvC,IAAAR,EACA,OAAQQ,EAAS,OACjB,WAAYA,EAAS,WACrB,KAAMC,EACN,aAAAxE,CAAA,CACD,EAEG,IAAI9B,EACR,uCAAuCqG,EAAS,MAAM,KAAKC,CAAO,GAClE,CAAE,OAAQD,EAAS,OAAQ,aAAAvE,CAAA,CAAa,CAE5C,CACF,OAASD,EAAO,CAId,GAHI,KAAK,OAAO,OACd,QAAQ,MAAM,wBAAyBgE,EAAKhE,CAAK,EAE/CA,aAAiB7B,EACnB,MAAM6B,EAER,GAAIsE,EACF,MAAM,IAAInG,EAAe,kCAAkCiG,CAAS,KAAM,CAAE,OAAQ,IAAK,EAE3F,MAAMhG,EAAU4B,aAAiB,MAAQA,EAAM,QAAU,OAAOA,CAAK,EACrE,MAAM,IAAI7B,EAAe,yBAAyBC,CAAO,EAAE,CAC7D,QAAA,CACE,aAAamG,CAAK,CACpB,CACF,CAEQ,QAAQG,EAAkBlF,EAAc,CAC9C,MAAMmF,EAAqBD,EAAS,QAAQ,OAAQ,EAAE,EAChDE,EAAiBpF,EAAK,QAAQ,OAAQ,EAAE,EAC9C,MAAO,GAAGmF,CAAkB,IAAIC,CAAc,EAChD,CAEA,MAAc,iBAAiBJ,EAAoB,CACjD,GAAI,CAEF,OADa,MAAMA,EAAS,KAAA,GAChB,MAAM,EAAG,GAAG,CAC1B,MAAQ,CACN,MAAO,EACT,CACF,CACF,CCtHO,MAAMK,CAA2C,CACtD,YAAoBC,EAAwB,GAAI,CAA5B,KAAA,SAAAA,CAA8B,CAE3C,cAAe,CACpB,OAAO,KAAK,SAAS,UACvB,CAEO,gBAAiB,CACtB,OAAO,KAAK,SAAS,aACvB,CAEO,eAAgB,CACrB,OAAO,KAAK,SAAS,WACvB,CAEO,gBAAiB,CACtB,OAAO,KAAK,SAAS,YACvB,CAEO,WAAY,CACjB,OAAO,KAAK,SAAS,OACvB,CAEO,cAAe,CACpB,OAAO,KAAK,SAAS,UACvB,CAEO,qBAAsB,CAC3B,OAAO,KAAK,SAAS,kBACvB,CAEO,mBAAoB,CACzB,OAAO,KAAK,SAAS,gBACvB,CAEO,kBAA8C,CACnD,OAAO,KAAK,SAAS,cACvB,CAEO,QAAe,CAEtB,CAEO,kBAAyB,CAEhC,CAEO,kBAAyB,CAEhC,CAEO,sBAA6B,CAEpC,CAEO,iBAAwB,CAE/B,CAEO,aAAmC,CACxC,OAAO,KAAK,SAAS,WACvB,CAEO,YAAYzD,EAAsB,CACvC,KAAK,SAAS,YAAcA,EAAQ,GAAO,MAC7C,CACF,CCvDO,MAAM0D,CAAS,CACpB,YAA6BC,EAAkB,CAAlB,KAAA,OAAAA,CAAmB,CAEzC,MAAMtE,EAAmBjB,EAAuB,GAAI,CACzD,KAAK,OAAO,MAAMiB,EAAWjB,CAAI,CACnC,CAEO,WAAWiB,EAAmBjB,EAAuB,GAAI,CAC9D,KAAK,OAAO,WAAWiB,EAAWjB,CAAI,CACxC,CAEO,SAASyB,EAAgBC,EAAyB,GAAI,CAC3D,KAAK,OAAO,SAASD,EAAQC,CAAM,CACrC,CAEO,WAAW5C,EAAuB,CACvC,KAAK,OAAO,WAAWA,CAAM,CAC/B,CAEO,OAAQ,CACb,OAAO,KAAK,OAAO,MAAA,CACrB,CAEO,OAAQ,CACb,KAAK,OAAO,MAAA,CACd,CAEO,UAAW,CAChB,OAAO,KAAK,OAAO,SAAA,CACrB,CAOO,cAAmC,CACxC,OAAO,KAAK,OAAO,aAAA,CACrB,CAMO,YAAY8C,EAAgB,CACjC,KAAK,OAAO,YAAYA,CAAK,CAC/B,CAEO,aAAuB,CAC5B,OAAO,KAAK,OAAO,YAAA,CACrB,CACF,CAEO,MAAM4D,EAAiB,CAAC1E,EAAoBlC,EAA2B,KAAO,CACnF,MAAM6G,EACJ7G,EAAQ,SACR,IAAIwG,EAAiB,CACnB,WAAYxG,EAAQ,iBAAiB,WACrC,QAASA,EAAQ,iBAAiB,QAClC,WAAYA,EAAQ,iBAAiB,WACrC,mBAAoBA,EAAQ,iBAAiB,mBAC7C,iBAAkBA,EAAQ,iBAAiB,iBAC3C,cAAeA,EAAQ,iBAAiB,cACxC,YAAaA,EAAQ,iBAAiB,YACtC,aAAcA,EAAQ,iBAAiB,aACvC,eAAgBA,EAAQ,iBAAiB,eACzC,YAAaA,EAAQ,iBAAiB,WAAA,CACvC,EAEGc,EAAYd,EAAQ,WAAa,IAAIyF,EAAcvD,CAAM,EACzDyE,EAAS,IAAI1E,EAASC,EAAQ,CAAE,QAAA2E,EAAS,UAAA/F,EAAW,EAE1D,OAAO,IAAI4F,EAASC,CAAM,CAC5B"}
package/dist/node.es.js CHANGED
@@ -1,4 +1,4 @@
1
- const p = "https://ingest.onelence.com";
1
+ const _ = "https://ingest.onelence.com";
2
2
  class f extends Error {
3
3
  status;
4
4
  retryAfterMs;
@@ -22,10 +22,10 @@ function y(a) {
22
22
  return i > 0 ? i : 0;
23
23
  }
24
24
  }
25
- const w = 5, b = 300, I = 15e3, k = 2880 * 60 * 1e3;
25
+ const w = 5, I = 300, b = 15e3, k = 2880 * 60 * 1e3;
26
26
  class v {
27
27
  constructor(t, e = {}) {
28
- this.transport = t, this.maxAttempts = e.maxAttempts ?? w, this.baseBackoffMs = e.baseBackoffMs ?? b, this.maxBackoffMs = e.maxBackoffMs ?? I, this.maxItemAgeMs = e.maxItemAgeMs ?? k, this.debug = e.debug ?? !1, this.loadPersisted = e.loadPersisted, this.savePersisted = e.savePersisted, this.onError = e.onError;
28
+ this.transport = t, this.maxAttempts = e.maxAttempts ?? w, this.baseBackoffMs = e.baseBackoffMs ?? I, this.maxBackoffMs = e.maxBackoffMs ?? b, this.maxItemAgeMs = e.maxItemAgeMs ?? k, this.debug = e.debug ?? !1, this.loadPersisted = e.loadPersisted, this.savePersisted = e.savePersisted, this.onError = e.onError;
29
29
  const s = this.loadPersisted?.() ?? [];
30
30
  if (s.length > 0) {
31
31
  const i = Date.now();
@@ -147,7 +147,7 @@ class v {
147
147
  }
148
148
  }
149
149
  }
150
- const M = /* @__PURE__ */ new Set(["event_name", "user_id", "consent_state", "source", "is_conversion"]), q = /* @__PURE__ */ new Set([
150
+ const M = /* @__PURE__ */ new Set(["event_name", "user_id", "consent_state", "source", "is_conversion"]), A = /* @__PURE__ */ new Set([
151
151
  "user_id",
152
152
  "visitor_id",
153
153
  "click_id",
@@ -156,10 +156,10 @@ const M = /* @__PURE__ */ new Set(["event_name", "user_id", "consent_state", "so
156
156
  "consent_state",
157
157
  "source"
158
158
  ]);
159
- class A {
159
+ class q {
160
160
  constructor(t, e) {
161
161
  this.deps = e, this.validateConfig(t), this.config = {
162
- endpoint: t.endpoint ?? p,
162
+ endpoint: t.endpoint ?? _,
163
163
  ...t,
164
164
  include_page_context: t.include_page_context ?? !0
165
165
  }, this.consentRequirement = t.require_consent ?? !1, this.siteId = t.site_id, this.siteHost = t.site_host, this.sessionTimeoutMs = t.session_timeout_ms ?? 1800 * 1e3, this.queue = new v(this.deps.transport, {
@@ -213,10 +213,10 @@ class A {
213
213
  ...n
214
214
  };
215
215
  s && (r.is_conversion = !0);
216
- const h = o.site_id ?? this.siteId, d = o.site_host ?? this.siteHost;
217
- h && (r.site_id = h), d && (r.site_host = d), this.config.include_page_context && typeof window < "u" && (this.applyPageContext(r), !d && r.site && (r.site_host = r.site)), this.applyInternalFlag(r, o.is_internal);
218
- const u = this.config.before_send ? this.config.before_send(r) : r;
219
- return u ? (this.ensureSession(), this.config.batching?.enabled && !s && !i?.preferBeacon ? (this.enqueueBatch(u), !0) : (this.queue.enqueue("/event", { ...u, __prefer_beacon: i?.preferBeacon === !0 }), !0)) : !0;
216
+ const h = o.site_id ?? this.siteId, u = o.site_host ?? this.siteHost;
217
+ h && (r.site_id = h), u && (r.site_host = u), this.config.include_page_context && typeof window < "u" && (this.applyPageContext(r), !u && r.site && (r.site_host = r.site)), this.applyInternalFlag(r, o.is_internal);
218
+ const c = this.config.before_send ? this.config.before_send(r) : r;
219
+ return c ? (this.ensureSession(), this.applySessionFields(c), this.config.batching?.enabled && !s && !i?.preferBeacon ? (this.enqueueBatch(c), !0) : (this.queue.enqueue("/event", { ...c, __prefer_beacon: i?.preferBeacon === !0 }), !0)) : !0;
220
220
  }
221
221
  identify(t, e = {}) {
222
222
  if (!t) {
@@ -236,7 +236,7 @@ class A {
236
236
  };
237
237
  this.siteId && (s.site_id = this.siteId), this.siteHost && (s.site_host = this.siteHost), this.applyInternalFlag(s);
238
238
  const i = this.config.before_send ? this.config.before_send(s) : s;
239
- i && this.queue.enqueue("/identify", i);
239
+ i && (this.ensureSession(), this.applySessionFields(i), this.queue.enqueue("/identify", i));
240
240
  }
241
241
  conversion(t, e = {}) {
242
242
  return this.trackInternal(t, e, !0);
@@ -318,8 +318,22 @@ class A {
318
318
  e === !0 || s ? t.is_internal = !0 : delete t.is_internal;
319
319
  }
320
320
  getIdentityFields(t) {
321
- const e = t?.visitor_id ?? this.deps.storage.getVisitorId(), s = t?.user_id ?? this.deps.storage.getUserId?.(), i = t?.click_id ?? this.deps.storage.getLastClickId(), o = t?.campaign_id ?? this.deps.storage.getCampaignId(), n = t?.session_id ?? this.deps.storage.getSessionId?.(), r = this.deps.storage.getQueryParams() ?? {}, h = t?.query ?? {}, d = { ...r, ...h }, u = {};
322
- return e && (u.visitor_id = e), s && (u.user_id = s), i && (u.click_id = i), o && (u.campaign_id = o), n && (u.session_id = n), Object.keys(d).length > 0 && (u.query = d), u;
321
+ const e = t?.visitor_id ?? this.deps.storage.getVisitorId(), s = t?.user_id ?? this.deps.storage.getUserId?.(), i = t?.click_id ?? this.deps.storage.getLastClickId(), o = t?.campaign_id ?? this.deps.storage.getCampaignId(), n = t?.session_id ?? this.deps.storage.getSessionId?.(), r = this.deps.storage.getQueryParams() ?? {}, h = t?.query ?? {}, u = { ...r, ...h }, c = {};
322
+ e && (c.visitor_id = e), s && (c.user_id = s), i && (c.click_id = i), o && (c.campaign_id = o), n && (c.session_id = n);
323
+ const l = this.deps.storage.getSessionStartedAt?.();
324
+ return n && l && (c.session_started_at = l, c.session_elapsed_ms = Date.now() - Date.parse(l)), Object.keys(u).length > 0 && (c.query = u), c;
325
+ }
326
+ /**
327
+ * Patches session fields after ensureSession when the first event in a session was built
328
+ * before rotation created an id. Rotation events keep the previous session_id from identity.
329
+ */
330
+ applySessionFields(t) {
331
+ if (!t.session_id) {
332
+ const s = this.deps.storage.getSessionId?.(), i = this.deps.storage.getSessionStartedAt?.();
333
+ s && (t.session_id = s), i && (t.session_started_at = i);
334
+ }
335
+ const e = t.session_started_at;
336
+ t.session_id && typeof e == "string" && (t.session_elapsed_ms = Date.now() - Date.parse(e));
323
337
  }
324
338
  hasConsent() {
325
339
  if (this.config.consent_source?.type === "tcf" && typeof window < "u" && !this.tcfCachedAllowed)
@@ -338,7 +352,7 @@ class A {
338
352
  sanitizeIdentifyTraits(t) {
339
353
  const e = {};
340
354
  for (const [s, i] of Object.entries(t))
341
- q.has(s) || (e[s] = i);
355
+ A.has(s) || (e[s] = i);
342
356
  return e;
343
357
  }
344
358
  validateConfig(t) {
@@ -448,7 +462,7 @@ class A {
448
462
  return;
449
463
  }
450
464
  const h = n.purpose?.consents ?? {};
451
- this.tcfCachedAllowed = e.every((d) => h[String(d)] === !0);
465
+ this.tcfCachedAllowed = e.every((u) => h[String(u)] === !0);
452
466
  });
453
467
  } catch {
454
468
  this.tcfCachedAllowed = !1;
@@ -457,12 +471,12 @@ class A {
457
471
  s(10);
458
472
  }
459
473
  }
460
- class D {
474
+ class S {
461
475
  config;
462
476
  endpoint;
463
477
  pending = /* @__PURE__ */ new Set();
464
478
  constructor(t) {
465
- this.config = t, this.endpoint = t.endpoint ?? p;
479
+ this.config = t, this.endpoint = t.endpoint ?? _;
466
480
  }
467
481
  async send(t, e, s) {
468
482
  const i = this.sendInternal(t, e, s);
@@ -484,47 +498,47 @@ class D {
484
498
  this.config.debug && console.log("[Mark] Sending", i, e);
485
499
  const r = JSON.stringify(e);
486
500
  if (s?.preferBeacon && typeof navigator < "u" && typeof navigator.sendBeacon == "function") {
487
- const c = new Blob([r], { type: "application/json" });
488
- if (navigator.sendBeacon(i, c))
501
+ const d = new Blob([r], { type: "application/json" });
502
+ if (navigator.sendBeacon(i, d))
489
503
  return;
490
504
  }
491
505
  if (typeof fetch != "function")
492
506
  throw this.config.debug && console.error("[Mark] Global fetch is not available in this runtime."), new f("[Mark] Global fetch is not available in this runtime.");
493
- const h = this.config.request_timeout_ms ?? 1e4, d = new AbortController();
494
- let u = !1;
495
- const _ = setTimeout(() => {
496
- u = !0, d.abort();
507
+ const h = this.config.request_timeout_ms ?? 1e4, u = new AbortController();
508
+ let c = !1;
509
+ const l = setTimeout(() => {
510
+ c = !0, u.abort();
497
511
  }, h);
498
512
  try {
499
- const c = await fetch(i, {
513
+ const d = await fetch(i, {
500
514
  method: "POST",
501
515
  headers: n,
502
516
  body: r,
503
517
  keepalive: !0,
504
- signal: d.signal
518
+ signal: u.signal
505
519
  });
506
- if (!c.ok) {
507
- const l = await this.readErrorSnippet(c), g = y(c.headers.get("Retry-After"));
520
+ if (!d.ok) {
521
+ const g = await this.readErrorSnippet(d), p = y(d.headers.get("Retry-After"));
508
522
  throw this.config.debug && console.error("[Mark] Request rejected", {
509
523
  url: i,
510
- status: c.status,
511
- statusText: c.statusText,
512
- body: l,
513
- retryAfterMs: g
524
+ status: d.status,
525
+ statusText: d.statusText,
526
+ body: g,
527
+ retryAfterMs: p
514
528
  }), new f(
515
- `[Mark] Request rejected with status ${c.status}: ${l}`,
516
- { status: c.status, retryAfterMs: g }
529
+ `[Mark] Request rejected with status ${d.status}: ${g}`,
530
+ { status: d.status, retryAfterMs: p }
517
531
  );
518
532
  }
519
- } catch (c) {
520
- if (this.config.debug && console.error("[Mark] Failed to send", i, c), c instanceof f)
521
- throw c;
522
- if (u)
533
+ } catch (d) {
534
+ if (this.config.debug && console.error("[Mark] Failed to send", i, d), d instanceof f)
535
+ throw d;
536
+ if (c)
523
537
  throw new f(`[Mark] Request timed out after ${h}ms`, { status: 408 });
524
- const l = c instanceof Error ? c.message : String(c);
525
- throw new f(`[Mark] Network error: ${l}`);
538
+ const g = d instanceof Error ? d.message : String(d);
539
+ throw new f(`[Mark] Network error: ${g}`);
526
540
  } finally {
527
- clearTimeout(_);
541
+ clearTimeout(l);
528
542
  }
529
543
  }
530
544
  joinUrl(t, e) {
@@ -539,7 +553,7 @@ class D {
539
553
  }
540
554
  }
541
555
  }
542
- class S {
556
+ class D {
543
557
  constructor(t = {}) {
544
558
  this.defaults = t;
545
559
  }
@@ -632,7 +646,7 @@ class T {
632
646
  }
633
647
  }
634
648
  const E = (a, t = {}) => {
635
- const e = t.storage ?? new S({
649
+ const e = t.storage ?? new D({
636
650
  visitor_id: t.storageDefaults?.visitor_id,
637
651
  user_id: t.storageDefaults?.user_id,
638
652
  session_id: t.storageDefaults?.session_id,
@@ -643,12 +657,12 @@ const E = (a, t = {}) => {
643
657
  query_params: t.storageDefaults?.query_params,
644
658
  consent_status: t.storageDefaults?.consent_status,
645
659
  is_internal: t.storageDefaults?.is_internal
646
- }), s = t.transport ?? new D(a), i = new A(a, { storage: e, transport: s });
660
+ }), s = t.transport ?? new S(a), i = new q(a, { storage: e, transport: s });
647
661
  return new T(i);
648
662
  };
649
663
  export {
650
664
  T as NodeMark,
651
- S as StatelessStorage,
665
+ D as StatelessStorage,
652
666
  E as createNodeMark
653
667
  };
654
668
  //# sourceMappingURL=node.es.js.map