qwik-umami 1.0.2 → 1.1.0

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
@@ -4,12 +4,13 @@
4
4
  [![license](https://img.shields.io/npm/l/qwik-umami.svg)](https://github.com/murcisluis/qwik-umami/blob/main/LICENSE)
5
5
  [![bundle size](https://img.shields.io/bundlephobia/minzip/qwik-umami)](https://bundlephobia.com/package/qwik-umami)
6
6
 
7
- SSR-safe [Umami Analytics](https://umami.is) integration for [Qwik](https://qwik.dev). Drop-in component + hook for automatic page views and custom event tracking.
7
+ SSR-safe [Umami Analytics](https://umami.is) integration for [Qwik](https://qwik.dev). Drop-in component + utility functions for automatic page views, custom event tracking, and server-side analytics.
8
8
 
9
9
  - **Zero config** — one component in `root.tsx` and you're tracking
10
10
  - **SSR-safe** — no-ops on the server, hydrates on the client
11
+ - **Server-side tracking** — send events from `routeLoader$`, `routeAction$`, or `server$`
11
12
  - **Self-hosted ready** — works with Umami Cloud and self-hosted instances
12
- - **Typed** — full TypeScript support with exported types
13
+ - **Typed** — full TypeScript support, typed against the official Umami API
13
14
  - **Lightweight** — no runtime dependencies, just Qwik as peer dep
14
15
 
15
16
  ## Installation
@@ -46,39 +47,45 @@ export default component$(() => {
46
47
 
47
48
  That's it. Page views are tracked automatically.
48
49
 
49
- ### 2. Track custom events
50
-
51
- Use the `useUmamiTrack` hook in any component:
50
+ ### 2. Track custom events (client-side)
52
51
 
53
52
  ```tsx
54
53
  import { component$ } from '@builder.io/qwik';
55
- import { useUmamiTrack } from 'qwik-umami';
54
+ import { umamiTrack } from 'qwik-umami';
56
55
 
57
56
  export const SignupButton = component$(() => {
58
- const { track } = useUmamiTrack();
59
-
60
57
  return (
61
- <button onClick$={() => track('signup-click', { plan: 'pro' })}>
58
+ <button onClick$={() => umamiTrack('signup-click', { plan: 'pro' })}>
62
59
  Start free trial
63
60
  </button>
64
61
  );
65
62
  });
66
63
  ```
67
64
 
68
- ### Self-hosted Umami
65
+ ### 3. Track events server-side
66
+
67
+ Use `serverUmamiTrack` inside `routeLoader$`, `routeAction$`, or `server$`:
69
68
 
70
69
  ```tsx
71
- <UmamiScript
72
- websiteId="your-website-id"
73
- src="https://your-umami-instance.com/script.js"
74
- />
70
+ import { routeAction$ } from '@builder.io/qwik-city';
71
+ import { serverUmamiTrack } from 'qwik-umami/server';
72
+
73
+ const options = {
74
+ websiteId: import.meta.env.UMAMI_WEBSITE_ID,
75
+ hostUrl: import.meta.env.UMAMI_HOST_URL,
76
+ };
77
+
78
+ export const useCheckout = routeAction$(async (data) => {
79
+ await serverUmamiTrack(options, 'purchase', { plan: data.plan });
80
+ // ...
81
+ });
75
82
  ```
76
83
 
77
84
  ## API
78
85
 
79
86
  ### `<UmamiScript>`
80
87
 
81
- Component that loads the Umami tracking script. Checks service availability before loading and handles errors gracefully.
88
+ Component that loads the Umami tracking script. Uses `useTask$` with `isServer` guard to preserve Qwik's resumability model. Loads via `requestIdleCallback` by default to avoid blocking the main thread.
82
89
 
83
90
  | Prop | Type | Default | Description |
84
91
  | --- | --- | --- | --- |
@@ -86,61 +93,218 @@ Component that loads the Umami tracking script. Checks service availability befo
86
93
  | `src` | `string` | `https://cloud.umami.is/script.js` | URL of the tracking script |
87
94
  | `hostUrl` | `string` | — | Custom host URL for data collection |
88
95
  | `autoTrack` | `boolean` | `true` | Enable automatic page view tracking |
89
- | `domains` | `string` | — | Comma-separated allowed domains |
90
- | `dataDomains` | `string` | — | Comma-separated data domains |
96
+ | `domains` | `string` | — | Comma-separated list of allowed domains |
97
+ | `tag` | `string` | — | Assign events to a specific tag |
98
+ | `excludeSearch` | `boolean` | — | Exclude URL search parameters from tracking |
99
+ | `excludeHash` | `boolean` | — | Exclude URL hash fragments from tracking |
100
+ | `doNotTrack` | `boolean` | — | Respect the browser's Do Not Track setting |
101
+ | `strategy` | `'eager' \| 'idle'` | `'idle'` | `'idle'` defers loading via `requestIdleCallback`, `'eager'` loads immediately |
91
102
 
92
- ### `useUmamiTrack()`
103
+ ---
93
104
 
94
- Hook that returns a `track` function for sending custom events.
105
+ ### `umamiTrack()`
106
+
107
+ Client-side utility function. SSR-safe (no-op on server). Import from `'qwik-umami'`.
95
108
 
96
109
  ```ts
97
- const { track } = useUmamiTrack();
110
+ // Track a pageview manually
111
+ umamiTrack();
112
+
113
+ // Track a named event
114
+ umamiTrack('button-click');
98
115
 
99
- // Simple event
100
- track('button-click');
116
+ // Track an event with data
117
+ umamiTrack('purchase', { plan: 'pro', price: 29 });
101
118
 
102
- // Event with data
103
- track('purchase', { product: 'plan-pro', price: 29 });
119
+ // Track with a full custom payload
120
+ umamiTrack({ website: 'id', url: '/checkout', title: 'Checkout' });
104
121
  ```
105
122
 
106
- **Behavior:**
107
- - SSR-safe: no-op on the server, works after hydration
108
- - Silent in production, logs warnings in development if Umami isn't loaded
109
- - Gracefully handles ad blockers and network failures — no errors, no broken pages
123
+ ---
110
124
 
111
- ### Types
125
+ ### `umamiIdentify()`
126
+
127
+ Associates the current session with a user or custom data. Client-side only. Import from `'qwik-umami'`.
112
128
 
113
129
  ```ts
114
- import type { UmamiConfig, UmamiEventData } from 'qwik-umami';
130
+ // Identify with session data only
131
+ umamiIdentify({ plan: 'pro', role: 'admin' });
132
+
133
+ // Identify with a unique ID
134
+ umamiIdentify('user-123');
115
135
 
116
- // UmamiConfig props for <UmamiScript>
117
- // UmamiEventData — Record<string, string | number | boolean>
136
+ // Identify with a unique ID and data
137
+ umamiIdentify('user-123', { plan: 'pro' });
138
+ ```
139
+
140
+ ---
141
+
142
+ ### `serverUmamiTrack()`
143
+
144
+ Server-side event tracking. Works in `routeLoader$`, `routeAction$`, `server$`. Import from `'qwik-umami/server'`.
145
+
146
+ ```ts
147
+ import { serverUmamiTrack } from 'qwik-umami/server';
148
+ import type { UmamiServerOptions } from 'qwik-umami/server';
149
+
150
+ const options: UmamiServerOptions = {
151
+ websiteId: 'your-website-id',
152
+ hostUrl: 'https://your-umami-instance.com', // or https://cloud.umami.is
153
+ };
154
+
155
+ // Track a named event
156
+ await serverUmamiTrack(options, 'signup');
157
+
158
+ // Track with data
159
+ await serverUmamiTrack(options, 'purchase', { plan: 'pro', amount: 29 });
160
+
161
+ // Track with full payload
162
+ await serverUmamiTrack(options, {
163
+ url: '/checkout',
164
+ title: 'Checkout',
165
+ name: 'page-view',
166
+ });
167
+ ```
168
+
169
+ ---
170
+
171
+ ### `createUmamiPlugin()`
172
+
173
+ Server-side Qwik City plugin for automatic page view tracking. Tracks every page request via the Umami API without requiring the client-side script. Import from `'qwik-umami/server'`. Requires `@builder.io/qwik-city` as peer dependency.
174
+
175
+ ```ts
176
+ // src/routes/plugin@umami.ts
177
+ import { createUmamiPlugin } from 'qwik-umami/server';
178
+
179
+ export const onRequest = createUmamiPlugin({
180
+ websiteId: import.meta.env.PUBLIC_UMAMI_ID,
181
+ hostUrl: import.meta.env.UMAMI_HOST_URL,
182
+ });
183
+ ```
184
+
185
+ | Option | Type | Default | Description |
186
+ | --- | --- | --- | --- |
187
+ | `websiteId` | `string` | **required** | Your Umami website ID |
188
+ | `hostUrl` | `string` | **required** | Umami instance URL |
189
+ | `userAgent` | `string` | request UA | Custom user-agent for tracking requests |
190
+ | `filter` | `(url: URL, headers: Headers) => boolean` | — | Return `false` to skip tracking for specific routes |
191
+
192
+ **How it works:**
193
+ - Only tracks `GET` requests (skips POST, PUT, DELETE, etc.)
194
+ - Automatically ignores static assets (`.js`, `.css`, `.png`, `.ico`, etc.)
195
+ - Fire-and-forget — the tracking request does NOT block the response
196
+ - Extracts `referrer`, `language`, and `user-agent` from request headers
197
+
198
+ **Filter example:**
199
+
200
+ ```ts
201
+ export const onRequest = createUmamiPlugin({
202
+ websiteId: 'your-id',
203
+ hostUrl: 'https://your-umami.com',
204
+ filter: (url) => !url.pathname.startsWith('/api/'),
205
+ });
206
+ ```
207
+
208
+ ---
209
+
210
+ ### `serverUmamiIdentify()`
211
+
212
+ Server-side session identification. Import from `'qwik-umami/server'`.
213
+
214
+ ```ts
215
+ import { serverUmamiIdentify } from 'qwik-umami/server';
216
+
217
+ await serverUmamiIdentify(options, { userId: 'abc', plan: 'pro' });
218
+ ```
219
+
220
+ ---
221
+
222
+ ### Types
223
+
224
+ ```ts
225
+ import type {
226
+ UmamiConfig, // Props for <UmamiScript>
227
+ UmamiEventData, // Record<string, string | number | boolean>
228
+ UmamiPayload, // Full browser tracker payload
229
+ } from 'qwik-umami';
230
+
231
+ import type {
232
+ UmamiServerOptions, // { websiteId, hostUrl, userAgent? }
233
+ UmamiServerPayload, // Server-side payload shape
234
+ UmamiPluginOptions, // { websiteId, hostUrl, userAgent?, filter? }
235
+ } from 'qwik-umami/server';
118
236
  ```
119
237
 
120
238
  ## Real-world example
121
239
 
122
- Tracking a full auth + conversion funnel:
240
+ ```tsx
241
+ // Tracking a full auth + conversion funnel
242
+
243
+ // Landing page CTA (client)
244
+ <button onClick$={() => umamiTrack('cta-click', { location: 'hero' })}>
245
+ Get started
246
+ </button>
247
+
248
+ // Auth action (server)
249
+ export const useLogin = routeAction$(async (data) => {
250
+ const user = await loginUser(data);
251
+ await serverUmamiTrack(options, 'login', { success: true });
252
+ return user;
253
+ });
254
+
255
+ // Identify after login (client)
256
+ umamiIdentify(user.id, { plan: user.plan });
257
+ ```
258
+
259
+ ## Self-hosted Umami
123
260
 
124
261
  ```tsx
125
- // Landing page CTA
126
- track('cta-click', { location: 'hero', label: 'get-started' });
262
+ <UmamiScript
263
+ websiteId="your-website-id"
264
+ src="https://your-umami-instance.com/script.js"
265
+ />
266
+ ```
127
267
 
128
- // Auth events
129
- track('login', { success: true });
130
- track('register', { success: true });
268
+ ```ts
269
+ const options = {
270
+ websiteId: 'your-website-id',
271
+ hostUrl: 'https://your-umami-instance.com',
272
+ };
273
+ ```
131
274
 
132
- // App events
133
- track('project-create');
134
- track('project-open', { status: 'ACTIVE' });
135
- track('chat-message-sent', { hasAttachments: false });
275
+ ## Which approach should I use?
276
+
277
+ | Approach | Best for |
278
+ | --- | --- |
279
+ | **`<UmamiScript>` only** | Most apps — full browser data (screen size, timezone, JS events) |
280
+ | **`createUmamiPlugin` only** | Server-rendered sites, bot tracking, ad-blocker resilience |
281
+ | **Both together** | Maximum coverage — use plugin for page views + component for events |
282
+
283
+ **Using both together:**
284
+
285
+ ```tsx
286
+ // src/routes/plugin@umami.ts — handles page views server-side
287
+ import { createUmamiPlugin } from 'qwik-umami/server';
288
+
289
+ export const onRequest = createUmamiPlugin({
290
+ websiteId: import.meta.env.PUBLIC_UMAMI_ID,
291
+ hostUrl: import.meta.env.UMAMI_HOST_URL,
292
+ });
293
+ ```
294
+
295
+ ```tsx
296
+ // src/root.tsx — handles custom events client-side (autoTrack disabled to avoid duplicate page views)
297
+ <UmamiScript websiteId="your-website-id" autoTrack={false} />
136
298
  ```
137
299
 
138
300
  ## How it works
139
301
 
140
- 1. `<UmamiScript>` uses Qwik's `useVisibleTask$` to load the tracker script client-side only
141
- 2. Before injecting the `<script>`, it sends a HEAD request to verify the Umami service is reachable (3s timeout)
142
- 3. If the service is down or blocked, it fails silently no errors, no broken pages
143
- 4. `useUmamiTrack()` checks for `window.umami` before calling `track()` — safe everywhere
302
+ 1. `<UmamiScript>` uses Qwik's `useTask$` with an `isServer` guard preserves resumability (no eager JS download)
303
+ 2. By default, the script loads via `requestIdleCallback` to avoid blocking the main thread
304
+ 3. If the script fails to load (blocked by ad blocker, network error), it fails silently and cleans up
305
+ 4. `umamiTrack()` and `umamiIdentify()` check for `window.umami` before calling — safe everywhere
306
+ 5. `serverUmamiTrack()` and `serverUmamiIdentify()` POST directly to the Umami `/api/send` endpoint — no browser required
307
+ 6. `createUmamiPlugin()` intercepts every GET request at the middleware level and sends a page view to Umami — fire-and-forget, never blocks the response
144
308
 
145
309
  ## License
146
310
 
@@ -1,72 +1,99 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const qwik = require("@builder.io/qwik");
4
- const UmamiScript = qwik.component$(({ websiteId, src = "https://cloud.umami.is/script.js", hostUrl, autoTrack = true, domains, dataDomains }) => {
5
- qwik.useVisibleTask$(() => {
4
+ const build = require("@builder.io/qwik/build");
5
+ const UmamiScript = qwik.component$(({ websiteId, src = "https://cloud.umami.is/script.js", hostUrl, autoTrack = true, domains, tag, excludeSearch, excludeHash, doNotTrack, strategy = "idle" }) => {
6
+ qwik.useTask$(({ cleanup }) => {
7
+ if (build.isServer) return;
6
8
  if (document.querySelector(`script[data-website-id="${websiteId}"]`)) {
7
9
  return;
8
10
  }
9
- const checkAndLoadScript = async () => {
10
- try {
11
- const controller = new AbortController();
12
- const timeoutId = setTimeout(() => controller.abort(), 3e3);
13
- await fetch(src, {
14
- method: "HEAD",
15
- mode: "no-cors",
16
- signal: controller.signal
17
- });
18
- clearTimeout(timeoutId);
19
- loadUmamiScript();
20
- } catch (error) {
11
+ let script = null;
12
+ const loadScript = () => {
13
+ script = document.createElement("script");
14
+ script.defer = true;
15
+ script.src = src;
16
+ script.setAttribute("data-website-id", websiteId);
17
+ if (hostUrl) {
18
+ script.setAttribute("data-host-url", hostUrl);
21
19
  }
22
- };
23
- const loadUmamiScript = () => {
24
- try {
25
- const script = document.createElement("script");
26
- script.defer = true;
27
- script.src = src;
28
- script.setAttribute("data-website-id", websiteId);
29
- if (hostUrl) {
30
- script.setAttribute("data-host-url", hostUrl);
31
- }
32
- if (!autoTrack) {
33
- script.setAttribute("data-auto-track", "false");
34
- }
35
- if (domains) {
36
- script.setAttribute("data-domains", domains);
37
- }
38
- if (dataDomains) {
39
- script.setAttribute("data-data-domains", dataDomains);
40
- }
41
- script.onerror = () => {
42
- if (false) ;
43
- script.remove();
44
- };
45
- script.onload = () => {
46
- if (false) ;
47
- };
48
- document.head.appendChild(script);
49
- } catch (error) {
20
+ if (!autoTrack) {
21
+ script.setAttribute("data-auto-track", "false");
22
+ }
23
+ if (domains) {
24
+ script.setAttribute("data-domains", domains);
25
+ }
26
+ if (tag) {
27
+ script.setAttribute("data-tag", tag);
28
+ }
29
+ if (excludeSearch) {
30
+ script.setAttribute("data-exclude-search", "true");
31
+ }
32
+ if (excludeHash) {
33
+ script.setAttribute("data-exclude-hash", "true");
34
+ }
35
+ if (doNotTrack) {
36
+ script.setAttribute("data-do-not-track", "true");
50
37
  }
38
+ script.onerror = () => {
39
+ script?.remove();
40
+ script = null;
41
+ };
42
+ document.head.appendChild(script);
51
43
  };
52
- checkAndLoadScript();
44
+ if (strategy === "eager") {
45
+ loadScript();
46
+ } else {
47
+ if ("requestIdleCallback" in window) {
48
+ const id = window.requestIdleCallback(loadScript);
49
+ cleanup(() => {
50
+ window.cancelIdleCallback(id);
51
+ script?.remove();
52
+ });
53
+ return;
54
+ }
55
+ const timeoutId = setTimeout(loadScript, 0);
56
+ cleanup(() => {
57
+ clearTimeout(timeoutId);
58
+ script?.remove();
59
+ });
60
+ return;
61
+ }
62
+ cleanup(() => {
63
+ script?.remove();
64
+ });
53
65
  });
54
66
  return null;
55
67
  });
56
- const useUmamiTrack = () => {
57
- const store = qwik.useStore({
58
- track: qwik.$(function(eventName, eventData) {
59
- if (typeof window === "undefined") return;
60
- try {
61
- if (window.umami?.track) {
62
- window.umami.track(eventName, eventData);
63
- } else if (false) ;
64
- } catch (error) {
68
+ function umamiTrack(eventNameOrPayload, data) {
69
+ if (typeof window === "undefined") return;
70
+ try {
71
+ if (window.umami?.track) {
72
+ if (eventNameOrPayload === void 0) {
73
+ window.umami.track();
74
+ } else if (typeof eventNameOrPayload === "string") {
75
+ window.umami.track(eventNameOrPayload, data);
76
+ } else {
77
+ window.umami.track(eventNameOrPayload);
65
78
  }
66
- })
67
- });
68
- return store;
69
- };
79
+ } else if (false) ;
80
+ } catch (error) {
81
+ }
82
+ }
83
+ function umamiIdentify(uniqueIdOrData, data) {
84
+ if (typeof window === "undefined") return;
85
+ try {
86
+ if (window.umami?.identify) {
87
+ if (typeof uniqueIdOrData === "string") {
88
+ window.umami.identify(uniqueIdOrData, data);
89
+ } else {
90
+ window.umami.identify(uniqueIdOrData);
91
+ }
92
+ } else if (false) ;
93
+ } catch (error) {
94
+ }
95
+ }
70
96
  exports.UmamiScript = UmamiScript;
71
- exports.useUmamiTrack = useUmamiTrack;
97
+ exports.umamiIdentify = umamiIdentify;
98
+ exports.umamiTrack = umamiTrack;
72
99
  //# sourceMappingURL=index.qwik.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.qwik.cjs","sources":["../src/components/umami-script.tsx","../src/hooks/use-umami-track.ts"],"sourcesContent":["import { component$, useVisibleTask$ } from '@builder.io/qwik';\r\nimport type { UmamiConfig } from '../types';\r\n\r\nexport const UmamiScript = component$<UmamiConfig>(\r\n ({\r\n websiteId,\r\n src = 'https://cloud.umami.is/script.js',\r\n hostUrl,\r\n autoTrack = true,\r\n domains,\r\n dataDomains,\r\n }) => {\r\n // eslint-disable-next-line qwik/no-use-visible-task\r\n useVisibleTask$(() => {\r\n if (document.querySelector(`script[data-website-id=\"${websiteId}\"]`)) {\r\n return;\r\n }\r\n\r\n const checkAndLoadScript = async () => {\r\n try {\r\n const controller = new AbortController();\r\n const timeoutId = setTimeout(() => controller.abort(), 3000);\r\n\r\n await fetch(src, {\r\n method: 'HEAD',\r\n mode: 'no-cors',\r\n signal: controller.signal,\r\n });\r\n\r\n clearTimeout(timeoutId);\r\n loadUmamiScript();\r\n } catch (error) {\r\n if (import.meta.env.DEV) {\r\n console.warn(\r\n '[qwik-umami] Service unavailable, tracking temporarily disabled',\r\n error,\r\n );\r\n }\r\n }\r\n };\r\n\r\n const loadUmamiScript = () => {\r\n try {\r\n const script = document.createElement('script');\r\n script.defer = true;\r\n script.src = src;\r\n script.setAttribute('data-website-id', websiteId);\r\n\r\n if (hostUrl) {\r\n script.setAttribute('data-host-url', hostUrl);\r\n }\r\n\r\n if (!autoTrack) {\r\n script.setAttribute('data-auto-track', 'false');\r\n }\r\n\r\n if (domains) {\r\n script.setAttribute('data-domains', domains);\r\n }\r\n\r\n if (dataDomains) {\r\n script.setAttribute('data-data-domains', dataDomains);\r\n }\r\n\r\n script.onerror = () => {\r\n if (import.meta.env.DEV) {\r\n console.warn('[qwik-umami] Failed to load script');\r\n }\r\n script.remove();\r\n };\r\n\r\n script.onload = () => {\r\n if (import.meta.env.DEV) {\r\n console.log('[qwik-umami] Script loaded successfully');\r\n }\r\n };\r\n\r\n document.head.appendChild(script);\r\n } catch (error) {\r\n if (import.meta.env.DEV) {\r\n console.warn('[qwik-umami] Initialization error:', error);\r\n }\r\n }\r\n };\r\n\r\n checkAndLoadScript();\r\n });\r\n\r\n return null;\r\n },\r\n);\r\n","import { $, useStore } from '@builder.io/qwik';\r\nimport type { UmamiEventData } from '../types';\r\n\r\nexport const useUmamiTrack = () => {\r\n const store = useStore({\r\n track: $(function (eventName: string, eventData?: UmamiEventData) {\r\n if (typeof window === 'undefined') return;\r\n try {\r\n if (window.umami?.track) {\r\n window.umami.track(eventName, eventData);\r\n } else if (import.meta.env.DEV) {\r\n console.warn('[qwik-umami] Tracking unavailable for event:', eventName);\r\n }\r\n } catch (error) {\r\n if (import.meta.env.DEV) {\r\n console.warn('[qwik-umami] Error sending event:', eventName, error);\r\n }\r\n }\r\n }),\r\n });\r\n\r\n return store;\r\n};\r\n"],"names":["component$","useVisibleTask$","useStore","$"],"mappings":";;;AAGO,MAAM,cAAcA,KAAAA,WACzB,CAAC,EACC,WACA,MAAM,oCACN,SACA,YAAY,MACZ,SACA,YAAA,MACD;AAECC,OAAAA,gBAAgB,MAAA;AACd,QAAI,SAAS,cAAc,2BAA2B,SAAA,IAAa,GAAG;AACpE;AAAA,IACF;AAEA,UAAM,qBAAqB,YAAA;AACzB,UAAI;AACF,cAAM,aAAa,IAAI,gBAAA;AACvB,cAAM,YAAY,WAAW,MAAM,WAAW,MAAA,GAAS,GAAA;AAEvD,cAAM,MAAM,KAAK;AAAA,UACf,QAAQ;AAAA,UACR,MAAM;AAAA,UACN,QAAQ,WAAW;AAAA,QAAA,CACrB;AAEA,qBAAa,SAAA;AACb,wBAAA;AAAA,MACF,SAAS,OAAO;AAAA,MAOhB;AAAA,IACF;AAEA,UAAM,kBAAkB,MAAA;AACtB,UAAI;AACF,cAAM,SAAS,SAAS,cAAc,QAAA;AACtC,eAAO,QAAQ;AACf,eAAO,MAAM;AACb,eAAO,aAAa,mBAAmB,SAAA;AAEvC,YAAI,SAAS;AACX,iBAAO,aAAa,iBAAiB,OAAA;AAAA,QACvC;AAEA,YAAI,CAAC,WAAW;AACd,iBAAO,aAAa,mBAAmB,OAAA;AAAA,QACzC;AAEA,YAAI,SAAS;AACX,iBAAO,aAAa,gBAAgB,OAAA;AAAA,QACtC;AAEA,YAAI,aAAa;AACf,iBAAO,aAAa,qBAAqB,WAAA;AAAA,QAC3C;AAEA,eAAO,UAAU,MAAA;AACf,cAAI,MAAqB;AAGzB,iBAAO,OAAA;AAAA,QACT;AAEA,eAAO,SAAS,MAAA;AACd,cAAI,MAAqB;AAAA,QAG3B;AAEA,iBAAS,KAAK,YAAY,MAAA;AAAA,MAC5B,SAAS,OAAO;AAAA,MAIhB;AAAA,IACF;AAEA,uBAAA;AAAA,EACF,CAAA;AAEA,SAAO;AACT,CAAA;ACtFK,MAAM,gBAAgB,MAAA;AAC3B,QAAM,QAAQC,KAAAA,SAAS;AAAA,IACrB,OAAOC,KAAAA,EAAE,SAAU,WAAmB,WAA0B;AAC9D,UAAI,OAAO,WAAW,YAAa;AACnC,UAAI;AACF,YAAI,OAAO,OAAO,OAAO;AACvB,iBAAO,MAAM,MAAM,WAAW,SAAA;AAAA,QAChC,WAAW,MAAqB;AAAA,MAGlC,SAAS,OAAO;AAAA,MAIhB;AAAA,IACF,CAAA;AAAA,EAAA,CACF;AAEA,SAAO;AACT;;;"}
1
+ {"version":3,"file":"index.qwik.cjs","sources":["../src/components/umami-script.tsx","../src/utils/track.ts"],"sourcesContent":["import { component$, useTask$ } from '@builder.io/qwik';\nimport { isServer } from '@builder.io/qwik/build';\nimport type { UmamiConfig } from '../types';\n\nexport const UmamiScript = component$<UmamiConfig>(\n ({\n websiteId,\n src = 'https://cloud.umami.is/script.js',\n hostUrl,\n autoTrack = true,\n domains,\n tag,\n excludeSearch,\n excludeHash,\n doNotTrack,\n strategy = 'idle',\n }) => {\n useTask$(({ cleanup }) => {\n if (isServer) return;\n\n if (document.querySelector(`script[data-website-id=\"${websiteId}\"]`)) {\n return;\n }\n\n let script: HTMLScriptElement | null = null;\n\n const loadScript = () => {\n script = document.createElement('script');\n script.defer = true;\n script.src = src;\n script.setAttribute('data-website-id', websiteId);\n\n if (hostUrl) {\n script.setAttribute('data-host-url', hostUrl);\n }\n\n if (!autoTrack) {\n script.setAttribute('data-auto-track', 'false');\n }\n\n if (domains) {\n script.setAttribute('data-domains', domains);\n }\n\n if (tag) {\n script.setAttribute('data-tag', tag);\n }\n\n if (excludeSearch) {\n script.setAttribute('data-exclude-search', 'true');\n }\n\n if (excludeHash) {\n script.setAttribute('data-exclude-hash', 'true');\n }\n\n if (doNotTrack) {\n script.setAttribute('data-do-not-track', 'true');\n }\n\n script.onerror = () => {\n if (import.meta.env.DEV) {\n console.warn('[qwik-umami] Failed to load script');\n }\n script?.remove();\n script = null;\n };\n\n if (import.meta.env.DEV) {\n script.onload = () => {\n console.log('[qwik-umami] Script loaded successfully');\n };\n }\n\n document.head.appendChild(script);\n };\n\n if (strategy === 'eager') {\n loadScript();\n } else {\n if ('requestIdleCallback' in window) {\n const id = window.requestIdleCallback(loadScript);\n cleanup(() => {\n window.cancelIdleCallback(id);\n script?.remove();\n });\n return;\n }\n const timeoutId = setTimeout(loadScript, 0);\n cleanup(() => {\n clearTimeout(timeoutId);\n script?.remove();\n });\n return;\n }\n\n cleanup(() => {\n script?.remove();\n });\n });\n\n return null;\n },\n);\n","import type { UmamiEventData, UmamiPayload } from '../types';\r\n\r\nexport function umamiTrack(): void;\r\nexport function umamiTrack(payload: UmamiPayload): void;\r\nexport function umamiTrack(eventName: string): void;\r\nexport function umamiTrack(eventName: string, data: UmamiEventData): void;\r\nexport function umamiTrack(\r\n eventNameOrPayload?: string | UmamiPayload,\r\n data?: UmamiEventData,\r\n): void {\r\n if (typeof window === 'undefined') return;\r\n\r\n try {\r\n if (window.umami?.track) {\r\n if (eventNameOrPayload === undefined) {\r\n window.umami.track();\r\n } else if (typeof eventNameOrPayload === 'string') {\r\n window.umami.track(eventNameOrPayload, data!);\r\n } else {\r\n window.umami.track(eventNameOrPayload);\r\n }\r\n } else if (import.meta.env.DEV) {\r\n console.warn('[qwik-umami] Tracking unavailable');\r\n }\r\n } catch (error) {\r\n if (import.meta.env.DEV) {\r\n console.warn('[qwik-umami] Error sending event:', eventNameOrPayload, error);\r\n }\r\n }\r\n}\r\n\r\nexport function umamiIdentify(data: UmamiEventData): void;\r\nexport function umamiIdentify(uniqueId: string): void;\r\nexport function umamiIdentify(uniqueId: string, data: UmamiEventData): void;\r\nexport function umamiIdentify(\r\n uniqueIdOrData: string | UmamiEventData,\r\n data?: UmamiEventData,\r\n): void {\r\n if (typeof window === 'undefined') return;\r\n\r\n try {\r\n if (window.umami?.identify) {\r\n if (typeof uniqueIdOrData === 'string') {\r\n window.umami.identify(uniqueIdOrData, data!);\r\n } else {\r\n window.umami.identify(uniqueIdOrData);\r\n }\r\n } else if (import.meta.env.DEV) {\r\n console.warn('[qwik-umami] Identify unavailable');\r\n }\r\n } catch (error) {\r\n if (import.meta.env.DEV) {\r\n console.warn('[qwik-umami] Error identifying:', error);\r\n }\r\n }\r\n}\r\n"],"names":["component$","useTask$","isServer"],"mappings":";;;;AAIO,MAAM,cAAcA,KAAAA,WACzB,CAAC,EACC,WACA,MAAM,oCACN,SACA,YAAY,MACZ,SACA,KACA,eACA,aACA,YACA,WAAW,aACZ;AACCC,gBAAS,CAAC,EAAE,cAAS;AACnB,QAAIC,eAAU;AAEd,QAAI,SAAS,cAAc,2BAA2B,SAAA,IAAa,GAAG;AACpE;AAAA,IACF;AAEA,QAAI,SAAmC;AAEvC,UAAM,aAAa,MAAA;AACjB,eAAS,SAAS,cAAc,QAAA;AAChC,aAAO,QAAQ;AACf,aAAO,MAAM;AACb,aAAO,aAAa,mBAAmB,SAAA;AAEvC,UAAI,SAAS;AACX,eAAO,aAAa,iBAAiB,OAAA;AAAA,MACvC;AAEA,UAAI,CAAC,WAAW;AACd,eAAO,aAAa,mBAAmB,OAAA;AAAA,MACzC;AAEA,UAAI,SAAS;AACX,eAAO,aAAa,gBAAgB,OAAA;AAAA,MACtC;AAEA,UAAI,KAAK;AACP,eAAO,aAAa,YAAY,GAAA;AAAA,MAClC;AAEA,UAAI,eAAe;AACjB,eAAO,aAAa,uBAAuB,MAAA;AAAA,MAC7C;AAEA,UAAI,aAAa;AACf,eAAO,aAAa,qBAAqB,MAAA;AAAA,MAC3C;AAEA,UAAI,YAAY;AACd,eAAO,aAAa,qBAAqB,MAAA;AAAA,MAC3C;AAEA,aAAO,UAAU,MAAA;AAIf,gBAAQ,OAAA;AACR,iBAAS;AAAA,MACX;AAQA,eAAS,KAAK,YAAY,MAAA;AAAA,IAC5B;AAEA,QAAI,aAAa,SAAS;AACxB,iBAAA;AAAA,IACF,OAAO;AACL,UAAI,yBAAyB,QAAQ;AACnC,cAAM,KAAK,OAAO,oBAAoB,UAAA;AACtC,gBAAQ,MAAA;AACN,iBAAO,mBAAmB,EAAA;AAC1B,kBAAQ,OAAA;AAAA,QACV,CAAA;AACA;AAAA,MACF;AACA,YAAM,YAAY,WAAW,YAAY,CAAA;AACzC,cAAQ,MAAA;AACN,qBAAa,SAAA;AACb,gBAAQ,OAAA;AAAA,MACV,CAAA;AACA;AAAA,IACF;AAEA,YAAQ,MAAA;AACN,cAAQ,OAAA;AAAA,IACV,CAAA;AAAA,EACF,CAAA;AAEA,SAAO;AACT,CAAA;AChGK,SAAS,WACd,oBACA,MAAqB;AAErB,MAAI,OAAO,WAAW,YAAa;AAEnC,MAAI;AACF,QAAI,OAAO,OAAO,OAAO;AACvB,UAAI,uBAAuB,QAAW;AACpC,eAAO,MAAM,MAAA;AAAA,MACf,WAAW,OAAO,uBAAuB,UAAU;AACjD,eAAO,MAAM,MAAM,oBAAoB,IAAA;AAAA,MACzC,OAAO;AACL,eAAO,MAAM,MAAM,kBAAA;AAAA,MACrB;AAAA,IACF,WAAW,MAAqB;AAAA,EAGlC,SAAS,OAAO;AAAA,EAIhB;AACF;AAKO,SAAS,cACd,gBACA,MAAqB;AAErB,MAAI,OAAO,WAAW,YAAa;AAEnC,MAAI;AACF,QAAI,OAAO,OAAO,UAAU;AAC1B,UAAI,OAAO,mBAAmB,UAAU;AACtC,eAAO,MAAM,SAAS,gBAAgB,IAAA;AAAA,MACxC,OAAO;AACL,eAAO,MAAM,SAAS,cAAA;AAAA,MACxB;AAAA,IACF,WAAW,MAAqB;AAAA,EAGlC,SAAS,OAAO;AAAA,EAIhB;AACF;;;;"}