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 +73 -6
- package/lib/index.qwik.cjs +53 -52
- package/lib/index.qwik.cjs.map +1 -1
- package/lib/index.qwik.mjs +53 -52
- package/lib/index.qwik.mjs.map +1 -1
- package/lib/server.cjs +22 -0
- package/lib/server.cjs.map +1 -1
- package/lib/server.mjs +22 -0
- package/lib/server.mjs.map +1 -1
- package/lib-types/server/index.d.ts +2 -1
- package/lib-types/server/umami-plugin.d.ts +3 -0
- package/lib-types/types.d.ts +7 -0
- package/llms.txt +59 -6
- package/package.json +72 -65
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.
|
|
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,
|
|
193
|
-
UmamiServerPayload,
|
|
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$`
|
|
237
|
-
2.
|
|
238
|
-
3. If the
|
|
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
|
|
package/lib/index.qwik.cjs
CHANGED
|
@@ -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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
script.
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
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
|
});
|
package/lib/index.qwik.cjs.map
CHANGED
|
@@ -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';\
|
|
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;;;;"}
|
package/lib/index.qwik.mjs
CHANGED
|
@@ -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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
script.
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
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
|
});
|
package/lib/index.qwik.mjs.map
CHANGED
|
@@ -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';\
|
|
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
|
package/lib/server.cjs.map
CHANGED
|
@@ -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
|
};
|
package/lib/server.mjs.map
CHANGED
|
@@ -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
|
|
2
|
+
export { createUmamiPlugin } from './umami-plugin';
|
|
3
|
+
export type { UmamiServerOptions, UmamiServerPayload, UmamiEventData, UmamiPluginOptions, } from '../types';
|
package/lib-types/types.d.ts
CHANGED
|
@@ -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
|
|
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
|
-
-
|
|
56
|
-
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
"
|
|
63
|
-
|
|
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
|
+
}
|