Skip to content
Open
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
bf148df
fix: add types instead of `any` and improve readability of analytics …
PatrykKuniczak Dec 24, 2025
f7223d5
clean(analysis): make code more readable and fix null check for mappe…
PatrykKuniczak Dec 24, 2025
2a0492e
clean(analysis): move new types for client.ts to types.ts
PatrykKuniczak Dec 24, 2025
e159734
clean(analysis): split comment of getBackgroundMeta a little better
PatrykKuniczak Dec 24, 2025
ea303d5
clean(auto-icons): add TODO suggestion and missing await for ensureDir
PatrykKuniczak Dec 24, 2025
ba6e877
fix(packages/browser): remove unnecessary argument of transformFile f…
PatrykKuniczak Dec 25, 2025
7bdaf83
fix(packages/browser): change tests type assertions from deprecated t…
PatrykKuniczak Dec 25, 2025
dc2a352
fix(packages/i18n): remove unnecessary staff from tests and add question
PatrykKuniczak Dec 25, 2025
6dd1874
fix(packages/i18n): improve readability of code
PatrykKuniczak Dec 25, 2025
bbd29ea
fix(packages/runner): remove unnecessary code
PatrykKuniczak Dec 25, 2025
64709e9
fix(packages/runner): make comparing to `null` more strict and fix JD…
PatrykKuniczak Dec 25, 2025
1d25b85
fix(packages/runner): simplify return of createStorage()
PatrykKuniczak Dec 25, 2025
dd4bc41
revert(packages/storage): back to check with `==` instead of `===`
PatrykKuniczak Dec 25, 2025
2459094
fix(packages/storage): create types to avoid `any`
PatrykKuniczak Dec 25, 2025
a03b9b4
fix(packages/runner): create types to avoid `any`
PatrykKuniczak Dec 25, 2025
553628e
fix(packages/runner): add clearingTimeout on createBidiConnection -> …
PatrykKuniczak Dec 25, 2025
604e895
fix(packages/runner): change `any` to more strict types and by this c…
PatrykKuniczak Dec 25, 2025
d417287
fix(packages/wxt): add missing `lang` prop for <html> for couple e2e …
PatrykKuniczak Dec 25, 2025
a994bbe
clean(packages/wxt): make code more readable and add 2 questions
PatrykKuniczak Dec 25, 2025
754d36d
fix(packages/wxt): add `example` props for InlineConfig for avoid ts-…
PatrykKuniczak Dec 25, 2025
4ecae1d
clean(packages/wxt): remove unnecessary serializeWxtDir
PatrykKuniczak Dec 25, 2025
db82216
fix(packages/wxt): change deprecated exists with pathExists
PatrykKuniczak Dec 25, 2025
941b547
fix(packages/wxt): change HtmlPublicPath from `type` to `const` for f…
PatrykKuniczak Dec 25, 2025
8bd9f8f
clean(packages/wxt): simplify ValidationError checking on cli-utils.ts
PatrykKuniczak Dec 26, 2025
5285c2e
fix(packages/wxt): add missing `lang` to `html` on devHtmlPrerender.t…
PatrykKuniczak Dec 26, 2025
41b7998
fix(packages/wxt): change `==` to `===` check, for real `null`, to fi…
PatrykKuniczak Dec 26, 2025
b1547be
fix(packages/wxt): change deprecated `exists` to `pathExists` of `fs-…
PatrykKuniczak Dec 26, 2025
2df5305
fix(packages/wxt): add missing await for removeEmptyDirs of builders/…
PatrykKuniczak Dec 26, 2025
db0df10
clean(packages/wxt): improve readability of plugins
PatrykKuniczak Dec 26, 2025
d7fc6c2
fix(packages/wxt): change deprecated `exists` to `pathExists` on npm.…
PatrykKuniczak Dec 26, 2025
c676a0c
clean(packages/wxt): improve readability of code for core/package-man…
PatrykKuniczak Dec 26, 2025
9fac3fc
fix(packages/wxt): add question for core/runners manual.ts
PatrykKuniczak Dec 26, 2025
780ff17
fix(packages/wxt): remove @ts-expect-error from manifest.test.ts and …
PatrykKuniczak Dec 26, 2025
a47b88e
fix(packages/wxt): add support for `null` type for `version` of fake …
PatrykKuniczak Dec 26, 2025
52f1cd8
clean(packages/wxt): make code more readable and make number and stri…
PatrykKuniczak Dec 26, 2025
1f161d6
clean(packages/wxt): make code more readable and make number and stri…
PatrykKuniczak Dec 26, 2025
a79bdb3
clean(packages/wxt-demo): make code more readable and make number and…
PatrykKuniczak Dec 27, 2025
832b286
clean(packages/storage): add question about remove deprecated
PatrykKuniczak Dec 27, 2025
715d876
clean(packages/i18n): make number and string const UPPER_CASE
PatrykKuniczak Dec 27, 2025
2ced9ec
fix(packages/wxt): import of a to A, after UPPER_CASE conversion
PatrykKuniczak Dec 27, 2025
9581ffe
fix(packages/wxt): add missing `null` to BaseAnalyticsEvent, `propert…
PatrykKuniczak Dec 27, 2025
8ee1128
fix(packages/wxt-demo): change deprecated function `presetUno` to `pr…
PatrykKuniczak Dec 27, 2025
7aad344
clean(packages/analytics): improve code readability
PatrykKuniczak Dec 28, 2025
87a3bae
clean(packages/analytics): improve code readability and simplify if s…
PatrykKuniczak Dec 28, 2025
b9fb718
clean(packages/i18n): improve code readability
PatrykKuniczak Dec 28, 2025
9960479
clean(packages/analytics): improve code readability
PatrykKuniczak Dec 28, 2025
c665a48
clean(packages/module-react): improve code readability
PatrykKuniczak Dec 28, 2025
7a13238
clean(packages/module-solid): improve code readability
PatrykKuniczak Dec 28, 2025
69de884
clean(packages/runner): improve code readability, make strings UPPER_…
PatrykKuniczak Dec 28, 2025
5536049
clean(packages/storage): improve code readability, remove unnecessary…
PatrykKuniczak Dec 28, 2025
ee53d9b
clean(packages/unocss): improve code readability
PatrykKuniczak Dec 28, 2025
fcf2f36
fix(packages/wxt): change @deprecated to @internal for resetBundleInc…
PatrykKuniczak Dec 28, 2025
5b36973
fix(packages/wxt): remove unnecessary `// @ts-ignore` from auto-impor…
PatrykKuniczak Dec 28, 2025
7f6bb03
clean(packages/wxt/e2e): make code more readable and remove some unne…
PatrykKuniczak Dec 28, 2025
00f06ca
clean(packages/wxt/@types): make code more readable and fix const name
PatrykKuniczak Dec 29, 2025
1c94c5e
clean(packages/wxt/__tests__): make code more readable and add as con…
PatrykKuniczak Dec 29, 2025
b05af62
clean(packages/wxt/builtin-modules): make code more readable
PatrykKuniczak Dec 29, 2025
49a55e1
clean(packages/wxt/cli): make code more readable
PatrykKuniczak Dec 29, 2025
281241d
clean(packages/wxt/core): make code more readable, remove some unnece…
PatrykKuniczak Dec 29, 2025
6ecb908
clean(packages/wxt/utils): make code more readable and add questions
PatrykKuniczak Dec 29, 2025
ee94b1d
clean(packages/wxt/utils): make code more readable, add question and …
PatrykKuniczak Dec 29, 2025
97c4780
clean(packages/wxt/src): make code more readable, add question
PatrykKuniczak Dec 29, 2025
e84bda0
clean(packages/wxt): make code more readable
PatrykKuniczak Dec 29, 2025
ff88495
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