qwik-umami 1.0.3 → 1.1.1

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
@@ -85,7 +85,7 @@ export const useCheckout = routeAction$(async (data) => {
85
85
 
86
86
  ### `<UmamiScript>`
87
87
 
88
- 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 `useVisibleTask$` for guaranteed one-shot client initialization. Loads via `requestIdleCallback` by default (`strategy: 'idle'`) to avoid blocking the main thread.
89
89
 
90
90
  | Prop | Type | Default | Description |
91
91
  | --- | --- | --- | --- |
@@ -98,6 +98,7 @@ Component that loads the Umami tracking script. Checks service availability befo
98
98
  | `excludeSearch` | `boolean` | — | Exclude URL search parameters from tracking |
99
99
  | `excludeHash` | `boolean` | — | Exclude URL hash fragments from tracking |
100
100
  | `doNotTrack` | `boolean` | — | Respect the browser's Do Not Track setting |
101
+ | `strategy` | `'eager' \| 'idle'` | `'idle'` | `'idle'` defers loading via `requestIdleCallback`, `'eager'` loads immediately |
101
102
 
102
103
  ---
103
104
 
@@ -167,6 +168,45 @@ await serverUmamiTrack(options, {
167
168
 
168
169
  ---
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
+
170
210
  ### `serverUmamiIdentify()`
171
211
 
172
212
  Server-side session identification. Import from `'qwik-umami/server'`.
@@ -189,8 +229,9 @@ import type {
189
229
  } from 'qwik-umami';
190
230
 
191
231
  import type {
192
- UmamiServerOptions, // { websiteId, hostUrl, userAgent? }
193
- UmamiServerPayload, // Server-side payload shape
232
+ UmamiServerOptions, // { websiteId, hostUrl, userAgent? }
233
+ UmamiServerPayload, // Server-side payload shape
234
+ UmamiPluginOptions, // { websiteId, hostUrl, userAgent?, filter? }
194
235
  } from 'qwik-umami/server';
195
236
  ```
196
237
 
@@ -231,13 +272,39 @@ const options = {
231
272
  };
232
273
  ```
233
274
 
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} />
298
+ ```
299
+
234
300
  ## How it works
235
301
 
236
- 1. `<UmamiScript>` uses Qwik's `useVisibleTask$` to load the tracker script client-side only
237
- 2. Before injecting the `<script>`, it sends a HEAD request to verify Umami is reachable (3s timeout)
238
- 3. If the service is down or blocked, it fails silently no errors, no broken pages
302
+ 1. `<UmamiScript>` uses Qwik's `useVisibleTask$` for guaranteed one-shot client initialization
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
239
305
  4. `umamiTrack()` and `umamiIdentify()` check for `window.umami` before calling — safe everywhere
240
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
241
308
 
242
309
  ## License
243
310
 
@@ -1,64 +1,65 @@
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, tag, excludeSearch, excludeHash, doNotTrack }) => {
5
- qwik.useVisibleTask$(() => {
4
+ const UmamiScript = qwik.component$(({ websiteId, src = "https://cloud.umami.is/script.js", hostUrl, autoTrack = true, domains, tag, excludeSearch, excludeHash, doNotTrack, strategy = "idle" }) => {
5
+ qwik.useVisibleTask$(({ cleanup }) => {
6
6
  if (document.querySelector(`script[data-website-id="${websiteId}"]`)) {
7
7
  return;
8
8
  }
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) {
9
+ let script = null;
10
+ const loadScript = () => {
11
+ script = document.createElement("script");
12
+ script.defer = true;
13
+ script.src = src;
14
+ script.setAttribute("data-website-id", websiteId);
15
+ if (hostUrl) {
16
+ script.setAttribute("data-host-url", hostUrl);
21
17
  }
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 (tag) {
39
- script.setAttribute("data-tag", tag);
40
- }
41
- if (excludeSearch) {
42
- script.setAttribute("data-exclude-search", "true");
43
- }
44
- if (excludeHash) {
45
- script.setAttribute("data-exclude-hash", "true");
46
- }
47
- if (doNotTrack) {
48
- script.setAttribute("data-do-not-track", "true");
49
- }
50
- script.onerror = () => {
51
- if (false) ;
52
- script.remove();
53
- };
54
- script.onload = () => {
55
- if (false) ;
56
- };
57
- document.head.appendChild(script);
58
- } catch (error) {
18
+ if (!autoTrack) {
19
+ script.setAttribute("data-auto-track", "false");
20
+ }
21
+ if (domains) {
22
+ script.setAttribute("data-domains", domains);
23
+ }
24
+ if (tag) {
25
+ script.setAttribute("data-tag", tag);
26
+ }
27
+ if (excludeSearch) {
28
+ script.setAttribute("data-exclude-search", "true");
29
+ }
30
+ if (excludeHash) {
31
+ script.setAttribute("data-exclude-hash", "true");
59
32
  }
33
+ if (doNotTrack) {
34
+ script.setAttribute("data-do-not-track", "true");
35
+ }
36
+ script.onerror = () => {
37
+ script?.remove();
38
+ script = null;
39
+ };
40
+ document.head.appendChild(script);
60
41
  };
61
- checkAndLoadScript();
42
+ if (strategy === "eager") {
43
+ loadScript();
44
+ } else {
45
+ if ("requestIdleCallback" in window) {
46
+ const id = window.requestIdleCallback(loadScript);
47
+ cleanup(() => {
48
+ window.cancelIdleCallback(id);
49
+ script?.remove();
50
+ });
51
+ return;
52
+ }
53
+ const timeoutId = setTimeout(loadScript, 0);
54
+ cleanup(() => {
55
+ clearTimeout(timeoutId);
56
+ script?.remove();
57
+ });
58
+ return;
59
+ }
60
+ cleanup(() => {
61
+ script?.remove();
62
+ });
62
63
  });
63
64
  return null;
64
65
  });
@@ -1 +1 @@
1
- {"version":3,"file":"index.qwik.cjs","sources":["../src/components/umami-script.tsx","../src/utils/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 tag,\r\n excludeSearch,\r\n excludeHash,\r\n doNotTrack,\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 (tag) {\r\n script.setAttribute('data-tag', tag);\r\n }\r\n\r\n if (excludeSearch) {\r\n script.setAttribute('data-exclude-search', 'true');\r\n }\r\n\r\n if (excludeHash) {\r\n script.setAttribute('data-exclude-hash', 'true');\r\n }\r\n\r\n if (doNotTrack) {\r\n script.setAttribute('data-do-not-track', 'true');\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 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$","useVisibleTask$"],"mappings":";;;AAGO,MAAM,cAAcA,KAAAA,WACzB,CAAC,EACC,WACA,MAAM,oCACN,SACA,YAAY,MACZ,SACA,KACA,eACA,aACA,iBACD;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,KAAK;AACP,iBAAO,aAAa,YAAY,GAAA;AAAA,QAClC;AAEA,YAAI,eAAe;AACjB,iBAAO,aAAa,uBAAuB,MAAA;AAAA,QAC7C;AAEA,YAAI,aAAa;AACf,iBAAO,aAAa,qBAAqB,MAAA;AAAA,QAC3C;AAEA,YAAI,YAAY;AACd,iBAAO,aAAa,qBAAqB,MAAA;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;AClGK,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;;;;"}
1
+ {"version":3,"file":"index.qwik.cjs","sources":["../src/components/umami-script.tsx","../src/utils/track.ts"],"sourcesContent":["import { component$, useVisibleTask$ } from '@builder.io/qwik';\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 // eslint-disable-next-line qwik/no-use-visible-task\n useVisibleTask$(({ cleanup }) => {\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$","useVisibleTask$"],"mappings":";;;AAGO,MAAM,cAAcA,KAAAA,WACzB,CAAC,EACC,WACA,MAAM,oCACN,SACA,YAAY,MACZ,SACA,KACA,eACA,aACA,YACA,WAAW,aACZ;AAECC,uBAAgB,CAAC,EAAE,cAAS;AAC1B,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;AC9FK,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;;;;"}
@@ -1,62 +1,63 @@
1
1
  import { component$, useVisibleTask$ } from "@builder.io/qwik";
2
- const UmamiScript = component$(({ websiteId, src = "https://cloud.umami.is/script.js", hostUrl, autoTrack = true, domains, tag, excludeSearch, excludeHash, doNotTrack }) => {
3
- useVisibleTask$(() => {
2
+ const UmamiScript = component$(({ websiteId, src = "https://cloud.umami.is/script.js", hostUrl, autoTrack = true, domains, tag, excludeSearch, excludeHash, doNotTrack, strategy = "idle" }) => {
3
+ useVisibleTask$(({ cleanup }) => {
4
4
  if (document.querySelector(`script[data-website-id="${websiteId}"]`)) {
5
5
  return;
6
6
  }
7
- const checkAndLoadScript = async () => {
8
- try {
9
- const controller = new AbortController();
10
- const timeoutId = setTimeout(() => controller.abort(), 3e3);
11
- await fetch(src, {
12
- method: "HEAD",
13
- mode: "no-cors",
14
- signal: controller.signal
15
- });
16
- clearTimeout(timeoutId);
17
- loadUmamiScript();
18
- } catch (error) {
7
+ let script = null;
8
+ const loadScript = () => {
9
+ script = document.createElement("script");
10
+ script.defer = true;
11
+ script.src = src;
12
+ script.setAttribute("data-website-id", websiteId);
13
+ if (hostUrl) {
14
+ script.setAttribute("data-host-url", hostUrl);
19
15
  }
20
- };
21
- const loadUmamiScript = () => {
22
- try {
23
- const script = document.createElement("script");
24
- script.defer = true;
25
- script.src = src;
26
- script.setAttribute("data-website-id", websiteId);
27
- if (hostUrl) {
28
- script.setAttribute("data-host-url", hostUrl);
29
- }
30
- if (!autoTrack) {
31
- script.setAttribute("data-auto-track", "false");
32
- }
33
- if (domains) {
34
- script.setAttribute("data-domains", domains);
35
- }
36
- if (tag) {
37
- script.setAttribute("data-tag", tag);
38
- }
39
- if (excludeSearch) {
40
- script.setAttribute("data-exclude-search", "true");
41
- }
42
- if (excludeHash) {
43
- script.setAttribute("data-exclude-hash", "true");
44
- }
45
- if (doNotTrack) {
46
- script.setAttribute("data-do-not-track", "true");
47
- }
48
- script.onerror = () => {
49
- if (false) ;
50
- script.remove();
51
- };
52
- script.onload = () => {
53
- if (false) ;
54
- };
55
- document.head.appendChild(script);
56
- } catch (error) {
16
+ if (!autoTrack) {
17
+ script.setAttribute("data-auto-track", "false");
18
+ }
19
+ if (domains) {
20
+ script.setAttribute("data-domains", domains);
21
+ }
22
+ if (tag) {
23
+ script.setAttribute("data-tag", tag);
24
+ }
25
+ if (excludeSearch) {
26
+ script.setAttribute("data-exclude-search", "true");
27
+ }
28
+ if (excludeHash) {
29
+ script.setAttribute("data-exclude-hash", "true");
57
30
  }
31
+ if (doNotTrack) {
32
+ script.setAttribute("data-do-not-track", "true");
33
+ }
34
+ script.onerror = () => {
35
+ script?.remove();
36
+ script = null;
37
+ };
38
+ document.head.appendChild(script);
58
39
  };
59
- checkAndLoadScript();
40
+ if (strategy === "eager") {
41
+ loadScript();
42
+ } else {
43
+ if ("requestIdleCallback" in window) {
44
+ const id = window.requestIdleCallback(loadScript);
45
+ cleanup(() => {
46
+ window.cancelIdleCallback(id);
47
+ script?.remove();
48
+ });
49
+ return;
50
+ }
51
+ const timeoutId = setTimeout(loadScript, 0);
52
+ cleanup(() => {
53
+ clearTimeout(timeoutId);
54
+ script?.remove();
55
+ });
56
+ return;
57
+ }
58
+ cleanup(() => {
59
+ script?.remove();
60
+ });
60
61
  });
61
62
  return null;
62
63
  });
@@ -1 +1 @@
1
- {"version":3,"file":"index.qwik.mjs","sources":["../src/components/umami-script.tsx","../src/utils/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 tag,\r\n excludeSearch,\r\n excludeHash,\r\n doNotTrack,\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 (tag) {\r\n script.setAttribute('data-tag', tag);\r\n }\r\n\r\n if (excludeSearch) {\r\n script.setAttribute('data-exclude-search', 'true');\r\n }\r\n\r\n if (excludeHash) {\r\n script.setAttribute('data-exclude-hash', 'true');\r\n }\r\n\r\n if (doNotTrack) {\r\n script.setAttribute('data-do-not-track', 'true');\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 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":[],"mappings":";AAGO,MAAM,cAAc,WACzB,CAAC,EACC,WACA,MAAM,oCACN,SACA,YAAY,MACZ,SACA,KACA,eACA,aACA,iBACD;AAEC,kBAAgB,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,KAAK;AACP,iBAAO,aAAa,YAAY,GAAA;AAAA,QAClC;AAEA,YAAI,eAAe;AACjB,iBAAO,aAAa,uBAAuB,MAAA;AAAA,QAC7C;AAEA,YAAI,aAAa;AACf,iBAAO,aAAa,qBAAqB,MAAA;AAAA,QAC3C;AAEA,YAAI,YAAY;AACd,iBAAO,aAAa,qBAAqB,MAAA;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;AClGK,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;"}
1
+ {"version":3,"file":"index.qwik.mjs","sources":["../src/components/umami-script.tsx","../src/utils/track.ts"],"sourcesContent":["import { component$, useVisibleTask$ } from '@builder.io/qwik';\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 // eslint-disable-next-line qwik/no-use-visible-task\n useVisibleTask$(({ cleanup }) => {\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":[],"mappings":";AAGO,MAAM,cAAc,WACzB,CAAC,EACC,WACA,MAAM,oCACN,SACA,YAAY,MACZ,SACA,KACA,eACA,aACA,YACA,WAAW,aACZ;AAEC,kBAAgB,CAAC,EAAE,cAAS;AAC1B,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;AC9FK,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;"}
package/lib/server.cjs CHANGED
@@ -33,6 +33,28 @@ async function serverUmamiIdentify(options, data) {
33
33
  data
34
34
  }, "identify");
35
35
  }
36
+ const IGNORED_EXTENSIONS = /\.(js|css|ico|png|jpg|jpeg|gif|svg|webp|woff2?|ttf|eot|map|json)$/;
37
+ function createUmamiPlugin(options) {
38
+ return async ({ url, headers, method }) => {
39
+ if (method !== "GET") return;
40
+ if (IGNORED_EXTENSIONS.test(url.pathname)) return;
41
+ if (options.filter && !options.filter(url, headers)) return;
42
+ const language = headers.get("accept-language")?.split(",")[0];
43
+ const referrer = headers.get("referer") || void 0;
44
+ const userAgent = headers.get("user-agent") || void 0;
45
+ serverUmamiTrack({
46
+ websiteId: options.websiteId,
47
+ hostUrl: options.hostUrl,
48
+ userAgent: userAgent || options.userAgent
49
+ }, {
50
+ url: url.pathname + url.search,
51
+ referrer,
52
+ language: language || void 0
53
+ }).catch(() => {
54
+ });
55
+ };
56
+ }
57
+ exports.createUmamiPlugin = createUmamiPlugin;
36
58
  exports.serverUmamiIdentify = serverUmamiIdentify;
37
59
  exports.serverUmamiTrack = serverUmamiTrack;
38
60
  //# sourceMappingURL=server.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"server.cjs","sources":["../src/server/umami-server.ts"],"sourcesContent":["import type { UmamiEventData, UmamiServerOptions, UmamiServerPayload } from '../types';\r\n\r\nconst UMAMI_VERSION = '2.0.0';\r\nconst DEFAULT_USER_AGENT = `Mozilla/5.0 (compatible; qwik-umami/${UMAMI_VERSION})`;\r\n\r\nasync function send(\r\n options: UmamiServerOptions,\r\n payload: UmamiServerPayload,\r\n type: 'event' | 'identify' = 'event',\r\n): Promise<Response> {\r\n const { hostUrl, websiteId, userAgent = DEFAULT_USER_AGENT } = options;\r\n\r\n return fetch(`${hostUrl}/api/send`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'User-Agent': userAgent,\r\n },\r\n body: JSON.stringify({\r\n type,\r\n payload: { website: websiteId, ...payload },\r\n }),\r\n });\r\n}\r\n\r\nexport async function serverUmamiTrack(\r\n options: UmamiServerOptions,\r\n event: string | UmamiServerPayload,\r\n data?: UmamiEventData,\r\n): Promise<Response> {\r\n if (typeof event === 'string') {\r\n return send(options, { name: event, data });\r\n }\r\n return send(options, event);\r\n}\r\n\r\nexport async function serverUmamiIdentify(\r\n options: UmamiServerOptions,\r\n data: UmamiEventData,\r\n): Promise<Response> {\r\n return send(options, { data }, 'identify');\r\n}\r\n"],"names":[],"mappings":";;AAEA,MAAM,gBAAgB;AACtB,MAAM,qBAAqB,uCAAuC,aAAA;AAElE,eAAe,KACb,SACA,SACA,OAA6B,SAAO;AAEpC,QAAM,EAAE,SAAS,WAAW,YAAY,uBAAuB;AAE/D,SAAO,MAAM,GAAG,OAAA,aAAoB;AAAA,IAClC,QAAQ;AAAA,IACR,SAAS;AAAA,MACP,gBAAgB;AAAA,MAChB,cAAc;AAAA,IAAA;AAAA,IAEhB,MAAM,KAAK,UAAU;AAAA,MACnB;AAAA,MACA,SAAS;AAAA,QAAE,SAAS;AAAA,QAAW,GAAG;AAAA,MAAA;AAAA,IAAQ,CAC5C;AAAA,EAAA,CACF;AACF;AAEA,eAAsB,iBACpB,SACA,OACA,MAAqB;AAErB,MAAI,OAAO,UAAU,UAAU;AAC7B,WAAO,KAAK,SAAS;AAAA,MAAE,MAAM;AAAA,MAAO;AAAA,IAAA,CAAK;AAAA,EAC3C;AACA,SAAO,KAAK,SAAS,KAAA;AACvB;AAEA,eAAsB,oBACpB,SACA,MAAoB;AAEpB,SAAO,KAAK,SAAS;AAAA,IAAE;AAAA,EAAA,GAAQ,UAAA;AACjC;;;"}
1
+ {"version":3,"file":"server.cjs","sources":["../src/server/umami-server.ts","../src/server/umami-plugin.ts"],"sourcesContent":["import type { UmamiEventData, UmamiServerOptions, UmamiServerPayload } from '../types';\r\n\r\nconst UMAMI_VERSION = '2.0.0';\r\nconst DEFAULT_USER_AGENT = `Mozilla/5.0 (compatible; qwik-umami/${UMAMI_VERSION})`;\r\n\r\nasync function send(\r\n options: UmamiServerOptions,\r\n payload: UmamiServerPayload,\r\n type: 'event' | 'identify' = 'event',\r\n): Promise<Response> {\r\n const { hostUrl, websiteId, userAgent = DEFAULT_USER_AGENT } = options;\r\n\r\n return fetch(`${hostUrl}/api/send`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'User-Agent': userAgent,\r\n },\r\n body: JSON.stringify({\r\n type,\r\n payload: { website: websiteId, ...payload },\r\n }),\r\n });\r\n}\r\n\r\nexport async function serverUmamiTrack(\r\n options: UmamiServerOptions,\r\n event: string | UmamiServerPayload,\r\n data?: UmamiEventData,\r\n): Promise<Response> {\r\n if (typeof event === 'string') {\r\n return send(options, { name: event, data });\r\n }\r\n return send(options, event);\r\n}\r\n\r\nexport async function serverUmamiIdentify(\r\n options: UmamiServerOptions,\r\n data: UmamiEventData,\r\n): Promise<Response> {\r\n return send(options, { data }, 'identify');\r\n}\r\n","import type { RequestHandler } from '@builder.io/qwik-city';\nimport type { UmamiPluginOptions } from '../types';\nimport { serverUmamiTrack } from './umami-server';\n\nconst IGNORED_EXTENSIONS =\n /\\.(js|css|ico|png|jpg|jpeg|gif|svg|webp|woff2?|ttf|eot|map|json)$/;\n\nexport function createUmamiPlugin(options: UmamiPluginOptions): RequestHandler {\n return async ({ url, headers, method }) => {\n if (method !== 'GET') return;\n if (IGNORED_EXTENSIONS.test(url.pathname)) return;\n\n if (options.filter && !options.filter(url, headers)) return;\n\n const language = headers.get('accept-language')?.split(',')[0];\n const referrer = headers.get('referer') || undefined;\n const userAgent = headers.get('user-agent') || undefined;\n\n serverUmamiTrack(\n {\n websiteId: options.websiteId,\n hostUrl: options.hostUrl,\n userAgent: userAgent || options.userAgent,\n },\n {\n url: url.pathname + url.search,\n referrer,\n language: language || undefined,\n },\n ).catch(() => {});\n };\n}\n"],"names":[],"mappings":";;AAEA,MAAM,gBAAgB;AACtB,MAAM,qBAAqB,uCAAuC,aAAA;AAElE,eAAe,KACb,SACA,SACA,OAA6B,SAAO;AAEpC,QAAM,EAAE,SAAS,WAAW,YAAY,uBAAuB;AAE/D,SAAO,MAAM,GAAG,OAAA,aAAoB;AAAA,IAClC,QAAQ;AAAA,IACR,SAAS;AAAA,MACP,gBAAgB;AAAA,MAChB,cAAc;AAAA,IAAA;AAAA,IAEhB,MAAM,KAAK,UAAU;AAAA,MACnB;AAAA,MACA,SAAS;AAAA,QAAE,SAAS;AAAA,QAAW,GAAG;AAAA,MAAA;AAAA,IAAQ,CAC5C;AAAA,EAAA,CACF;AACF;AAEA,eAAsB,iBACpB,SACA,OACA,MAAqB;AAErB,MAAI,OAAO,UAAU,UAAU;AAC7B,WAAO,KAAK,SAAS;AAAA,MAAE,MAAM;AAAA,MAAO;AAAA,IAAA,CAAK;AAAA,EAC3C;AACA,SAAO,KAAK,SAAS,KAAA;AACvB;AAEA,eAAsB,oBACpB,SACA,MAAoB;AAEpB,SAAO,KAAK,SAAS;AAAA,IAAE;AAAA,EAAA,GAAQ,UAAA;AACjC;ACrCA,MAAM,qBACJ;AAEK,SAAS,kBAAkB,SAA2B;AAC3D,SAAO,OAAO,EAAE,KAAK,SAAS,aAAQ;AACpC,QAAI,WAAW,MAAO;AACtB,QAAI,mBAAmB,KAAK,IAAI,QAAQ,EAAG;AAE3C,QAAI,QAAQ,UAAU,CAAC,QAAQ,OAAO,KAAK,OAAA,EAAU;AAErD,UAAM,WAAW,QAAQ,IAAI,oBAAoB,MAAM,GAAA,EAAK,CAAA;AAC5D,UAAM,WAAW,QAAQ,IAAI,SAAA,KAAc;AAC3C,UAAM,YAAY,QAAQ,IAAI,YAAA,KAAiB;AAE/C,qBACE;AAAA,MACE,WAAW,QAAQ;AAAA,MACnB,SAAS,QAAQ;AAAA,MACjB,WAAW,aAAa,QAAQ;AAAA,IAAA,GAElC;AAAA,MACE,KAAK,IAAI,WAAW,IAAI;AAAA,MACxB;AAAA,MACA,UAAU,YAAY;AAAA,IAAA,CACxB,EACA,MAAM,MAAA;AAAA,IAAO,CAAA;AAAA,EACjB;AACF;;;;"}
package/lib/server.mjs CHANGED
@@ -31,7 +31,29 @@ async function serverUmamiIdentify(options, data) {
31
31
  data
32
32
  }, "identify");
33
33
  }
34
+ const IGNORED_EXTENSIONS = /\.(js|css|ico|png|jpg|jpeg|gif|svg|webp|woff2?|ttf|eot|map|json)$/;
35
+ function createUmamiPlugin(options) {
36
+ return async ({ url, headers, method }) => {
37
+ if (method !== "GET") return;
38
+ if (IGNORED_EXTENSIONS.test(url.pathname)) return;
39
+ if (options.filter && !options.filter(url, headers)) return;
40
+ const language = headers.get("accept-language")?.split(",")[0];
41
+ const referrer = headers.get("referer") || void 0;
42
+ const userAgent = headers.get("user-agent") || void 0;
43
+ serverUmamiTrack({
44
+ websiteId: options.websiteId,
45
+ hostUrl: options.hostUrl,
46
+ userAgent: userAgent || options.userAgent
47
+ }, {
48
+ url: url.pathname + url.search,
49
+ referrer,
50
+ language: language || void 0
51
+ }).catch(() => {
52
+ });
53
+ };
54
+ }
34
55
  export {
56
+ createUmamiPlugin,
35
57
  serverUmamiIdentify,
36
58
  serverUmamiTrack
37
59
  };
@@ -1 +1 @@
1
- {"version":3,"file":"server.mjs","sources":["../src/server/umami-server.ts"],"sourcesContent":["import type { UmamiEventData, UmamiServerOptions, UmamiServerPayload } from '../types';\r\n\r\nconst UMAMI_VERSION = '2.0.0';\r\nconst DEFAULT_USER_AGENT = `Mozilla/5.0 (compatible; qwik-umami/${UMAMI_VERSION})`;\r\n\r\nasync function send(\r\n options: UmamiServerOptions,\r\n payload: UmamiServerPayload,\r\n type: 'event' | 'identify' = 'event',\r\n): Promise<Response> {\r\n const { hostUrl, websiteId, userAgent = DEFAULT_USER_AGENT } = options;\r\n\r\n return fetch(`${hostUrl}/api/send`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'User-Agent': userAgent,\r\n },\r\n body: JSON.stringify({\r\n type,\r\n payload: { website: websiteId, ...payload },\r\n }),\r\n });\r\n}\r\n\r\nexport async function serverUmamiTrack(\r\n options: UmamiServerOptions,\r\n event: string | UmamiServerPayload,\r\n data?: UmamiEventData,\r\n): Promise<Response> {\r\n if (typeof event === 'string') {\r\n return send(options, { name: event, data });\r\n }\r\n return send(options, event);\r\n}\r\n\r\nexport async function serverUmamiIdentify(\r\n options: UmamiServerOptions,\r\n data: UmamiEventData,\r\n): Promise<Response> {\r\n return send(options, { data }, 'identify');\r\n}\r\n"],"names":[],"mappings":"AAEA,MAAM,gBAAgB;AACtB,MAAM,qBAAqB,uCAAuC,aAAA;AAElE,eAAe,KACb,SACA,SACA,OAA6B,SAAO;AAEpC,QAAM,EAAE,SAAS,WAAW,YAAY,uBAAuB;AAE/D,SAAO,MAAM,GAAG,OAAA,aAAoB;AAAA,IAClC,QAAQ;AAAA,IACR,SAAS;AAAA,MACP,gBAAgB;AAAA,MAChB,cAAc;AAAA,IAAA;AAAA,IAEhB,MAAM,KAAK,UAAU;AAAA,MACnB;AAAA,MACA,SAAS;AAAA,QAAE,SAAS;AAAA,QAAW,GAAG;AAAA,MAAA;AAAA,IAAQ,CAC5C;AAAA,EAAA,CACF;AACF;AAEA,eAAsB,iBACpB,SACA,OACA,MAAqB;AAErB,MAAI,OAAO,UAAU,UAAU;AAC7B,WAAO,KAAK,SAAS;AAAA,MAAE,MAAM;AAAA,MAAO;AAAA,IAAA,CAAK;AAAA,EAC3C;AACA,SAAO,KAAK,SAAS,KAAA;AACvB;AAEA,eAAsB,oBACpB,SACA,MAAoB;AAEpB,SAAO,KAAK,SAAS;AAAA,IAAE;AAAA,EAAA,GAAQ,UAAA;AACjC;"}
1
+ {"version":3,"file":"server.mjs","sources":["../src/server/umami-server.ts","../src/server/umami-plugin.ts"],"sourcesContent":["import type { UmamiEventData, UmamiServerOptions, UmamiServerPayload } from '../types';\r\n\r\nconst UMAMI_VERSION = '2.0.0';\r\nconst DEFAULT_USER_AGENT = `Mozilla/5.0 (compatible; qwik-umami/${UMAMI_VERSION})`;\r\n\r\nasync function send(\r\n options: UmamiServerOptions,\r\n payload: UmamiServerPayload,\r\n type: 'event' | 'identify' = 'event',\r\n): Promise<Response> {\r\n const { hostUrl, websiteId, userAgent = DEFAULT_USER_AGENT } = options;\r\n\r\n return fetch(`${hostUrl}/api/send`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'User-Agent': userAgent,\r\n },\r\n body: JSON.stringify({\r\n type,\r\n payload: { website: websiteId, ...payload },\r\n }),\r\n });\r\n}\r\n\r\nexport async function serverUmamiTrack(\r\n options: UmamiServerOptions,\r\n event: string | UmamiServerPayload,\r\n data?: UmamiEventData,\r\n): Promise<Response> {\r\n if (typeof event === 'string') {\r\n return send(options, { name: event, data });\r\n }\r\n return send(options, event);\r\n}\r\n\r\nexport async function serverUmamiIdentify(\r\n options: UmamiServerOptions,\r\n data: UmamiEventData,\r\n): Promise<Response> {\r\n return send(options, { data }, 'identify');\r\n}\r\n","import type { RequestHandler } from '@builder.io/qwik-city';\nimport type { UmamiPluginOptions } from '../types';\nimport { serverUmamiTrack } from './umami-server';\n\nconst IGNORED_EXTENSIONS =\n /\\.(js|css|ico|png|jpg|jpeg|gif|svg|webp|woff2?|ttf|eot|map|json)$/;\n\nexport function createUmamiPlugin(options: UmamiPluginOptions): RequestHandler {\n return async ({ url, headers, method }) => {\n if (method !== 'GET') return;\n if (IGNORED_EXTENSIONS.test(url.pathname)) return;\n\n if (options.filter && !options.filter(url, headers)) return;\n\n const language = headers.get('accept-language')?.split(',')[0];\n const referrer = headers.get('referer') || undefined;\n const userAgent = headers.get('user-agent') || undefined;\n\n serverUmamiTrack(\n {\n websiteId: options.websiteId,\n hostUrl: options.hostUrl,\n userAgent: userAgent || options.userAgent,\n },\n {\n url: url.pathname + url.search,\n referrer,\n language: language || undefined,\n },\n ).catch(() => {});\n };\n}\n"],"names":[],"mappings":"AAEA,MAAM,gBAAgB;AACtB,MAAM,qBAAqB,uCAAuC,aAAA;AAElE,eAAe,KACb,SACA,SACA,OAA6B,SAAO;AAEpC,QAAM,EAAE,SAAS,WAAW,YAAY,uBAAuB;AAE/D,SAAO,MAAM,GAAG,OAAA,aAAoB;AAAA,IAClC,QAAQ;AAAA,IACR,SAAS;AAAA,MACP,gBAAgB;AAAA,MAChB,cAAc;AAAA,IAAA;AAAA,IAEhB,MAAM,KAAK,UAAU;AAAA,MACnB;AAAA,MACA,SAAS;AAAA,QAAE,SAAS;AAAA,QAAW,GAAG;AAAA,MAAA;AAAA,IAAQ,CAC5C;AAAA,EAAA,CACF;AACF;AAEA,eAAsB,iBACpB,SACA,OACA,MAAqB;AAErB,MAAI,OAAO,UAAU,UAAU;AAC7B,WAAO,KAAK,SAAS;AAAA,MAAE,MAAM;AAAA,MAAO;AAAA,IAAA,CAAK;AAAA,EAC3C;AACA,SAAO,KAAK,SAAS,KAAA;AACvB;AAEA,eAAsB,oBACpB,SACA,MAAoB;AAEpB,SAAO,KAAK,SAAS;AAAA,IAAE;AAAA,EAAA,GAAQ,UAAA;AACjC;ACrCA,MAAM,qBACJ;AAEK,SAAS,kBAAkB,SAA2B;AAC3D,SAAO,OAAO,EAAE,KAAK,SAAS,aAAQ;AACpC,QAAI,WAAW,MAAO;AACtB,QAAI,mBAAmB,KAAK,IAAI,QAAQ,EAAG;AAE3C,QAAI,QAAQ,UAAU,CAAC,QAAQ,OAAO,KAAK,OAAA,EAAU;AAErD,UAAM,WAAW,QAAQ,IAAI,oBAAoB,MAAM,GAAA,EAAK,CAAA;AAC5D,UAAM,WAAW,QAAQ,IAAI,SAAA,KAAc;AAC3C,UAAM,YAAY,QAAQ,IAAI,YAAA,KAAiB;AAE/C,qBACE;AAAA,MACE,WAAW,QAAQ;AAAA,MACnB,SAAS,QAAQ;AAAA,MACjB,WAAW,aAAa,QAAQ;AAAA,IAAA,GAElC;AAAA,MACE,KAAK,IAAI,WAAW,IAAI;AAAA,MACxB;AAAA,MACA,UAAU,YAAY;AAAA,IAAA,CACxB,EACA,MAAM,MAAA;AAAA,IAAO,CAAA;AAAA,EACjB;AACF;"}
@@ -1,2 +1,3 @@
1
1
  export { serverUmamiTrack, serverUmamiIdentify } from './umami-server';
2
- export type { UmamiServerOptions, UmamiServerPayload, UmamiEventData } from '../types';
2
+ export { createUmamiPlugin } from './umami-plugin';
3
+ export type { UmamiServerOptions, UmamiServerPayload, UmamiEventData, UmamiPluginOptions, } from '../types';
@@ -0,0 +1,3 @@
1
+ import type { RequestHandler } from '@builder.io/qwik-city';
2
+ import type { UmamiPluginOptions } from '../types';
3
+ export declare function createUmamiPlugin(options: UmamiPluginOptions): RequestHandler;
@@ -8,6 +8,7 @@ export interface UmamiConfig {
8
8
  excludeSearch?: boolean;
9
9
  excludeHash?: boolean;
10
10
  doNotTrack?: boolean;
11
+ strategy?: 'eager' | 'idle';
11
12
  }
12
13
  export type UmamiEventData = Record<string, string | number | boolean>;
13
14
  export type UmamiPayload = {
@@ -35,6 +36,12 @@ export interface UmamiServerPayload {
35
36
  name?: string;
36
37
  data?: UmamiEventData;
37
38
  }
39
+ export interface UmamiPluginOptions {
40
+ websiteId: string;
41
+ hostUrl: string;
42
+ userAgent?: string;
43
+ filter?: (url: URL, headers: Headers) => boolean;
44
+ }
38
45
  export interface UmamiTracker {
39
46
  track(): void;
40
47
  track(payload: UmamiPayload): void;
package/llms.txt CHANGED
@@ -6,6 +6,7 @@
6
6
 
7
7
  - npm: `qwik-umami`
8
8
  - Peer dependency: `@builder.io/qwik >= 1.0.0`
9
+ - Optional peer dependency: `@builder.io/qwik-city >= 1.0.0` (only needed for `createUmamiPlugin`)
9
10
  - No runtime dependencies
10
11
  - Two entry points: `qwik-umami` (browser) and `qwik-umami/server` (server-side)
11
12
 
@@ -17,13 +18,13 @@ import { UmamiScript, umamiTrack, umamiIdentify } from 'qwik-umami';
17
18
  import type { UmamiConfig, UmamiEventData, UmamiPayload } from 'qwik-umami';
18
19
 
19
20
  // Server — import from 'qwik-umami/server'
20
- import { serverUmamiTrack, serverUmamiIdentify } from 'qwik-umami/server';
21
- import type { UmamiServerOptions, UmamiServerPayload } from 'qwik-umami/server';
21
+ import { serverUmamiTrack, serverUmamiIdentify, createUmamiPlugin } from 'qwik-umami/server';
22
+ import type { UmamiServerOptions, UmamiServerPayload, UmamiPluginOptions } from 'qwik-umami/server';
22
23
  ```
23
24
 
24
25
  ## UmamiScript component
25
26
 
26
- Qwik component. Place in `<head>` inside `root.tsx`. Loads the Umami tracker script client-side only using `useVisibleTask$`. Renders `null`.
27
+ Qwik component. Place in `<head>` inside `root.tsx`. Loads the Umami tracker script client-side only using `useVisibleTask$` (one-shot client init). Renders `null`.
27
28
 
28
29
  ```tsx
29
30
  // Minimum setup — automatic page view tracking
@@ -49,11 +50,14 @@ Qwik component. Place in `<head>` inside `root.tsx`. Loads the Umami tracker scr
49
50
  | excludeSearch | boolean | — | no |
50
51
  | excludeHash | boolean | — | no |
51
52
  | doNotTrack | boolean | — | no |
53
+ | strategy | 'eager' \| 'idle' | 'idle' | no |
52
54
 
53
55
  Implementation details:
54
56
  - Deduplication: checks `document.querySelector('script[data-website-id]')` before loading
55
- - Availability check: sends HEAD request with 3s timeout before injecting script
56
- - Fails silently if service is unreachable or blocked by ad blocker
57
+ - Uses `requestIdleCallback` by default (`strategy: 'idle'`) to avoid blocking the main thread
58
+ - Set `strategy: 'eager'` to load immediately on component mount
59
+ - Cleanup removes the script element on component unmount
60
+ - Fails silently if script fails to load (onerror handler)
57
61
 
58
62
  ## umamiTrack()
59
63
 
@@ -115,6 +119,44 @@ function serverUmamiTrack(options: UmamiServerOptions, payload: UmamiServerPaylo
115
119
 
116
120
  HTTP call: `POST ${hostUrl}/api/send` with body `{ type: 'event', payload: { website, ...payload } }`
117
121
 
122
+ ## createUmamiPlugin()
123
+
124
+ Factory that returns a Qwik City `RequestHandler` for server-side page view tracking. Use inside `plugin@umami.ts`. Tracks every GET request as a page view via the Umami API, without requiring the client-side script. Fire-and-forget (does NOT block the response).
125
+
126
+ ```ts
127
+ // src/routes/plugin@umami.ts
128
+ import { createUmamiPlugin } from 'qwik-umami/server';
129
+
130
+ export const onRequest = createUmamiPlugin({
131
+ websiteId: import.meta.env.PUBLIC_UMAMI_ID,
132
+ hostUrl: import.meta.env.UMAMI_HOST_URL,
133
+ });
134
+ ```
135
+
136
+ ### UmamiPluginOptions
137
+
138
+ | Prop | Type | Default | Required |
139
+ | --- | --- | --- | --- |
140
+ | websiteId | string | — | yes |
141
+ | hostUrl | string | — | yes |
142
+ | userAgent | string | request user-agent | no |
143
+ | filter | (url: URL, headers: Headers) => boolean | — | no |
144
+
145
+ Implementation details:
146
+ - Only tracks GET requests (skips POST, PUT, etc.)
147
+ - Ignores static asset requests (.js, .css, .png, .ico, etc.)
148
+ - Custom `filter` function to exclude specific routes (return false to skip)
149
+ - Extracts referrer, language, and user-agent from request headers
150
+ - Fire-and-forget: errors are silently caught, response is never blocked
151
+
152
+ ### When to use which
153
+
154
+ | Approach | Best for |
155
+ | --- | --- |
156
+ | `<UmamiScript>` only | Most apps — full browser data (screen, timezone, JS events) |
157
+ | `createUmamiPlugin` only | Server-rendered sites, bots, ad-blocker resilience, no JS needed |
158
+ | Both together | Maximum coverage — set `autoTrack: false` on `<UmamiScript>` to avoid duplicate page views, use client for events only |
159
+
118
160
  ## serverUmamiIdentify()
119
161
 
120
162
  Server-side session identification. POSTs to `/api/send` with `type: 'identify'`.
@@ -143,6 +185,7 @@ interface UmamiConfig {
143
185
  excludeSearch?: boolean
144
186
  excludeHash?: boolean
145
187
  doNotTrack?: boolean
188
+ strategy?: 'eager' | 'idle'
146
189
  }
147
190
 
148
191
  type UmamiPayload = {
@@ -172,6 +215,13 @@ interface UmamiServerPayload {
172
215
  name?: string
173
216
  data?: UmamiEventData
174
217
  }
218
+
219
+ interface UmamiPluginOptions {
220
+ websiteId: string
221
+ hostUrl: string
222
+ userAgent?: string
223
+ filter?: (url: URL, headers: Headers) => boolean
224
+ }
175
225
  ```
176
226
 
177
227
  ## Source structure
@@ -186,7 +236,8 @@ src/
186
236
  │ └── track.ts # umamiTrack() and umamiIdentify()
187
237
  └── server/
188
238
  ├── index.ts # Server barrel export
189
- └── umami-server.ts # serverUmamiTrack() and serverUmamiIdentify()
239
+ ├── umami-server.ts # serverUmamiTrack() and serverUmamiIdentify()
240
+ └── umami-plugin.ts # createUmamiPlugin() factory
190
241
  ```
191
242
 
192
243
  ## Important notes for LLMs
@@ -195,5 +246,7 @@ src/
195
246
  - Do NOT prefix `umamiTrack` with `use` — it is not a Qwik hook. The `use` prefix is reserved for functions that call Qwik primitives like `useSignal`, `useStore`, etc.
196
247
  - `serverUmamiTrack` and `serverUmamiIdentify` must only be used in server contexts (routeLoader$, routeAction$, server$). They use `fetch` directly and have no browser guards.
197
248
  - `<UmamiScript>` renders `null`. It is a side-effect-only component.
249
+ - `createUmamiPlugin` returns a `RequestHandler` — it must be exported as `onRequest` from a `plugin@name.ts` file. It requires `@builder.io/qwik-city` as peer dependency.
250
+ - When using both `createUmamiPlugin` AND `<UmamiScript>`, set `autoTrack: false` on the component to avoid duplicate page views. The plugin handles page views server-side, the component handles custom events client-side.
198
251
  - Do NOT use `data-umami-event` HTML attributes. Use `umamiTrack()` instead for proper Qwik integration.
199
252
  - `UmamiEventData` values are limited by Umami: strings max 500 chars, numbers max 4 decimal places, max 50 properties per object.
package/package.json CHANGED
@@ -1,65 +1,72 @@
1
- {
2
- "name": "qwik-umami",
3
- "version": "1.0.3",
4
- "description": "SSR-safe Umami Analytics component and hook for Qwik — automatic page views + custom event tracking with zero config",
5
- "type": "module",
6
- "main": "./lib/index.qwik.mjs",
7
- "qwik": "./lib/index.qwik.mjs",
8
- "types": "./lib-types/index.d.ts",
9
- "exports": {
10
- ".": {
11
- "import": "./lib/index.qwik.mjs",
12
- "require": "./lib/index.qwik.cjs",
13
- "types": "./lib-types/index.d.ts"
14
- },
15
- "./server": {
16
- "import": "./lib/server.mjs",
17
- "require": "./lib/server.cjs",
18
- "types": "./lib-types/server/index.d.ts"
19
- }
20
- },
21
- "files": [
22
- "lib",
23
- "lib-types",
24
- "llms.txt"
25
- ],
26
- "scripts": {
27
- "build": "vite build --mode lib && tsc --emitDeclarationOnly",
28
- "dev": "vite",
29
- "type-check": "tsc --noEmit"
30
- },
31
- "keywords": [
32
- "qwik",
33
- "qwik-city",
34
- "analytics",
35
- "umami",
36
- "tracking",
37
- "privacy",
38
- "ssr",
39
- "page-views",
40
- "event-tracking",
41
- "self-hosted",
42
- "google-analytics-alternative"
43
- ],
44
- "author": {
45
- "name": "murcisluis",
46
- "url": "https://github.com/murcisluis"
47
- },
48
- "repository": {
49
- "type": "git",
50
- "url": "https://github.com/murcisluis/qwik-umami"
51
- },
52
- "homepage": "https://github.com/murcisluis/qwik-umami#readme",
53
- "bugs": {
54
- "url": "https://github.com/murcisluis/qwik-umami/issues"
55
- },
56
- "license": "MIT",
57
- "peerDependencies": {
58
- "@builder.io/qwik": ">=1.0.0"
59
- },
60
- "devDependencies": {
61
- "@builder.io/qwik": "^1.13.0",
62
- "typescript": "^5.4.0",
63
- "vite": "^5.0.0"
64
- }
65
- }
1
+ {
2
+ "name": "qwik-umami",
3
+ "version": "1.1.1",
4
+ "description": "SSR-safe Umami Analytics component and hook for Qwik — automatic page views + custom event tracking with zero config",
5
+ "type": "module",
6
+ "main": "./lib/index.qwik.mjs",
7
+ "qwik": "./lib/index.qwik.mjs",
8
+ "types": "./lib-types/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./lib/index.qwik.mjs",
12
+ "require": "./lib/index.qwik.cjs",
13
+ "types": "./lib-types/index.d.ts"
14
+ },
15
+ "./server": {
16
+ "import": "./lib/server.mjs",
17
+ "require": "./lib/server.cjs",
18
+ "types": "./lib-types/server/index.d.ts"
19
+ }
20
+ },
21
+ "files": [
22
+ "lib",
23
+ "lib-types",
24
+ "llms.txt"
25
+ ],
26
+ "scripts": {
27
+ "build": "vite build --mode lib && tsc --emitDeclarationOnly",
28
+ "dev": "vite",
29
+ "type-check": "tsc --noEmit"
30
+ },
31
+ "keywords": [
32
+ "qwik",
33
+ "qwik-city",
34
+ "analytics",
35
+ "umami",
36
+ "tracking",
37
+ "privacy",
38
+ "ssr",
39
+ "page-views",
40
+ "event-tracking",
41
+ "self-hosted",
42
+ "google-analytics-alternative"
43
+ ],
44
+ "author": {
45
+ "name": "murcisluis",
46
+ "url": "https://github.com/murcisluis"
47
+ },
48
+ "repository": {
49
+ "type": "git",
50
+ "url": "git+https://github.com/murcisluis/qwik-umami.git"
51
+ },
52
+ "homepage": "https://github.com/murcisluis/qwik-umami#readme",
53
+ "bugs": {
54
+ "url": "https://github.com/murcisluis/qwik-umami/issues"
55
+ },
56
+ "license": "MIT",
57
+ "peerDependencies": {
58
+ "@builder.io/qwik": ">=1.0.0",
59
+ "@builder.io/qwik-city": ">=1.0.0"
60
+ },
61
+ "peerDependenciesMeta": {
62
+ "@builder.io/qwik-city": {
63
+ "optional": true
64
+ }
65
+ },
66
+ "devDependencies": {
67
+ "@builder.io/qwik": "^1.19.0",
68
+ "@builder.io/qwik-city": "^1.19.0",
69
+ "typescript": "^5.4.0",
70
+ "vite": "^5.0.0"
71
+ }
72
+ }