Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
a858604
fix: add types instead of `any` and improve readability of analytics …
PatrykKuniczak Dec 24, 2025
008d61a
clean(analysis): make code more readable and fix null check for mappe…
PatrykKuniczak Dec 24, 2025
25b7ed0
clean(analysis): move new types for client.ts to types.ts
PatrykKuniczak Dec 24, 2025
4603179
clean(analysis): split comment of getBackgroundMeta a little better
PatrykKuniczak Dec 24, 2025
01029eb
clean(auto-icons): add TODO suggestion and missing await for ensureDir
PatrykKuniczak Dec 24, 2025
740be65
fix(packages/browser): remove unnecessary argument of transformFile f…
PatrykKuniczak Dec 25, 2025
0d37284
fix(packages/browser): change tests type assertions from deprecated t…
PatrykKuniczak Dec 25, 2025
6577a3f
fix(packages/i18n): remove unnecessary staff from tests and add question
PatrykKuniczak Dec 25, 2025
0db62e5
fix(packages/i18n): improve readability of code
PatrykKuniczak Dec 25, 2025
61a91c4
fix(packages/runner): remove unnecessary code
PatrykKuniczak Dec 25, 2025
22dc6bc
fix(packages/runner): make comparing to `null` more strict and fix JD…
PatrykKuniczak Dec 25, 2025
6eabd61
fix(packages/runner): simplify return of createStorage()
PatrykKuniczak Dec 25, 2025
6e6145d
revert(packages/storage): back to check with `==` instead of `===`
PatrykKuniczak Dec 25, 2025
6b42f59
fix(packages/storage): create types to avoid `any`
PatrykKuniczak Dec 25, 2025
bdc7b4e
fix(packages/runner): create types to avoid `any`
PatrykKuniczak Dec 25, 2025
f1f2d25
fix(packages/runner): add clearingTimeout on createBidiConnection -> …
PatrykKuniczak Dec 25, 2025
f8ee7c7
fix(packages/runner): change `any` to more strict types and by this c…
PatrykKuniczak Dec 25, 2025
464f986
fix(packages/wxt): add missing `lang` prop for <html> for couple e2e …
PatrykKuniczak Dec 25, 2025
6da7097
clean(packages/wxt): make code more readable and add 2 questions
PatrykKuniczak Dec 25, 2025
6a9108e
fix(packages/wxt): add `example` props for InlineConfig for avoid ts-…
PatrykKuniczak Dec 25, 2025
b02d1c8
clean(packages/wxt): remove unnecessary serializeWxtDir
PatrykKuniczak Dec 25, 2025
f61703c
fix(packages/wxt): change deprecated exists with pathExists
PatrykKuniczak Dec 25, 2025
b263898
fix(packages/wxt): change HtmlPublicPath from `type` to `const` for f…
PatrykKuniczak Dec 25, 2025
c996095
clean(packages/wxt): simplify ValidationError checking on cli-utils.ts
PatrykKuniczak Dec 26, 2025
da5d0f1
fix(packages/wxt): add missing `lang` to `html` on devHtmlPrerender.t…
PatrykKuniczak Dec 26, 2025
77255a9
fix(packages/wxt): change `==` to `===` check, for real `null`, to fi…
PatrykKuniczak Dec 26, 2025
625f740
fix(packages/wxt): change deprecated `exists` to `pathExists` of `fs-…
PatrykKuniczak Dec 26, 2025
a658389
fix(packages/wxt): add missing await for removeEmptyDirs of builders/…
PatrykKuniczak Dec 26, 2025
3bf865d
clean(packages/wxt): improve readability of plugins
PatrykKuniczak Dec 26, 2025
ffb539a
fix(packages/wxt): change deprecated `exists` to `pathExists` on npm.…
PatrykKuniczak Dec 26, 2025
6622340
clean(packages/wxt): improve readability of code for core/package-man…
PatrykKuniczak Dec 26, 2025
511f441
fix(packages/wxt): add question for core/runners manual.ts
PatrykKuniczak Dec 26, 2025
5c5df58
fix(packages/wxt): remove @ts-expect-error from manifest.test.ts and …
PatrykKuniczak Dec 26, 2025
502d079
fix(packages/wxt): add support for `null` type for `version` of fake …
PatrykKuniczak Dec 26, 2025
b8d82d6
clean(packages/wxt): make code more readable and make number and stri…
PatrykKuniczak Dec 26, 2025
0006fa2
clean(packages/wxt): make code more readable and make number and stri…
PatrykKuniczak Dec 26, 2025
027d93e
clean(packages/wxt-demo): make code more readable and make number and…
PatrykKuniczak Dec 27, 2025
b5d9ba8
clean(packages/storage): add question about remove deprecated
PatrykKuniczak Dec 27, 2025
61973a6
clean(packages/i18n): make number and string const UPPER_CASE
PatrykKuniczak Dec 27, 2025
f5344b0
fix(packages/wxt): import of a to A, after UPPER_CASE conversion
PatrykKuniczak Dec 27, 2025
555ab40
fix(packages/wxt): add missing `null` to BaseAnalyticsEvent, `propert…
PatrykKuniczak Dec 27, 2025
ee8b76b
fix(packages/wxt-demo): change deprecated function `presetUno` to `pr…
PatrykKuniczak Dec 27, 2025
a886d13
clean(packages/analytics): improve code readability
PatrykKuniczak Dec 28, 2025
fe973ef
clean(packages/analytics): improve code readability and simplify if s…
PatrykKuniczak Dec 28, 2025
2c1e3d1
clean(packages/i18n): improve code readability
PatrykKuniczak Dec 28, 2025
7ad210f
clean(packages/analytics): improve code readability
PatrykKuniczak Dec 28, 2025
c1b4ef0
clean(packages/module-react): improve code readability
PatrykKuniczak Dec 28, 2025
0275113
clean(packages/module-solid): improve code readability
PatrykKuniczak Dec 28, 2025
f8bd188
clean(packages/runner): improve code readability, make strings UPPER_…
PatrykKuniczak Dec 28, 2025
a3a5312
clean(packages/storage): improve code readability, remove unnecessary…
PatrykKuniczak Dec 28, 2025
75c572f
clean(packages/unocss): improve code readability
PatrykKuniczak Dec 28, 2025
a54629e
fix(packages/wxt): change @deprecated to @internal for resetBundleInc…
PatrykKuniczak Dec 28, 2025
036c21a
fix(packages/wxt): remove unnecessary `// @ts-ignore` from auto-impor…
PatrykKuniczak Dec 28, 2025
cc8074d
clean(packages/wxt/e2e): make code more readable and remove some unne…
PatrykKuniczak Dec 28, 2025
e136e0b
clean(packages/wxt/@types): make code more readable and fix const name
PatrykKuniczak Dec 29, 2025
5e883ee
clean(packages/wxt/__tests__): make code more readable and add as con…
PatrykKuniczak Dec 29, 2025
4abf313
clean(packages/wxt/builtin-modules): make code more readable
PatrykKuniczak Dec 29, 2025
167b075
clean(packages/wxt/cli): make code more readable
PatrykKuniczak Dec 29, 2025
9a63ee4
clean(packages/wxt/core): make code more readable, remove some unnece…
PatrykKuniczak Dec 29, 2025
840a87b
clean(packages/wxt/utils): make code more readable and add questions
PatrykKuniczak Dec 29, 2025
1a13972
clean(packages/wxt/utils): make code more readable, add question and …
PatrykKuniczak Dec 29, 2025
0dbe275
clean(packages/wxt/src): make code more readable, add question
PatrykKuniczak Dec 29, 2025
cd4a306
clean(packages/wxt): make code more readable
PatrykKuniczak Dec 29, 2025
5c7c647
fix(packages/wxt): make type for __ENTRYPOINT__
PatrykKuniczak Dec 29, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/analytics/entrypoints/popup/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<input id="enabledCheckbox" type="checkbox" />
&emsp;Analytics enabled
</label>
<button id="button1">Button 1</button>
<button id="button-1">Button 1</button>
<button class="cool-button">Button 2</button>
<script type="module" src="./main.ts"></script>
</body>
Expand Down
108 changes: 62 additions & 46 deletions packages/analytics/modules/analytics/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,51 @@ import { UAParser } from 'ua-parser-js';
import type {
Analytics,
AnalyticsConfig,
AnalyticsEventMetadata,
AnalyticsPageViewEvent,
AnalyticsProvider,
AnalyticsStorageItem,
AnalyticsTrackEvent,
BaseAnalyticsEvent,
AnalyticsEventMetadata,
AnalyticsProvider,
TAnalyticsMessage,
TAnalyticsMethod,
TMethodForwarder,
} from './types';
import { browser } from '@wxt-dev/browser';

const ANALYTICS_PORT = '@wxt-dev/analytics';

const INTERACTIVE_TAGS = new Set([
'A',
'BUTTON',
'INPUT',
'SELECT',
'TEXTAREA',
]);

const INTERACTIVE_ROLES = new Set([
'button',
'link',
'checkbox',
'menuitem',
'tab',
'radio',
]);

export function createAnalytics(config?: AnalyticsConfig): Analytics {
if (!browser?.runtime?.id)
throw Error(
'Cannot use WXT analytics in contexts without access to the browser.runtime APIs',
);
if (config == null) {

if (config === null) {
console.warn(
"[@wxt-dev/analytics] Config not provided to createAnalytics. If you're using WXT, add the 'analytics' property to '<srcDir>/app.config.ts'.",
);
}

// TODO: This only works for standard WXT extensions, add a more generic
// background script detector that works with non-WXT projects.
// Background script detector that works with non-WXT projects.
if (location.pathname === '/background.js')
return createBackgroundAnalytics(config);

Expand Down Expand Up @@ -54,16 +75,18 @@ function createBackgroundAnalytics(
// Cached values
const platformInfo = browser.runtime.getPlatformInfo();
const userAgent = UAParser();

let userId = Promise.resolve(userIdStorage.getValue()).then(
(id) => id ?? globalThis.crypto.randomUUID(),
);
let userProperties = userPropertiesStorage.getValue();

const manifest = browser.runtime.getManifest();

const getBackgroundMeta = () => ({
timestamp: Date.now(),
// Don't track sessions for the background, it can be running
// indefinitely, and will inflate session duration stats.
// Don't track sessions for the background, it can be running indefinitely
// and will inflate session duration stats.
sessionId: undefined,
language: navigator.language,
referrer: undefined,
Expand All @@ -75,7 +98,8 @@ function createBackgroundAnalytics(
const getBaseEvent = async (
meta: AnalyticsEventMetadata,
): Promise<BaseAnalyticsEvent> => {
const platform = await platformInfo;
const { arch, os } = await platformInfo;

return {
meta,
user: {
Expand All @@ -84,8 +108,8 @@ function createBackgroundAnalytics(
version: config?.version ?? manifest.version_name ?? manifest.version,
wxtMode: import.meta.env.MODE,
wxtBrowser: import.meta.env.BROWSER,
arch: platform.arch,
os: platform.os,
arch,
os,
browser: userAgent.browser.name,
browserVersion: userAgent.browser.version,
...(await userProperties),
Expand All @@ -110,7 +134,9 @@ function createBackgroundAnalytics(
]);
// Notify providers
const event = await getBaseEvent(meta);

if (config?.debug) console.debug('[@wxt-dev/analytics] identify', event);

if (await enabled.getValue()) {
await Promise.allSettled(
providers.map((provider) => provider.identify(event)),
Expand All @@ -134,7 +160,9 @@ function createBackgroundAnalytics(
title: meta?.title ?? globalThis.document?.title,
},
};

if (config?.debug) console.debug('[@wxt-dev/analytics] page', event);

if (await enabled.getValue()) {
await Promise.allSettled(
providers.map((provider) => provider.page(event)),
Expand All @@ -155,7 +183,9 @@ function createBackgroundAnalytics(
...baseEvent,
event: { name: eventName, properties: eventProperties },
};

if (config?.debug) console.debug('[@wxt-dev/analytics] track', event);

if (await enabled.getValue()) {
await Promise.allSettled(
providers.map((provider) => provider.track(event)),
Expand All @@ -181,9 +211,8 @@ function createBackgroundAnalytics(
// Listen for messages from the rest of the extension
browser.runtime.onConnect.addListener((port) => {
if (port.name === ANALYTICS_PORT) {
port.onMessage.addListener(({ fn, args }) => {
// @ts-expect-error: Untyped fn key
void analytics[fn]?.(...args);
port.onMessage.addListener(({ fn, args }: TAnalyticsMessage) => {
void (analytics[fn] as TAnalyticsMethod)?.(...args);
});
}
});
Expand All @@ -197,21 +226,20 @@ function createBackgroundAnalytics(
function createFrontendAnalytics(): Analytics {
const port = browser.runtime.connect({ name: ANALYTICS_PORT });
const sessionId = Date.now();

const getFrontendMetadata = (): AnalyticsEventMetadata => ({
sessionId,
timestamp: Date.now(),
language: navigator.language,
referrer: globalThis.document?.referrer || undefined,
screen: globalThis.window
? `${globalThis.window.screen.width}x${globalThis.window.screen.height}`
: undefined,
referrer: globalThis.document?.referrer,
screen: `${globalThis.window.screen.width}x${globalThis.window.screen.height}`,
url: location.href,
title: document.title || undefined,
title: document.title,
});

const methodForwarder =
(fn: string) =>
(...args: any[]) => {
const methodForwarder: TMethodForwarder =
(fn) =>
(...args) => {
port.postMessage({ fn, args: [...args, getFrontendMetadata()] });
};

Expand All @@ -222,59 +250,47 @@ function createFrontendAnalytics(): Analytics {
setEnabled: methodForwarder('setEnabled'),
autoTrack: (root) => {
const onClick = (event: Event) => {
const element = event.target as any;
const element = event.target as HTMLElement | null;

if (
!element ||
(!INTERACTIVE_TAGS.has(element.tagName) &&
!INTERACTIVE_ROLES.has(element.getAttribute('role')))
)
!INTERACTIVE_ROLES.has(element.getAttribute('role') ?? ''))
) {
return;
}

void analytics.track('click', {
tagName: element.tagName?.toLowerCase(),
id: element.id || undefined,
className: element.className || undefined,
textContent: element.textContent?.substring(0, 50) || undefined, // Limit text content length
href: element.href,
id: element.id,
className: element.className,
textContent: element.textContent?.substring(0, 50), // Limit text content length
href: (element as HTMLAnchorElement).href,
});
};
root.addEventListener('click', onClick, { capture: true, passive: true });

return () => {
root.removeEventListener('click', onClick);
};
},
};

return analytics;
}

function defineStorageItem<T>(
key: string,
defaultValue?: NonNullable<T>,
defaultValue?: T,
): AnalyticsStorageItem<T> {
return {
getValue: async () =>
(await browser.storage.local.get<Record<string, any>>(key))[key] ??
defaultValue,
(((await browser.storage.local.get(key)) as Record<string, T>)[key] ??
defaultValue) as T,
setValue: (newValue) => browser.storage.local.set({ [key]: newValue }),
};
}

const INTERACTIVE_TAGS = new Set([
'A',
'BUTTON',
'INPUT',
'SELECT',
'TEXTAREA',
]);
const INTERACTIVE_ROLES = new Set([
'button',
'link',
'checkbox',
'menuitem',
'tab',
'radio',
]);

export function defineAnalyticsProvider<T = never>(
definition: (
/** The analytics object. */
Expand Down
9 changes: 5 additions & 4 deletions packages/analytics/modules/analytics/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export default defineWxtModule({
// Paths
const wxtAnalyticsFolder = resolve(wxt.config.wxtDir, 'analytics');
const wxtAnalyticsIndex = resolve(wxtAnalyticsFolder, 'index.ts');

const clientModuleId = process.env.NPM
? '@wxt-dev/analytics'
: resolve(wxt.config.modulesDir, 'analytics/client');
Expand All @@ -44,11 +45,10 @@ export default defineWxtModule({
? clientModuleId
: relative(wxtAnalyticsFolder, clientModuleId)
}';`,
`import { useAppConfig } from '#imports';`,
``,
`export const analytics = createAnalytics(useAppConfig().analytics);`,
``,
`import { useAppConfig } from '#imports';\n`,
`export const analytics = createAnalytics(useAppConfig().analytics);\n`,
].join('\n');

addAlias(wxt, '#analytics', wxtAnalyticsIndex);
wxt.hook('prepare:types', async (_, entries) => {
entries.push({
Expand All @@ -62,6 +62,7 @@ export default defineWxtModule({
const hasBackground = entrypoints.find(
(entry) => entry.type === 'background',
);

if (!hasBackground) {
entrypoints.push({
type: 'background',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@ export const googleAnalytics4 =
data: BaseAnalyticsEvent,
eventName: string,
eventProperties: Record<string, string | undefined> | undefined,
): Promise<void> => {
) => {
const url = new URL(
config?.debug ? '/debug/mp/collect' : '/mp/collect',
'https://www.google-analytics.com',
);

if (options.apiSecret)
url.searchParams.set('api_secret', options.apiSecret);

if (options.measurementId)
url.searchParams.set('measurement_id', options.measurementId);

Expand All @@ -30,10 +32,11 @@ export const googleAnalytics4 =
screen: data.meta.screen,
...data.user.properties,
};

const mappedUserProperties = Object.fromEntries(
Object.entries(userProperties).map(([name, value]) => [
name,
value == null ? undefined : { value },
value === null ? undefined : { value },
]),
);

Expand Down
3 changes: 2 additions & 1 deletion packages/analytics/modules/analytics/providers/umami.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export const umami = defineAnalyticsProvider<UmamiProviderOptions>(
if (config.debug) {
console.debug('[@wxt-dev/analytics] Sending event to Umami:', payload);
}

return fetch(`${options.apiUrl}/send`, {
method: 'POST',
headers: {
Expand Down Expand Up @@ -66,5 +67,5 @@ interface UmamiPayload {
url?: string;
website: string;
name: string;
data?: Record<string, string | undefined>;
data?: Record<string, string | undefined | null>;
}
17 changes: 16 additions & 1 deletion packages/analytics/modules/analytics/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export interface BaseAnalyticsEvent {
meta: AnalyticsEventMetadata;
user: {
id: string;
properties: Record<string, string | undefined>;
properties: Record<string, string | undefined | null>;
};
}

Expand Down Expand Up @@ -97,3 +97,18 @@ export interface AnalyticsTrackEvent extends BaseAnalyticsEvent {
properties?: Record<string, string>;
};
}

export type TAnalyticsMessage = {
[K in keyof Analytics]: {
fn: K;
args: Parameters<Analytics[K]>;
};
}[keyof Analytics];

export type TAnalyticsMethod =
| ((...args: Parameters<Analytics[keyof Analytics]>) => void)
| undefined;

export type TMethodForwarder = <K extends keyof Analytics>(
fn: K,
) => (...args: Parameters<Analytics[K]>) => void;
Loading
Loading