@behindthescenes/analytics 0.0.1 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,10 +5,58 @@ Browser SDK for BTS external-site analytics.
5
5
  ## What it does
6
6
 
7
7
  - Records standard web events such as `page_view`, `view_content`, `lead`, `sign_up`, `begin_checkout`, and `purchase`
8
- - Auto-tracks pageviews, SPA history changes, outbound clicks, button clicks, and form submissions by default
8
+ - Auto-tracks pageviews, SPA history changes, outbound clicks, button clicks, form submissions, safe GET search forms, and opt-in content views by default
9
9
  - Supports custom client-side events with `track(...)`
10
10
  - Preserves journey tokens, UTM params, and click IDs for downstream attribution and CAPI delivery
11
11
 
12
+ ## Auto-tracked events
13
+
14
+ The SDK automatically captures events that can be detected safely from browser behavior:
15
+
16
+ - `page_view`: initial page load and SPA navigation
17
+ - `outbound_click`: links to external origins
18
+ - `button_click`: clicks on `button`, `[role="button"]`, or `[data-bts-track-click]`
19
+ - `form_submit`: form submissions
20
+ - `search`: GET search forms with common query keys (`q`, `query`, `search`)
21
+ - `view_content`: elements that opt in with `data-bts-view-content`
22
+
23
+ Opt into automatic content-view tracking by marking elements that represent content cards, offers, posts, videos, or products:
24
+
25
+ ```html
26
+ <article
27
+ data-bts-view-content="offer_123"
28
+ data-bts-content-type="offer"
29
+ data-bts-content-title="Creator Accelerator"
30
+ >
31
+ Creator Accelerator
32
+ </article>
33
+ ```
34
+
35
+ Identity, purchases, signups, payment steps, and arbitrary custom events are not auto-detected because they need product context. Call those explicitly after your app knows what happened.
36
+
37
+ ```ts
38
+ // analytics.identify("user_123", { email: "fan@example.com" });
39
+ // analytics.track("watch_preview_clicked", { placement: "hero" });
40
+ // analytics.trackStandard("lead", { formId: "newsletter" });
41
+ // analytics.trackStandard("sign_up", { method: "email" });
42
+ // analytics.trackStandard("begin_checkout", { checkoutId: "offer_123" });
43
+ // analytics.trackStandard("add_payment_info", { checkoutId: "offer_123" });
44
+ // analytics.trackStandard("purchase", { orderId: "order_123", monetaryValue: 149 });
45
+ // const { journeyToken } = await analytics.startFunnel("/landing");
46
+ // const checkoutUrl = analytics.decorateUrl("https://behindthescenes.com/checkout", journeyToken);
47
+ // await analytics.flushNow();
48
+ ```
49
+
50
+ ## Auto pageviews and SPA navigation
51
+
52
+ - `**autoPageviews: false**` turns off the initial `page_view` **and** disables SPA history hooks (`pushState` / `replaceState` / `popstate`), so route changes do not emit automatic `page_view` events.
53
+ - To track SPA navigations without the first-load pageview, pass an `autoTrack` object, for example `{ pageviews: false, history: true, outboundLinks: true, buttonClicks: true, formSubmissions: true, search: true, viewContent: true }`.
54
+
55
+ ## Flush reliability
56
+
57
+ - If a batch flush fails (network error or non-2xx response), events are **put back on the queue** and a later flush is scheduled (including the keepalive path used when `requestHeaders` is set).
58
+ - The default `sendBeacon` unload path cannot attach custom headers or observe success; use `requestHeaders` if you need the same retry semantics on `pagehide`.
59
+
12
60
  ## Install from npm
13
61
 
14
62
  ```bash
@@ -32,11 +80,88 @@ import { createBTSAnalytics } from "@behindthescenes/analytics";
32
80
 
33
81
  const analytics = createBTSAnalytics({
34
82
  siteKey: "your-public-site-key",
35
- endpoint: "https://api.bts.dev/v2/website/analytics",
36
83
  });
37
84
 
38
- analytics.track("video_played", {
39
- videoId: "intro-01",
85
+ // Optional explicit-only examples:
86
+ // analytics.identify("user_123", { email: "fan@example.com" });
87
+ // analytics.track("video_played", { videoId: "intro-01" });
88
+ // analytics.trackStandard("lead", { formId: "newsletter" });
89
+ // analytics.trackStandard("begin_checkout", { checkoutId: "offer_123" });
90
+ // const { journeyToken } = await analytics.startFunnel("/landing");
91
+ // const checkoutUrl = analytics.decorateUrl("https://behindthescenes.com/checkout", journeyToken);
92
+ // await analytics.flushNow();
93
+ ```
94
+
95
+ ## Endpoint Override
96
+
97
+ The SDK defaults to the production BTS analytics endpoint. Override `endpoint` only for staging, local development, or a custom proxy.
98
+
99
+ ```ts
100
+ const analytics = createBTSAnalytics({
101
+ siteKey: "your-public-site-key",
102
+ endpoint: "https://staging-api.bts.dev/v2/website/analytics",
103
+ });
104
+ ```
105
+
106
+ ## Contact forms
107
+
108
+ Use `submitContactForm(...)` when an external website needs to submit a simple contact enquiry into the space's GoHighLevel contact list.
109
+
110
+ ```ts
111
+ await analytics.submitContactForm({
112
+ locationId: "your-ghl-location-id",
113
+ email: "fan@example.com",
114
+ subject: "Partnership enquiry",
115
+ body: "Tell us more about working together.",
116
+ });
117
+ ```
118
+
119
+ Required fields:
120
+
121
+ - `locationId`: the GoHighLevel location configured for the BTS space
122
+ - `email`: the visitor's email address
123
+ - `subject`: short enquiry subject
124
+ - `body`: the message or "tell us more" text
125
+
126
+ Optional fields:
127
+
128
+ - `name`, `firstName`, `lastName`: contact identity fields
129
+ - `source`: defaults to `behind_the_scenes` when omitted
130
+ - `tags`: extra GHL tags to attach to the contact
131
+ - `customFields`: GHL custom fields to pass through directly
132
+ - `metadata`: extra BTS metadata; values are forwarded as `bts_meta_<key>` contact fields
133
+
134
+ `subject` and `body` are also stored as BTS metadata fields on the GHL contact (`bts_meta_subject` and `bts_meta_body`). This helper creates or updates the contact only; it does not create a GHL opportunity.
135
+
136
+ The SDK uses the production website analytics endpoint by default:
137
+
138
+ ```ts
139
+ const analytics = createBTSAnalytics({
140
+ siteKey: "your-public-site-key",
141
+ });
142
+ ```
143
+
144
+ `submitContactForm(...)` derives the sibling contact endpoint from the configured analytics URL and posts to `/v2/website/ghl/leads`.
145
+
146
+ Example form wiring:
147
+
148
+ ```ts
149
+ const form = document.querySelector<HTMLFormElement>("#contact-form");
150
+
151
+ form?.addEventListener("submit", async (event) => {
152
+ event.preventDefault();
153
+
154
+ const data = new FormData(form);
155
+ await analytics.submitContactForm({
156
+ locationId: "your-ghl-location-id",
157
+ email: String(data.get("email") ?? ""),
158
+ subject: String(data.get("subject") ?? ""),
159
+ body: String(data.get("body") ?? ""),
160
+ name: String(data.get("name") ?? ""),
161
+ metadata: {
162
+ page: window.location.pathname,
163
+ },
164
+ });
40
165
  });
41
166
  ```
42
167
 
@@ -47,14 +172,31 @@ analytics.track("video_played", {
47
172
  import { createBTSAnalytics } from "https://app.behindthescenes.com/sdk/analytics/latest/browser/browser.js";
48
173
 
49
174
  window.btsAnalytics = createBTSAnalytics({
50
- siteKey: "your-public-site-key",
51
- endpoint: "https://api.bts.dev/v2/website/analytics"
175
+ siteKey: "your-public-site-key"
52
176
  });
53
177
  </script>
54
178
  ```
55
179
 
56
180
  The hosted bundle is generated from `packages/analytics/dist` and synced into `apps/bts-web/public/sdk/analytics`.
57
181
 
182
+ Release builds do not emit or publish source maps. The package build fails if any `.map` files are present in `dist`.
183
+
184
+ ## Releases
185
+
186
+ Publishing the npm package bumps the version with standard semver release types:
187
+
188
+ - `patch`: bug fixes and backwards-compatible corrections, for example `1.2.3` to `1.2.4`
189
+ - `minor`: backwards-compatible features, for example `1.2.3` to `1.3.0`
190
+ - `major`: breaking changes, for example `1.2.3` to `2.0.0`
191
+
192
+ Run the GitHub Actions release workflow manually and choose the release type to publish the next package version. The workflow reads the latest published npm version, increments it, validates the package, publishes it, and commits the updated `package.json` version.
193
+
194
+ For local publishing, set the release type before running the publish script:
195
+
196
+ ```sh
197
+ RELEASE_TYPE=minor bun run release:publish
198
+ ```
199
+
58
200
  ## Standard events
59
201
 
60
202
  ```ts
@@ -94,31 +236,32 @@ analytics.track("cta_clicked", {
94
236
  });
95
237
  ```
96
238
 
97
- ## Journeys
239
+ ## Request signing headers
98
240
 
99
- ```ts
100
- const { journeyToken } = await analytics.startFunnel();
101
- const checkoutUrl = analytics.decorateUrl("https://behindthescenes.com/checkout", journeyToken);
102
- ```
103
-
104
- ## Scripts
241
+ Use `requestHeaders` to attach custom anti-spoof headers to every API request. The hook receives the serialized request body, so you can sign the exact payload being sent.
105
242
 
106
- ```bash
107
- bun run build
108
- bun run build:hosted
109
- bun run test
243
+ ```ts
244
+ const analytics = createBTSAnalytics({
245
+ siteKey: "your-public-site-key",
246
+ requestHeaders: async ({ bodyText, headers, path }) => {
247
+ const timestamp = new Date().toISOString();
248
+ const signature = await signAnalyticsPayload(`${timestamp}:${path}:${bodyText}`);
249
+
250
+ return {
251
+ ...headers,
252
+ "X-BTS-Analytics-Timestamp": timestamp,
253
+ "X-BTS-Analytics-Signature": signature,
254
+ };
255
+ },
256
+ });
110
257
  ```
111
258
 
112
- `build:hosted` rebuilds the package and syncs the hosted browser bundle into `apps/bts-web/public/sdk/analytics`.
259
+ When `requestHeaders` is configured, page-unload flushes use `fetch(..., { keepalive: true })` instead of `sendBeacon`, because browsers do not allow custom headers on beacon requests.
113
260
 
114
- ## Release
261
+ ## Journeys
115
262
 
116
- ```bash
117
- bun run release:check
263
+ ```ts
264
+ const { journeyToken } = await analytics.startFunnel();
265
+ const checkoutUrl = analytics.decorateUrl("https://behindthescenes.com/checkout", journeyToken);
118
266
  ```
119
267
 
120
- 1. Bump the version in `packages/analytics/package.json`.
121
- 2. For a local/manual publish, run `bun run release:publish`.
122
- 3. For GitHub Actions, push a tag named `analytics-sdk-v<version>` or run the `Analytics SDK Release` workflow manually.
123
- 4. The GitHub Actions release workflow publishes with provenance; local publishes should use `npm publish --access public` without `--provenance`.
124
-
@@ -1,4 +1 @@
1
- var M=["page_view","view_content","search","lead","sign_up","begin_checkout","add_payment_info","purchase","outbound_click","button_click","form_submit"],G=new Set(M),P=new Set(["lead","sign_up","begin_checkout","add_payment_info","purchase"]),T={$pageview:"page_view",checkout_started:"begin_checkout",lead_capture:"lead",purchase_completed:"purchase",registration_complete:"sign_up"};function w(B){return G.has(B)}function K(B){let c=B.trim();if(c.length===0)return{eventName:c,canonicalEventName:c,isStandard:!1};let _=c.toLowerCase(),E=T[_];if(E)return{eventName:E,canonicalEventName:E,aliasOf:E,isStandard:!0};return{eventName:c,canonicalEventName:c,isStandard:w(c)}}function F(B,c){if(c)return c;if(B==="page_view")return"page_view";if(B==="identify")return"identify";if(P.has(B))return"conversion";return"custom"}function $(){return[...M]}var H="bts_analytics_vid",J="bts_analytics_sid",Q="bts_analytics_jt",k="bts_analytics_attr",z="bts_site",v="bts_jt",I=300,U={pageviews:!0,history:!0,outboundLinks:!0,buttonClicks:!0,formSubmissions:!0},p=["utm_source","utm_medium","utm_campaign","utm_term","utm_content","fbclid","gclid","gbraid","li_fat_id","msclkid","ttclid","wbraid"];function A(){return typeof window>"u"?null:window}function f(){return typeof document>"u"?null:document}function N(){return A()?.localStorage??null}function W(){if(typeof crypto<"u"&&typeof crypto.randomUUID==="function")return crypto.randomUUID();return`v_${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`}function q(B){return B!==null&&typeof B==="object"&&!Array.isArray(B)}function O(B){if(!B)return null;try{let c=JSON.parse(B);if(!q(c))return null;let _=q(c.utm)?Object.fromEntries(Object.entries(c.utm).filter((S)=>typeof S[1]==="string")):{},E=q(c.clickIds)?Object.fromEntries(Object.entries(c.clickIds).filter((S)=>typeof S[1]==="string")):{};return{utm:_,clickIds:E,landingUrl:typeof c.landingUrl==="string"?c.landingUrl:void 0,lastSeenAt:typeof c.lastSeenAt==="string"?c.lastSeenAt:new Date().toISOString(),referrer:typeof c.referrer==="string"?c.referrer:void 0}}catch{return null}}function j(){let B=N();return O(B?.getItem(k)??null)}function d(B){let c=N();if(!c)return;c.setItem(k,JSON.stringify(B))}function D(){let B=A();if(!B)return;return`${B.location.pathname}${B.location.search}`}function C(){return A()?.location.href}function X(){return A()?.location.origin??"https://behindthescenes.com"}function Y(){return f()?.referrer||void 0}function Z(){let B=A();if(!B)return j();let c=new URLSearchParams(B.location.search),_={utm:{},clickIds:{},landingUrl:B.location.href,lastSeenAt:new Date().toISOString(),referrer:Y()};for(let y of p){let V=c.get(y);if(!V)continue;if(y.startsWith("utm_")){_.utm[y]=V;continue}_.clickIds[y]=V}let E=j(),S={utm:{...E?.utm??{},..._.utm},clickIds:{...E?.clickIds??{},..._.clickIds},landingUrl:_.landingUrl??E?.landingUrl,lastSeenAt:_.lastSeenAt,referrer:_.referrer??E?.referrer};return d(S),S}function l(B){let c=B?.trim();return c?c.slice(0,512):void 0}function g(B){let c={...U};if(B.autoTrack===!1)return{pageviews:!1,history:!1,outboundLinks:!1,buttonClicks:!1,formSubmissions:!1};if(B.autoTrack&&typeof B.autoTrack==="object")return{pageviews:B.autoTrack.pageviews??c.pageviews,history:B.autoTrack.history??c.history,outboundLinks:B.autoTrack.outboundLinks??c.outboundLinks,buttonClicks:B.autoTrack.buttonClicks??c.buttonClicks,formSubmissions:B.autoTrack.formSubmissions??c.formSubmissions};if(B.autoPageviews===!1)return{...c,pageviews:!1};if(B.autoPageviews===!0)return{...c,pageviews:!0};return c}class m{siteKey;endpoint;debug;flushIntervalMs;autoTrack;queue=[];flushTimer=null;lastTrackedUrl=null;unpatchHistory=null;clickHandler=null;submitHandler=null;pagehideHandler=null;popstateHandler=null;constructor(B){if(this.siteKey=B.siteKey,this.endpoint=B.endpoint.replace(/\/$/,""),this.debug=B.debug??!1,this.flushIntervalMs=B.flushIntervalMs??I,this.autoTrack=g(B),Z(),A())this.installAutoTracking()}static init(B){return new m(B)}getVisitorId(){let B=N(),c=B?.getItem(H);if(c)return c;let _=W();return B?.setItem(H,_),_}getSessionId(){let B=N(),c=B?.getItem(J);if(c)return c;let _=W();return B?.setItem(J,_),_}log(...B){if(this.debug)console.log("[@bts/analytics]",...B)}installAutoTracking(){let B=A(),c=f();if(!B||!c)return;if(this.autoTrack.pageviews)this.recordPageView();if(this.autoTrack.history)this.unpatchHistory=this.patchHistory(B),this.popstateHandler=()=>{this.recordPageView()},B.addEventListener("popstate",this.popstateHandler);if(this.clickHandler=(_)=>{this.handleAutoClick(_)},this.submitHandler=(_)=>{this.handleAutoSubmit(_)},this.pagehideHandler=()=>{this.flushWithBeacon()},this.autoTrack.outboundLinks||this.autoTrack.buttonClicks)c.addEventListener("click",this.clickHandler,!0);if(this.autoTrack.formSubmissions)c.addEventListener("submit",this.submitHandler,!0);B.addEventListener("pagehide",this.pagehideHandler)}patchHistory(B){let c=B.history,_=c.pushState.bind(c),E=c.replaceState.bind(c),S=()=>{Z(),this.recordPageView()};return c.pushState=(...y)=>{_(...y),S()},c.replaceState=(...y)=>{E(...y),S()},()=>{c.pushState=_,c.replaceState=E}}handleAutoClick(B){if(B.defaultPrevented)return;let c=B.target;if(!(c instanceof Element))return;if(this.autoTrack.outboundLinks){let E=c.closest("a[href]");if(E instanceof HTMLAnchorElement){let S=l(E.href);if(!S)return;let y=new URL(S,X());if(y.origin!==X()){this.trackStandard("outbound_click",{elementHref:S,elementId:l(E.id),elementText:l(E.textContent),linkHost:y.hostname});return}}}if(!this.autoTrack.buttonClicks)return;let _=c.closest("button,[role='button'],[data-bts-track-click]");if(!(_ instanceof Element))return;this.trackStandard("button_click",{elementId:l(_.id),elementName:l(_.getAttribute("name")),elementRole:l(_.getAttribute("role")),elementText:l(_.textContent)})}handleAutoSubmit(B){if(B.defaultPrevented||!this.autoTrack.formSubmissions)return;let c=B.target;if(!(c instanceof HTMLFormElement))return;this.trackStandard("form_submit",{formAction:l(c.action),formId:l(c.id),formMethod:l(c.method),formName:l(c.getAttribute("name"))})}buildEvent(B,c,_,E){let S=Z(),y=E?.path??D(),V=Y();return{eventName:B,eventType:c,path:y,referrer:V,occurredAt:new Date().toISOString(),visitorId:this.getVisitorId(),sessionId:this.getSessionId(),properties:{..._??{},attribution:S?{utm:S.utm,clickIds:S.clickIds,landingUrl:S.landingUrl,referrer:S.referrer}:void 0,page:{title:f()?.title,url:C()}}}}queueEvent(B,c,_){let E=K(B),S=F(E.canonicalEventName,_),y={...c??{}};if(E.aliasOf)y.originalEventName=B;if(this.queue.push(this.buildEvent(E.eventName,S,y)),this.queue.length>=50){this.flushNow();return}this.scheduleFlush()}scheduleFlush(){if(this.flushTimer)return;this.flushTimer=setTimeout(()=>{this.flushTimer=null,this.flushNow()},this.flushIntervalMs)}async postJson(B,c){let _=`${this.endpoint}${B}`;return this.log("POST",_,c),fetch(_,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(c)})}flushWithBeacon(){if(this.queue.length===0)return;let B=A(),c=B?.navigator?.sendBeacon?.bind(B.navigator);if(!c)return;let _=[...this.queue];this.queue=[];let E=`${this.endpoint}/ingest/batch`,S=JSON.stringify({siteKey:this.siteKey,events:_}),y=new Blob([S],{type:"application/json"});c(E,y)}async flushNow(){if(this.queue.length===0)return;let B=[...this.queue];this.queue=[];try{let c=await this.postJson("/ingest/batch",{siteKey:this.siteKey,events:B});if(!c.ok)this.log("flush failed",c.status)}catch(c){this.log("flush error",c)}}recordPageView(B){let c=B??D(),_=C()??c??"/";if(_===this.lastTrackedUrl)return;this.lastTrackedUrl=_,this.queue.push(this.buildEvent("page_view","page_view",{autoCaptured:!0},{path:c})),this.scheduleFlush()}page(B){this.lastTrackedUrl=null,this.recordPageView(B)}track(B,c){this.queueEvent(B,c)}trackStandard(B,c){this.queueEvent(B,c)}identify(B,c){this.queue.push(this.buildEvent("identify","identify",{traits:c??{},userId:B})),this.scheduleFlush()}listStandardEvents(){return $()}async startFunnel(B){let c=await this.postJson("/journey/start",{siteKey:this.siteKey,entryPath:B??D()});if(!c.ok)throw Error(`journey/start failed: ${c.status}`);let _=await c.json();return N()?.setItem(Q,_.journeyToken),_}getPersistedJourneyToken(){return N()?.getItem(Q)??null}decorateUrl(B,c){let _=c??this.getPersistedJourneyToken(),E=new URL(B,X());if(E.searchParams.set(z,this.siteKey),_)E.searchParams.set(v,_);return E.toString()}async notifyHandoff(B,c){let _=await this.postJson("/journey/handoff",{journeyToken:B,context:c});if(!_.ok)throw Error(`journey/handoff failed: ${_.status}`)}destroy(){let B=A(),c=f();if(c&&this.clickHandler&&(this.autoTrack.outboundLinks||this.autoTrack.buttonClicks))c.removeEventListener("click",this.clickHandler,!0);if(c&&this.submitHandler&&this.autoTrack.formSubmissions)c.removeEventListener("submit",this.submitHandler,!0);if(B&&this.popstateHandler)B.removeEventListener("popstate",this.popstateHandler);if(B&&this.pagehideHandler)B.removeEventListener("pagehide",this.pagehideHandler);if(this.flushTimer)clearTimeout(this.flushTimer),this.flushTimer=null;this.unpatchHistory?.(),this.unpatchHistory=null,this.flushWithBeacon()}}function L(B){return m.init(B)}if(typeof window<"u")window.BTSAnalytics={BTSAnalytics:m,createBTSAnalytics:L,listStandardWebEvents:$},window.createBTSAnalytics=L;export{$ as listStandardWebEvents,L as createBTSAnalytics,m as BTSAnalytics};
2
-
3
- //# debugId=8CE5844CD6E1239864756E2164756E21
4
- //# sourceMappingURL=browser.js.map
1
+ var J=["page_view","view_content","search","lead","sign_up","begin_checkout","add_payment_info","purchase","outbound_click","button_click","form_submit"],g=new Set(J),u=new Set(["lead","sign_up","begin_checkout","add_payment_info","purchase"]),v={$pageview:"page_view",checkout_started:"begin_checkout",lead_capture:"lead",purchase_completed:"purchase",registration_complete:"sign_up"};function m(S){return g.has(S)}function X(S){let A=S.trim();if(A.length===0)return{eventName:A,canonicalEventName:A,isStandard:!1};let f=A.toLowerCase(),c=v[f];if(c)return{eventName:c,canonicalEventName:c,aliasOf:c,isStandard:!0};return{eventName:A,canonicalEventName:A,isStandard:m(A)}}function Z(S,A){if(A)return A;if(S==="page_view")return"page_view";if(S==="identify")return"identify";if(u.has(S))return"conversion";return"custom"}function C(){return[...J]}var j="bts_analytics_vid",G="bts_analytics_sid",M="bts_analytics_jt",O="bts_analytics_jid",U="bts_analytics_attr",b="bts_site",h="bts_jt",l=300,s="https://api.bts.dev/v2/website/analytics",d={pageviews:!0,history:!0,outboundLinks:!0,buttonClicks:!0,formSubmissions:!0,search:!0,viewContent:!0},Q=["q","query","search"],p=["utm_source","utm_medium","utm_campaign","utm_term","utm_content","fbclid","gclid","gbraid","li_fat_id","msclkid","ttclid","wbraid"],a=["_fbp","_fbc"];function N(){return typeof window>"u"?null:window}function D(){return typeof document>"u"?null:document}function L(){return N()?.localStorage??null}function V(){if(typeof crypto<"u"&&typeof crypto.randomUUID==="function")return crypto.randomUUID();return`v_${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`}function q(S){return S!==null&&typeof S==="object"&&!Array.isArray(S)}function n(S){if(!S)return null;try{let A=JSON.parse(S);if(!q(A))return null;let f=q(A.utm)?Object.fromEntries(Object.entries(A.utm).filter((B)=>typeof B[1]==="string")):{},c=q(A.clickIds)?Object.fromEntries(Object.entries(A.clickIds).filter((B)=>typeof B[1]==="string")):{};return{utm:f,clickIds:c,landingUrl:typeof A.landingUrl==="string"?A.landingUrl:void 0,lastSeenAt:typeof A.lastSeenAt==="string"?A.lastSeenAt:new Date().toISOString(),referrer:typeof A.referrer==="string"?A.referrer:void 0}}catch{return null}}function K(){let S=L();return n(S?.getItem(U)??null)}function o(S){let A=L();if(!A)return;A.setItem(U,JSON.stringify(S))}function F(){let S=N();if(!S)return;return`${S.location.pathname}${S.location.search}`}function H(){return N()?.location.href}function R(){return N()?.location.origin??"https://behindthescenes.com"}function P(){return D()?.referrer||void 0}function z(S){let A=D();if(!A?.cookie)return;let f=`${S}=`,c=A.cookie.split(";").map((E)=>E.trim()).find((E)=>E.startsWith(f));if(!c)return;let B=c.slice(f.length);if(B==="")return;try{return decodeURIComponent(B)}catch{return B}}function r(){let S=z("_ga");if(!S)return;let A=S.split(".");if(A.length>=4&&/^GA\d+$/i.test(A[0]??""))return A.slice(-2).join(".");return _(S)}function t(){let A=D()?.querySelector?.('script[src*="googletagmanager.com/gtag/js?id="]'),f=A&&"src"in A&&typeof A.src==="string"?A.src:void 0;if(!f)return;try{return _(new URL(f).searchParams.get("id"))}catch{return}}function i(){let S=N(),A=r(),f=t(),c=typeof S?.gtag==="function"||!!f||!!A;if(!c&&!A&&!f)return;return{tagInstalled:c,clientId:A,measurementId:f}}function k(S){if(!S.googleAnalytics||typeof S.googleAnalytics==="boolean")return;return _(S.googleAnalytics.measurementId)}function e(S){if(!S.googleAnalytics)return!1;if(S.googleAnalytics===!0)return!1;return S.googleAnalytics.loadTag!==!1&&!!k(S)}function SS(S){let A=N(),f=D();if(!A||!f)return;A.dataLayer=Array.isArray(A.dataLayer)?A.dataLayer:[],A.gtag??=(...B)=>{A.dataLayer?.push(B)};let c=`script[src*="googletagmanager.com/gtag/js?id=${S}"]`;if(!f.querySelector?.(c)){let B=f.createElement("script");B.async=!0,B.src=`https://www.googletagmanager.com/gtag/js?id=${encodeURIComponent(S)}`,B.setAttribute("data-bts-ga-proxy","true"),(f.head??f.documentElement).appendChild(B)}A.gtag("js",new Date),A.gtag("config",S,{send_page_view:!1})}function AS(){let S=N();if(!S)return;let A=typeof Intl<"u"?_(Intl.DateTimeFormat().resolvedOptions().timeZone):void 0,f=S.navigator,c=_(f?.language),B=Array.isArray(f?.languages)?f.languages.filter((y)=>typeof y==="string").slice(0,10):[],E=_(f?.userAgent),T={timezone:A,language:c,languages:B.length>0?B:void 0,userAgent:E};return Object.values(T).some((y)=>y!==void 0)?T:void 0}function Y(S){let A=$(),f=i();return{event_id:S,ga_client_id:typeof f?.clientId==="string"?f.clientId:void 0,ga_tag_installed:typeof f?.tagInstalled==="boolean"?f.tagInstalled:void 0,attribution:A?{utm:A.utm,clickIds:A.clickIds,...A.clickIds,landingUrl:A.landingUrl,referrer:A.referrer}:void 0,client:AS(),googleAnalytics:f,page:{title:D()?.title,url:H()}}}function $(){let S=N();if(!S)return K();let A=new URLSearchParams(S.location.search),f={utm:{},clickIds:{},landingUrl:S.location.href,lastSeenAt:new Date().toISOString(),referrer:P()};for(let E of p){let T=A.get(E);if(!T)continue;if(E.startsWith("utm_")){f.utm[E]=T;continue}f.clickIds[E]=T}for(let E of a){let T=z(E);if(T)f.clickIds[E.replace(/^_/,"")]=T}let c=K(),B={utm:{...c?.utm??{},...f.utm},clickIds:{...c?.clickIds??{},...f.clickIds},landingUrl:f.landingUrl??c?.landingUrl,lastSeenAt:f.lastSeenAt,referrer:f.referrer??c?.referrer};return o(B),B}function _(S){let A=S?.trim();return A?A.slice(0,512):void 0}function fS(S){if(!S)return{};if(S instanceof Headers){let A={};return S.forEach((f,c)=>{A[c]=f}),A}if(Array.isArray(S))return Object.fromEntries(S.map(([A,f])=>[A,String(f)]));return Object.fromEntries(Object.entries(S).map(([A,f])=>[A,String(f)]))}function cS(S){let A={...d};if(S.autoTrack===!1)return{pageviews:!1,history:!1,outboundLinks:!1,buttonClicks:!1,formSubmissions:!1,search:!1,viewContent:!1};if(S.autoTrack&&typeof S.autoTrack==="object")return{pageviews:S.autoTrack.pageviews??A.pageviews,history:S.autoTrack.pageviews===!1?!1:S.autoTrack.history??A.history,outboundLinks:S.autoTrack.outboundLinks??A.outboundLinks,buttonClicks:S.autoTrack.buttonClicks??A.buttonClicks,formSubmissions:S.autoTrack.formSubmissions??A.formSubmissions,search:S.autoTrack.search??A.search,viewContent:S.autoTrack.viewContent??A.viewContent};if(S.autoPageviews===!1)return{...A,pageviews:!1,history:!1};if(S.autoPageviews===!0)return{...A,pageviews:!0};return A}class W{siteKey;endpoint;debug;flushIntervalMs;autoTrack;requestHeaders;queue=[];flushTimer=null;destroyed=!1;lastTrackedUrl=null;unpatchHistory=null;clickHandler=null;submitHandler=null;pagehideHandler=null;popstateHandler=null;viewContentObserver=null;viewContentMutationObserver=null;viewContentRescanPending=!1;viewContentListenersTornDown=!1;observedViewContentElements=new WeakSet;trackedViewContentElements=new WeakSet;constructor(S){if(this.siteKey=S.siteKey,this.endpoint=(S.endpoint??s).replace(/\/$/,""),this.debug=S.debug??!1,this.flushIntervalMs=S.flushIntervalMs??l,this.autoTrack=cS(S),this.requestHeaders=S.requestHeaders,e(S)){let A=k(S);if(A)SS(A)}if($(),N())this.installAutoTracking()}static init(S){return new W(S)}getVisitorId(){let S=L(),A=S?.getItem(j);if(A)return A;let f=V();return S?.setItem(j,f),f}getSessionId(){let S=L(),A=S?.getItem(G);if(A)return A;let f=V();return S?.setItem(G,f),f}getPersistedJourneyId(){return L()?.getItem(O)??null}log(...S){if(this.debug)console.log("[@bts/analytics]",...S)}installAutoTracking(){let S=N(),A=D();if(!S||!A)return;if(this.autoTrack.pageviews)this.recordPageView();if(this.autoTrack.history)this.unpatchHistory=this.patchHistory(S),this.popstateHandler=()=>{this.recordPageView(),this.rescanViewContentAfterNavigation()},S.addEventListener("popstate",this.popstateHandler);if(this.clickHandler=(f)=>{this.handleAutoClick(f)},this.submitHandler=(f)=>{this.handleAutoSubmit(f)},this.pagehideHandler=()=>{this.viewContentListenersTornDown=!0,this.flushWithBeacon(),this.viewContentObserver?.disconnect(),this.viewContentObserver=null,this.viewContentMutationObserver?.disconnect(),this.viewContentMutationObserver=null},this.autoTrack.outboundLinks||this.autoTrack.buttonClicks)A.addEventListener("click",this.clickHandler,!0);if(this.autoTrack.formSubmissions||this.autoTrack.search)A.addEventListener("submit",this.submitHandler,!0);if(this.autoTrack.viewContent)this.installViewContentTracking(A);S.addEventListener("pagehide",this.pagehideHandler)}getOrCreateViewContentIntersectionObserver(){if(typeof IntersectionObserver>"u"||this.viewContentListenersTornDown)return null;if(this.viewContentObserver)return this.viewContentObserver;return this.viewContentObserver=new IntersectionObserver((S,A)=>{for(let f of S){if(!f.isIntersecting||!(f.target instanceof Element))continue;this.trackViewContentElement(f.target),A.unobserve(f.target)}}),this.viewContentObserver}observeViewContentElements(S){if(this.viewContentListenersTornDown||this.destroyed)return;let A=this.getOrCreateViewContentIntersectionObserver();if(!A)return;for(let f of Array.from(S.querySelectorAll("[data-bts-view-content]"))){if(this.observedViewContentElements.has(f))continue;this.observedViewContentElements.add(f),A.observe(f)}}scheduleViewContentRescanFromMutations(){if(this.viewContentListenersTornDown)return;if(this.viewContentRescanPending)return;this.viewContentRescanPending=!0,queueMicrotask(()=>{if(this.viewContentRescanPending=!1,this.viewContentListenersTornDown||this.destroyed)return;let S=D();if(S)this.observeViewContentElements(S)})}rescanViewContentAfterNavigation(){if(!this.autoTrack.viewContent)return;if(this.viewContentListenersTornDown)return;let S=D();if(S)this.observeViewContentElements(S)}installViewContentTracking(S){if(typeof IntersectionObserver>"u")return;if(this.observeViewContentElements(S),typeof MutationObserver>"u")return;let A=S.body??S.documentElement;if(!A)return;this.viewContentMutationObserver=new MutationObserver(()=>{this.scheduleViewContentRescanFromMutations()}),this.viewContentMutationObserver.observe(A,{childList:!0,subtree:!0})}trackViewContentElement(S){if(this.trackedViewContentElements.has(S))return;this.trackedViewContentElements.add(S);let A=_(S.getAttribute("data-bts-view-content")||S.getAttribute("data-bts-content-id")||S.id);this.trackStandard("view_content",{autoCaptured:!0,contentId:A,contentTitle:_(S.getAttribute("data-bts-content-title")||S.textContent),contentType:_(S.getAttribute("data-bts-content-type")),elementId:_(S.id)})}patchHistory(S){let A=S.history,f=A.pushState.bind(A),c=A.replaceState.bind(A),B=()=>{$(),this.recordPageView(),this.rescanViewContentAfterNavigation()};return A.pushState=(...E)=>{f(...E),B()},A.replaceState=(...E)=>{c(...E),B()},()=>{A.pushState=f,A.replaceState=c}}handleAutoClick(S){if(S.defaultPrevented)return;let A=S.target;if(!(A instanceof Element))return;if(this.autoTrack.outboundLinks){let c=A.closest("a[href]");if(c instanceof HTMLAnchorElement){let B=_(c.href);if(!B)return;let E=new URL(B,R());if(E.origin!==R()){this.trackStandard("outbound_click",{elementHref:B,elementId:_(c.id),elementText:_(c.textContent),linkHost:E.hostname});return}}}if(!this.autoTrack.buttonClicks)return;let f=A.closest("button,[role='button'],[data-bts-track-click]");if(!(f instanceof Element))return;this.trackStandard("button_click",{elementId:_(f.id),elementName:_(f.getAttribute("name")),elementRole:_(f.getAttribute("role")),elementText:_(f.textContent)})}handleAutoSubmit(S){if(S.defaultPrevented||!this.autoTrack.formSubmissions&&!this.autoTrack.search)return;let A=S.target;if(!(A instanceof HTMLFormElement))return;if(this.autoTrack.search)this.trackSearchForm(A);if(!this.autoTrack.formSubmissions)return;this.trackStandard("form_submit",{formAction:_(A.action),formId:_(A.id),formMethod:_(A.method),formName:_(A.getAttribute("name"))})}trackSearchForm(S){if((S.method||"get").toLowerCase()!=="get")return;let f=this.extractSearchQuery(S);if(!f)return;this.trackStandard("search",{autoCaptured:!0,formAction:_(S.action),formId:_(S.id),formName:_(S.getAttribute("name")),queryKey:f.key,searchQuery:f.value})}extractSearchQuery(S){try{let f=new FormData(S);for(let c of Q){let B=f.get(c);if(typeof B==="string"){let E=_(B);if(E)return{key:c,value:E}}}}catch{}let A=S.elements;for(let f of Q){let c=A?.namedItem?.(f),B=c&&"value"in c?c.value:void 0;if(typeof B==="string"){let E=_(B);if(E)return{key:f,value:E}}}return null}buildEvent(S,A,f,c){let B=c?.path??F(),E=P(),T=V(),y=Y(T);return{eventName:S,eventType:A,path:B,referrer:E,occurredAt:new Date().toISOString(),journeyId:this.getPersistedJourneyId()??void 0,visitorId:this.getVisitorId(),sessionId:this.getSessionId(),properties:{...f??{},...y,event_id:f?.event_id??f?.eventId??T}}}queueEvent(S,A,f){let c=X(S),B=Z(c.canonicalEventName,f),E={...A??{}};if(c.aliasOf)E.originalEventName=S;if(this.queue.push(this.buildEvent(c.eventName,B,E)),this.queue.length>=50){this.flushNow();return}this.scheduleFlush()}scheduleFlush(){if(this.flushTimer)return;this.flushTimer=setTimeout(()=>{this.flushTimer=null,this.flushNow()},this.flushIntervalMs)}async postJson(S,A){let f=`${this.endpoint}${S}`;this.log("POST",f,A);let c=JSON.stringify(A),B=await this.resolveRequestHeaders(S,f,A,c,this.endpoint);return fetch(f,{method:"POST",headers:B,body:c})}async postWebsiteJson(S,A){let f=this.endpoint.replace(/\/analytics$/,""),c=`${f}${S}`;this.log("POST",c,A);let B=JSON.stringify(A),E=await this.resolveRequestHeaders(S,c,A,B,f);return fetch(c,{method:"POST",headers:E,body:B})}async postJsonKeepalive(S,A){let f=`${this.endpoint}${S}`;this.log("POST keepalive",f,A);let c=JSON.stringify(A),B=await this.resolveRequestHeaders(S,f,A,c,this.endpoint);return fetch(f,{method:"POST",headers:B,body:c,keepalive:!0})}async resolveRequestHeaders(S,A,f,c,B=this.endpoint){let E={"Content-Type":"application/json"};if(!this.requestHeaders)return E;let T=typeof this.requestHeaders==="function"?await this.requestHeaders({body:f,bodyText:c,endpoint:B,headers:{...E},path:S,siteKey:this.siteKey,url:A}):this.requestHeaders;return{...E,...fS(T)}}async flushBatch(S,A=!1){try{let f=A?await this.postJsonKeepalive("/ingest/batch",{siteKey:this.siteKey,events:S}):await this.postJson("/ingest/batch",{siteKey:this.siteKey,events:S});if(!f.ok)return this.log("flush failed",f.status),!1;return!0}catch(f){return this.log("flush error",f),!1}}flushWithBeacon(){if(this.queue.length===0)return;if(this.requestHeaders){let T=[...this.queue];this.queue=[],this.flushBatch(T,!0).then((y)=>{if(!y&&!this.destroyed)this.queue.unshift(...T),this.scheduleFlush()});return}let S=N(),A=S?.navigator?.sendBeacon?.bind(S.navigator);if(!A)return;let f=[...this.queue];this.queue=[];let c=`${this.endpoint}/ingest/batch`,B=JSON.stringify({siteKey:this.siteKey,events:f}),E=new Blob([B],{type:"application/json"});A(c,E)}async flushNow(){if(this.queue.length===0)return;let S=[...this.queue];if(this.queue=[],!await this.flushBatch(S)&&!this.destroyed)this.queue.unshift(...S),this.scheduleFlush()}recordPageView(S){let A=S??F(),f=H()??A??"/";if(f===this.lastTrackedUrl)return;this.lastTrackedUrl=f,this.queue.push(this.buildEvent("page_view","page_view",{autoCaptured:!0},{path:A})),this.scheduleFlush()}page(S){this.lastTrackedUrl=null,this.recordPageView(S)}track(S,A){this.queueEvent(S,A)}trackStandard(S,A){this.queueEvent(S,A)}identify(S,A){this.queue.push(this.buildEvent("identify","identify",{traits:A??{},userId:S})),this.scheduleFlush()}listStandardEvents(){return C()}async submitContactForm(S){let A=V(),f=Y(A),c=await this.postWebsiteJson("/ghl/leads",{siteKey:this.siteKey,locationId:S.locationId,email:S.email,phone:S.phone,subject:S.subject,body:S.body,name:S.name,firstName:S.firstName,lastName:S.lastName,source:S.source,tags:S.tags,customFields:S.customFields,metadata:{...S.metadata??{},...f}});if(!c.ok)throw Error(`contact form submit failed: ${c.status}`);return await c.json()}async startFunnel(S){let A=await this.postJson("/journey/start",{siteKey:this.siteKey,entryPath:S??F()});if(!A.ok)throw Error(`journey/start failed: ${A.status}`);let f=await A.json();return L()?.setItem(M,f.journeyToken),L()?.setItem(O,f.journeyId),f}getPersistedJourneyToken(){return L()?.getItem(M)??null}decorateUrl(S,A){let f=A??this.getPersistedJourneyToken(),c=new URL(S,R());if(c.searchParams.set(b,this.siteKey),f)c.searchParams.set(h,f);return c.toString()}async notifyHandoff(S,A){let f=await this.postJson("/journey/handoff",{journeyToken:S,context:A});if(!f.ok)throw Error(`journey/handoff failed: ${f.status}`)}destroy(){this.destroyed=!0,this.viewContentListenersTornDown=!0;let S=N(),A=D();if(A&&this.clickHandler&&(this.autoTrack.outboundLinks||this.autoTrack.buttonClicks))A.removeEventListener("click",this.clickHandler,!0);if(A&&this.submitHandler&&(this.autoTrack.formSubmissions||this.autoTrack.search))A.removeEventListener("submit",this.submitHandler,!0);if(this.viewContentObserver?.disconnect(),this.viewContentObserver=null,this.viewContentMutationObserver?.disconnect(),this.viewContentMutationObserver=null,S&&this.popstateHandler)S.removeEventListener("popstate",this.popstateHandler);if(S&&this.pagehideHandler)S.removeEventListener("pagehide",this.pagehideHandler);if(this.flushTimer)clearTimeout(this.flushTimer),this.flushTimer=null;this.unpatchHistory?.(),this.unpatchHistory=null,this.flushWithBeacon()}}function w(S){return W.init(S)}function I(S){return typeof S==="object"&&S!==null&&!Array.isArray(S)}function BS(S){return Array.from(S)}function x(S){let A=typeof window>"u"?null:window;if(!A)return;let[f,c,B]=S;if(typeof f!=="string")return;if(f==="js")return;if(f==="config"){if(typeof c!=="string"||c.trim().length===0)return;let y=I(B)?B:{};A.btsAnalytics=w({...y,siteKey:c});return}let E=A.btsAnalytics;if(!E)return;let T=I(B)?B:void 0;if(f==="event"&&typeof c==="string"){E.track(c,T);return}if(f==="identify"&&typeof c==="string"){E.identify(c,T);return}if(f==="page"){E.page(typeof c==="string"?c:void 0);return}if(f==="flush")E.flushNow()}if(typeof window<"u"){let S=Array.isArray(window.btsDataLayer)?[...window.btsDataLayer]:[];window.btsDataLayer=Array.isArray(window.btsDataLayer)?window.btsDataLayer:[],window.BTSAnalytics={BTSAnalytics:W,createBTSAnalytics:w,listStandardWebEvents:C},window.createBTSAnalytics=w;for(let A of S)x(BS(A));window.bts=(...A)=>{window.btsDataLayer?.push(A),x(A)}}export{C as listStandardWebEvents,w as createBTSAnalytics,W as BTSAnalytics};
package/dist/cjs/index.js CHANGED
@@ -1,4 +1 @@
1
- var{defineProperty:M,getOwnPropertyNames:U,getOwnPropertyDescriptor:l}=Object,I=Object.prototype.hasOwnProperty;var Q=new WeakMap,O=(E)=>{var _=Q.get(E),c;if(_)return _;if(_=M({},"__esModule",{value:!0}),E&&typeof E==="object"||typeof E==="function")U(E).map((N)=>!I.call(_,N)&&M(_,N,{get:()=>E[N],enumerable:!(c=l(E,N))||c.enumerable}));return Q.set(E,_),_};var w=(E,_)=>{for(var c in _)M(E,c,{get:_[c],enumerable:!0,configurable:!0,set:(N)=>_[c]=()=>N})};var a={};w(a,{listStandardWebEvents:()=>K,createBTSAnalytics:()=>o,BTSAnalytics:()=>L});module.exports=O(a);var j=["page_view","view_content","search","lead","sign_up","begin_checkout","add_payment_info","purchase","outbound_click","button_click","form_submit"],y=new Set(j),g=new Set(["lead","sign_up","begin_checkout","add_payment_info","purchase"]),v={$pageview:"page_view",checkout_started:"begin_checkout",lead_capture:"lead",purchase_completed:"purchase",registration_complete:"sign_up"};function R(E){return y.has(E)}function C(E){let _=E.trim();if(_.length===0)return{eventName:_,canonicalEventName:_,isStandard:!1};let c=_.toLowerCase(),N=v[c];if(N)return{eventName:N,canonicalEventName:N,aliasOf:N,isStandard:!0};return{eventName:_,canonicalEventName:_,isStandard:R(_)}}function k(E,_){if(_)return _;if(E==="page_view")return"page_view";if(E==="identify")return"identify";if(g.has(E))return"conversion";return"custom"}function K(){return[...j]}var Y="bts_analytics_vid",G="bts_analytics_sid",W="bts_analytics_jt",P="bts_analytics_attr",h="bts_site",x="bts_jt",b=300,T={pageviews:!0,history:!0,outboundLinks:!0,buttonClicks:!0,formSubmissions:!0},p=["utm_source","utm_medium","utm_campaign","utm_term","utm_content","fbclid","gclid","gbraid","li_fat_id","msclkid","ttclid","wbraid"];function q(){return typeof window>"u"?null:window}function Z(){return typeof document>"u"?null:document}function D(){return q()?.localStorage??null}function f(){if(typeof crypto<"u"&&typeof crypto.randomUUID==="function")return crypto.randomUUID();return`v_${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`}function m(E){return E!==null&&typeof E==="object"&&!Array.isArray(E)}function u(E){if(!E)return null;try{let _=JSON.parse(E);if(!m(_))return null;let c=m(_.utm)?Object.fromEntries(Object.entries(_.utm).filter((V)=>typeof V[1]==="string")):{},N=m(_.clickIds)?Object.fromEntries(Object.entries(_.clickIds).filter((V)=>typeof V[1]==="string")):{};return{utm:c,clickIds:N,landingUrl:typeof _.landingUrl==="string"?_.landingUrl:void 0,lastSeenAt:typeof _.lastSeenAt==="string"?_.lastSeenAt:new Date().toISOString(),referrer:typeof _.referrer==="string"?_.referrer:void 0}}catch{return null}}function S(){let E=D();return u(E?.getItem(P)??null)}function d(E){let _=D();if(!_)return;_.setItem(P,JSON.stringify(E))}function F(){let E=q();if(!E)return;return`${E.location.pathname}${E.location.search}`}function A(){return q()?.location.href}function H(){return q()?.location.origin??"https://behindthescenes.com"}function z(){return Z()?.referrer||void 0}function J(){let E=q();if(!E)return S();let _=new URLSearchParams(E.location.search),c={utm:{},clickIds:{},landingUrl:E.location.href,lastSeenAt:new Date().toISOString(),referrer:z()};for(let B of p){let X=_.get(B);if(!X)continue;if(B.startsWith("utm_")){c.utm[B]=X;continue}c.clickIds[B]=X}let N=S(),V={utm:{...N?.utm??{},...c.utm},clickIds:{...N?.clickIds??{},...c.clickIds},landingUrl:c.landingUrl??N?.landingUrl,lastSeenAt:c.lastSeenAt,referrer:c.referrer??N?.referrer};return d(V),V}function $(E){let _=E?.trim();return _?_.slice(0,512):void 0}function s(E){let _={...T};if(E.autoTrack===!1)return{pageviews:!1,history:!1,outboundLinks:!1,buttonClicks:!1,formSubmissions:!1};if(E.autoTrack&&typeof E.autoTrack==="object")return{pageviews:E.autoTrack.pageviews??_.pageviews,history:E.autoTrack.history??_.history,outboundLinks:E.autoTrack.outboundLinks??_.outboundLinks,buttonClicks:E.autoTrack.buttonClicks??_.buttonClicks,formSubmissions:E.autoTrack.formSubmissions??_.formSubmissions};if(E.autoPageviews===!1)return{..._,pageviews:!1};if(E.autoPageviews===!0)return{..._,pageviews:!0};return _}class L{siteKey;endpoint;debug;flushIntervalMs;autoTrack;queue=[];flushTimer=null;lastTrackedUrl=null;unpatchHistory=null;clickHandler=null;submitHandler=null;pagehideHandler=null;popstateHandler=null;constructor(E){if(this.siteKey=E.siteKey,this.endpoint=E.endpoint.replace(/\/$/,""),this.debug=E.debug??!1,this.flushIntervalMs=E.flushIntervalMs??b,this.autoTrack=s(E),J(),q())this.installAutoTracking()}static init(E){return new L(E)}getVisitorId(){let E=D(),_=E?.getItem(Y);if(_)return _;let c=f();return E?.setItem(Y,c),c}getSessionId(){let E=D(),_=E?.getItem(G);if(_)return _;let c=f();return E?.setItem(G,c),c}log(...E){if(this.debug)console.log("[@bts/analytics]",...E)}installAutoTracking(){let E=q(),_=Z();if(!E||!_)return;if(this.autoTrack.pageviews)this.recordPageView();if(this.autoTrack.history)this.unpatchHistory=this.patchHistory(E),this.popstateHandler=()=>{this.recordPageView()},E.addEventListener("popstate",this.popstateHandler);if(this.clickHandler=(c)=>{this.handleAutoClick(c)},this.submitHandler=(c)=>{this.handleAutoSubmit(c)},this.pagehideHandler=()=>{this.flushWithBeacon()},this.autoTrack.outboundLinks||this.autoTrack.buttonClicks)_.addEventListener("click",this.clickHandler,!0);if(this.autoTrack.formSubmissions)_.addEventListener("submit",this.submitHandler,!0);E.addEventListener("pagehide",this.pagehideHandler)}patchHistory(E){let _=E.history,c=_.pushState.bind(_),N=_.replaceState.bind(_),V=()=>{J(),this.recordPageView()};return _.pushState=(...B)=>{c(...B),V()},_.replaceState=(...B)=>{N(...B),V()},()=>{_.pushState=c,_.replaceState=N}}handleAutoClick(E){if(E.defaultPrevented)return;let _=E.target;if(!(_ instanceof Element))return;if(this.autoTrack.outboundLinks){let N=_.closest("a[href]");if(N instanceof HTMLAnchorElement){let V=$(N.href);if(!V)return;let B=new URL(V,H());if(B.origin!==H()){this.trackStandard("outbound_click",{elementHref:V,elementId:$(N.id),elementText:$(N.textContent),linkHost:B.hostname});return}}}if(!this.autoTrack.buttonClicks)return;let c=_.closest("button,[role='button'],[data-bts-track-click]");if(!(c instanceof Element))return;this.trackStandard("button_click",{elementId:$(c.id),elementName:$(c.getAttribute("name")),elementRole:$(c.getAttribute("role")),elementText:$(c.textContent)})}handleAutoSubmit(E){if(E.defaultPrevented||!this.autoTrack.formSubmissions)return;let _=E.target;if(!(_ instanceof HTMLFormElement))return;this.trackStandard("form_submit",{formAction:$(_.action),formId:$(_.id),formMethod:$(_.method),formName:$(_.getAttribute("name"))})}buildEvent(E,_,c,N){let V=J(),B=N?.path??F(),X=z();return{eventName:E,eventType:_,path:B,referrer:X,occurredAt:new Date().toISOString(),visitorId:this.getVisitorId(),sessionId:this.getSessionId(),properties:{...c??{},attribution:V?{utm:V.utm,clickIds:V.clickIds,landingUrl:V.landingUrl,referrer:V.referrer}:void 0,page:{title:Z()?.title,url:A()}}}}queueEvent(E,_,c){let N=C(E),V=k(N.canonicalEventName,c),B={..._??{}};if(N.aliasOf)B.originalEventName=E;if(this.queue.push(this.buildEvent(N.eventName,V,B)),this.queue.length>=50){this.flushNow();return}this.scheduleFlush()}scheduleFlush(){if(this.flushTimer)return;this.flushTimer=setTimeout(()=>{this.flushTimer=null,this.flushNow()},this.flushIntervalMs)}async postJson(E,_){let c=`${this.endpoint}${E}`;return this.log("POST",c,_),fetch(c,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(_)})}flushWithBeacon(){if(this.queue.length===0)return;let E=q(),_=E?.navigator?.sendBeacon?.bind(E.navigator);if(!_)return;let c=[...this.queue];this.queue=[];let N=`${this.endpoint}/ingest/batch`,V=JSON.stringify({siteKey:this.siteKey,events:c}),B=new Blob([V],{type:"application/json"});_(N,B)}async flushNow(){if(this.queue.length===0)return;let E=[...this.queue];this.queue=[];try{let _=await this.postJson("/ingest/batch",{siteKey:this.siteKey,events:E});if(!_.ok)this.log("flush failed",_.status)}catch(_){this.log("flush error",_)}}recordPageView(E){let _=E??F(),c=A()??_??"/";if(c===this.lastTrackedUrl)return;this.lastTrackedUrl=c,this.queue.push(this.buildEvent("page_view","page_view",{autoCaptured:!0},{path:_})),this.scheduleFlush()}page(E){this.lastTrackedUrl=null,this.recordPageView(E)}track(E,_){this.queueEvent(E,_)}trackStandard(E,_){this.queueEvent(E,_)}identify(E,_){this.queue.push(this.buildEvent("identify","identify",{traits:_??{},userId:E})),this.scheduleFlush()}listStandardEvents(){return K()}async startFunnel(E){let _=await this.postJson("/journey/start",{siteKey:this.siteKey,entryPath:E??F()});if(!_.ok)throw Error(`journey/start failed: ${_.status}`);let c=await _.json();return D()?.setItem(W,c.journeyToken),c}getPersistedJourneyToken(){return D()?.getItem(W)??null}decorateUrl(E,_){let c=_??this.getPersistedJourneyToken(),N=new URL(E,H());if(N.searchParams.set(h,this.siteKey),c)N.searchParams.set(x,c);return N.toString()}async notifyHandoff(E,_){let c=await this.postJson("/journey/handoff",{journeyToken:E,context:_});if(!c.ok)throw Error(`journey/handoff failed: ${c.status}`)}destroy(){let E=q(),_=Z();if(_&&this.clickHandler&&(this.autoTrack.outboundLinks||this.autoTrack.buttonClicks))_.removeEventListener("click",this.clickHandler,!0);if(_&&this.submitHandler&&this.autoTrack.formSubmissions)_.removeEventListener("submit",this.submitHandler,!0);if(E&&this.popstateHandler)E.removeEventListener("popstate",this.popstateHandler);if(E&&this.pagehideHandler)E.removeEventListener("pagehide",this.pagehideHandler);if(this.flushTimer)clearTimeout(this.flushTimer),this.flushTimer=null;this.unpatchHistory?.(),this.unpatchHistory=null,this.flushWithBeacon()}}function o(E){return L.init(E)}
2
-
3
- //# debugId=164F30813325DE1964756E2164756E21
4
- //# sourceMappingURL=index.js.map
1
+ var{defineProperty:q,getOwnPropertyNames:g,getOwnPropertyDescriptor:v}=Object,h=Object.prototype.hasOwnProperty;var j=new WeakMap,b=(_)=>{var E=j.get(_),S;if(E)return E;if(E=q({},"__esModule",{value:!0}),_&&typeof _==="object"||typeof _==="function")g(_).map((N)=>!h.call(E,N)&&q(E,N,{get:()=>_[N],enumerable:!(S=v(_,N))||S.enumerable}));return j.set(_,E),E};var m=(_,E)=>{for(var S in E)q(_,S,{get:E[S],enumerable:!0,configurable:!0,set:(N)=>E[S]=()=>N})};var $_={};m($_,{listStandardWebEvents:()=>G,createBTSAnalytics:()=>L_,BTSAnalytics:()=>F});module.exports=b($_);var A=["page_view","view_content","search","lead","sign_up","begin_checkout","add_payment_info","purchase","outbound_click","button_click","form_submit"],y=new Set(A),u=new Set(["lead","sign_up","begin_checkout","add_payment_info","purchase"]),l={$pageview:"page_view",checkout_started:"begin_checkout",lead_capture:"lead",purchase_completed:"purchase",registration_complete:"sign_up"};function p(_){return y.has(_)}function Y(_){let E=_.trim();if(E.length===0)return{eventName:E,canonicalEventName:E,isStandard:!1};let S=E.toLowerCase(),N=l[S];if(N)return{eventName:N,canonicalEventName:N,aliasOf:N,isStandard:!0};return{eventName:E,canonicalEventName:E,isStandard:p(E)}}function f(_,E){if(E)return E;if(_==="page_view")return"page_view";if(_==="identify")return"identify";if(u.has(_))return"conversion";return"custom"}function G(){return[...A]}var K="bts_analytics_vid",U="bts_analytics_sid",R="bts_analytics_jt",H="bts_analytics_jid",k="bts_analytics_attr",d="bts_site",s="bts_jt",o=300,a="https://api.bts.dev/v2/website/analytics",r={pageviews:!0,history:!0,outboundLinks:!0,buttonClicks:!0,formSubmissions:!0,search:!0,viewContent:!0},P=["q","query","search"],n=["utm_source","utm_medium","utm_campaign","utm_term","utm_content","fbclid","gclid","gbraid","li_fat_id","msclkid","ttclid","wbraid"],i=["_fbp","_fbc"];function L(){return typeof window>"u"?null:window}function $(){return typeof document>"u"?null:document}function J(){return L()?.localStorage??null}function Z(){if(typeof crypto<"u"&&typeof crypto.randomUUID==="function")return crypto.randomUUID();return`v_${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`}function M(_){return _!==null&&typeof _==="object"&&!Array.isArray(_)}function t(_){if(!_)return null;try{let E=JSON.parse(_);if(!M(E))return null;let S=M(E.utm)?Object.fromEntries(Object.entries(E.utm).filter((V)=>typeof V[1]==="string")):{},N=M(E.clickIds)?Object.fromEntries(Object.entries(E.clickIds).filter((V)=>typeof V[1]==="string")):{};return{utm:S,clickIds:N,landingUrl:typeof E.landingUrl==="string"?E.landingUrl:void 0,lastSeenAt:typeof E.lastSeenAt==="string"?E.lastSeenAt:new Date().toISOString(),referrer:typeof E.referrer==="string"?E.referrer:void 0}}catch{return null}}function T(){let _=J();return t(_?.getItem(k)??null)}function e(_){let E=J();if(!E)return;E.setItem(k,JSON.stringify(_))}function O(){let _=L();if(!_)return;return`${_.location.pathname}${_.location.search}`}function I(){return L()?.location.href}function Q(){return L()?.location.origin??"https://behindthescenes.com"}function c(){return $()?.referrer||void 0}function x(_){let E=$();if(!E?.cookie)return;let S=`${_}=`,N=E.cookie.split(";").map((D)=>D.trim()).find((D)=>D.startsWith(S));if(!N)return;let V=N.slice(S.length);if(V==="")return;try{return decodeURIComponent(V)}catch{return V}}function __(){let _=x("_ga");if(!_)return;let E=_.split(".");if(E.length>=4&&/^GA\d+$/i.test(E[0]??""))return E.slice(-2).join(".");return W(_)}function E_(){let E=$()?.querySelector?.('script[src*="googletagmanager.com/gtag/js?id="]'),S=E&&"src"in E&&typeof E.src==="string"?E.src:void 0;if(!S)return;try{return W(new URL(S).searchParams.get("id"))}catch{return}}function S_(){let _=L(),E=__(),S=E_(),N=typeof _?.gtag==="function"||!!S||!!E;if(!N&&!E&&!S)return;return{tagInstalled:N,clientId:E,measurementId:S}}function w(_){if(!_.googleAnalytics||typeof _.googleAnalytics==="boolean")return;return W(_.googleAnalytics.measurementId)}function N_(_){if(!_.googleAnalytics)return!1;if(_.googleAnalytics===!0)return!1;return _.googleAnalytics.loadTag!==!1&&!!w(_)}function V_(_){let E=L(),S=$();if(!E||!S)return;E.dataLayer=Array.isArray(E.dataLayer)?E.dataLayer:[],E.gtag??=(...V)=>{E.dataLayer?.push(V)};let N=`script[src*="googletagmanager.com/gtag/js?id=${_}"]`;if(!S.querySelector?.(N)){let V=S.createElement("script");V.async=!0,V.src=`https://www.googletagmanager.com/gtag/js?id=${encodeURIComponent(_)}`,V.setAttribute("data-bts-ga-proxy","true"),(S.head??S.documentElement).appendChild(V)}E.gtag("js",new Date),E.gtag("config",_,{send_page_view:!1})}function D_(){let _=L();if(!_)return;let E=typeof Intl<"u"?W(Intl.DateTimeFormat().resolvedOptions().timeZone):void 0,S=_.navigator,N=W(S?.language),V=Array.isArray(S?.languages)?S.languages.filter((X)=>typeof X==="string").slice(0,10):[],D=W(S?.userAgent),B={timezone:E,language:N,languages:V.length>0?V:void 0,userAgent:D};return Object.values(B).some((X)=>X!==void 0)?B:void 0}function z(_){let E=C(),S=S_();return{event_id:_,ga_client_id:typeof S?.clientId==="string"?S.clientId:void 0,ga_tag_installed:typeof S?.tagInstalled==="boolean"?S.tagInstalled:void 0,attribution:E?{utm:E.utm,clickIds:E.clickIds,...E.clickIds,landingUrl:E.landingUrl,referrer:E.referrer}:void 0,client:D_(),googleAnalytics:S,page:{title:$()?.title,url:I()}}}function C(){let _=L();if(!_)return T();let E=new URLSearchParams(_.location.search),S={utm:{},clickIds:{},landingUrl:_.location.href,lastSeenAt:new Date().toISOString(),referrer:c()};for(let D of n){let B=E.get(D);if(!B)continue;if(D.startsWith("utm_")){S.utm[D]=B;continue}S.clickIds[D]=B}for(let D of i){let B=x(D);if(B)S.clickIds[D.replace(/^_/,"")]=B}let N=T(),V={utm:{...N?.utm??{},...S.utm},clickIds:{...N?.clickIds??{},...S.clickIds},landingUrl:S.landingUrl??N?.landingUrl,lastSeenAt:S.lastSeenAt,referrer:S.referrer??N?.referrer};return e(V),V}function W(_){let E=_?.trim();return E?E.slice(0,512):void 0}function W_(_){if(!_)return{};if(_ instanceof Headers){let E={};return _.forEach((S,N)=>{E[N]=S}),E}if(Array.isArray(_))return Object.fromEntries(_.map(([E,S])=>[E,String(S)]));return Object.fromEntries(Object.entries(_).map(([E,S])=>[E,String(S)]))}function B_(_){let E={...r};if(_.autoTrack===!1)return{pageviews:!1,history:!1,outboundLinks:!1,buttonClicks:!1,formSubmissions:!1,search:!1,viewContent:!1};if(_.autoTrack&&typeof _.autoTrack==="object")return{pageviews:_.autoTrack.pageviews??E.pageviews,history:_.autoTrack.pageviews===!1?!1:_.autoTrack.history??E.history,outboundLinks:_.autoTrack.outboundLinks??E.outboundLinks,buttonClicks:_.autoTrack.buttonClicks??E.buttonClicks,formSubmissions:_.autoTrack.formSubmissions??E.formSubmissions,search:_.autoTrack.search??E.search,viewContent:_.autoTrack.viewContent??E.viewContent};if(_.autoPageviews===!1)return{...E,pageviews:!1,history:!1};if(_.autoPageviews===!0)return{...E,pageviews:!0};return E}class F{siteKey;endpoint;debug;flushIntervalMs;autoTrack;requestHeaders;queue=[];flushTimer=null;destroyed=!1;lastTrackedUrl=null;unpatchHistory=null;clickHandler=null;submitHandler=null;pagehideHandler=null;popstateHandler=null;viewContentObserver=null;viewContentMutationObserver=null;viewContentRescanPending=!1;viewContentListenersTornDown=!1;observedViewContentElements=new WeakSet;trackedViewContentElements=new WeakSet;constructor(_){if(this.siteKey=_.siteKey,this.endpoint=(_.endpoint??a).replace(/\/$/,""),this.debug=_.debug??!1,this.flushIntervalMs=_.flushIntervalMs??o,this.autoTrack=B_(_),this.requestHeaders=_.requestHeaders,N_(_)){let E=w(_);if(E)V_(E)}if(C(),L())this.installAutoTracking()}static init(_){return new F(_)}getVisitorId(){let _=J(),E=_?.getItem(K);if(E)return E;let S=Z();return _?.setItem(K,S),S}getSessionId(){let _=J(),E=_?.getItem(U);if(E)return E;let S=Z();return _?.setItem(U,S),S}getPersistedJourneyId(){return J()?.getItem(H)??null}log(..._){if(this.debug)console.log("[@bts/analytics]",..._)}installAutoTracking(){let _=L(),E=$();if(!_||!E)return;if(this.autoTrack.pageviews)this.recordPageView();if(this.autoTrack.history)this.unpatchHistory=this.patchHistory(_),this.popstateHandler=()=>{this.recordPageView(),this.rescanViewContentAfterNavigation()},_.addEventListener("popstate",this.popstateHandler);if(this.clickHandler=(S)=>{this.handleAutoClick(S)},this.submitHandler=(S)=>{this.handleAutoSubmit(S)},this.pagehideHandler=()=>{this.viewContentListenersTornDown=!0,this.flushWithBeacon(),this.viewContentObserver?.disconnect(),this.viewContentObserver=null,this.viewContentMutationObserver?.disconnect(),this.viewContentMutationObserver=null},this.autoTrack.outboundLinks||this.autoTrack.buttonClicks)E.addEventListener("click",this.clickHandler,!0);if(this.autoTrack.formSubmissions||this.autoTrack.search)E.addEventListener("submit",this.submitHandler,!0);if(this.autoTrack.viewContent)this.installViewContentTracking(E);_.addEventListener("pagehide",this.pagehideHandler)}getOrCreateViewContentIntersectionObserver(){if(typeof IntersectionObserver>"u"||this.viewContentListenersTornDown)return null;if(this.viewContentObserver)return this.viewContentObserver;return this.viewContentObserver=new IntersectionObserver((_,E)=>{for(let S of _){if(!S.isIntersecting||!(S.target instanceof Element))continue;this.trackViewContentElement(S.target),E.unobserve(S.target)}}),this.viewContentObserver}observeViewContentElements(_){if(this.viewContentListenersTornDown||this.destroyed)return;let E=this.getOrCreateViewContentIntersectionObserver();if(!E)return;for(let S of Array.from(_.querySelectorAll("[data-bts-view-content]"))){if(this.observedViewContentElements.has(S))continue;this.observedViewContentElements.add(S),E.observe(S)}}scheduleViewContentRescanFromMutations(){if(this.viewContentListenersTornDown)return;if(this.viewContentRescanPending)return;this.viewContentRescanPending=!0,queueMicrotask(()=>{if(this.viewContentRescanPending=!1,this.viewContentListenersTornDown||this.destroyed)return;let _=$();if(_)this.observeViewContentElements(_)})}rescanViewContentAfterNavigation(){if(!this.autoTrack.viewContent)return;if(this.viewContentListenersTornDown)return;let _=$();if(_)this.observeViewContentElements(_)}installViewContentTracking(_){if(typeof IntersectionObserver>"u")return;if(this.observeViewContentElements(_),typeof MutationObserver>"u")return;let E=_.body??_.documentElement;if(!E)return;this.viewContentMutationObserver=new MutationObserver(()=>{this.scheduleViewContentRescanFromMutations()}),this.viewContentMutationObserver.observe(E,{childList:!0,subtree:!0})}trackViewContentElement(_){if(this.trackedViewContentElements.has(_))return;this.trackedViewContentElements.add(_);let E=W(_.getAttribute("data-bts-view-content")||_.getAttribute("data-bts-content-id")||_.id);this.trackStandard("view_content",{autoCaptured:!0,contentId:E,contentTitle:W(_.getAttribute("data-bts-content-title")||_.textContent),contentType:W(_.getAttribute("data-bts-content-type")),elementId:W(_.id)})}patchHistory(_){let E=_.history,S=E.pushState.bind(E),N=E.replaceState.bind(E),V=()=>{C(),this.recordPageView(),this.rescanViewContentAfterNavigation()};return E.pushState=(...D)=>{S(...D),V()},E.replaceState=(...D)=>{N(...D),V()},()=>{E.pushState=S,E.replaceState=N}}handleAutoClick(_){if(_.defaultPrevented)return;let E=_.target;if(!(E instanceof Element))return;if(this.autoTrack.outboundLinks){let N=E.closest("a[href]");if(N instanceof HTMLAnchorElement){let V=W(N.href);if(!V)return;let D=new URL(V,Q());if(D.origin!==Q()){this.trackStandard("outbound_click",{elementHref:V,elementId:W(N.id),elementText:W(N.textContent),linkHost:D.hostname});return}}}if(!this.autoTrack.buttonClicks)return;let S=E.closest("button,[role='button'],[data-bts-track-click]");if(!(S instanceof Element))return;this.trackStandard("button_click",{elementId:W(S.id),elementName:W(S.getAttribute("name")),elementRole:W(S.getAttribute("role")),elementText:W(S.textContent)})}handleAutoSubmit(_){if(_.defaultPrevented||!this.autoTrack.formSubmissions&&!this.autoTrack.search)return;let E=_.target;if(!(E instanceof HTMLFormElement))return;if(this.autoTrack.search)this.trackSearchForm(E);if(!this.autoTrack.formSubmissions)return;this.trackStandard("form_submit",{formAction:W(E.action),formId:W(E.id),formMethod:W(E.method),formName:W(E.getAttribute("name"))})}trackSearchForm(_){if((_.method||"get").toLowerCase()!=="get")return;let S=this.extractSearchQuery(_);if(!S)return;this.trackStandard("search",{autoCaptured:!0,formAction:W(_.action),formId:W(_.id),formName:W(_.getAttribute("name")),queryKey:S.key,searchQuery:S.value})}extractSearchQuery(_){try{let S=new FormData(_);for(let N of P){let V=S.get(N);if(typeof V==="string"){let D=W(V);if(D)return{key:N,value:D}}}}catch{}let E=_.elements;for(let S of P){let N=E?.namedItem?.(S),V=N&&"value"in N?N.value:void 0;if(typeof V==="string"){let D=W(V);if(D)return{key:S,value:D}}}return null}buildEvent(_,E,S,N){let V=N?.path??O(),D=c(),B=Z(),X=z(B);return{eventName:_,eventType:E,path:V,referrer:D,occurredAt:new Date().toISOString(),journeyId:this.getPersistedJourneyId()??void 0,visitorId:this.getVisitorId(),sessionId:this.getSessionId(),properties:{...S??{},...X,event_id:S?.event_id??S?.eventId??B}}}queueEvent(_,E,S){let N=Y(_),V=f(N.canonicalEventName,S),D={...E??{}};if(N.aliasOf)D.originalEventName=_;if(this.queue.push(this.buildEvent(N.eventName,V,D)),this.queue.length>=50){this.flushNow();return}this.scheduleFlush()}scheduleFlush(){if(this.flushTimer)return;this.flushTimer=setTimeout(()=>{this.flushTimer=null,this.flushNow()},this.flushIntervalMs)}async postJson(_,E){let S=`${this.endpoint}${_}`;this.log("POST",S,E);let N=JSON.stringify(E),V=await this.resolveRequestHeaders(_,S,E,N,this.endpoint);return fetch(S,{method:"POST",headers:V,body:N})}async postWebsiteJson(_,E){let S=this.endpoint.replace(/\/analytics$/,""),N=`${S}${_}`;this.log("POST",N,E);let V=JSON.stringify(E),D=await this.resolveRequestHeaders(_,N,E,V,S);return fetch(N,{method:"POST",headers:D,body:V})}async postJsonKeepalive(_,E){let S=`${this.endpoint}${_}`;this.log("POST keepalive",S,E);let N=JSON.stringify(E),V=await this.resolveRequestHeaders(_,S,E,N,this.endpoint);return fetch(S,{method:"POST",headers:V,body:N,keepalive:!0})}async resolveRequestHeaders(_,E,S,N,V=this.endpoint){let D={"Content-Type":"application/json"};if(!this.requestHeaders)return D;let B=typeof this.requestHeaders==="function"?await this.requestHeaders({body:S,bodyText:N,endpoint:V,headers:{...D},path:_,siteKey:this.siteKey,url:E}):this.requestHeaders;return{...D,...W_(B)}}async flushBatch(_,E=!1){try{let S=E?await this.postJsonKeepalive("/ingest/batch",{siteKey:this.siteKey,events:_}):await this.postJson("/ingest/batch",{siteKey:this.siteKey,events:_});if(!S.ok)return this.log("flush failed",S.status),!1;return!0}catch(S){return this.log("flush error",S),!1}}flushWithBeacon(){if(this.queue.length===0)return;if(this.requestHeaders){let B=[...this.queue];this.queue=[],this.flushBatch(B,!0).then((X)=>{if(!X&&!this.destroyed)this.queue.unshift(...B),this.scheduleFlush()});return}let _=L(),E=_?.navigator?.sendBeacon?.bind(_.navigator);if(!E)return;let S=[...this.queue];this.queue=[];let N=`${this.endpoint}/ingest/batch`,V=JSON.stringify({siteKey:this.siteKey,events:S}),D=new Blob([V],{type:"application/json"});E(N,D)}async flushNow(){if(this.queue.length===0)return;let _=[...this.queue];if(this.queue=[],!await this.flushBatch(_)&&!this.destroyed)this.queue.unshift(..._),this.scheduleFlush()}recordPageView(_){let E=_??O(),S=I()??E??"/";if(S===this.lastTrackedUrl)return;this.lastTrackedUrl=S,this.queue.push(this.buildEvent("page_view","page_view",{autoCaptured:!0},{path:E})),this.scheduleFlush()}page(_){this.lastTrackedUrl=null,this.recordPageView(_)}track(_,E){this.queueEvent(_,E)}trackStandard(_,E){this.queueEvent(_,E)}identify(_,E){this.queue.push(this.buildEvent("identify","identify",{traits:E??{},userId:_})),this.scheduleFlush()}listStandardEvents(){return G()}async submitContactForm(_){let E=Z(),S=z(E),N=await this.postWebsiteJson("/ghl/leads",{siteKey:this.siteKey,locationId:_.locationId,email:_.email,phone:_.phone,subject:_.subject,body:_.body,name:_.name,firstName:_.firstName,lastName:_.lastName,source:_.source,tags:_.tags,customFields:_.customFields,metadata:{..._.metadata??{},...S}});if(!N.ok)throw Error(`contact form submit failed: ${N.status}`);return await N.json()}async startFunnel(_){let E=await this.postJson("/journey/start",{siteKey:this.siteKey,entryPath:_??O()});if(!E.ok)throw Error(`journey/start failed: ${E.status}`);let S=await E.json();return J()?.setItem(R,S.journeyToken),J()?.setItem(H,S.journeyId),S}getPersistedJourneyToken(){return J()?.getItem(R)??null}decorateUrl(_,E){let S=E??this.getPersistedJourneyToken(),N=new URL(_,Q());if(N.searchParams.set(d,this.siteKey),S)N.searchParams.set(s,S);return N.toString()}async notifyHandoff(_,E){let S=await this.postJson("/journey/handoff",{journeyToken:_,context:E});if(!S.ok)throw Error(`journey/handoff failed: ${S.status}`)}destroy(){this.destroyed=!0,this.viewContentListenersTornDown=!0;let _=L(),E=$();if(E&&this.clickHandler&&(this.autoTrack.outboundLinks||this.autoTrack.buttonClicks))E.removeEventListener("click",this.clickHandler,!0);if(E&&this.submitHandler&&(this.autoTrack.formSubmissions||this.autoTrack.search))E.removeEventListener("submit",this.submitHandler,!0);if(this.viewContentObserver?.disconnect(),this.viewContentObserver=null,this.viewContentMutationObserver?.disconnect(),this.viewContentMutationObserver=null,_&&this.popstateHandler)_.removeEventListener("popstate",this.popstateHandler);if(_&&this.pagehideHandler)_.removeEventListener("pagehide",this.pagehideHandler);if(this.flushTimer)clearTimeout(this.flushTimer),this.flushTimer=null;this.unpatchHistory?.(),this.unpatchHistory=null,this.flushWithBeacon()}}function L_(_){return F.init(_)}
package/dist/esm/index.js CHANGED
@@ -1,4 +1 @@
1
- var H=["page_view","view_content","search","lead","sign_up","begin_checkout","add_payment_info","purchase","outbound_click","button_click","form_submit"],P=new Set(H),z=new Set(["lead","sign_up","begin_checkout","add_payment_info","purchase"]),U={$pageview:"page_view",checkout_started:"begin_checkout",lead_capture:"lead",purchase_completed:"purchase",registration_complete:"sign_up"};function l(E){return P.has(E)}function J(E){let _=E.trim();if(_.length===0)return{eventName:_,canonicalEventName:_,isStandard:!1};let c=_.toLowerCase(),N=U[c];if(N)return{eventName:N,canonicalEventName:N,aliasOf:N,isStandard:!0};return{eventName:_,canonicalEventName:_,isStandard:l(_)}}function Q(E,_){if(_)return _;if(E==="page_view")return"page_view";if(E==="identify")return"identify";if(z.has(E))return"conversion";return"custom"}function j(){return[...H]}var C="bts_analytics_vid",k="bts_analytics_sid",Y="bts_analytics_jt",S="bts_analytics_attr",I="bts_site",O="bts_jt",w=300,y={pageviews:!0,history:!0,outboundLinks:!0,buttonClicks:!0,formSubmissions:!0},g=["utm_source","utm_medium","utm_campaign","utm_term","utm_content","fbclid","gclid","gbraid","li_fat_id","msclkid","ttclid","wbraid"];function q(){return typeof window>"u"?null:window}function Z(){return typeof document>"u"?null:document}function D(){return q()?.localStorage??null}function G(){if(typeof crypto<"u"&&typeof crypto.randomUUID==="function")return crypto.randomUUID();return`v_${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`}function L(E){return E!==null&&typeof E==="object"&&!Array.isArray(E)}function v(E){if(!E)return null;try{let _=JSON.parse(E);if(!L(_))return null;let c=L(_.utm)?Object.fromEntries(Object.entries(_.utm).filter((V)=>typeof V[1]==="string")):{},N=L(_.clickIds)?Object.fromEntries(Object.entries(_.clickIds).filter((V)=>typeof V[1]==="string")):{};return{utm:c,clickIds:N,landingUrl:typeof _.landingUrl==="string"?_.landingUrl:void 0,lastSeenAt:typeof _.lastSeenAt==="string"?_.lastSeenAt:new Date().toISOString(),referrer:typeof _.referrer==="string"?_.referrer:void 0}}catch{return null}}function W(){let E=D();return v(E?.getItem(S)??null)}function R(E){let _=D();if(!_)return;_.setItem(S,JSON.stringify(E))}function M(){let E=q();if(!E)return;return`${E.location.pathname}${E.location.search}`}function f(){return q()?.location.href}function K(){return q()?.location.origin??"https://behindthescenes.com"}function A(){return Z()?.referrer||void 0}function m(){let E=q();if(!E)return W();let _=new URLSearchParams(E.location.search),c={utm:{},clickIds:{},landingUrl:E.location.href,lastSeenAt:new Date().toISOString(),referrer:A()};for(let B of g){let X=_.get(B);if(!X)continue;if(B.startsWith("utm_")){c.utm[B]=X;continue}c.clickIds[B]=X}let N=W(),V={utm:{...N?.utm??{},...c.utm},clickIds:{...N?.clickIds??{},...c.clickIds},landingUrl:c.landingUrl??N?.landingUrl,lastSeenAt:c.lastSeenAt,referrer:c.referrer??N?.referrer};return R(V),V}function $(E){let _=E?.trim();return _?_.slice(0,512):void 0}function h(E){let _={...y};if(E.autoTrack===!1)return{pageviews:!1,history:!1,outboundLinks:!1,buttonClicks:!1,formSubmissions:!1};if(E.autoTrack&&typeof E.autoTrack==="object")return{pageviews:E.autoTrack.pageviews??_.pageviews,history:E.autoTrack.history??_.history,outboundLinks:E.autoTrack.outboundLinks??_.outboundLinks,buttonClicks:E.autoTrack.buttonClicks??_.buttonClicks,formSubmissions:E.autoTrack.formSubmissions??_.formSubmissions};if(E.autoPageviews===!1)return{..._,pageviews:!1};if(E.autoPageviews===!0)return{..._,pageviews:!0};return _}class F{siteKey;endpoint;debug;flushIntervalMs;autoTrack;queue=[];flushTimer=null;lastTrackedUrl=null;unpatchHistory=null;clickHandler=null;submitHandler=null;pagehideHandler=null;popstateHandler=null;constructor(E){if(this.siteKey=E.siteKey,this.endpoint=E.endpoint.replace(/\/$/,""),this.debug=E.debug??!1,this.flushIntervalMs=E.flushIntervalMs??w,this.autoTrack=h(E),m(),q())this.installAutoTracking()}static init(E){return new F(E)}getVisitorId(){let E=D(),_=E?.getItem(C);if(_)return _;let c=G();return E?.setItem(C,c),c}getSessionId(){let E=D(),_=E?.getItem(k);if(_)return _;let c=G();return E?.setItem(k,c),c}log(...E){if(this.debug)console.log("[@bts/analytics]",...E)}installAutoTracking(){let E=q(),_=Z();if(!E||!_)return;if(this.autoTrack.pageviews)this.recordPageView();if(this.autoTrack.history)this.unpatchHistory=this.patchHistory(E),this.popstateHandler=()=>{this.recordPageView()},E.addEventListener("popstate",this.popstateHandler);if(this.clickHandler=(c)=>{this.handleAutoClick(c)},this.submitHandler=(c)=>{this.handleAutoSubmit(c)},this.pagehideHandler=()=>{this.flushWithBeacon()},this.autoTrack.outboundLinks||this.autoTrack.buttonClicks)_.addEventListener("click",this.clickHandler,!0);if(this.autoTrack.formSubmissions)_.addEventListener("submit",this.submitHandler,!0);E.addEventListener("pagehide",this.pagehideHandler)}patchHistory(E){let _=E.history,c=_.pushState.bind(_),N=_.replaceState.bind(_),V=()=>{m(),this.recordPageView()};return _.pushState=(...B)=>{c(...B),V()},_.replaceState=(...B)=>{N(...B),V()},()=>{_.pushState=c,_.replaceState=N}}handleAutoClick(E){if(E.defaultPrevented)return;let _=E.target;if(!(_ instanceof Element))return;if(this.autoTrack.outboundLinks){let N=_.closest("a[href]");if(N instanceof HTMLAnchorElement){let V=$(N.href);if(!V)return;let B=new URL(V,K());if(B.origin!==K()){this.trackStandard("outbound_click",{elementHref:V,elementId:$(N.id),elementText:$(N.textContent),linkHost:B.hostname});return}}}if(!this.autoTrack.buttonClicks)return;let c=_.closest("button,[role='button'],[data-bts-track-click]");if(!(c instanceof Element))return;this.trackStandard("button_click",{elementId:$(c.id),elementName:$(c.getAttribute("name")),elementRole:$(c.getAttribute("role")),elementText:$(c.textContent)})}handleAutoSubmit(E){if(E.defaultPrevented||!this.autoTrack.formSubmissions)return;let _=E.target;if(!(_ instanceof HTMLFormElement))return;this.trackStandard("form_submit",{formAction:$(_.action),formId:$(_.id),formMethod:$(_.method),formName:$(_.getAttribute("name"))})}buildEvent(E,_,c,N){let V=m(),B=N?.path??M(),X=A();return{eventName:E,eventType:_,path:B,referrer:X,occurredAt:new Date().toISOString(),visitorId:this.getVisitorId(),sessionId:this.getSessionId(),properties:{...c??{},attribution:V?{utm:V.utm,clickIds:V.clickIds,landingUrl:V.landingUrl,referrer:V.referrer}:void 0,page:{title:Z()?.title,url:f()}}}}queueEvent(E,_,c){let N=J(E),V=Q(N.canonicalEventName,c),B={..._??{}};if(N.aliasOf)B.originalEventName=E;if(this.queue.push(this.buildEvent(N.eventName,V,B)),this.queue.length>=50){this.flushNow();return}this.scheduleFlush()}scheduleFlush(){if(this.flushTimer)return;this.flushTimer=setTimeout(()=>{this.flushTimer=null,this.flushNow()},this.flushIntervalMs)}async postJson(E,_){let c=`${this.endpoint}${E}`;return this.log("POST",c,_),fetch(c,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(_)})}flushWithBeacon(){if(this.queue.length===0)return;let E=q(),_=E?.navigator?.sendBeacon?.bind(E.navigator);if(!_)return;let c=[...this.queue];this.queue=[];let N=`${this.endpoint}/ingest/batch`,V=JSON.stringify({siteKey:this.siteKey,events:c}),B=new Blob([V],{type:"application/json"});_(N,B)}async flushNow(){if(this.queue.length===0)return;let E=[...this.queue];this.queue=[];try{let _=await this.postJson("/ingest/batch",{siteKey:this.siteKey,events:E});if(!_.ok)this.log("flush failed",_.status)}catch(_){this.log("flush error",_)}}recordPageView(E){let _=E??M(),c=f()??_??"/";if(c===this.lastTrackedUrl)return;this.lastTrackedUrl=c,this.queue.push(this.buildEvent("page_view","page_view",{autoCaptured:!0},{path:_})),this.scheduleFlush()}page(E){this.lastTrackedUrl=null,this.recordPageView(E)}track(E,_){this.queueEvent(E,_)}trackStandard(E,_){this.queueEvent(E,_)}identify(E,_){this.queue.push(this.buildEvent("identify","identify",{traits:_??{},userId:E})),this.scheduleFlush()}listStandardEvents(){return j()}async startFunnel(E){let _=await this.postJson("/journey/start",{siteKey:this.siteKey,entryPath:E??M()});if(!_.ok)throw Error(`journey/start failed: ${_.status}`);let c=await _.json();return D()?.setItem(Y,c.journeyToken),c}getPersistedJourneyToken(){return D()?.getItem(Y)??null}decorateUrl(E,_){let c=_??this.getPersistedJourneyToken(),N=new URL(E,K());if(N.searchParams.set(I,this.siteKey),c)N.searchParams.set(O,c);return N.toString()}async notifyHandoff(E,_){let c=await this.postJson("/journey/handoff",{journeyToken:E,context:_});if(!c.ok)throw Error(`journey/handoff failed: ${c.status}`)}destroy(){let E=q(),_=Z();if(_&&this.clickHandler&&(this.autoTrack.outboundLinks||this.autoTrack.buttonClicks))_.removeEventListener("click",this.clickHandler,!0);if(_&&this.submitHandler&&this.autoTrack.formSubmissions)_.removeEventListener("submit",this.submitHandler,!0);if(E&&this.popstateHandler)E.removeEventListener("popstate",this.popstateHandler);if(E&&this.pagehideHandler)E.removeEventListener("pagehide",this.pagehideHandler);if(this.flushTimer)clearTimeout(this.flushTimer),this.flushTimer=null;this.unpatchHistory?.(),this.unpatchHistory=null,this.flushWithBeacon()}}function T(E){return F.init(E)}export{j as listStandardWebEvents,T as createBTSAnalytics,F as BTSAnalytics};
2
-
3
- //# debugId=BF4C211124B7A30C64756E2164756E21
4
- //# sourceMappingURL=index.js.map
1
+ var Q=["page_view","view_content","search","lead","sign_up","begin_checkout","add_payment_info","purchase","outbound_click","button_click","form_submit"],x=new Set(Q),w=new Set(["lead","sign_up","begin_checkout","add_payment_info","purchase"]),g={$pageview:"page_view",checkout_started:"begin_checkout",lead_capture:"lead",purchase_completed:"purchase",registration_complete:"sign_up"};function v(_){return x.has(_)}function C(_){let E=_.trim();if(E.length===0)return{eventName:E,canonicalEventName:E,isStandard:!1};let S=E.toLowerCase(),N=g[S];if(N)return{eventName:N,canonicalEventName:N,aliasOf:N,isStandard:!0};return{eventName:E,canonicalEventName:E,isStandard:v(E)}}function j(_,E){if(E)return E;if(_==="page_view")return"page_view";if(_==="identify")return"identify";if(w.has(_))return"conversion";return"custom"}function A(){return[...Q]}var Y="bts_analytics_vid",f="bts_analytics_sid",K="bts_analytics_jt",U="bts_analytics_jid",T="bts_analytics_attr",h="bts_site",b="bts_jt",m=300,y="https://api.bts.dev/v2/website/analytics",u={pageviews:!0,history:!0,outboundLinks:!0,buttonClicks:!0,formSubmissions:!0,search:!0,viewContent:!0},R=["q","query","search"],l=["utm_source","utm_medium","utm_campaign","utm_term","utm_content","fbclid","gclid","gbraid","li_fat_id","msclkid","ttclid","wbraid"],p=["_fbp","_fbc"];function L(){return typeof window>"u"?null:window}function $(){return typeof document>"u"?null:document}function J(){return L()?.localStorage??null}function Z(){if(typeof crypto<"u"&&typeof crypto.randomUUID==="function")return crypto.randomUUID();return`v_${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`}function F(_){return _!==null&&typeof _==="object"&&!Array.isArray(_)}function d(_){if(!_)return null;try{let E=JSON.parse(_);if(!F(E))return null;let S=F(E.utm)?Object.fromEntries(Object.entries(E.utm).filter((V)=>typeof V[1]==="string")):{},N=F(E.clickIds)?Object.fromEntries(Object.entries(E.clickIds).filter((V)=>typeof V[1]==="string")):{};return{utm:S,clickIds:N,landingUrl:typeof E.landingUrl==="string"?E.landingUrl:void 0,lastSeenAt:typeof E.lastSeenAt==="string"?E.lastSeenAt:new Date().toISOString(),referrer:typeof E.referrer==="string"?E.referrer:void 0}}catch{return null}}function H(){let _=J();return d(_?.getItem(T)??null)}function s(_){let E=J();if(!E)return;E.setItem(T,JSON.stringify(_))}function q(){let _=L();if(!_)return;return`${_.location.pathname}${_.location.search}`}function z(){return L()?.location.href}function G(){return L()?.location.origin??"https://behindthescenes.com"}function k(){return $()?.referrer||void 0}function I(_){let E=$();if(!E?.cookie)return;let S=`${_}=`,N=E.cookie.split(";").map((D)=>D.trim()).find((D)=>D.startsWith(S));if(!N)return;let V=N.slice(S.length);if(V==="")return;try{return decodeURIComponent(V)}catch{return V}}function o(){let _=I("_ga");if(!_)return;let E=_.split(".");if(E.length>=4&&/^GA\d+$/i.test(E[0]??""))return E.slice(-2).join(".");return W(_)}function a(){let E=$()?.querySelector?.('script[src*="googletagmanager.com/gtag/js?id="]'),S=E&&"src"in E&&typeof E.src==="string"?E.src:void 0;if(!S)return;try{return W(new URL(S).searchParams.get("id"))}catch{return}}function r(){let _=L(),E=o(),S=a(),N=typeof _?.gtag==="function"||!!S||!!E;if(!N&&!E&&!S)return;return{tagInstalled:N,clientId:E,measurementId:S}}function c(_){if(!_.googleAnalytics||typeof _.googleAnalytics==="boolean")return;return W(_.googleAnalytics.measurementId)}function n(_){if(!_.googleAnalytics)return!1;if(_.googleAnalytics===!0)return!1;return _.googleAnalytics.loadTag!==!1&&!!c(_)}function i(_){let E=L(),S=$();if(!E||!S)return;E.dataLayer=Array.isArray(E.dataLayer)?E.dataLayer:[],E.gtag??=(...V)=>{E.dataLayer?.push(V)};let N=`script[src*="googletagmanager.com/gtag/js?id=${_}"]`;if(!S.querySelector?.(N)){let V=S.createElement("script");V.async=!0,V.src=`https://www.googletagmanager.com/gtag/js?id=${encodeURIComponent(_)}`,V.setAttribute("data-bts-ga-proxy","true"),(S.head??S.documentElement).appendChild(V)}E.gtag("js",new Date),E.gtag("config",_,{send_page_view:!1})}function t(){let _=L();if(!_)return;let E=typeof Intl<"u"?W(Intl.DateTimeFormat().resolvedOptions().timeZone):void 0,S=_.navigator,N=W(S?.language),V=Array.isArray(S?.languages)?S.languages.filter((X)=>typeof X==="string").slice(0,10):[],D=W(S?.userAgent),B={timezone:E,language:N,languages:V.length>0?V:void 0,userAgent:D};return Object.values(B).some((X)=>X!==void 0)?B:void 0}function P(_){let E=M(),S=r();return{event_id:_,ga_client_id:typeof S?.clientId==="string"?S.clientId:void 0,ga_tag_installed:typeof S?.tagInstalled==="boolean"?S.tagInstalled:void 0,attribution:E?{utm:E.utm,clickIds:E.clickIds,...E.clickIds,landingUrl:E.landingUrl,referrer:E.referrer}:void 0,client:t(),googleAnalytics:S,page:{title:$()?.title,url:z()}}}function M(){let _=L();if(!_)return H();let E=new URLSearchParams(_.location.search),S={utm:{},clickIds:{},landingUrl:_.location.href,lastSeenAt:new Date().toISOString(),referrer:k()};for(let D of l){let B=E.get(D);if(!B)continue;if(D.startsWith("utm_")){S.utm[D]=B;continue}S.clickIds[D]=B}for(let D of p){let B=I(D);if(B)S.clickIds[D.replace(/^_/,"")]=B}let N=H(),V={utm:{...N?.utm??{},...S.utm},clickIds:{...N?.clickIds??{},...S.clickIds},landingUrl:S.landingUrl??N?.landingUrl,lastSeenAt:S.lastSeenAt,referrer:S.referrer??N?.referrer};return s(V),V}function W(_){let E=_?.trim();return E?E.slice(0,512):void 0}function e(_){if(!_)return{};if(_ instanceof Headers){let E={};return _.forEach((S,N)=>{E[N]=S}),E}if(Array.isArray(_))return Object.fromEntries(_.map(([E,S])=>[E,String(S)]));return Object.fromEntries(Object.entries(_).map(([E,S])=>[E,String(S)]))}function __(_){let E={...u};if(_.autoTrack===!1)return{pageviews:!1,history:!1,outboundLinks:!1,buttonClicks:!1,formSubmissions:!1,search:!1,viewContent:!1};if(_.autoTrack&&typeof _.autoTrack==="object")return{pageviews:_.autoTrack.pageviews??E.pageviews,history:_.autoTrack.pageviews===!1?!1:_.autoTrack.history??E.history,outboundLinks:_.autoTrack.outboundLinks??E.outboundLinks,buttonClicks:_.autoTrack.buttonClicks??E.buttonClicks,formSubmissions:_.autoTrack.formSubmissions??E.formSubmissions,search:_.autoTrack.search??E.search,viewContent:_.autoTrack.viewContent??E.viewContent};if(_.autoPageviews===!1)return{...E,pageviews:!1,history:!1};if(_.autoPageviews===!0)return{...E,pageviews:!0};return E}class O{siteKey;endpoint;debug;flushIntervalMs;autoTrack;requestHeaders;queue=[];flushTimer=null;destroyed=!1;lastTrackedUrl=null;unpatchHistory=null;clickHandler=null;submitHandler=null;pagehideHandler=null;popstateHandler=null;viewContentObserver=null;viewContentMutationObserver=null;viewContentRescanPending=!1;viewContentListenersTornDown=!1;observedViewContentElements=new WeakSet;trackedViewContentElements=new WeakSet;constructor(_){if(this.siteKey=_.siteKey,this.endpoint=(_.endpoint??y).replace(/\/$/,""),this.debug=_.debug??!1,this.flushIntervalMs=_.flushIntervalMs??m,this.autoTrack=__(_),this.requestHeaders=_.requestHeaders,n(_)){let E=c(_);if(E)i(E)}if(M(),L())this.installAutoTracking()}static init(_){return new O(_)}getVisitorId(){let _=J(),E=_?.getItem(Y);if(E)return E;let S=Z();return _?.setItem(Y,S),S}getSessionId(){let _=J(),E=_?.getItem(f);if(E)return E;let S=Z();return _?.setItem(f,S),S}getPersistedJourneyId(){return J()?.getItem(U)??null}log(..._){if(this.debug)console.log("[@bts/analytics]",..._)}installAutoTracking(){let _=L(),E=$();if(!_||!E)return;if(this.autoTrack.pageviews)this.recordPageView();if(this.autoTrack.history)this.unpatchHistory=this.patchHistory(_),this.popstateHandler=()=>{this.recordPageView(),this.rescanViewContentAfterNavigation()},_.addEventListener("popstate",this.popstateHandler);if(this.clickHandler=(S)=>{this.handleAutoClick(S)},this.submitHandler=(S)=>{this.handleAutoSubmit(S)},this.pagehideHandler=()=>{this.viewContentListenersTornDown=!0,this.flushWithBeacon(),this.viewContentObserver?.disconnect(),this.viewContentObserver=null,this.viewContentMutationObserver?.disconnect(),this.viewContentMutationObserver=null},this.autoTrack.outboundLinks||this.autoTrack.buttonClicks)E.addEventListener("click",this.clickHandler,!0);if(this.autoTrack.formSubmissions||this.autoTrack.search)E.addEventListener("submit",this.submitHandler,!0);if(this.autoTrack.viewContent)this.installViewContentTracking(E);_.addEventListener("pagehide",this.pagehideHandler)}getOrCreateViewContentIntersectionObserver(){if(typeof IntersectionObserver>"u"||this.viewContentListenersTornDown)return null;if(this.viewContentObserver)return this.viewContentObserver;return this.viewContentObserver=new IntersectionObserver((_,E)=>{for(let S of _){if(!S.isIntersecting||!(S.target instanceof Element))continue;this.trackViewContentElement(S.target),E.unobserve(S.target)}}),this.viewContentObserver}observeViewContentElements(_){if(this.viewContentListenersTornDown||this.destroyed)return;let E=this.getOrCreateViewContentIntersectionObserver();if(!E)return;for(let S of Array.from(_.querySelectorAll("[data-bts-view-content]"))){if(this.observedViewContentElements.has(S))continue;this.observedViewContentElements.add(S),E.observe(S)}}scheduleViewContentRescanFromMutations(){if(this.viewContentListenersTornDown)return;if(this.viewContentRescanPending)return;this.viewContentRescanPending=!0,queueMicrotask(()=>{if(this.viewContentRescanPending=!1,this.viewContentListenersTornDown||this.destroyed)return;let _=$();if(_)this.observeViewContentElements(_)})}rescanViewContentAfterNavigation(){if(!this.autoTrack.viewContent)return;if(this.viewContentListenersTornDown)return;let _=$();if(_)this.observeViewContentElements(_)}installViewContentTracking(_){if(typeof IntersectionObserver>"u")return;if(this.observeViewContentElements(_),typeof MutationObserver>"u")return;let E=_.body??_.documentElement;if(!E)return;this.viewContentMutationObserver=new MutationObserver(()=>{this.scheduleViewContentRescanFromMutations()}),this.viewContentMutationObserver.observe(E,{childList:!0,subtree:!0})}trackViewContentElement(_){if(this.trackedViewContentElements.has(_))return;this.trackedViewContentElements.add(_);let E=W(_.getAttribute("data-bts-view-content")||_.getAttribute("data-bts-content-id")||_.id);this.trackStandard("view_content",{autoCaptured:!0,contentId:E,contentTitle:W(_.getAttribute("data-bts-content-title")||_.textContent),contentType:W(_.getAttribute("data-bts-content-type")),elementId:W(_.id)})}patchHistory(_){let E=_.history,S=E.pushState.bind(E),N=E.replaceState.bind(E),V=()=>{M(),this.recordPageView(),this.rescanViewContentAfterNavigation()};return E.pushState=(...D)=>{S(...D),V()},E.replaceState=(...D)=>{N(...D),V()},()=>{E.pushState=S,E.replaceState=N}}handleAutoClick(_){if(_.defaultPrevented)return;let E=_.target;if(!(E instanceof Element))return;if(this.autoTrack.outboundLinks){let N=E.closest("a[href]");if(N instanceof HTMLAnchorElement){let V=W(N.href);if(!V)return;let D=new URL(V,G());if(D.origin!==G()){this.trackStandard("outbound_click",{elementHref:V,elementId:W(N.id),elementText:W(N.textContent),linkHost:D.hostname});return}}}if(!this.autoTrack.buttonClicks)return;let S=E.closest("button,[role='button'],[data-bts-track-click]");if(!(S instanceof Element))return;this.trackStandard("button_click",{elementId:W(S.id),elementName:W(S.getAttribute("name")),elementRole:W(S.getAttribute("role")),elementText:W(S.textContent)})}handleAutoSubmit(_){if(_.defaultPrevented||!this.autoTrack.formSubmissions&&!this.autoTrack.search)return;let E=_.target;if(!(E instanceof HTMLFormElement))return;if(this.autoTrack.search)this.trackSearchForm(E);if(!this.autoTrack.formSubmissions)return;this.trackStandard("form_submit",{formAction:W(E.action),formId:W(E.id),formMethod:W(E.method),formName:W(E.getAttribute("name"))})}trackSearchForm(_){if((_.method||"get").toLowerCase()!=="get")return;let S=this.extractSearchQuery(_);if(!S)return;this.trackStandard("search",{autoCaptured:!0,formAction:W(_.action),formId:W(_.id),formName:W(_.getAttribute("name")),queryKey:S.key,searchQuery:S.value})}extractSearchQuery(_){try{let S=new FormData(_);for(let N of R){let V=S.get(N);if(typeof V==="string"){let D=W(V);if(D)return{key:N,value:D}}}}catch{}let E=_.elements;for(let S of R){let N=E?.namedItem?.(S),V=N&&"value"in N?N.value:void 0;if(typeof V==="string"){let D=W(V);if(D)return{key:S,value:D}}}return null}buildEvent(_,E,S,N){let V=N?.path??q(),D=k(),B=Z(),X=P(B);return{eventName:_,eventType:E,path:V,referrer:D,occurredAt:new Date().toISOString(),journeyId:this.getPersistedJourneyId()??void 0,visitorId:this.getVisitorId(),sessionId:this.getSessionId(),properties:{...S??{},...X,event_id:S?.event_id??S?.eventId??B}}}queueEvent(_,E,S){let N=C(_),V=j(N.canonicalEventName,S),D={...E??{}};if(N.aliasOf)D.originalEventName=_;if(this.queue.push(this.buildEvent(N.eventName,V,D)),this.queue.length>=50){this.flushNow();return}this.scheduleFlush()}scheduleFlush(){if(this.flushTimer)return;this.flushTimer=setTimeout(()=>{this.flushTimer=null,this.flushNow()},this.flushIntervalMs)}async postJson(_,E){let S=`${this.endpoint}${_}`;this.log("POST",S,E);let N=JSON.stringify(E),V=await this.resolveRequestHeaders(_,S,E,N,this.endpoint);return fetch(S,{method:"POST",headers:V,body:N})}async postWebsiteJson(_,E){let S=this.endpoint.replace(/\/analytics$/,""),N=`${S}${_}`;this.log("POST",N,E);let V=JSON.stringify(E),D=await this.resolveRequestHeaders(_,N,E,V,S);return fetch(N,{method:"POST",headers:D,body:V})}async postJsonKeepalive(_,E){let S=`${this.endpoint}${_}`;this.log("POST keepalive",S,E);let N=JSON.stringify(E),V=await this.resolveRequestHeaders(_,S,E,N,this.endpoint);return fetch(S,{method:"POST",headers:V,body:N,keepalive:!0})}async resolveRequestHeaders(_,E,S,N,V=this.endpoint){let D={"Content-Type":"application/json"};if(!this.requestHeaders)return D;let B=typeof this.requestHeaders==="function"?await this.requestHeaders({body:S,bodyText:N,endpoint:V,headers:{...D},path:_,siteKey:this.siteKey,url:E}):this.requestHeaders;return{...D,...e(B)}}async flushBatch(_,E=!1){try{let S=E?await this.postJsonKeepalive("/ingest/batch",{siteKey:this.siteKey,events:_}):await this.postJson("/ingest/batch",{siteKey:this.siteKey,events:_});if(!S.ok)return this.log("flush failed",S.status),!1;return!0}catch(S){return this.log("flush error",S),!1}}flushWithBeacon(){if(this.queue.length===0)return;if(this.requestHeaders){let B=[...this.queue];this.queue=[],this.flushBatch(B,!0).then((X)=>{if(!X&&!this.destroyed)this.queue.unshift(...B),this.scheduleFlush()});return}let _=L(),E=_?.navigator?.sendBeacon?.bind(_.navigator);if(!E)return;let S=[...this.queue];this.queue=[];let N=`${this.endpoint}/ingest/batch`,V=JSON.stringify({siteKey:this.siteKey,events:S}),D=new Blob([V],{type:"application/json"});E(N,D)}async flushNow(){if(this.queue.length===0)return;let _=[...this.queue];if(this.queue=[],!await this.flushBatch(_)&&!this.destroyed)this.queue.unshift(..._),this.scheduleFlush()}recordPageView(_){let E=_??q(),S=z()??E??"/";if(S===this.lastTrackedUrl)return;this.lastTrackedUrl=S,this.queue.push(this.buildEvent("page_view","page_view",{autoCaptured:!0},{path:E})),this.scheduleFlush()}page(_){this.lastTrackedUrl=null,this.recordPageView(_)}track(_,E){this.queueEvent(_,E)}trackStandard(_,E){this.queueEvent(_,E)}identify(_,E){this.queue.push(this.buildEvent("identify","identify",{traits:E??{},userId:_})),this.scheduleFlush()}listStandardEvents(){return A()}async submitContactForm(_){let E=Z(),S=P(E),N=await this.postWebsiteJson("/ghl/leads",{siteKey:this.siteKey,locationId:_.locationId,email:_.email,phone:_.phone,subject:_.subject,body:_.body,name:_.name,firstName:_.firstName,lastName:_.lastName,source:_.source,tags:_.tags,customFields:_.customFields,metadata:{..._.metadata??{},...S}});if(!N.ok)throw Error(`contact form submit failed: ${N.status}`);return await N.json()}async startFunnel(_){let E=await this.postJson("/journey/start",{siteKey:this.siteKey,entryPath:_??q()});if(!E.ok)throw Error(`journey/start failed: ${E.status}`);let S=await E.json();return J()?.setItem(K,S.journeyToken),J()?.setItem(U,S.journeyId),S}getPersistedJourneyToken(){return J()?.getItem(K)??null}decorateUrl(_,E){let S=E??this.getPersistedJourneyToken(),N=new URL(_,G());if(N.searchParams.set(h,this.siteKey),S)N.searchParams.set(b,S);return N.toString()}async notifyHandoff(_,E){let S=await this.postJson("/journey/handoff",{journeyToken:_,context:E});if(!S.ok)throw Error(`journey/handoff failed: ${S.status}`)}destroy(){this.destroyed=!0,this.viewContentListenersTornDown=!0;let _=L(),E=$();if(E&&this.clickHandler&&(this.autoTrack.outboundLinks||this.autoTrack.buttonClicks))E.removeEventListener("click",this.clickHandler,!0);if(E&&this.submitHandler&&(this.autoTrack.formSubmissions||this.autoTrack.search))E.removeEventListener("submit",this.submitHandler,!0);if(this.viewContentObserver?.disconnect(),this.viewContentObserver=null,this.viewContentMutationObserver?.disconnect(),this.viewContentMutationObserver=null,_&&this.popstateHandler)_.removeEventListener("popstate",this.popstateHandler);if(_&&this.pagehideHandler)_.removeEventListener("pagehide",this.pagehideHandler);if(this.flushTimer)clearTimeout(this.flushTimer),this.flushTimer=null;this.unpatchHistory?.(),this.unpatchHistory=null,this.flushWithBeacon()}}function N_(_){return O.init(_)}export{A as listStandardWebEvents,N_ as createBTSAnalytics,O as BTSAnalytics};
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@behindthescenes/analytics",
3
- "version": "0.0.1",
4
- "generatedAt": "2026-04-10T02:21:22.018Z",
3
+ "version": "0.0.5",
4
+ "generatedAt": "2026-04-29T23:17:34.940Z",
5
5
  "artifacts": {
6
6
  "browser": "browser/browser.js",
7
7
  "cjs": "cjs/index.js",
@@ -1,4 +1,4 @@
1
- import { BTSAnalytics, createBTSAnalytics, listStandardWebEvents, type BTSAnalyticsEventType, type BTSAnalyticsInit, type BTSAnalyticsStandardEventName } from "./index";
1
+ import { BTSAnalytics, createBTSAnalytics, listStandardWebEvents, type BTSAnalyticsEventType, type BTSAnalyticsContactFormInput, type BTSAnalyticsContactFormResult, type BTSAnalyticsInit, type BTSAnalyticsRequestContext, type BTSAnalyticsRequestHeaders, type BTSAnalyticsStandardEventName } from "./index";
2
2
  declare global {
3
3
  interface Window {
4
4
  BTSAnalytics?: {
@@ -7,6 +7,9 @@ declare global {
7
7
  listStandardWebEvents: typeof listStandardWebEvents;
8
8
  };
9
9
  createBTSAnalytics?: typeof createBTSAnalytics;
10
+ btsAnalytics?: BTSAnalytics;
11
+ btsDataLayer?: Array<ArrayLike<unknown>>;
12
+ bts?: (...args: unknown[]) => void;
10
13
  }
11
14
  }
12
- export { BTSAnalytics, createBTSAnalytics, listStandardWebEvents, type BTSAnalyticsEventType, type BTSAnalyticsInit, type BTSAnalyticsStandardEventName, };
15
+ export { BTSAnalytics, createBTSAnalytics, listStandardWebEvents, type BTSAnalyticsEventType, type BTSAnalyticsContactFormInput, type BTSAnalyticsContactFormResult, type BTSAnalyticsInit, type BTSAnalyticsRequestContext, type BTSAnalyticsRequestHeaders, type BTSAnalyticsStandardEventName, };
@@ -5,43 +5,112 @@ type AutoTrackConfig = {
5
5
  outboundLinks: boolean;
6
6
  buttonClicks: boolean;
7
7
  formSubmissions: boolean;
8
+ search: boolean;
9
+ viewContent: boolean;
8
10
  };
11
+ export type BTSAnalyticsRequestContext = {
12
+ body: unknown;
13
+ bodyText: string;
14
+ /** Base URL for the outgoing request (e.g. analytics `.../website/analytics` vs website `.../website`). */
15
+ endpoint: string;
16
+ headers: Record<string, string>;
17
+ path: string;
18
+ siteKey: string;
19
+ url: string;
20
+ };
21
+ export type BTSAnalyticsRequestHeaders = HeadersInit | ((request: BTSAnalyticsRequestContext) => HeadersInit | Promise<HeadersInit>);
9
22
  export type BTSAnalyticsInit = {
10
23
  siteKey: string;
11
- endpoint: string;
24
+ /** Defaults to the production BTS website analytics endpoint. Override for staging/dev. */
25
+ endpoint?: string;
12
26
  autoPageviews?: boolean;
13
27
  autoTrack?: boolean | Partial<AutoTrackConfig>;
14
28
  debug?: boolean;
15
29
  flushIntervalMs?: number;
30
+ /**
31
+ * Optional GA4 compatibility mode. When a measurement ID is provided, the SDK loads the Google tag with
32
+ * `send_page_view: false` so GA scanners can detect an installed tag while BTS forwards events server-side.
33
+ */
34
+ googleAnalytics?: boolean | {
35
+ loadTag?: boolean;
36
+ measurementId?: string;
37
+ };
38
+ requestHeaders?: BTSAnalyticsRequestHeaders;
16
39
  };
17
40
  export type BTSAnalyticsPayloadProperties = Record<string, unknown>;
41
+ /**
42
+ * Contact form payload for BTS website lead capture.
43
+ *
44
+ * The backend requires at least one of `email` or `phone`; validation is enforced server-side.
45
+ */
46
+ export type BTSAnalyticsContactFormInput = {
47
+ locationId: string;
48
+ email?: string;
49
+ phone?: string;
50
+ subject?: string;
51
+ body?: string;
52
+ name?: string;
53
+ firstName?: string;
54
+ lastName?: string;
55
+ source?: string;
56
+ tags?: string[];
57
+ customFields?: Record<string, string | number | boolean>;
58
+ metadata?: Record<string, unknown>;
59
+ };
60
+ export type BTSAnalyticsContactFormResult = {
61
+ ok: boolean;
62
+ data?: unknown;
63
+ };
18
64
  export declare class BTSAnalytics {
19
65
  private siteKey;
20
66
  private endpoint;
21
67
  private debug;
22
68
  private flushIntervalMs;
23
69
  private autoTrack;
70
+ private requestHeaders?;
24
71
  private queue;
25
72
  private flushTimer;
73
+ private destroyed;
26
74
  private lastTrackedUrl;
27
75
  private unpatchHistory;
28
76
  private clickHandler;
29
77
  private submitHandler;
30
78
  private pagehideHandler;
31
79
  private popstateHandler;
80
+ private viewContentObserver;
81
+ private viewContentMutationObserver;
82
+ private viewContentRescanPending;
83
+ private viewContentListenersTornDown;
84
+ private observedViewContentElements;
85
+ private trackedViewContentElements;
32
86
  constructor(init: BTSAnalyticsInit);
33
87
  static init(opts: BTSAnalyticsInit): BTSAnalytics;
34
88
  getVisitorId(): string;
35
89
  getSessionId(): string;
90
+ private getPersistedJourneyId;
36
91
  private log;
37
92
  private installAutoTracking;
93
+ private getOrCreateViewContentIntersectionObserver;
94
+ /** Re-query `[data-bts-view-content]` and register any new elements with the intersection observer. */
95
+ private observeViewContentElements;
96
+ private scheduleViewContentRescanFromMutations;
97
+ private rescanViewContentAfterNavigation;
98
+ private installViewContentTracking;
99
+ private trackViewContentElement;
38
100
  private patchHistory;
39
101
  private handleAutoClick;
40
102
  private handleAutoSubmit;
103
+ private trackSearchForm;
104
+ private extractSearchQuery;
41
105
  private buildEvent;
42
106
  private queueEvent;
43
107
  private scheduleFlush;
44
108
  private postJson;
109
+ private postWebsiteJson;
110
+ private postJsonKeepalive;
111
+ private resolveRequestHeaders;
112
+ /** @returns true when the batch was accepted (2xx). */
113
+ private flushBatch;
45
114
  private flushWithBeacon;
46
115
  flushNow(): Promise<void>;
47
116
  private recordPageView;
@@ -50,6 +119,7 @@ export declare class BTSAnalytics {
50
119
  trackStandard(eventName: BTSAnalyticsStandardEventName, properties?: BTSAnalyticsPayloadProperties): void;
51
120
  identify(userId: string, traits?: BTSAnalyticsPayloadProperties): void;
52
121
  listStandardEvents(): BTSAnalyticsStandardEventName[];
122
+ submitContactForm(input: BTSAnalyticsContactFormInput): Promise<BTSAnalyticsContactFormResult>;
53
123
  /** Start a cross-site funnel; persists journey token for decorateUrl. */
54
124
  startFunnel(entryPath?: string): Promise<{
55
125
  journeyId: string;
@@ -64,4 +134,4 @@ export declare class BTSAnalytics {
64
134
  destroy(): void;
65
135
  }
66
136
  export declare function createBTSAnalytics(init: BTSAnalyticsInit): BTSAnalytics;
67
- export { listStandardWebEvents, type BTSAnalyticsEventType, type BTSAnalyticsStandardEventName };
137
+ export { listStandardWebEvents, type BTSAnalyticsEventType, type BTSAnalyticsStandardEventName, };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@behindthescenes/analytics",
3
- "version": "0.0.1",
3
+ "version": "0.0.5",
4
4
  "description": "Browser analytics SDK for BTS external-site event tracking and attribution.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
@@ -47,12 +47,13 @@
47
47
  "scripts": {
48
48
  "build": "bun ./scripts/build.ts",
49
49
  "build:hosted": "bun ./scripts/sync-hosted-bundle.ts",
50
+ "release:version": "bun ./scripts/bump-release-version.ts",
50
51
  "type-check": "tsc --noEmit",
51
52
  "test": "bun test",
52
53
  "lint": "eslint . --fix",
53
54
  "lint:check": "eslint .",
54
55
  "release:check": "bun run lint:check && bun run type-check && bun run test && bun run build && npm pack --dry-run",
55
- "release:publish": "bun run release:check && npm publish --access public",
56
+ "release:publish": "bun run release:version && bun run release:check && npm publish --access public",
56
57
  "prepublishOnly": "bun run release:check"
57
58
  },
58
59
  "devDependencies": {
@@ -61,4 +62,4 @@
61
62
  "eslint": "^9.17.0",
62
63
  "typescript": "^5.8.3"
63
64
  }
64
- }
65
+ }
@@ -1,12 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../src/events.ts", "../../src/index.ts", "../../src/browser.ts"],
4
- "sourcesContent": [
5
- "export const STANDARD_WEB_EVENTS = [\n \"page_view\",\n \"view_content\",\n \"search\",\n \"lead\",\n \"sign_up\",\n \"begin_checkout\",\n \"add_payment_info\",\n \"purchase\",\n \"outbound_click\",\n \"button_click\",\n \"form_submit\",\n] as const;\n\nexport type BTSAnalyticsStandardEventName = (typeof STANDARD_WEB_EVENTS)[number];\n\nexport type BTSAnalyticsEventType = \"page_view\" | \"custom\" | \"identify\" | \"conversion\";\n\nconst STANDARD_EVENT_SET = new Set<string>(STANDARD_WEB_EVENTS);\n\nconst CONVERSION_EVENTS = new Set<string>([\"lead\", \"sign_up\", \"begin_checkout\", \"add_payment_info\", \"purchase\"]);\n\nconst LEGACY_EVENT_ALIASES: Record<string, BTSAnalyticsStandardEventName> = {\n $pageview: \"page_view\",\n checkout_started: \"begin_checkout\",\n lead_capture: \"lead\",\n purchase_completed: \"purchase\",\n registration_complete: \"sign_up\",\n};\n\nexport function isStandardWebEventName(eventName: string): eventName is BTSAnalyticsStandardEventName {\n return STANDARD_EVENT_SET.has(eventName);\n}\n\nexport function normalizeEventName(eventName: string): {\n eventName: string;\n canonicalEventName: string;\n aliasOf?: BTSAnalyticsStandardEventName;\n isStandard: boolean;\n} {\n const trimmed = eventName.trim();\n if (trimmed.length === 0) {\n return {\n eventName: trimmed,\n canonicalEventName: trimmed,\n isStandard: false,\n };\n }\n\n const lowered = trimmed.toLowerCase();\n const aliasOf = LEGACY_EVENT_ALIASES[lowered];\n if (aliasOf) {\n return {\n eventName: aliasOf,\n canonicalEventName: aliasOf,\n aliasOf,\n isStandard: true,\n };\n }\n\n return {\n eventName: trimmed,\n canonicalEventName: trimmed,\n isStandard: isStandardWebEventName(trimmed),\n };\n}\n\nexport function resolveEventType(eventName: string, explicitType?: BTSAnalyticsEventType): BTSAnalyticsEventType {\n if (explicitType) {\n return explicitType;\n }\n if (eventName === \"page_view\") {\n return \"page_view\";\n }\n if (eventName === \"identify\") {\n return \"identify\";\n }\n if (CONVERSION_EVENTS.has(eventName)) {\n return \"conversion\";\n }\n return \"custom\";\n}\n\nexport function listStandardWebEvents(): BTSAnalyticsStandardEventName[] {\n return [...STANDARD_WEB_EVENTS];\n}\n",
6
- "import {\n listStandardWebEvents,\n normalizeEventName,\n resolveEventType,\n type BTSAnalyticsEventType,\n type BTSAnalyticsStandardEventName,\n} from \"./events\";\n\nconst STORAGE_VISITOR = \"bts_analytics_vid\";\nconst STORAGE_SESSION = \"bts_analytics_sid\";\nconst STORAGE_JOURNEY = \"bts_analytics_jt\";\nconst STORAGE_ATTRIBUTION = \"bts_analytics_attr\";\nconst QUERY_SITE = \"bts_site\";\nconst QUERY_JOURNEY = \"bts_jt\";\n\nconst DEFAULT_FLUSH_INTERVAL_MS = 300;\n\nconst DEFAULT_AUTO_TRACK = {\n pageviews: true,\n history: true,\n outboundLinks: true,\n buttonClicks: true,\n formSubmissions: true,\n} as const;\n\nconst ATTRIBUTION_QUERY_KEYS = [\n \"utm_source\",\n \"utm_medium\",\n \"utm_campaign\",\n \"utm_term\",\n \"utm_content\",\n \"fbclid\",\n \"gclid\",\n \"gbraid\",\n \"li_fat_id\",\n \"msclkid\",\n \"ttclid\",\n \"wbraid\",\n] as const;\n\ntype AutoTrackConfig = {\n pageviews: boolean;\n history: boolean;\n outboundLinks: boolean;\n buttonClicks: boolean;\n formSubmissions: boolean;\n};\n\nexport type BTSAnalyticsInit = {\n siteKey: string;\n endpoint: string;\n autoPageviews?: boolean;\n autoTrack?: boolean | Partial<AutoTrackConfig>;\n debug?: boolean;\n flushIntervalMs?: number;\n};\n\nexport type BTSAnalyticsPayloadProperties = Record<string, unknown>;\n\ntype StoredAttribution = {\n utm: Record<string, string>;\n clickIds: Record<string, string>;\n landingUrl?: string;\n lastSeenAt: string;\n referrer?: string;\n};\n\ntype QueuedAnalyticsEvent = {\n eventName: string;\n eventType: BTSAnalyticsEventType;\n path?: string;\n referrer?: string;\n occurredAt: string;\n visitorId: string;\n sessionId: string;\n properties: BTSAnalyticsPayloadProperties;\n};\n\nfunction safeWindow(): Window | null {\n return typeof window === \"undefined\" ? null : window;\n}\n\nfunction safeDocument(): Document | null {\n return typeof document === \"undefined\" ? null : document;\n}\n\nfunction safeStorage(): Storage | null {\n const win = safeWindow();\n return win?.localStorage ?? null;\n}\n\nfunction randomId(): string {\n if (typeof crypto !== \"undefined\" && typeof crypto.randomUUID === \"function\") {\n return crypto.randomUUID();\n }\n return `v_${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\n\nfunction parseStoredAttribution(raw: string | null): StoredAttribution | null {\n if (!raw) {\n return null;\n }\n try {\n const parsed = JSON.parse(raw) as unknown;\n if (!isRecord(parsed)) {\n return null;\n }\n const utm = isRecord(parsed.utm)\n ? Object.fromEntries(Object.entries(parsed.utm).filter((entry): entry is [string, string] => typeof entry[1] === \"string\"))\n : {};\n const clickIds = isRecord(parsed.clickIds)\n ? Object.fromEntries(\n Object.entries(parsed.clickIds).filter((entry): entry is [string, string] => typeof entry[1] === \"string\"),\n )\n : {};\n return {\n utm,\n clickIds,\n landingUrl: typeof parsed.landingUrl === \"string\" ? parsed.landingUrl : undefined,\n lastSeenAt: typeof parsed.lastSeenAt === \"string\" ? parsed.lastSeenAt : new Date().toISOString(),\n referrer: typeof parsed.referrer === \"string\" ? parsed.referrer : undefined,\n };\n } catch {\n return null;\n }\n}\n\nfunction readStoredAttribution(): StoredAttribution | null {\n const storage = safeStorage();\n return parseStoredAttribution(storage?.getItem(STORAGE_ATTRIBUTION) ?? null);\n}\n\nfunction writeStoredAttribution(next: StoredAttribution): void {\n const storage = safeStorage();\n if (!storage) {\n return;\n }\n storage.setItem(STORAGE_ATTRIBUTION, JSON.stringify(next));\n}\n\nfunction currentPath(): string | undefined {\n const win = safeWindow();\n if (!win) {\n return undefined;\n }\n return `${win.location.pathname}${win.location.search}`;\n}\n\nfunction currentUrl(): string | undefined {\n return safeWindow()?.location.href;\n}\n\nfunction currentOrigin(): string {\n return safeWindow()?.location.origin ?? \"https://behindthescenes.com\";\n}\n\nfunction currentReferrer(): string | undefined {\n return safeDocument()?.referrer || undefined;\n}\n\nfunction readCurrentAttribution(): StoredAttribution | null {\n const win = safeWindow();\n if (!win) {\n return readStoredAttribution();\n }\n const params = new URLSearchParams(win.location.search);\n const next: StoredAttribution = {\n utm: {},\n clickIds: {},\n landingUrl: win.location.href,\n lastSeenAt: new Date().toISOString(),\n referrer: currentReferrer(),\n };\n for (const key of ATTRIBUTION_QUERY_KEYS) {\n const value = params.get(key);\n if (!value) {\n continue;\n }\n if (key.startsWith(\"utm_\")) {\n next.utm[key] = value;\n continue;\n }\n next.clickIds[key] = value;\n }\n\n const previous = readStoredAttribution();\n const merged: StoredAttribution = {\n utm: { ...(previous?.utm ?? {}), ...next.utm },\n clickIds: { ...(previous?.clickIds ?? {}), ...next.clickIds },\n landingUrl: next.landingUrl ?? previous?.landingUrl,\n lastSeenAt: next.lastSeenAt,\n referrer: next.referrer ?? previous?.referrer,\n };\n writeStoredAttribution(merged);\n return merged;\n}\n\nfunction sanitizeText(value: string | null | undefined): string | undefined {\n const trimmed = value?.trim();\n return trimmed ? trimmed.slice(0, 512) : undefined;\n}\n\nfunction createAutoTrackConfig(init: BTSAnalyticsInit): AutoTrackConfig {\n const base = { ...DEFAULT_AUTO_TRACK };\n if (init.autoTrack === false) {\n return {\n pageviews: false,\n history: false,\n outboundLinks: false,\n buttonClicks: false,\n formSubmissions: false,\n };\n }\n if (init.autoTrack && typeof init.autoTrack === \"object\") {\n return {\n pageviews: init.autoTrack.pageviews ?? base.pageviews,\n history: init.autoTrack.history ?? base.history,\n outboundLinks: init.autoTrack.outboundLinks ?? base.outboundLinks,\n buttonClicks: init.autoTrack.buttonClicks ?? base.buttonClicks,\n formSubmissions: init.autoTrack.formSubmissions ?? base.formSubmissions,\n };\n }\n if (init.autoPageviews === false) {\n return {\n ...base,\n pageviews: false,\n }\n }\n if (init.autoPageviews === true) {\n return {\n ...base,\n pageviews: true,\n }\n }\n return base;\n}\n\nexport class BTSAnalytics {\n private siteKey: string;\n private endpoint: string;\n private debug: boolean;\n private flushIntervalMs: number;\n private autoTrack: AutoTrackConfig;\n private queue: QueuedAnalyticsEvent[] = [];\n private flushTimer: ReturnType<typeof setTimeout> | null = null;\n private lastTrackedUrl: string | null = null;\n private unpatchHistory: (() => void) | null = null;\n private clickHandler: ((event: Event) => void) | null = null;\n private submitHandler: ((event: Event) => void) | null = null;\n private pagehideHandler: (() => void) | null = null;\n private popstateHandler: (() => void) | null = null;\n\n constructor(init: BTSAnalyticsInit) {\n this.siteKey = init.siteKey;\n this.endpoint = init.endpoint.replace(/\\/$/, \"\");\n this.debug = init.debug ?? false;\n this.flushIntervalMs = init.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS;\n this.autoTrack = createAutoTrackConfig(init);\n\n readCurrentAttribution();\n\n if (safeWindow()) {\n this.installAutoTracking();\n }\n }\n\n static init(opts: BTSAnalyticsInit): BTSAnalytics {\n return new BTSAnalytics(opts);\n }\n\n getVisitorId(): string {\n const storage = safeStorage();\n const existing = storage?.getItem(STORAGE_VISITOR);\n if (existing) {\n return existing;\n }\n const id = randomId();\n storage?.setItem(STORAGE_VISITOR, id);\n return id;\n }\n\n getSessionId(): string {\n const storage = safeStorage();\n const existing = storage?.getItem(STORAGE_SESSION);\n if (existing) {\n return existing;\n }\n const id = randomId();\n storage?.setItem(STORAGE_SESSION, id);\n return id;\n }\n\n private log(...args: unknown[]) {\n if (this.debug) {\n // eslint-disable-next-line no-console\n console.log(\"[@bts/analytics]\", ...args);\n }\n }\n\n private installAutoTracking(): void {\n const win = safeWindow();\n const doc = safeDocument();\n if (!win || !doc) {\n return;\n }\n\n if (this.autoTrack.pageviews) {\n this.recordPageView();\n }\n\n if (this.autoTrack.history) {\n this.unpatchHistory = this.patchHistory(win);\n this.popstateHandler = () => {\n this.recordPageView();\n };\n win.addEventListener(\"popstate\", this.popstateHandler);\n }\n\n this.clickHandler = (event: Event) => {\n this.handleAutoClick(event);\n };\n this.submitHandler = (event: Event) => {\n this.handleAutoSubmit(event);\n };\n this.pagehideHandler = () => {\n this.flushWithBeacon();\n };\n\n if (this.autoTrack.outboundLinks || this.autoTrack.buttonClicks) {\n doc.addEventListener(\"click\", this.clickHandler, true);\n }\n if (this.autoTrack.formSubmissions) {\n doc.addEventListener(\"submit\", this.submitHandler, true);\n }\n win.addEventListener(\"pagehide\", this.pagehideHandler);\n }\n\n private patchHistory(win: Window): () => void {\n const history = win.history;\n const originalPushState = history.pushState.bind(history);\n const originalReplaceState = history.replaceState.bind(history);\n\n const onHistoryChange = () => {\n readCurrentAttribution();\n this.recordPageView();\n };\n\n history.pushState = ((...args: Parameters<History[\"pushState\"]>) => {\n originalPushState(...args);\n onHistoryChange();\n }) as History[\"pushState\"];\n\n history.replaceState = ((...args: Parameters<History[\"replaceState\"]>) => {\n originalReplaceState(...args);\n onHistoryChange();\n }) as History[\"replaceState\"];\n\n return () => {\n history.pushState = originalPushState;\n history.replaceState = originalReplaceState;\n };\n }\n\n private handleAutoClick(event: Event): void {\n if (event.defaultPrevented) {\n return;\n }\n const target = event.target;\n if (!(target instanceof Element)) {\n return;\n }\n\n if (this.autoTrack.outboundLinks) {\n const anchor = target.closest(\"a[href]\");\n if (anchor instanceof HTMLAnchorElement) {\n const href = sanitizeText(anchor.href);\n if (!href) {\n return;\n }\n const destination = new URL(href, currentOrigin());\n if (destination.origin !== currentOrigin()) {\n this.trackStandard(\"outbound_click\", {\n elementHref: href,\n elementId: sanitizeText(anchor.id),\n elementText: sanitizeText(anchor.textContent),\n linkHost: destination.hostname,\n });\n return;\n }\n }\n }\n\n if (!this.autoTrack.buttonClicks) {\n return;\n }\n\n const button = target.closest(\"button,[role='button'],[data-bts-track-click]\");\n if (!(button instanceof Element)) {\n return;\n }\n this.trackStandard(\"button_click\", {\n elementId: sanitizeText(button.id),\n elementName: sanitizeText(button.getAttribute(\"name\")),\n elementRole: sanitizeText(button.getAttribute(\"role\")),\n elementText: sanitizeText(button.textContent),\n });\n }\n\n private handleAutoSubmit(event: Event): void {\n if (event.defaultPrevented || !this.autoTrack.formSubmissions) {\n return;\n }\n const target = event.target;\n if (!(target instanceof HTMLFormElement)) {\n return;\n }\n this.trackStandard(\"form_submit\", {\n formAction: sanitizeText(target.action),\n formId: sanitizeText(target.id),\n formMethod: sanitizeText(target.method),\n formName: sanitizeText(target.getAttribute(\"name\")),\n });\n }\n\n private buildEvent(\n eventName: string,\n eventType: BTSAnalyticsEventType,\n properties?: BTSAnalyticsPayloadProperties,\n overrides?: { path?: string },\n ): QueuedAnalyticsEvent {\n const attribution = readCurrentAttribution();\n const path = overrides?.path ?? currentPath();\n const referrer = currentReferrer();\n return {\n eventName,\n eventType,\n path,\n referrer,\n occurredAt: new Date().toISOString(),\n visitorId: this.getVisitorId(),\n sessionId: this.getSessionId(),\n properties: {\n ...(properties ?? {}),\n attribution: attribution\n ? {\n utm: attribution.utm,\n clickIds: attribution.clickIds,\n landingUrl: attribution.landingUrl,\n referrer: attribution.referrer,\n }\n : undefined,\n page: {\n title: safeDocument()?.title,\n url: currentUrl(),\n },\n },\n };\n }\n\n private queueEvent(eventName: string, properties?: BTSAnalyticsPayloadProperties, explicitType?: BTSAnalyticsEventType): void {\n const normalized = normalizeEventName(eventName);\n const eventType = resolveEventType(normalized.canonicalEventName, explicitType);\n const nextProperties: BTSAnalyticsPayloadProperties = {\n ...(properties ?? {}),\n };\n if (normalized.aliasOf) {\n nextProperties.originalEventName = eventName;\n }\n this.queue.push(this.buildEvent(normalized.eventName, eventType, nextProperties));\n if (this.queue.length >= 50) {\n void this.flushNow();\n return;\n }\n this.scheduleFlush();\n }\n\n private scheduleFlush(): void {\n if (this.flushTimer) {\n return;\n }\n this.flushTimer = setTimeout(() => {\n this.flushTimer = null;\n void this.flushNow();\n }, this.flushIntervalMs);\n }\n\n private async postJson(path: string, body: unknown): Promise<Response> {\n const url = `${this.endpoint}${path}`;\n this.log(\"POST\", url, body);\n return fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(body),\n });\n }\n\n private flushWithBeacon(): void {\n if (this.queue.length === 0) {\n return;\n }\n const win = safeWindow();\n const beacon = win?.navigator?.sendBeacon?.bind(win.navigator);\n if (!beacon) {\n return;\n }\n const batch = [...this.queue];\n this.queue = [];\n const url = `${this.endpoint}/ingest/batch`;\n const body = JSON.stringify({ siteKey: this.siteKey, events: batch });\n const blob = new Blob([body], { type: \"application/json\" });\n beacon(url, blob);\n }\n\n async flushNow(): Promise<void> {\n if (this.queue.length === 0) {\n return;\n }\n const batch = [...this.queue];\n this.queue = [];\n try {\n const response = await this.postJson(\"/ingest/batch\", {\n siteKey: this.siteKey,\n events: batch,\n });\n if (!response.ok) {\n this.log(\"flush failed\", response.status);\n }\n } catch (error) {\n this.log(\"flush error\", error);\n }\n }\n\n private recordPageView(path?: string): void {\n const derivedPath = path ?? currentPath();\n const url = currentUrl() ?? derivedPath ?? \"/\";\n if (url === this.lastTrackedUrl) {\n return;\n }\n this.lastTrackedUrl = url;\n this.queue.push(\n this.buildEvent(\n \"page_view\",\n \"page_view\",\n {\n autoCaptured: true,\n },\n { path: derivedPath },\n ),\n );\n this.scheduleFlush();\n }\n\n page(path?: string): void {\n this.lastTrackedUrl = null;\n this.recordPageView(path);\n }\n\n track(eventName: string, properties?: BTSAnalyticsPayloadProperties): void {\n this.queueEvent(eventName, properties);\n }\n\n trackStandard(eventName: BTSAnalyticsStandardEventName, properties?: BTSAnalyticsPayloadProperties): void {\n this.queueEvent(eventName, properties);\n }\n\n identify(userId: string, traits?: BTSAnalyticsPayloadProperties): void {\n this.queue.push(\n this.buildEvent(\"identify\", \"identify\", {\n traits: traits ?? {},\n userId,\n }),\n );\n this.scheduleFlush();\n }\n\n listStandardEvents(): BTSAnalyticsStandardEventName[] {\n return listStandardWebEvents();\n }\n\n /** Start a cross-site funnel; persists journey token for decorateUrl. */\n async startFunnel(entryPath?: string): Promise<{ journeyId: string; journeyToken: string; expiresAt: number }> {\n const response = await this.postJson(\"/journey/start\", {\n siteKey: this.siteKey,\n entryPath: entryPath ?? currentPath(),\n });\n if (!response.ok) {\n throw new Error(`journey/start failed: ${response.status}`);\n }\n const data = (await response.json()) as {\n journeyId: string;\n journeyToken: string;\n expiresAt: number;\n };\n safeStorage()?.setItem(STORAGE_JOURNEY, data.journeyToken);\n return data;\n }\n\n getPersistedJourneyToken(): string | null {\n return safeStorage()?.getItem(STORAGE_JOURNEY) ?? null;\n }\n\n /** Append site key + journey token to a BTS URL (checkout, join, etc.). */\n decorateUrl(url: string, journeyToken?: string): string {\n const token = journeyToken ?? this.getPersistedJourneyToken();\n const next = new URL(url, currentOrigin());\n next.searchParams.set(QUERY_SITE, this.siteKey);\n if (token) {\n next.searchParams.set(QUERY_JOURNEY, token);\n }\n return next.toString();\n }\n\n /** Call from BTS after accepting the journey (optional; server can also infer). */\n async notifyHandoff(journeyToken: string, context?: Record<string, unknown>): Promise<void> {\n const response = await this.postJson(\"/journey/handoff\", { journeyToken, context });\n if (!response.ok) {\n throw new Error(`journey/handoff failed: ${response.status}`);\n }\n }\n\n destroy(): void {\n const win = safeWindow();\n const doc = safeDocument();\n if (doc && this.clickHandler && (this.autoTrack.outboundLinks || this.autoTrack.buttonClicks)) {\n doc.removeEventListener(\"click\", this.clickHandler, true);\n }\n if (doc && this.submitHandler && this.autoTrack.formSubmissions) {\n doc.removeEventListener(\"submit\", this.submitHandler, true);\n }\n if (win && this.popstateHandler) {\n win.removeEventListener(\"popstate\", this.popstateHandler);\n }\n if (win && this.pagehideHandler) {\n win.removeEventListener(\"pagehide\", this.pagehideHandler);\n }\n if (this.flushTimer) {\n clearTimeout(this.flushTimer);\n this.flushTimer = null;\n }\n this.unpatchHistory?.();\n this.unpatchHistory = null;\n this.flushWithBeacon();\n }\n}\n\nexport function createBTSAnalytics(init: BTSAnalyticsInit): BTSAnalytics {\n return BTSAnalytics.init(init);\n}\n\nexport { listStandardWebEvents, type BTSAnalyticsEventType, type BTSAnalyticsStandardEventName };\n",
7
- "import {\n BTSAnalytics,\n createBTSAnalytics,\n listStandardWebEvents,\n type BTSAnalyticsEventType,\n type BTSAnalyticsInit,\n type BTSAnalyticsStandardEventName,\n} from \"./index\";\n\ndeclare global {\n interface Window {\n BTSAnalytics?: {\n BTSAnalytics: typeof BTSAnalytics;\n createBTSAnalytics: typeof createBTSAnalytics;\n listStandardWebEvents: typeof listStandardWebEvents;\n };\n createBTSAnalytics?: typeof createBTSAnalytics;\n }\n}\n\nif (typeof window !== \"undefined\") {\n window.BTSAnalytics = {\n BTSAnalytics,\n createBTSAnalytics,\n listStandardWebEvents,\n };\n window.createBTSAnalytics = createBTSAnalytics;\n}\n\nexport {\n BTSAnalytics,\n createBTSAnalytics,\n listStandardWebEvents,\n type BTSAnalyticsEventType,\n type BTSAnalyticsInit,\n type BTSAnalyticsStandardEventName,\n};\n"
8
- ],
9
- "mappings": "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",
10
- "debugId": "8CE5844CD6E1239864756E2164756E21",
11
- "names": []
12
- }
@@ -1,11 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../src/events.ts", "../../src/index.ts"],
4
- "sourcesContent": [
5
- "export const STANDARD_WEB_EVENTS = [\n \"page_view\",\n \"view_content\",\n \"search\",\n \"lead\",\n \"sign_up\",\n \"begin_checkout\",\n \"add_payment_info\",\n \"purchase\",\n \"outbound_click\",\n \"button_click\",\n \"form_submit\",\n] as const;\n\nexport type BTSAnalyticsStandardEventName = (typeof STANDARD_WEB_EVENTS)[number];\n\nexport type BTSAnalyticsEventType = \"page_view\" | \"custom\" | \"identify\" | \"conversion\";\n\nconst STANDARD_EVENT_SET = new Set<string>(STANDARD_WEB_EVENTS);\n\nconst CONVERSION_EVENTS = new Set<string>([\"lead\", \"sign_up\", \"begin_checkout\", \"add_payment_info\", \"purchase\"]);\n\nconst LEGACY_EVENT_ALIASES: Record<string, BTSAnalyticsStandardEventName> = {\n $pageview: \"page_view\",\n checkout_started: \"begin_checkout\",\n lead_capture: \"lead\",\n purchase_completed: \"purchase\",\n registration_complete: \"sign_up\",\n};\n\nexport function isStandardWebEventName(eventName: string): eventName is BTSAnalyticsStandardEventName {\n return STANDARD_EVENT_SET.has(eventName);\n}\n\nexport function normalizeEventName(eventName: string): {\n eventName: string;\n canonicalEventName: string;\n aliasOf?: BTSAnalyticsStandardEventName;\n isStandard: boolean;\n} {\n const trimmed = eventName.trim();\n if (trimmed.length === 0) {\n return {\n eventName: trimmed,\n canonicalEventName: trimmed,\n isStandard: false,\n };\n }\n\n const lowered = trimmed.toLowerCase();\n const aliasOf = LEGACY_EVENT_ALIASES[lowered];\n if (aliasOf) {\n return {\n eventName: aliasOf,\n canonicalEventName: aliasOf,\n aliasOf,\n isStandard: true,\n };\n }\n\n return {\n eventName: trimmed,\n canonicalEventName: trimmed,\n isStandard: isStandardWebEventName(trimmed),\n };\n}\n\nexport function resolveEventType(eventName: string, explicitType?: BTSAnalyticsEventType): BTSAnalyticsEventType {\n if (explicitType) {\n return explicitType;\n }\n if (eventName === \"page_view\") {\n return \"page_view\";\n }\n if (eventName === \"identify\") {\n return \"identify\";\n }\n if (CONVERSION_EVENTS.has(eventName)) {\n return \"conversion\";\n }\n return \"custom\";\n}\n\nexport function listStandardWebEvents(): BTSAnalyticsStandardEventName[] {\n return [...STANDARD_WEB_EVENTS];\n}\n",
6
- "import {\n listStandardWebEvents,\n normalizeEventName,\n resolveEventType,\n type BTSAnalyticsEventType,\n type BTSAnalyticsStandardEventName,\n} from \"./events\";\n\nconst STORAGE_VISITOR = \"bts_analytics_vid\";\nconst STORAGE_SESSION = \"bts_analytics_sid\";\nconst STORAGE_JOURNEY = \"bts_analytics_jt\";\nconst STORAGE_ATTRIBUTION = \"bts_analytics_attr\";\nconst QUERY_SITE = \"bts_site\";\nconst QUERY_JOURNEY = \"bts_jt\";\n\nconst DEFAULT_FLUSH_INTERVAL_MS = 300;\n\nconst DEFAULT_AUTO_TRACK = {\n pageviews: true,\n history: true,\n outboundLinks: true,\n buttonClicks: true,\n formSubmissions: true,\n} as const;\n\nconst ATTRIBUTION_QUERY_KEYS = [\n \"utm_source\",\n \"utm_medium\",\n \"utm_campaign\",\n \"utm_term\",\n \"utm_content\",\n \"fbclid\",\n \"gclid\",\n \"gbraid\",\n \"li_fat_id\",\n \"msclkid\",\n \"ttclid\",\n \"wbraid\",\n] as const;\n\ntype AutoTrackConfig = {\n pageviews: boolean;\n history: boolean;\n outboundLinks: boolean;\n buttonClicks: boolean;\n formSubmissions: boolean;\n};\n\nexport type BTSAnalyticsInit = {\n siteKey: string;\n endpoint: string;\n autoPageviews?: boolean;\n autoTrack?: boolean | Partial<AutoTrackConfig>;\n debug?: boolean;\n flushIntervalMs?: number;\n};\n\nexport type BTSAnalyticsPayloadProperties = Record<string, unknown>;\n\ntype StoredAttribution = {\n utm: Record<string, string>;\n clickIds: Record<string, string>;\n landingUrl?: string;\n lastSeenAt: string;\n referrer?: string;\n};\n\ntype QueuedAnalyticsEvent = {\n eventName: string;\n eventType: BTSAnalyticsEventType;\n path?: string;\n referrer?: string;\n occurredAt: string;\n visitorId: string;\n sessionId: string;\n properties: BTSAnalyticsPayloadProperties;\n};\n\nfunction safeWindow(): Window | null {\n return typeof window === \"undefined\" ? null : window;\n}\n\nfunction safeDocument(): Document | null {\n return typeof document === \"undefined\" ? null : document;\n}\n\nfunction safeStorage(): Storage | null {\n const win = safeWindow();\n return win?.localStorage ?? null;\n}\n\nfunction randomId(): string {\n if (typeof crypto !== \"undefined\" && typeof crypto.randomUUID === \"function\") {\n return crypto.randomUUID();\n }\n return `v_${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\n\nfunction parseStoredAttribution(raw: string | null): StoredAttribution | null {\n if (!raw) {\n return null;\n }\n try {\n const parsed = JSON.parse(raw) as unknown;\n if (!isRecord(parsed)) {\n return null;\n }\n const utm = isRecord(parsed.utm)\n ? Object.fromEntries(Object.entries(parsed.utm).filter((entry): entry is [string, string] => typeof entry[1] === \"string\"))\n : {};\n const clickIds = isRecord(parsed.clickIds)\n ? Object.fromEntries(\n Object.entries(parsed.clickIds).filter((entry): entry is [string, string] => typeof entry[1] === \"string\"),\n )\n : {};\n return {\n utm,\n clickIds,\n landingUrl: typeof parsed.landingUrl === \"string\" ? parsed.landingUrl : undefined,\n lastSeenAt: typeof parsed.lastSeenAt === \"string\" ? parsed.lastSeenAt : new Date().toISOString(),\n referrer: typeof parsed.referrer === \"string\" ? parsed.referrer : undefined,\n };\n } catch {\n return null;\n }\n}\n\nfunction readStoredAttribution(): StoredAttribution | null {\n const storage = safeStorage();\n return parseStoredAttribution(storage?.getItem(STORAGE_ATTRIBUTION) ?? null);\n}\n\nfunction writeStoredAttribution(next: StoredAttribution): void {\n const storage = safeStorage();\n if (!storage) {\n return;\n }\n storage.setItem(STORAGE_ATTRIBUTION, JSON.stringify(next));\n}\n\nfunction currentPath(): string | undefined {\n const win = safeWindow();\n if (!win) {\n return undefined;\n }\n return `${win.location.pathname}${win.location.search}`;\n}\n\nfunction currentUrl(): string | undefined {\n return safeWindow()?.location.href;\n}\n\nfunction currentOrigin(): string {\n return safeWindow()?.location.origin ?? \"https://behindthescenes.com\";\n}\n\nfunction currentReferrer(): string | undefined {\n return safeDocument()?.referrer || undefined;\n}\n\nfunction readCurrentAttribution(): StoredAttribution | null {\n const win = safeWindow();\n if (!win) {\n return readStoredAttribution();\n }\n const params = new URLSearchParams(win.location.search);\n const next: StoredAttribution = {\n utm: {},\n clickIds: {},\n landingUrl: win.location.href,\n lastSeenAt: new Date().toISOString(),\n referrer: currentReferrer(),\n };\n for (const key of ATTRIBUTION_QUERY_KEYS) {\n const value = params.get(key);\n if (!value) {\n continue;\n }\n if (key.startsWith(\"utm_\")) {\n next.utm[key] = value;\n continue;\n }\n next.clickIds[key] = value;\n }\n\n const previous = readStoredAttribution();\n const merged: StoredAttribution = {\n utm: { ...(previous?.utm ?? {}), ...next.utm },\n clickIds: { ...(previous?.clickIds ?? {}), ...next.clickIds },\n landingUrl: next.landingUrl ?? previous?.landingUrl,\n lastSeenAt: next.lastSeenAt,\n referrer: next.referrer ?? previous?.referrer,\n };\n writeStoredAttribution(merged);\n return merged;\n}\n\nfunction sanitizeText(value: string | null | undefined): string | undefined {\n const trimmed = value?.trim();\n return trimmed ? trimmed.slice(0, 512) : undefined;\n}\n\nfunction createAutoTrackConfig(init: BTSAnalyticsInit): AutoTrackConfig {\n const base = { ...DEFAULT_AUTO_TRACK };\n if (init.autoTrack === false) {\n return {\n pageviews: false,\n history: false,\n outboundLinks: false,\n buttonClicks: false,\n formSubmissions: false,\n };\n }\n if (init.autoTrack && typeof init.autoTrack === \"object\") {\n return {\n pageviews: init.autoTrack.pageviews ?? base.pageviews,\n history: init.autoTrack.history ?? base.history,\n outboundLinks: init.autoTrack.outboundLinks ?? base.outboundLinks,\n buttonClicks: init.autoTrack.buttonClicks ?? base.buttonClicks,\n formSubmissions: init.autoTrack.formSubmissions ?? base.formSubmissions,\n };\n }\n if (init.autoPageviews === false) {\n return {\n ...base,\n pageviews: false,\n }\n }\n if (init.autoPageviews === true) {\n return {\n ...base,\n pageviews: true,\n }\n }\n return base;\n}\n\nexport class BTSAnalytics {\n private siteKey: string;\n private endpoint: string;\n private debug: boolean;\n private flushIntervalMs: number;\n private autoTrack: AutoTrackConfig;\n private queue: QueuedAnalyticsEvent[] = [];\n private flushTimer: ReturnType<typeof setTimeout> | null = null;\n private lastTrackedUrl: string | null = null;\n private unpatchHistory: (() => void) | null = null;\n private clickHandler: ((event: Event) => void) | null = null;\n private submitHandler: ((event: Event) => void) | null = null;\n private pagehideHandler: (() => void) | null = null;\n private popstateHandler: (() => void) | null = null;\n\n constructor(init: BTSAnalyticsInit) {\n this.siteKey = init.siteKey;\n this.endpoint = init.endpoint.replace(/\\/$/, \"\");\n this.debug = init.debug ?? false;\n this.flushIntervalMs = init.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS;\n this.autoTrack = createAutoTrackConfig(init);\n\n readCurrentAttribution();\n\n if (safeWindow()) {\n this.installAutoTracking();\n }\n }\n\n static init(opts: BTSAnalyticsInit): BTSAnalytics {\n return new BTSAnalytics(opts);\n }\n\n getVisitorId(): string {\n const storage = safeStorage();\n const existing = storage?.getItem(STORAGE_VISITOR);\n if (existing) {\n return existing;\n }\n const id = randomId();\n storage?.setItem(STORAGE_VISITOR, id);\n return id;\n }\n\n getSessionId(): string {\n const storage = safeStorage();\n const existing = storage?.getItem(STORAGE_SESSION);\n if (existing) {\n return existing;\n }\n const id = randomId();\n storage?.setItem(STORAGE_SESSION, id);\n return id;\n }\n\n private log(...args: unknown[]) {\n if (this.debug) {\n // eslint-disable-next-line no-console\n console.log(\"[@bts/analytics]\", ...args);\n }\n }\n\n private installAutoTracking(): void {\n const win = safeWindow();\n const doc = safeDocument();\n if (!win || !doc) {\n return;\n }\n\n if (this.autoTrack.pageviews) {\n this.recordPageView();\n }\n\n if (this.autoTrack.history) {\n this.unpatchHistory = this.patchHistory(win);\n this.popstateHandler = () => {\n this.recordPageView();\n };\n win.addEventListener(\"popstate\", this.popstateHandler);\n }\n\n this.clickHandler = (event: Event) => {\n this.handleAutoClick(event);\n };\n this.submitHandler = (event: Event) => {\n this.handleAutoSubmit(event);\n };\n this.pagehideHandler = () => {\n this.flushWithBeacon();\n };\n\n if (this.autoTrack.outboundLinks || this.autoTrack.buttonClicks) {\n doc.addEventListener(\"click\", this.clickHandler, true);\n }\n if (this.autoTrack.formSubmissions) {\n doc.addEventListener(\"submit\", this.submitHandler, true);\n }\n win.addEventListener(\"pagehide\", this.pagehideHandler);\n }\n\n private patchHistory(win: Window): () => void {\n const history = win.history;\n const originalPushState = history.pushState.bind(history);\n const originalReplaceState = history.replaceState.bind(history);\n\n const onHistoryChange = () => {\n readCurrentAttribution();\n this.recordPageView();\n };\n\n history.pushState = ((...args: Parameters<History[\"pushState\"]>) => {\n originalPushState(...args);\n onHistoryChange();\n }) as History[\"pushState\"];\n\n history.replaceState = ((...args: Parameters<History[\"replaceState\"]>) => {\n originalReplaceState(...args);\n onHistoryChange();\n }) as History[\"replaceState\"];\n\n return () => {\n history.pushState = originalPushState;\n history.replaceState = originalReplaceState;\n };\n }\n\n private handleAutoClick(event: Event): void {\n if (event.defaultPrevented) {\n return;\n }\n const target = event.target;\n if (!(target instanceof Element)) {\n return;\n }\n\n if (this.autoTrack.outboundLinks) {\n const anchor = target.closest(\"a[href]\");\n if (anchor instanceof HTMLAnchorElement) {\n const href = sanitizeText(anchor.href);\n if (!href) {\n return;\n }\n const destination = new URL(href, currentOrigin());\n if (destination.origin !== currentOrigin()) {\n this.trackStandard(\"outbound_click\", {\n elementHref: href,\n elementId: sanitizeText(anchor.id),\n elementText: sanitizeText(anchor.textContent),\n linkHost: destination.hostname,\n });\n return;\n }\n }\n }\n\n if (!this.autoTrack.buttonClicks) {\n return;\n }\n\n const button = target.closest(\"button,[role='button'],[data-bts-track-click]\");\n if (!(button instanceof Element)) {\n return;\n }\n this.trackStandard(\"button_click\", {\n elementId: sanitizeText(button.id),\n elementName: sanitizeText(button.getAttribute(\"name\")),\n elementRole: sanitizeText(button.getAttribute(\"role\")),\n elementText: sanitizeText(button.textContent),\n });\n }\n\n private handleAutoSubmit(event: Event): void {\n if (event.defaultPrevented || !this.autoTrack.formSubmissions) {\n return;\n }\n const target = event.target;\n if (!(target instanceof HTMLFormElement)) {\n return;\n }\n this.trackStandard(\"form_submit\", {\n formAction: sanitizeText(target.action),\n formId: sanitizeText(target.id),\n formMethod: sanitizeText(target.method),\n formName: sanitizeText(target.getAttribute(\"name\")),\n });\n }\n\n private buildEvent(\n eventName: string,\n eventType: BTSAnalyticsEventType,\n properties?: BTSAnalyticsPayloadProperties,\n overrides?: { path?: string },\n ): QueuedAnalyticsEvent {\n const attribution = readCurrentAttribution();\n const path = overrides?.path ?? currentPath();\n const referrer = currentReferrer();\n return {\n eventName,\n eventType,\n path,\n referrer,\n occurredAt: new Date().toISOString(),\n visitorId: this.getVisitorId(),\n sessionId: this.getSessionId(),\n properties: {\n ...(properties ?? {}),\n attribution: attribution\n ? {\n utm: attribution.utm,\n clickIds: attribution.clickIds,\n landingUrl: attribution.landingUrl,\n referrer: attribution.referrer,\n }\n : undefined,\n page: {\n title: safeDocument()?.title,\n url: currentUrl(),\n },\n },\n };\n }\n\n private queueEvent(eventName: string, properties?: BTSAnalyticsPayloadProperties, explicitType?: BTSAnalyticsEventType): void {\n const normalized = normalizeEventName(eventName);\n const eventType = resolveEventType(normalized.canonicalEventName, explicitType);\n const nextProperties: BTSAnalyticsPayloadProperties = {\n ...(properties ?? {}),\n };\n if (normalized.aliasOf) {\n nextProperties.originalEventName = eventName;\n }\n this.queue.push(this.buildEvent(normalized.eventName, eventType, nextProperties));\n if (this.queue.length >= 50) {\n void this.flushNow();\n return;\n }\n this.scheduleFlush();\n }\n\n private scheduleFlush(): void {\n if (this.flushTimer) {\n return;\n }\n this.flushTimer = setTimeout(() => {\n this.flushTimer = null;\n void this.flushNow();\n }, this.flushIntervalMs);\n }\n\n private async postJson(path: string, body: unknown): Promise<Response> {\n const url = `${this.endpoint}${path}`;\n this.log(\"POST\", url, body);\n return fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(body),\n });\n }\n\n private flushWithBeacon(): void {\n if (this.queue.length === 0) {\n return;\n }\n const win = safeWindow();\n const beacon = win?.navigator?.sendBeacon?.bind(win.navigator);\n if (!beacon) {\n return;\n }\n const batch = [...this.queue];\n this.queue = [];\n const url = `${this.endpoint}/ingest/batch`;\n const body = JSON.stringify({ siteKey: this.siteKey, events: batch });\n const blob = new Blob([body], { type: \"application/json\" });\n beacon(url, blob);\n }\n\n async flushNow(): Promise<void> {\n if (this.queue.length === 0) {\n return;\n }\n const batch = [...this.queue];\n this.queue = [];\n try {\n const response = await this.postJson(\"/ingest/batch\", {\n siteKey: this.siteKey,\n events: batch,\n });\n if (!response.ok) {\n this.log(\"flush failed\", response.status);\n }\n } catch (error) {\n this.log(\"flush error\", error);\n }\n }\n\n private recordPageView(path?: string): void {\n const derivedPath = path ?? currentPath();\n const url = currentUrl() ?? derivedPath ?? \"/\";\n if (url === this.lastTrackedUrl) {\n return;\n }\n this.lastTrackedUrl = url;\n this.queue.push(\n this.buildEvent(\n \"page_view\",\n \"page_view\",\n {\n autoCaptured: true,\n },\n { path: derivedPath },\n ),\n );\n this.scheduleFlush();\n }\n\n page(path?: string): void {\n this.lastTrackedUrl = null;\n this.recordPageView(path);\n }\n\n track(eventName: string, properties?: BTSAnalyticsPayloadProperties): void {\n this.queueEvent(eventName, properties);\n }\n\n trackStandard(eventName: BTSAnalyticsStandardEventName, properties?: BTSAnalyticsPayloadProperties): void {\n this.queueEvent(eventName, properties);\n }\n\n identify(userId: string, traits?: BTSAnalyticsPayloadProperties): void {\n this.queue.push(\n this.buildEvent(\"identify\", \"identify\", {\n traits: traits ?? {},\n userId,\n }),\n );\n this.scheduleFlush();\n }\n\n listStandardEvents(): BTSAnalyticsStandardEventName[] {\n return listStandardWebEvents();\n }\n\n /** Start a cross-site funnel; persists journey token for decorateUrl. */\n async startFunnel(entryPath?: string): Promise<{ journeyId: string; journeyToken: string; expiresAt: number }> {\n const response = await this.postJson(\"/journey/start\", {\n siteKey: this.siteKey,\n entryPath: entryPath ?? currentPath(),\n });\n if (!response.ok) {\n throw new Error(`journey/start failed: ${response.status}`);\n }\n const data = (await response.json()) as {\n journeyId: string;\n journeyToken: string;\n expiresAt: number;\n };\n safeStorage()?.setItem(STORAGE_JOURNEY, data.journeyToken);\n return data;\n }\n\n getPersistedJourneyToken(): string | null {\n return safeStorage()?.getItem(STORAGE_JOURNEY) ?? null;\n }\n\n /** Append site key + journey token to a BTS URL (checkout, join, etc.). */\n decorateUrl(url: string, journeyToken?: string): string {\n const token = journeyToken ?? this.getPersistedJourneyToken();\n const next = new URL(url, currentOrigin());\n next.searchParams.set(QUERY_SITE, this.siteKey);\n if (token) {\n next.searchParams.set(QUERY_JOURNEY, token);\n }\n return next.toString();\n }\n\n /** Call from BTS after accepting the journey (optional; server can also infer). */\n async notifyHandoff(journeyToken: string, context?: Record<string, unknown>): Promise<void> {\n const response = await this.postJson(\"/journey/handoff\", { journeyToken, context });\n if (!response.ok) {\n throw new Error(`journey/handoff failed: ${response.status}`);\n }\n }\n\n destroy(): void {\n const win = safeWindow();\n const doc = safeDocument();\n if (doc && this.clickHandler && (this.autoTrack.outboundLinks || this.autoTrack.buttonClicks)) {\n doc.removeEventListener(\"click\", this.clickHandler, true);\n }\n if (doc && this.submitHandler && this.autoTrack.formSubmissions) {\n doc.removeEventListener(\"submit\", this.submitHandler, true);\n }\n if (win && this.popstateHandler) {\n win.removeEventListener(\"popstate\", this.popstateHandler);\n }\n if (win && this.pagehideHandler) {\n win.removeEventListener(\"pagehide\", this.pagehideHandler);\n }\n if (this.flushTimer) {\n clearTimeout(this.flushTimer);\n this.flushTimer = null;\n }\n this.unpatchHistory?.();\n this.unpatchHistory = null;\n this.flushWithBeacon();\n }\n}\n\nexport function createBTSAnalytics(init: BTSAnalyticsInit): BTSAnalytics {\n return BTSAnalytics.init(init);\n}\n\nexport { listStandardWebEvents, type BTSAnalyticsEventType, type BTSAnalyticsStandardEventName };\n"
7
- ],
8
- "mappings": "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",
9
- "debugId": "164F30813325DE1964756E2164756E21",
10
- "names": []
11
- }
@@ -1,11 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../src/events.ts", "../../src/index.ts"],
4
- "sourcesContent": [
5
- "export const STANDARD_WEB_EVENTS = [\n \"page_view\",\n \"view_content\",\n \"search\",\n \"lead\",\n \"sign_up\",\n \"begin_checkout\",\n \"add_payment_info\",\n \"purchase\",\n \"outbound_click\",\n \"button_click\",\n \"form_submit\",\n] as const;\n\nexport type BTSAnalyticsStandardEventName = (typeof STANDARD_WEB_EVENTS)[number];\n\nexport type BTSAnalyticsEventType = \"page_view\" | \"custom\" | \"identify\" | \"conversion\";\n\nconst STANDARD_EVENT_SET = new Set<string>(STANDARD_WEB_EVENTS);\n\nconst CONVERSION_EVENTS = new Set<string>([\"lead\", \"sign_up\", \"begin_checkout\", \"add_payment_info\", \"purchase\"]);\n\nconst LEGACY_EVENT_ALIASES: Record<string, BTSAnalyticsStandardEventName> = {\n $pageview: \"page_view\",\n checkout_started: \"begin_checkout\",\n lead_capture: \"lead\",\n purchase_completed: \"purchase\",\n registration_complete: \"sign_up\",\n};\n\nexport function isStandardWebEventName(eventName: string): eventName is BTSAnalyticsStandardEventName {\n return STANDARD_EVENT_SET.has(eventName);\n}\n\nexport function normalizeEventName(eventName: string): {\n eventName: string;\n canonicalEventName: string;\n aliasOf?: BTSAnalyticsStandardEventName;\n isStandard: boolean;\n} {\n const trimmed = eventName.trim();\n if (trimmed.length === 0) {\n return {\n eventName: trimmed,\n canonicalEventName: trimmed,\n isStandard: false,\n };\n }\n\n const lowered = trimmed.toLowerCase();\n const aliasOf = LEGACY_EVENT_ALIASES[lowered];\n if (aliasOf) {\n return {\n eventName: aliasOf,\n canonicalEventName: aliasOf,\n aliasOf,\n isStandard: true,\n };\n }\n\n return {\n eventName: trimmed,\n canonicalEventName: trimmed,\n isStandard: isStandardWebEventName(trimmed),\n };\n}\n\nexport function resolveEventType(eventName: string, explicitType?: BTSAnalyticsEventType): BTSAnalyticsEventType {\n if (explicitType) {\n return explicitType;\n }\n if (eventName === \"page_view\") {\n return \"page_view\";\n }\n if (eventName === \"identify\") {\n return \"identify\";\n }\n if (CONVERSION_EVENTS.has(eventName)) {\n return \"conversion\";\n }\n return \"custom\";\n}\n\nexport function listStandardWebEvents(): BTSAnalyticsStandardEventName[] {\n return [...STANDARD_WEB_EVENTS];\n}\n",
6
- "import {\n listStandardWebEvents,\n normalizeEventName,\n resolveEventType,\n type BTSAnalyticsEventType,\n type BTSAnalyticsStandardEventName,\n} from \"./events\";\n\nconst STORAGE_VISITOR = \"bts_analytics_vid\";\nconst STORAGE_SESSION = \"bts_analytics_sid\";\nconst STORAGE_JOURNEY = \"bts_analytics_jt\";\nconst STORAGE_ATTRIBUTION = \"bts_analytics_attr\";\nconst QUERY_SITE = \"bts_site\";\nconst QUERY_JOURNEY = \"bts_jt\";\n\nconst DEFAULT_FLUSH_INTERVAL_MS = 300;\n\nconst DEFAULT_AUTO_TRACK = {\n pageviews: true,\n history: true,\n outboundLinks: true,\n buttonClicks: true,\n formSubmissions: true,\n} as const;\n\nconst ATTRIBUTION_QUERY_KEYS = [\n \"utm_source\",\n \"utm_medium\",\n \"utm_campaign\",\n \"utm_term\",\n \"utm_content\",\n \"fbclid\",\n \"gclid\",\n \"gbraid\",\n \"li_fat_id\",\n \"msclkid\",\n \"ttclid\",\n \"wbraid\",\n] as const;\n\ntype AutoTrackConfig = {\n pageviews: boolean;\n history: boolean;\n outboundLinks: boolean;\n buttonClicks: boolean;\n formSubmissions: boolean;\n};\n\nexport type BTSAnalyticsInit = {\n siteKey: string;\n endpoint: string;\n autoPageviews?: boolean;\n autoTrack?: boolean | Partial<AutoTrackConfig>;\n debug?: boolean;\n flushIntervalMs?: number;\n};\n\nexport type BTSAnalyticsPayloadProperties = Record<string, unknown>;\n\ntype StoredAttribution = {\n utm: Record<string, string>;\n clickIds: Record<string, string>;\n landingUrl?: string;\n lastSeenAt: string;\n referrer?: string;\n};\n\ntype QueuedAnalyticsEvent = {\n eventName: string;\n eventType: BTSAnalyticsEventType;\n path?: string;\n referrer?: string;\n occurredAt: string;\n visitorId: string;\n sessionId: string;\n properties: BTSAnalyticsPayloadProperties;\n};\n\nfunction safeWindow(): Window | null {\n return typeof window === \"undefined\" ? null : window;\n}\n\nfunction safeDocument(): Document | null {\n return typeof document === \"undefined\" ? null : document;\n}\n\nfunction safeStorage(): Storage | null {\n const win = safeWindow();\n return win?.localStorage ?? null;\n}\n\nfunction randomId(): string {\n if (typeof crypto !== \"undefined\" && typeof crypto.randomUUID === \"function\") {\n return crypto.randomUUID();\n }\n return `v_${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\n\nfunction parseStoredAttribution(raw: string | null): StoredAttribution | null {\n if (!raw) {\n return null;\n }\n try {\n const parsed = JSON.parse(raw) as unknown;\n if (!isRecord(parsed)) {\n return null;\n }\n const utm = isRecord(parsed.utm)\n ? Object.fromEntries(Object.entries(parsed.utm).filter((entry): entry is [string, string] => typeof entry[1] === \"string\"))\n : {};\n const clickIds = isRecord(parsed.clickIds)\n ? Object.fromEntries(\n Object.entries(parsed.clickIds).filter((entry): entry is [string, string] => typeof entry[1] === \"string\"),\n )\n : {};\n return {\n utm,\n clickIds,\n landingUrl: typeof parsed.landingUrl === \"string\" ? parsed.landingUrl : undefined,\n lastSeenAt: typeof parsed.lastSeenAt === \"string\" ? parsed.lastSeenAt : new Date().toISOString(),\n referrer: typeof parsed.referrer === \"string\" ? parsed.referrer : undefined,\n };\n } catch {\n return null;\n }\n}\n\nfunction readStoredAttribution(): StoredAttribution | null {\n const storage = safeStorage();\n return parseStoredAttribution(storage?.getItem(STORAGE_ATTRIBUTION) ?? null);\n}\n\nfunction writeStoredAttribution(next: StoredAttribution): void {\n const storage = safeStorage();\n if (!storage) {\n return;\n }\n storage.setItem(STORAGE_ATTRIBUTION, JSON.stringify(next));\n}\n\nfunction currentPath(): string | undefined {\n const win = safeWindow();\n if (!win) {\n return undefined;\n }\n return `${win.location.pathname}${win.location.search}`;\n}\n\nfunction currentUrl(): string | undefined {\n return safeWindow()?.location.href;\n}\n\nfunction currentOrigin(): string {\n return safeWindow()?.location.origin ?? \"https://behindthescenes.com\";\n}\n\nfunction currentReferrer(): string | undefined {\n return safeDocument()?.referrer || undefined;\n}\n\nfunction readCurrentAttribution(): StoredAttribution | null {\n const win = safeWindow();\n if (!win) {\n return readStoredAttribution();\n }\n const params = new URLSearchParams(win.location.search);\n const next: StoredAttribution = {\n utm: {},\n clickIds: {},\n landingUrl: win.location.href,\n lastSeenAt: new Date().toISOString(),\n referrer: currentReferrer(),\n };\n for (const key of ATTRIBUTION_QUERY_KEYS) {\n const value = params.get(key);\n if (!value) {\n continue;\n }\n if (key.startsWith(\"utm_\")) {\n next.utm[key] = value;\n continue;\n }\n next.clickIds[key] = value;\n }\n\n const previous = readStoredAttribution();\n const merged: StoredAttribution = {\n utm: { ...(previous?.utm ?? {}), ...next.utm },\n clickIds: { ...(previous?.clickIds ?? {}), ...next.clickIds },\n landingUrl: next.landingUrl ?? previous?.landingUrl,\n lastSeenAt: next.lastSeenAt,\n referrer: next.referrer ?? previous?.referrer,\n };\n writeStoredAttribution(merged);\n return merged;\n}\n\nfunction sanitizeText(value: string | null | undefined): string | undefined {\n const trimmed = value?.trim();\n return trimmed ? trimmed.slice(0, 512) : undefined;\n}\n\nfunction createAutoTrackConfig(init: BTSAnalyticsInit): AutoTrackConfig {\n const base = { ...DEFAULT_AUTO_TRACK };\n if (init.autoTrack === false) {\n return {\n pageviews: false,\n history: false,\n outboundLinks: false,\n buttonClicks: false,\n formSubmissions: false,\n };\n }\n if (init.autoTrack && typeof init.autoTrack === \"object\") {\n return {\n pageviews: init.autoTrack.pageviews ?? base.pageviews,\n history: init.autoTrack.history ?? base.history,\n outboundLinks: init.autoTrack.outboundLinks ?? base.outboundLinks,\n buttonClicks: init.autoTrack.buttonClicks ?? base.buttonClicks,\n formSubmissions: init.autoTrack.formSubmissions ?? base.formSubmissions,\n };\n }\n if (init.autoPageviews === false) {\n return {\n ...base,\n pageviews: false,\n }\n }\n if (init.autoPageviews === true) {\n return {\n ...base,\n pageviews: true,\n }\n }\n return base;\n}\n\nexport class BTSAnalytics {\n private siteKey: string;\n private endpoint: string;\n private debug: boolean;\n private flushIntervalMs: number;\n private autoTrack: AutoTrackConfig;\n private queue: QueuedAnalyticsEvent[] = [];\n private flushTimer: ReturnType<typeof setTimeout> | null = null;\n private lastTrackedUrl: string | null = null;\n private unpatchHistory: (() => void) | null = null;\n private clickHandler: ((event: Event) => void) | null = null;\n private submitHandler: ((event: Event) => void) | null = null;\n private pagehideHandler: (() => void) | null = null;\n private popstateHandler: (() => void) | null = null;\n\n constructor(init: BTSAnalyticsInit) {\n this.siteKey = init.siteKey;\n this.endpoint = init.endpoint.replace(/\\/$/, \"\");\n this.debug = init.debug ?? false;\n this.flushIntervalMs = init.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS;\n this.autoTrack = createAutoTrackConfig(init);\n\n readCurrentAttribution();\n\n if (safeWindow()) {\n this.installAutoTracking();\n }\n }\n\n static init(opts: BTSAnalyticsInit): BTSAnalytics {\n return new BTSAnalytics(opts);\n }\n\n getVisitorId(): string {\n const storage = safeStorage();\n const existing = storage?.getItem(STORAGE_VISITOR);\n if (existing) {\n return existing;\n }\n const id = randomId();\n storage?.setItem(STORAGE_VISITOR, id);\n return id;\n }\n\n getSessionId(): string {\n const storage = safeStorage();\n const existing = storage?.getItem(STORAGE_SESSION);\n if (existing) {\n return existing;\n }\n const id = randomId();\n storage?.setItem(STORAGE_SESSION, id);\n return id;\n }\n\n private log(...args: unknown[]) {\n if (this.debug) {\n // eslint-disable-next-line no-console\n console.log(\"[@bts/analytics]\", ...args);\n }\n }\n\n private installAutoTracking(): void {\n const win = safeWindow();\n const doc = safeDocument();\n if (!win || !doc) {\n return;\n }\n\n if (this.autoTrack.pageviews) {\n this.recordPageView();\n }\n\n if (this.autoTrack.history) {\n this.unpatchHistory = this.patchHistory(win);\n this.popstateHandler = () => {\n this.recordPageView();\n };\n win.addEventListener(\"popstate\", this.popstateHandler);\n }\n\n this.clickHandler = (event: Event) => {\n this.handleAutoClick(event);\n };\n this.submitHandler = (event: Event) => {\n this.handleAutoSubmit(event);\n };\n this.pagehideHandler = () => {\n this.flushWithBeacon();\n };\n\n if (this.autoTrack.outboundLinks || this.autoTrack.buttonClicks) {\n doc.addEventListener(\"click\", this.clickHandler, true);\n }\n if (this.autoTrack.formSubmissions) {\n doc.addEventListener(\"submit\", this.submitHandler, true);\n }\n win.addEventListener(\"pagehide\", this.pagehideHandler);\n }\n\n private patchHistory(win: Window): () => void {\n const history = win.history;\n const originalPushState = history.pushState.bind(history);\n const originalReplaceState = history.replaceState.bind(history);\n\n const onHistoryChange = () => {\n readCurrentAttribution();\n this.recordPageView();\n };\n\n history.pushState = ((...args: Parameters<History[\"pushState\"]>) => {\n originalPushState(...args);\n onHistoryChange();\n }) as History[\"pushState\"];\n\n history.replaceState = ((...args: Parameters<History[\"replaceState\"]>) => {\n originalReplaceState(...args);\n onHistoryChange();\n }) as History[\"replaceState\"];\n\n return () => {\n history.pushState = originalPushState;\n history.replaceState = originalReplaceState;\n };\n }\n\n private handleAutoClick(event: Event): void {\n if (event.defaultPrevented) {\n return;\n }\n const target = event.target;\n if (!(target instanceof Element)) {\n return;\n }\n\n if (this.autoTrack.outboundLinks) {\n const anchor = target.closest(\"a[href]\");\n if (anchor instanceof HTMLAnchorElement) {\n const href = sanitizeText(anchor.href);\n if (!href) {\n return;\n }\n const destination = new URL(href, currentOrigin());\n if (destination.origin !== currentOrigin()) {\n this.trackStandard(\"outbound_click\", {\n elementHref: href,\n elementId: sanitizeText(anchor.id),\n elementText: sanitizeText(anchor.textContent),\n linkHost: destination.hostname,\n });\n return;\n }\n }\n }\n\n if (!this.autoTrack.buttonClicks) {\n return;\n }\n\n const button = target.closest(\"button,[role='button'],[data-bts-track-click]\");\n if (!(button instanceof Element)) {\n return;\n }\n this.trackStandard(\"button_click\", {\n elementId: sanitizeText(button.id),\n elementName: sanitizeText(button.getAttribute(\"name\")),\n elementRole: sanitizeText(button.getAttribute(\"role\")),\n elementText: sanitizeText(button.textContent),\n });\n }\n\n private handleAutoSubmit(event: Event): void {\n if (event.defaultPrevented || !this.autoTrack.formSubmissions) {\n return;\n }\n const target = event.target;\n if (!(target instanceof HTMLFormElement)) {\n return;\n }\n this.trackStandard(\"form_submit\", {\n formAction: sanitizeText(target.action),\n formId: sanitizeText(target.id),\n formMethod: sanitizeText(target.method),\n formName: sanitizeText(target.getAttribute(\"name\")),\n });\n }\n\n private buildEvent(\n eventName: string,\n eventType: BTSAnalyticsEventType,\n properties?: BTSAnalyticsPayloadProperties,\n overrides?: { path?: string },\n ): QueuedAnalyticsEvent {\n const attribution = readCurrentAttribution();\n const path = overrides?.path ?? currentPath();\n const referrer = currentReferrer();\n return {\n eventName,\n eventType,\n path,\n referrer,\n occurredAt: new Date().toISOString(),\n visitorId: this.getVisitorId(),\n sessionId: this.getSessionId(),\n properties: {\n ...(properties ?? {}),\n attribution: attribution\n ? {\n utm: attribution.utm,\n clickIds: attribution.clickIds,\n landingUrl: attribution.landingUrl,\n referrer: attribution.referrer,\n }\n : undefined,\n page: {\n title: safeDocument()?.title,\n url: currentUrl(),\n },\n },\n };\n }\n\n private queueEvent(eventName: string, properties?: BTSAnalyticsPayloadProperties, explicitType?: BTSAnalyticsEventType): void {\n const normalized = normalizeEventName(eventName);\n const eventType = resolveEventType(normalized.canonicalEventName, explicitType);\n const nextProperties: BTSAnalyticsPayloadProperties = {\n ...(properties ?? {}),\n };\n if (normalized.aliasOf) {\n nextProperties.originalEventName = eventName;\n }\n this.queue.push(this.buildEvent(normalized.eventName, eventType, nextProperties));\n if (this.queue.length >= 50) {\n void this.flushNow();\n return;\n }\n this.scheduleFlush();\n }\n\n private scheduleFlush(): void {\n if (this.flushTimer) {\n return;\n }\n this.flushTimer = setTimeout(() => {\n this.flushTimer = null;\n void this.flushNow();\n }, this.flushIntervalMs);\n }\n\n private async postJson(path: string, body: unknown): Promise<Response> {\n const url = `${this.endpoint}${path}`;\n this.log(\"POST\", url, body);\n return fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(body),\n });\n }\n\n private flushWithBeacon(): void {\n if (this.queue.length === 0) {\n return;\n }\n const win = safeWindow();\n const beacon = win?.navigator?.sendBeacon?.bind(win.navigator);\n if (!beacon) {\n return;\n }\n const batch = [...this.queue];\n this.queue = [];\n const url = `${this.endpoint}/ingest/batch`;\n const body = JSON.stringify({ siteKey: this.siteKey, events: batch });\n const blob = new Blob([body], { type: \"application/json\" });\n beacon(url, blob);\n }\n\n async flushNow(): Promise<void> {\n if (this.queue.length === 0) {\n return;\n }\n const batch = [...this.queue];\n this.queue = [];\n try {\n const response = await this.postJson(\"/ingest/batch\", {\n siteKey: this.siteKey,\n events: batch,\n });\n if (!response.ok) {\n this.log(\"flush failed\", response.status);\n }\n } catch (error) {\n this.log(\"flush error\", error);\n }\n }\n\n private recordPageView(path?: string): void {\n const derivedPath = path ?? currentPath();\n const url = currentUrl() ?? derivedPath ?? \"/\";\n if (url === this.lastTrackedUrl) {\n return;\n }\n this.lastTrackedUrl = url;\n this.queue.push(\n this.buildEvent(\n \"page_view\",\n \"page_view\",\n {\n autoCaptured: true,\n },\n { path: derivedPath },\n ),\n );\n this.scheduleFlush();\n }\n\n page(path?: string): void {\n this.lastTrackedUrl = null;\n this.recordPageView(path);\n }\n\n track(eventName: string, properties?: BTSAnalyticsPayloadProperties): void {\n this.queueEvent(eventName, properties);\n }\n\n trackStandard(eventName: BTSAnalyticsStandardEventName, properties?: BTSAnalyticsPayloadProperties): void {\n this.queueEvent(eventName, properties);\n }\n\n identify(userId: string, traits?: BTSAnalyticsPayloadProperties): void {\n this.queue.push(\n this.buildEvent(\"identify\", \"identify\", {\n traits: traits ?? {},\n userId,\n }),\n );\n this.scheduleFlush();\n }\n\n listStandardEvents(): BTSAnalyticsStandardEventName[] {\n return listStandardWebEvents();\n }\n\n /** Start a cross-site funnel; persists journey token for decorateUrl. */\n async startFunnel(entryPath?: string): Promise<{ journeyId: string; journeyToken: string; expiresAt: number }> {\n const response = await this.postJson(\"/journey/start\", {\n siteKey: this.siteKey,\n entryPath: entryPath ?? currentPath(),\n });\n if (!response.ok) {\n throw new Error(`journey/start failed: ${response.status}`);\n }\n const data = (await response.json()) as {\n journeyId: string;\n journeyToken: string;\n expiresAt: number;\n };\n safeStorage()?.setItem(STORAGE_JOURNEY, data.journeyToken);\n return data;\n }\n\n getPersistedJourneyToken(): string | null {\n return safeStorage()?.getItem(STORAGE_JOURNEY) ?? null;\n }\n\n /** Append site key + journey token to a BTS URL (checkout, join, etc.). */\n decorateUrl(url: string, journeyToken?: string): string {\n const token = journeyToken ?? this.getPersistedJourneyToken();\n const next = new URL(url, currentOrigin());\n next.searchParams.set(QUERY_SITE, this.siteKey);\n if (token) {\n next.searchParams.set(QUERY_JOURNEY, token);\n }\n return next.toString();\n }\n\n /** Call from BTS after accepting the journey (optional; server can also infer). */\n async notifyHandoff(journeyToken: string, context?: Record<string, unknown>): Promise<void> {\n const response = await this.postJson(\"/journey/handoff\", { journeyToken, context });\n if (!response.ok) {\n throw new Error(`journey/handoff failed: ${response.status}`);\n }\n }\n\n destroy(): void {\n const win = safeWindow();\n const doc = safeDocument();\n if (doc && this.clickHandler && (this.autoTrack.outboundLinks || this.autoTrack.buttonClicks)) {\n doc.removeEventListener(\"click\", this.clickHandler, true);\n }\n if (doc && this.submitHandler && this.autoTrack.formSubmissions) {\n doc.removeEventListener(\"submit\", this.submitHandler, true);\n }\n if (win && this.popstateHandler) {\n win.removeEventListener(\"popstate\", this.popstateHandler);\n }\n if (win && this.pagehideHandler) {\n win.removeEventListener(\"pagehide\", this.pagehideHandler);\n }\n if (this.flushTimer) {\n clearTimeout(this.flushTimer);\n this.flushTimer = null;\n }\n this.unpatchHistory?.();\n this.unpatchHistory = null;\n this.flushWithBeacon();\n }\n}\n\nexport function createBTSAnalytics(init: BTSAnalyticsInit): BTSAnalytics {\n return BTSAnalytics.init(init);\n}\n\nexport { listStandardWebEvents, type BTSAnalyticsEventType, type BTSAnalyticsStandardEventName };\n"
7
- ],
8
- "mappings": "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",
9
- "debugId": "BF4C211124B7A30C64756E2164756E21",
10
- "names": []
11
- }