@wxt-dev/analytics 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +89 -0
- package/dist/client.d.mts +6 -0
- package/dist/client.d.ts +6 -0
- package/dist/client.mjs +179 -0
- package/dist/index.d.mts +11 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.mjs +24 -0
- package/dist/providers/google-analytics-4.d.mts +9 -0
- package/dist/providers/google-analytics-4.d.ts +9 -0
- package/dist/providers/google-analytics-4.mjs +56 -0
- package/dist/providers/umami.d.mts +10 -0
- package/dist/providers/umami.d.ts +10 -0
- package/dist/providers/umami.mjs +44 -0
- package/dist/shared/analytics.c704a57b.d.mts +86 -0
- package/dist/shared/analytics.c704a57b.d.ts +86 -0
- package/package.json +57 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Aaron
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# WXT Analytics
|
|
2
|
+
|
|
3
|
+
Add analytics, like google analytics, to your WXT extension.
|
|
4
|
+
|
|
5
|
+
## Supported Analytics Providers
|
|
6
|
+
|
|
7
|
+
- Google Analytics (Measurement Protocol)
|
|
8
|
+
- Umami
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
Install the NPM package:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pnpm i @wxt-dev/analytics
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Then add the module to your `wxt.config.ts` file:
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
export default defineConfig({
|
|
22
|
+
modules: ['@wxt-dev/analytics'],
|
|
23
|
+
});
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Create an `app.config.ts` file and fill out the required config:
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
// <srcDir>/app.config.ts
|
|
30
|
+
export default defineAppConfig({
|
|
31
|
+
analytics: {
|
|
32
|
+
debug: true,
|
|
33
|
+
providers: [
|
|
34
|
+
// ...
|
|
35
|
+
],
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Then use the `analytics` import to report events:
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
import { analytics } from '@wxt-dev/analytics';
|
|
44
|
+
|
|
45
|
+
await analytics.track('some-event');
|
|
46
|
+
await analytics.page();
|
|
47
|
+
await analytics.identify('some-user-id');
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Providers
|
|
51
|
+
|
|
52
|
+
### Google Analytics (Measurement Protocol)
|
|
53
|
+
|
|
54
|
+
Follow [Google's documentation](https://developer.chrome.com/docs/extensions/how-to/integrate/google-analytics-4#setup-credentials) to obtain your credentials:
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
import { googleAnalytics4 } from '@wxt-dev/analytics/providers/google-analytics-4';
|
|
58
|
+
|
|
59
|
+
export default defineAppConfig({
|
|
60
|
+
analytics: {
|
|
61
|
+
providers: [
|
|
62
|
+
googleAnalytics4({
|
|
63
|
+
apiSecret: '...',
|
|
64
|
+
measurementId: '...',
|
|
65
|
+
}),
|
|
66
|
+
],
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
> [Why use the Measurement Protocol instead of GTag?](https://developer.chrome.com/docs/extensions/how-to/integrate/google-analytics-4#measurement-protocol)
|
|
72
|
+
|
|
73
|
+
### Umami
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
import { umami } from '@wxt-dev/analytics/providers/umami';
|
|
77
|
+
|
|
78
|
+
export default defineAppConfig({
|
|
79
|
+
analytics: {
|
|
80
|
+
providers: [
|
|
81
|
+
umami({
|
|
82
|
+
baseUrl: 'https://your-domain.com',
|
|
83
|
+
websiteId: '...',
|
|
84
|
+
hostname: '...',
|
|
85
|
+
}),
|
|
86
|
+
],
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
```
|
package/dist/client.d.ts
ADDED
package/dist/client.mjs
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { defineWxtPlugin } from 'wxt/sandbox';
|
|
2
|
+
import uaParser from 'ua-parser-js';
|
|
3
|
+
|
|
4
|
+
let analytics;
|
|
5
|
+
const ANALYTICS_PORT = "wxt-analytics";
|
|
6
|
+
const interactiveTags = /* @__PURE__ */ new Set(["A", "BUTTON", "INPUT", "SELECT", "TEXTAREA"]);
|
|
7
|
+
const interactiveRoles = /* @__PURE__ */ new Set([
|
|
8
|
+
"button",
|
|
9
|
+
"link",
|
|
10
|
+
"checkbox",
|
|
11
|
+
"menuitem",
|
|
12
|
+
"tab",
|
|
13
|
+
"radio"
|
|
14
|
+
]);
|
|
15
|
+
const client = defineWxtPlugin(() => {
|
|
16
|
+
const isBackground = globalThis.window == null;
|
|
17
|
+
analytics = isBackground ? createBackgroundAnalytics() : createAnalyticsForwarder();
|
|
18
|
+
});
|
|
19
|
+
function createAnalyticsForwarder() {
|
|
20
|
+
const port = browser.runtime.connect({ name: ANALYTICS_PORT });
|
|
21
|
+
const sessionId = Date.now();
|
|
22
|
+
const getMetadata = () => ({
|
|
23
|
+
sessionId,
|
|
24
|
+
timestamp: Date.now(),
|
|
25
|
+
language: navigator.language,
|
|
26
|
+
referrer: globalThis.document?.referrer || void 0,
|
|
27
|
+
screen: globalThis.window ? `${globalThis.window.screen.width}x${globalThis.window.screen.height}` : void 0,
|
|
28
|
+
url: location.href
|
|
29
|
+
});
|
|
30
|
+
const methodForwarder = (fn) => (...args) => port.postMessage({ fn, args: [...args, getMetadata()] });
|
|
31
|
+
const analytics2 = {
|
|
32
|
+
identify: methodForwarder("identify"),
|
|
33
|
+
page: methodForwarder("page"),
|
|
34
|
+
track: methodForwarder("track"),
|
|
35
|
+
setEnabled: methodForwarder("setEnabled"),
|
|
36
|
+
autoTrack: (root) => {
|
|
37
|
+
const onClick = (event) => {
|
|
38
|
+
const element = event.target;
|
|
39
|
+
if (!element || !interactiveTags.has(element.tagName) && !interactiveRoles.has(element.getAttribute("role")))
|
|
40
|
+
return;
|
|
41
|
+
void analytics2.track("click", {
|
|
42
|
+
tagName: element.tagName?.toLowerCase(),
|
|
43
|
+
id: element.id || void 0,
|
|
44
|
+
className: element.className || void 0,
|
|
45
|
+
textContent: element.textContent?.substring(0, 50) || void 0,
|
|
46
|
+
// Limit text content length
|
|
47
|
+
href: element.href
|
|
48
|
+
});
|
|
49
|
+
};
|
|
50
|
+
root.addEventListener("click", onClick, { capture: true, passive: true });
|
|
51
|
+
return () => {
|
|
52
|
+
root.removeEventListener("click", onClick);
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
return analytics2;
|
|
57
|
+
}
|
|
58
|
+
function createBackgroundAnalytics() {
|
|
59
|
+
const config = useAppConfig().analytics;
|
|
60
|
+
const userIdStorage = config?.userId ?? storage.defineItem("local:wxt-analytics:user-id");
|
|
61
|
+
const userPropertiesStorage = config?.userProperties ?? storage.defineItem(
|
|
62
|
+
"local:wxt-analytics:user-properties",
|
|
63
|
+
{ defaultValue: {} }
|
|
64
|
+
);
|
|
65
|
+
const enabled = config?.enabled ?? storage.defineItem("local:wxt-analytics:enabled", {
|
|
66
|
+
defaultValue: false
|
|
67
|
+
});
|
|
68
|
+
const platformInfo = browser.runtime.getPlatformInfo();
|
|
69
|
+
const userAgent = uaParser();
|
|
70
|
+
let userId = Promise.resolve(userIdStorage.getValue()).then(
|
|
71
|
+
(id) => id ?? globalThis.crypto.randomUUID()
|
|
72
|
+
);
|
|
73
|
+
let userProperties = userPropertiesStorage.getValue();
|
|
74
|
+
const manifest = browser.runtime.getManifest();
|
|
75
|
+
const getBaseEvent = async (meta = {
|
|
76
|
+
timestamp: Date.now(),
|
|
77
|
+
// Don't track sessions for the background, it can be running
|
|
78
|
+
// indefinitely, and will inflate session duration stats.
|
|
79
|
+
sessionId: void 0,
|
|
80
|
+
language: navigator.language,
|
|
81
|
+
referrer: void 0,
|
|
82
|
+
screen: void 0,
|
|
83
|
+
url: location.href
|
|
84
|
+
}) => {
|
|
85
|
+
const platform = await platformInfo;
|
|
86
|
+
return {
|
|
87
|
+
meta: {
|
|
88
|
+
sessionId: meta.sessionId,
|
|
89
|
+
timestamp: meta.timestamp,
|
|
90
|
+
screen: meta.screen,
|
|
91
|
+
referrer: meta.referrer,
|
|
92
|
+
language: meta.language
|
|
93
|
+
},
|
|
94
|
+
user: {
|
|
95
|
+
id: await userId,
|
|
96
|
+
properties: {
|
|
97
|
+
version: config?.version ?? manifest.version_name ?? manifest.version,
|
|
98
|
+
wxtMode: import.meta.env.MODE,
|
|
99
|
+
wxtBrowser: import.meta.env.BROWSER,
|
|
100
|
+
arch: platform.arch,
|
|
101
|
+
os: platform.os,
|
|
102
|
+
browser: userAgent.browser.name,
|
|
103
|
+
browserVersion: userAgent.browser.version,
|
|
104
|
+
...await userProperties
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
};
|
|
109
|
+
const analytics2 = {
|
|
110
|
+
identify: async (newUserId, newUserProperties = {}, forwardMeta) => {
|
|
111
|
+
userId = Promise.resolve(newUserId);
|
|
112
|
+
userProperties = Promise.resolve(newUserProperties);
|
|
113
|
+
await Promise.all([
|
|
114
|
+
userIdStorage.setValue?.(newUserId),
|
|
115
|
+
userPropertiesStorage.setValue?.(newUserProperties)
|
|
116
|
+
]);
|
|
117
|
+
const event = await getBaseEvent(forwardMeta);
|
|
118
|
+
if (config?.debug)
|
|
119
|
+
console.debug("[analytics] identify", event);
|
|
120
|
+
if (await enabled.getValue()) {
|
|
121
|
+
await Promise.allSettled(
|
|
122
|
+
providers.map((provider) => provider.identify(event))
|
|
123
|
+
);
|
|
124
|
+
} else if (config?.debug) {
|
|
125
|
+
console.debug("[analytics] Disabled, identify() not called");
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
page: async (url, forwardMeta) => {
|
|
129
|
+
const baseEvent = await getBaseEvent(forwardMeta);
|
|
130
|
+
const event = {
|
|
131
|
+
...baseEvent,
|
|
132
|
+
page: { url }
|
|
133
|
+
};
|
|
134
|
+
if (config?.debug)
|
|
135
|
+
console.debug("[analytics] page", event);
|
|
136
|
+
if (await enabled.getValue()) {
|
|
137
|
+
await Promise.allSettled(
|
|
138
|
+
providers.map((provider) => provider.page(event))
|
|
139
|
+
);
|
|
140
|
+
} else if (config?.debug) {
|
|
141
|
+
console.debug("[analytics] Disabled, page() not called");
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
track: async (eventName, eventProperties, forwardMeta) => {
|
|
145
|
+
const baseEvent = await getBaseEvent(forwardMeta);
|
|
146
|
+
const event = {
|
|
147
|
+
...baseEvent,
|
|
148
|
+
event: { name: eventName, properties: eventProperties }
|
|
149
|
+
};
|
|
150
|
+
if (config?.debug)
|
|
151
|
+
console.debug("[analytics] track", event);
|
|
152
|
+
if (await enabled.getValue()) {
|
|
153
|
+
await Promise.allSettled(
|
|
154
|
+
providers.map((provider) => provider.track(event))
|
|
155
|
+
);
|
|
156
|
+
} else if (config?.debug) {
|
|
157
|
+
console.debug("[analytics] Disabled, track() not called");
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
setEnabled: async (newEnabled) => {
|
|
161
|
+
await enabled.setValue?.(newEnabled);
|
|
162
|
+
},
|
|
163
|
+
autoTrack: () => {
|
|
164
|
+
return () => {
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
const providers = config?.providers?.map((provider) => provider(analytics2, config)) ?? [];
|
|
169
|
+
chrome.runtime.onConnect.addListener((port) => {
|
|
170
|
+
if (port.name === ANALYTICS_PORT) {
|
|
171
|
+
port.onMessage.addListener(({ fn, args }) => {
|
|
172
|
+
void analytics2[fn]?.(...args);
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
return analytics2;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export { analytics, client as default };
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import * as wxt from 'wxt';
|
|
2
|
+
import { A as AnalyticsConfig } from './shared/analytics.c704a57b.mjs';
|
|
3
|
+
|
|
4
|
+
declare module 'wxt/sandbox' {
|
|
5
|
+
interface WxtAppConfig {
|
|
6
|
+
analytics: AnalyticsConfig;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
declare const _default: wxt.WxtModule<wxt.WxtModuleOptions>;
|
|
10
|
+
|
|
11
|
+
export { _default as default };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import * as wxt from 'wxt';
|
|
2
|
+
import { A as AnalyticsConfig } from './shared/analytics.c704a57b.js';
|
|
3
|
+
|
|
4
|
+
declare module 'wxt/sandbox' {
|
|
5
|
+
interface WxtAppConfig {
|
|
6
|
+
analytics: AnalyticsConfig;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
declare const _default: wxt.WxtModule<wxt.WxtModuleOptions>;
|
|
10
|
+
|
|
11
|
+
export { _default as default };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import 'wxt';
|
|
2
|
+
import 'wxt/sandbox';
|
|
3
|
+
import { defineWxtModule, addWxtPlugin } from 'wxt/modules';
|
|
4
|
+
import { resolve } from 'node:path';
|
|
5
|
+
|
|
6
|
+
const pluginId = "@wxt-dev/analytics/client" ;
|
|
7
|
+
const index = defineWxtModule({
|
|
8
|
+
name: "analytics",
|
|
9
|
+
imports: [{ name: "analytics", from: pluginId }],
|
|
10
|
+
setup(wxt) {
|
|
11
|
+
addWxtPlugin(
|
|
12
|
+
wxt,
|
|
13
|
+
resolve(__dirname, "client.mjs" )
|
|
14
|
+
);
|
|
15
|
+
wxt.hooks.hook("build:manifestGenerated", (_, manifest) => {
|
|
16
|
+
manifest.permissions ?? (manifest.permissions = []);
|
|
17
|
+
if (!manifest.permissions.includes("storage")) {
|
|
18
|
+
manifest.permissions.push("storage");
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
export { index as default };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { b as AnalyticsProvider } from '../shared/analytics.c704a57b.mjs';
|
|
2
|
+
|
|
3
|
+
interface GoogleAnalyticsProviderOptions {
|
|
4
|
+
apiSecret: string;
|
|
5
|
+
measurementId: string;
|
|
6
|
+
}
|
|
7
|
+
declare const googleAnalytics4: (options: GoogleAnalyticsProviderOptions) => AnalyticsProvider;
|
|
8
|
+
|
|
9
|
+
export { type GoogleAnalyticsProviderOptions, googleAnalytics4 };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { b as AnalyticsProvider } from '../shared/analytics.c704a57b.js';
|
|
2
|
+
|
|
3
|
+
interface GoogleAnalyticsProviderOptions {
|
|
4
|
+
apiSecret: string;
|
|
5
|
+
measurementId: string;
|
|
6
|
+
}
|
|
7
|
+
declare const googleAnalytics4: (options: GoogleAnalyticsProviderOptions) => AnalyticsProvider;
|
|
8
|
+
|
|
9
|
+
export { type GoogleAnalyticsProviderOptions, googleAnalytics4 };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
const DEFAULT_ENGAGEMENT_TIME_IN_MSEC = 100;
|
|
2
|
+
const googleAnalytics4 = (options) => (_, config) => {
|
|
3
|
+
const send = async (data, eventName, eventProperties) => {
|
|
4
|
+
const url = new URL(
|
|
5
|
+
config?.debug ? "/debug/mp/collect" : "/mp/collect",
|
|
6
|
+
"https://www.google-analytics.com"
|
|
7
|
+
);
|
|
8
|
+
if (options.apiSecret)
|
|
9
|
+
url.searchParams.set("api_secret", options.apiSecret);
|
|
10
|
+
if (options.measurementId)
|
|
11
|
+
url.searchParams.set("measurement_id", options.measurementId);
|
|
12
|
+
const userProperties = {
|
|
13
|
+
language: data.meta.language,
|
|
14
|
+
screen: data.meta.screen,
|
|
15
|
+
...data.user.properties
|
|
16
|
+
};
|
|
17
|
+
const mappedUserProperties = Object.fromEntries(
|
|
18
|
+
Object.entries(userProperties).map(([name, value]) => [
|
|
19
|
+
name,
|
|
20
|
+
value == null ? void 0 : { value }
|
|
21
|
+
])
|
|
22
|
+
);
|
|
23
|
+
await fetch(url.href, {
|
|
24
|
+
method: "POST",
|
|
25
|
+
body: JSON.stringify({
|
|
26
|
+
client_id: data.user.id,
|
|
27
|
+
consent: {
|
|
28
|
+
ad_user_data: "DENIED",
|
|
29
|
+
ad_personalization: "DENIED"
|
|
30
|
+
},
|
|
31
|
+
user_properties: mappedUserProperties,
|
|
32
|
+
events: [
|
|
33
|
+
{
|
|
34
|
+
name: eventName,
|
|
35
|
+
params: {
|
|
36
|
+
session_id: data.meta.sessionId,
|
|
37
|
+
engagement_time_msec: DEFAULT_ENGAGEMENT_TIME_IN_MSEC,
|
|
38
|
+
...eventProperties
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
})
|
|
43
|
+
});
|
|
44
|
+
};
|
|
45
|
+
return {
|
|
46
|
+
identify: () => Promise.resolve(),
|
|
47
|
+
// No-op, user data uploaded in page/track
|
|
48
|
+
page: (event) => send(event, "page_view", {
|
|
49
|
+
page_title: event.page.title,
|
|
50
|
+
page_location: event.page.location
|
|
51
|
+
}),
|
|
52
|
+
track: (event) => send(event, event.event.name, event.event.properties)
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export { googleAnalytics4 };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { b as AnalyticsProvider } from '../shared/analytics.c704a57b.mjs';
|
|
2
|
+
|
|
3
|
+
interface UmamiProviderOptions {
|
|
4
|
+
baseUrl: string;
|
|
5
|
+
websiteId: string;
|
|
6
|
+
hostname: string;
|
|
7
|
+
}
|
|
8
|
+
declare const umami: (options: UmamiProviderOptions) => AnalyticsProvider;
|
|
9
|
+
|
|
10
|
+
export { type UmamiProviderOptions, umami };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { b as AnalyticsProvider } from '../shared/analytics.c704a57b.js';
|
|
2
|
+
|
|
3
|
+
interface UmamiProviderOptions {
|
|
4
|
+
baseUrl: string;
|
|
5
|
+
websiteId: string;
|
|
6
|
+
hostname: string;
|
|
7
|
+
}
|
|
8
|
+
declare const umami: (options: UmamiProviderOptions) => AnalyticsProvider;
|
|
9
|
+
|
|
10
|
+
export { type UmamiProviderOptions, umami };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
const umami = (options) => (analytics, config) => {
|
|
2
|
+
const send = (payload) => fetch(`${options.baseUrl}/api/send`, {
|
|
3
|
+
method: "POST",
|
|
4
|
+
headers: {
|
|
5
|
+
"Content-Type": "application/json"
|
|
6
|
+
},
|
|
7
|
+
body: JSON.stringify({ type: "event", payload })
|
|
8
|
+
});
|
|
9
|
+
return {
|
|
10
|
+
identify: () => Promise.resolve(),
|
|
11
|
+
// No-op, user data uploaded in page/track
|
|
12
|
+
page: async (event) => {
|
|
13
|
+
await send({
|
|
14
|
+
name: "page_view",
|
|
15
|
+
website: options.websiteId,
|
|
16
|
+
url: event.page.url,
|
|
17
|
+
hostname: options.hostname,
|
|
18
|
+
language: event.meta.language ?? "",
|
|
19
|
+
referrer: event.meta.referrer ?? "",
|
|
20
|
+
screen: event.meta.screen ?? "",
|
|
21
|
+
title: event.page.title ?? "<blank>",
|
|
22
|
+
data: event.user.properties
|
|
23
|
+
});
|
|
24
|
+
},
|
|
25
|
+
track: async (event) => {
|
|
26
|
+
await send({
|
|
27
|
+
name: event.event.name,
|
|
28
|
+
website: options.websiteId,
|
|
29
|
+
url: event.meta.url ?? "/",
|
|
30
|
+
title: "<blank>",
|
|
31
|
+
hostname: options.hostname,
|
|
32
|
+
language: event.meta.language ?? "",
|
|
33
|
+
referrer: event.meta.referrer ?? "",
|
|
34
|
+
screen: event.meta.screen ?? "",
|
|
35
|
+
data: {
|
|
36
|
+
...event.event.properties,
|
|
37
|
+
...event.user.properties
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export { umami };
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
interface Analytics {
|
|
2
|
+
/** Report a page change */
|
|
3
|
+
page: (url: string) => void;
|
|
4
|
+
/** Report a custom event */
|
|
5
|
+
track: (eventName: string, eventProperties: Record<string, string>) => void;
|
|
6
|
+
/** Save information about the user */
|
|
7
|
+
identify: (userId: string, userProperties?: Record<string, string>) => void;
|
|
8
|
+
/** Automatically setup and track user interactions, returning a function to remove any listeners that were setup. */
|
|
9
|
+
autoTrack: (root: Document | ShadowRoot | Element) => () => void;
|
|
10
|
+
/** Calls `config.enabled.setValue` */
|
|
11
|
+
setEnabled: (enabled: boolean) => void;
|
|
12
|
+
}
|
|
13
|
+
interface AnalyticsConfig {
|
|
14
|
+
/**
|
|
15
|
+
* Array of providers to send analytics to.
|
|
16
|
+
*/
|
|
17
|
+
providers: AnalyticsProvider[];
|
|
18
|
+
/**
|
|
19
|
+
* Enable debug logs and other provider-specific debugging features.
|
|
20
|
+
*/
|
|
21
|
+
debug?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Extension version, defaults to `browser.runtime.getManifest().version`.
|
|
24
|
+
*/
|
|
25
|
+
version?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Configure how the enabled flag is persisted
|
|
28
|
+
*/
|
|
29
|
+
enabled?: AnalyticsStorageItemConfig<boolean>;
|
|
30
|
+
/**
|
|
31
|
+
* Configure how the user Id is persisted
|
|
32
|
+
*/
|
|
33
|
+
userId?: AnalyticsStorageItemConfig<string>;
|
|
34
|
+
/**
|
|
35
|
+
* Configure how user properties are persisted
|
|
36
|
+
*/
|
|
37
|
+
userProperties?: AnalyticsStorageItemConfig<Record<string, string>>;
|
|
38
|
+
}
|
|
39
|
+
interface AnalyticsStorageItemConfig<T> {
|
|
40
|
+
getValue: () => T | Promise<T>;
|
|
41
|
+
setValue?: (newValue: T) => void | Promise<void>;
|
|
42
|
+
}
|
|
43
|
+
type AnalyticsProvider = (analytics: Analytics, config: AnalyticsConfig) => {
|
|
44
|
+
/** Upload a page view event */
|
|
45
|
+
page: (event: AnalyticsPageViewEvent) => Promise<void>;
|
|
46
|
+
/** Upload a custom event */
|
|
47
|
+
track: (event: AnalyticsTrackEvent) => Promise<void>;
|
|
48
|
+
/** Upload or save information about the user */
|
|
49
|
+
identify: (event: BaseAnalyticsEvent) => Promise<void>;
|
|
50
|
+
};
|
|
51
|
+
interface BaseAnalyticsEvent {
|
|
52
|
+
meta: {
|
|
53
|
+
/** Identifier of the session the event was fired from */
|
|
54
|
+
sessionId: number | undefined;
|
|
55
|
+
/** `Date.now()` of when the event was reported */
|
|
56
|
+
timestamp: number;
|
|
57
|
+
/** `"1920x1080"` */
|
|
58
|
+
screen?: string;
|
|
59
|
+
/** `document.referrer` */
|
|
60
|
+
referrer?: string;
|
|
61
|
+
/** `navigator.language` */
|
|
62
|
+
language?: string;
|
|
63
|
+
/** `location.href` */
|
|
64
|
+
url?: string;
|
|
65
|
+
};
|
|
66
|
+
user: {
|
|
67
|
+
id: string;
|
|
68
|
+
properties: Record<string, string | undefined>;
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
interface AnalyticsPageInfo {
|
|
72
|
+
url: string;
|
|
73
|
+
title?: string;
|
|
74
|
+
location?: string;
|
|
75
|
+
}
|
|
76
|
+
interface AnalyticsPageViewEvent extends BaseAnalyticsEvent {
|
|
77
|
+
page: AnalyticsPageInfo;
|
|
78
|
+
}
|
|
79
|
+
interface AnalyticsTrackEvent extends BaseAnalyticsEvent {
|
|
80
|
+
event: {
|
|
81
|
+
name: string;
|
|
82
|
+
properties?: Record<string, string>;
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export type { AnalyticsConfig as A, Analytics as a, AnalyticsProvider as b };
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
interface Analytics {
|
|
2
|
+
/** Report a page change */
|
|
3
|
+
page: (url: string) => void;
|
|
4
|
+
/** Report a custom event */
|
|
5
|
+
track: (eventName: string, eventProperties: Record<string, string>) => void;
|
|
6
|
+
/** Save information about the user */
|
|
7
|
+
identify: (userId: string, userProperties?: Record<string, string>) => void;
|
|
8
|
+
/** Automatically setup and track user interactions, returning a function to remove any listeners that were setup. */
|
|
9
|
+
autoTrack: (root: Document | ShadowRoot | Element) => () => void;
|
|
10
|
+
/** Calls `config.enabled.setValue` */
|
|
11
|
+
setEnabled: (enabled: boolean) => void;
|
|
12
|
+
}
|
|
13
|
+
interface AnalyticsConfig {
|
|
14
|
+
/**
|
|
15
|
+
* Array of providers to send analytics to.
|
|
16
|
+
*/
|
|
17
|
+
providers: AnalyticsProvider[];
|
|
18
|
+
/**
|
|
19
|
+
* Enable debug logs and other provider-specific debugging features.
|
|
20
|
+
*/
|
|
21
|
+
debug?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Extension version, defaults to `browser.runtime.getManifest().version`.
|
|
24
|
+
*/
|
|
25
|
+
version?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Configure how the enabled flag is persisted
|
|
28
|
+
*/
|
|
29
|
+
enabled?: AnalyticsStorageItemConfig<boolean>;
|
|
30
|
+
/**
|
|
31
|
+
* Configure how the user Id is persisted
|
|
32
|
+
*/
|
|
33
|
+
userId?: AnalyticsStorageItemConfig<string>;
|
|
34
|
+
/**
|
|
35
|
+
* Configure how user properties are persisted
|
|
36
|
+
*/
|
|
37
|
+
userProperties?: AnalyticsStorageItemConfig<Record<string, string>>;
|
|
38
|
+
}
|
|
39
|
+
interface AnalyticsStorageItemConfig<T> {
|
|
40
|
+
getValue: () => T | Promise<T>;
|
|
41
|
+
setValue?: (newValue: T) => void | Promise<void>;
|
|
42
|
+
}
|
|
43
|
+
type AnalyticsProvider = (analytics: Analytics, config: AnalyticsConfig) => {
|
|
44
|
+
/** Upload a page view event */
|
|
45
|
+
page: (event: AnalyticsPageViewEvent) => Promise<void>;
|
|
46
|
+
/** Upload a custom event */
|
|
47
|
+
track: (event: AnalyticsTrackEvent) => Promise<void>;
|
|
48
|
+
/** Upload or save information about the user */
|
|
49
|
+
identify: (event: BaseAnalyticsEvent) => Promise<void>;
|
|
50
|
+
};
|
|
51
|
+
interface BaseAnalyticsEvent {
|
|
52
|
+
meta: {
|
|
53
|
+
/** Identifier of the session the event was fired from */
|
|
54
|
+
sessionId: number | undefined;
|
|
55
|
+
/** `Date.now()` of when the event was reported */
|
|
56
|
+
timestamp: number;
|
|
57
|
+
/** `"1920x1080"` */
|
|
58
|
+
screen?: string;
|
|
59
|
+
/** `document.referrer` */
|
|
60
|
+
referrer?: string;
|
|
61
|
+
/** `navigator.language` */
|
|
62
|
+
language?: string;
|
|
63
|
+
/** `location.href` */
|
|
64
|
+
url?: string;
|
|
65
|
+
};
|
|
66
|
+
user: {
|
|
67
|
+
id: string;
|
|
68
|
+
properties: Record<string, string | undefined>;
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
interface AnalyticsPageInfo {
|
|
72
|
+
url: string;
|
|
73
|
+
title?: string;
|
|
74
|
+
location?: string;
|
|
75
|
+
}
|
|
76
|
+
interface AnalyticsPageViewEvent extends BaseAnalyticsEvent {
|
|
77
|
+
page: AnalyticsPageInfo;
|
|
78
|
+
}
|
|
79
|
+
interface AnalyticsTrackEvent extends BaseAnalyticsEvent {
|
|
80
|
+
event: {
|
|
81
|
+
name: string;
|
|
82
|
+
properties?: Record<string, string>;
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export type { AnalyticsConfig as A, Analytics as a, AnalyticsProvider as b };
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@wxt-dev/analytics",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Add analytics to your web extension",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/wxt-dev/wxt.git",
|
|
8
|
+
"directory": "packages/analytics"
|
|
9
|
+
},
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"type": "module",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/index.d.mts",
|
|
15
|
+
"default": "./dist/index.mjs"
|
|
16
|
+
},
|
|
17
|
+
"./client": {
|
|
18
|
+
"types": "./dist/client.d.mts",
|
|
19
|
+
"default": "./dist/client.mjs"
|
|
20
|
+
},
|
|
21
|
+
"./providers/google-analytics-4": {
|
|
22
|
+
"types": "./dist/providers/google-analytics-4.d.mts",
|
|
23
|
+
"default": "./dist/providers/google-analytics-4.mjs"
|
|
24
|
+
},
|
|
25
|
+
"./providers/umami": {
|
|
26
|
+
"types": "./dist/providers/umami.d.mts",
|
|
27
|
+
"default": "./dist/providers/umami.mjs"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"module": "./dist/index.mjs",
|
|
31
|
+
"types": "./dist/index.d.ts",
|
|
32
|
+
"files": [
|
|
33
|
+
"dist"
|
|
34
|
+
],
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"wxt": ">=0.18.12"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@aklinker1/check": "^1.3.1",
|
|
40
|
+
"@types/chrome": "^0.0.268",
|
|
41
|
+
"@types/ua-parser-js": "^0.7.39",
|
|
42
|
+
"prettier": "^3.3.2",
|
|
43
|
+
"publint": "^0.2.8",
|
|
44
|
+
"typescript": "^5.5.2",
|
|
45
|
+
"unbuild": "^2.0.0",
|
|
46
|
+
"wxt": "0.18.13"
|
|
47
|
+
},
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"ua-parser-js": "^1.0.38"
|
|
50
|
+
},
|
|
51
|
+
"scripts": {
|
|
52
|
+
"dev": "buildc --deps-only -- wxt",
|
|
53
|
+
"dev:build": "buildc --deps-only -- wxt build",
|
|
54
|
+
"check": "buildc --deps-only -- check",
|
|
55
|
+
"build": "buildc -- unbuild"
|
|
56
|
+
}
|
|
57
|
+
}
|