From 52172790707c1c33c4fd047fe986e4fd89a20be9 Mon Sep 17 00:00:00 2001 From: Aaron Date: Tue, 2 Jul 2024 15:10:44 -0500 Subject: [PATCH] feat: Analytics module --- packages/analytics/README.md | 65 +++++++++ packages/analytics/app.config.ts | 21 +++ packages/analytics/build.config.ts | 33 +++++ packages/analytics/entrypoints/popup.html | 12 ++ .../analytics/modules/analytics/client.ts | 27 ++++ packages/analytics/modules/analytics/index.ts | 19 +++ .../analytics/providers/google-analytics.ts | 13 ++ .../modules/analytics/providers/umami.ts | 5 + packages/analytics/modules/analytics/types.ts | 50 +++++++ packages/analytics/package.json | 44 +++++++ packages/analytics/public/.keep | 0 packages/analytics/tsconfig.json | 6 + packages/analytics/wxt.config.ts | 12 ++ pnpm-lock.yaml | 124 ++++++++++++++++++ 14 files changed, 431 insertions(+) create mode 100644 packages/analytics/README.md create mode 100644 packages/analytics/app.config.ts create mode 100644 packages/analytics/build.config.ts create mode 100644 packages/analytics/entrypoints/popup.html create mode 100644 packages/analytics/modules/analytics/client.ts create mode 100644 packages/analytics/modules/analytics/index.ts create mode 100644 packages/analytics/modules/analytics/providers/google-analytics.ts create mode 100644 packages/analytics/modules/analytics/providers/umami.ts create mode 100644 packages/analytics/modules/analytics/types.ts create mode 100644 packages/analytics/package.json create mode 100644 packages/analytics/public/.keep create mode 100644 packages/analytics/tsconfig.json create mode 100644 packages/analytics/wxt.config.ts diff --git a/packages/analytics/README.md b/packages/analytics/README.md new file mode 100644 index 00000000..06f041d7 --- /dev/null +++ b/packages/analytics/README.md @@ -0,0 +1,65 @@ + + +# My Module + +My new WXT module for doing amazing things. + +## Features + + + +- ⛰  Foo +- 🚠  Bar +- 🌲  Baz + +## Installation + +Install the module to your WXT extension with one command: + +```bash +pnpm i my-module +``` + +Then add the module to your `wxt.config.ts` file: + +```ts +export default defineConfig({ + modules: ['my-module'], +}); +``` + +That's it! You can now use My Module in your WXT extension ✨ + +## Contribution + +
+ Local development + +```bash +# Install dependencies +pnpm install + +# Generate type stubs +pnpm wxt prepare + +# Develop test extension +pnpm dev + +# Build the test extension +pnpm dev:build + +# Run prettier, publint, and type checks +pnpm check +``` + +
diff --git a/packages/analytics/app.config.ts b/packages/analytics/app.config.ts new file mode 100644 index 00000000..99c54acb --- /dev/null +++ b/packages/analytics/app.config.ts @@ -0,0 +1,21 @@ +import { googleAnalytics } from './modules/analytics/providers/google-analytics'; +import { AnalyticsConfig } from './modules/analytics/types'; + +interface AppConfig { + analytics: AnalyticsConfig; +} +function defineAppConfig(config: AppConfig): AppConfig { + return config; +} + +export default defineAppConfig({ + analytics: { + providers: [ + googleAnalytics({ + apiSecret: '...', + measurementId: '...', + }), + ], + debug: true, + }, +}); diff --git a/packages/analytics/build.config.ts b/packages/analytics/build.config.ts new file mode 100644 index 00000000..5f3dbc67 --- /dev/null +++ b/packages/analytics/build.config.ts @@ -0,0 +1,33 @@ +import { defineBuildConfig } from 'unbuild'; +import * as vite from 'vite'; +import { resolve } from 'node:path'; + +// Build module and plugins +export default defineBuildConfig({ + rootDir: 'modules/my-module', + outDir: resolve(__dirname, 'dist'), + entries: ['index.ts', 'plugin.ts'], + replace: { + 'process.env.NPM': 'true', + }, + declaration: true, + hooks: { + 'build:done': prebuildEntrypoints, + }, +}); + +// Prebuild entrypoints +async function prebuildEntrypoints() { + await vite.build({ + root: 'modules/my-module', + build: { + emptyOutDir: false, + rollupOptions: { + input: 'modules/my-module/example.html', + output: { + dir: 'dist/prebuilt', + }, + }, + }, + }); +} diff --git a/packages/analytics/entrypoints/popup.html b/packages/analytics/entrypoints/popup.html new file mode 100644 index 00000000..eb37be63 --- /dev/null +++ b/packages/analytics/entrypoints/popup.html @@ -0,0 +1,12 @@ + + + + + + Popup + + + + + + diff --git a/packages/analytics/modules/analytics/client.ts b/packages/analytics/modules/analytics/client.ts new file mode 100644 index 00000000..6d4726a7 --- /dev/null +++ b/packages/analytics/modules/analytics/client.ts @@ -0,0 +1,27 @@ +import { defineWxtPlugin } from 'wxt/sandbox'; +import { Analytics } from './types'; + +export let analytics: Analytics; + +export default defineWxtPlugin(() => { + const isBackground = globalThis.window == null; // TODO: Support MV2 + analytics = isBackground + ? createBackgroundAnalytics() + : createAnalyticsForwarder(); +}); + +function createBackgroundAnalytics(): Analytics { + return { + identify: () => {}, + page: () => {}, + track: () => {}, + }; +} + +function createAnalyticsForwarder(): Analytics { + return { + identify: () => {}, + page: () => {}, + track: () => {}, + }; +} diff --git a/packages/analytics/modules/analytics/index.ts b/packages/analytics/modules/analytics/index.ts new file mode 100644 index 00000000..9e779c04 --- /dev/null +++ b/packages/analytics/modules/analytics/index.ts @@ -0,0 +1,19 @@ +import 'wxt'; +import { addWxtPlugin, defineWxtModule } from 'wxt/modules'; +import { resolve } from 'node:path'; + +const pluginId = process.env.NPM + ? 'analytics/client' + : resolve(__dirname, 'client.ts'); + +export default defineWxtModule({ + name: 'analytics', + imports: [{ name: 'analytics', from: pluginId }], + setup(wxt, options) { + // Add a plugin + addWxtPlugin( + wxt, + resolve(__dirname, process.env.NPM ? 'plugin.mjs' : 'plugin.ts'), + ); + }, +}); diff --git a/packages/analytics/modules/analytics/providers/google-analytics.ts b/packages/analytics/modules/analytics/providers/google-analytics.ts new file mode 100644 index 00000000..faeb567f --- /dev/null +++ b/packages/analytics/modules/analytics/providers/google-analytics.ts @@ -0,0 +1,13 @@ +import type { AnalyticsProvider } from '../types'; + +export interface GoogleAnalyticsProviderOptions {} + +export const googleAnalytics = + (options: GoogleAnalyticsProviderOptions): AnalyticsProvider => + (analytics, config) => { + return { + identify: async () => {}, + page: async () => {}, + track: async () => {}, + }; + }; diff --git a/packages/analytics/modules/analytics/providers/umami.ts b/packages/analytics/modules/analytics/providers/umami.ts new file mode 100644 index 00000000..e04bc41e --- /dev/null +++ b/packages/analytics/modules/analytics/providers/umami.ts @@ -0,0 +1,5 @@ +export interface UmamiProviderOptions {} + +export const umami = (options: UmamiProviderOptions) => (analytics, config) => { + throw Error('TODO'); +}; diff --git a/packages/analytics/modules/analytics/types.ts b/packages/analytics/modules/analytics/types.ts new file mode 100644 index 00000000..7387912a --- /dev/null +++ b/packages/analytics/modules/analytics/types.ts @@ -0,0 +1,50 @@ +export interface Analytics { + /** Report a page change */ + page: (url: string | URL) => void; + /** Report a custom event */ + track: (eventName: string, eventProperties: string) => void; + /** Save information about the user */ + identify: (userId: string, userProperties?: Record) => void; +} + +export interface AnalyticsConfig { + /** Array of providers to send analytics to. */ + providers: AnalyticsProvider[]; + /** Enable debug logs and other provider-specific debugging features. */ + debug?: boolean; + /** Extension version, defaults to `browser.runtime.getManifest().version`. */ + version?: string; +} + +export type AnalyticsProvider = ( + analytics: Analytics, + config: AnalyticsConfig, +) => { + /** Upload a page view event */ + page: (event: AnalyticsPageViewEvent) => Promise; + /** Upload a custom event */ + track: (event: AnalyticsTrackEvent) => Promise; + /** Upload or save information about the user */ + identify: (event: AnalyticsIdentifyEvent) => Promise; +}; + +export interface BaseAnalyticsEvent { + /** Identifier of the session the event was fired from */ + sessionId: string; + /** `Date.now()` of when the event was reported */ + time: number; +} + +export interface AnalyticsPageViewEvent extends BaseAnalyticsEvent { + url: string; + sessionId: string; + title: string; + location: string; +} + +export interface AnalyticsTrackEvent extends BaseAnalyticsEvent { + eventName: string; + eventProperties: Record; +} + +export interface AnalyticsIdentifyEvent extends BaseAnalyticsEvent {} diff --git a/packages/analytics/package.json b/packages/analytics/package.json new file mode 100644 index 00000000..c0865103 --- /dev/null +++ b/packages/analytics/package.json @@ -0,0 +1,44 @@ +{ + "name": "my-module", + "version": "1.0.0", + "description": "My new WXT module", + "repository": "your-org/my-module", + "license": "MIT", + "type": "module", + "exports": { + ".": { + "types": "./dist/index.d.mts", + "default": "./dist/index.mjs" + }, + "./plugin": { + "types": "./dist/plugin.d.mts", + "default": "./dist/plugin.mjs" + } + }, + "module": "./dist/index.mjs", + "types": "./dist/index.d.ts", + "files": [ + "dist" + ], + "scripts": { + "dev": "wxt", + "dev:build": "wxt build", + "check": "check", + "build": "unbuild", + "prepack": "unbuild", + "postinstall": "wxt prepare" + }, + "peerDependencies": { + "wxt": ">=0.18.10" + }, + "devDependencies": { + "@aklinker1/check": "^1.3.1", + "@types/chrome": "^0.0.268", + "prettier": "^3.3.2", + "publint": "^0.2.8", + "typescript": "^5.5.2", + "unbuild": "^2.0.0", + "vite": "^5.3.1", + "wxt": "^0.18.10" + } +} diff --git a/packages/analytics/public/.keep b/packages/analytics/public/.keep new file mode 100644 index 00000000..e69de29b diff --git a/packages/analytics/tsconfig.json b/packages/analytics/tsconfig.json new file mode 100644 index 00000000..47d6ba41 --- /dev/null +++ b/packages/analytics/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": ["../../tsconfig.base.json", "./.wxt/tsconfig.json"], + "compilerOptions": { + "types": ["chrome"] + } +} diff --git a/packages/analytics/wxt.config.ts b/packages/analytics/wxt.config.ts new file mode 100644 index 00000000..0ebb6402 --- /dev/null +++ b/packages/analytics/wxt.config.ts @@ -0,0 +1,12 @@ +import { defineConfig } from 'wxt'; + +export default defineConfig({ + vite: () => ({ + define: { + 'process.env.NPM': 'false', + }, + }), + myModule: { + example: 'options', + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 602ac57c..673250ef 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -84,6 +84,33 @@ importers: specifier: ^2.5.0 version: 2.5.0 + packages/analytics: + devDependencies: + '@aklinker1/check': + specifier: ^1.3.1 + version: 1.4.5(typescript@5.5.4) + '@types/chrome': + specifier: ^0.0.268 + version: 0.0.268 + prettier: + specifier: ^3.3.2 + version: 3.3.3 + publint: + specifier: ^0.2.8 + version: 0.2.9 + typescript: + specifier: ^5.5.2 + version: 5.5.4 + unbuild: + specifier: ^2.0.0 + version: 2.0.0(sass@1.77.8)(typescript@5.5.4) + vite: + specifier: ^5.3.1 + version: 5.3.5(@types/node@20.14.12)(sass@1.77.8) + wxt: + specifier: ^0.18.10 + version: 0.18.15(@types/node@20.14.12)(rollup@4.19.0)(sass@1.77.8) + packages/auto-icons: dependencies: defu: @@ -1913,6 +1940,9 @@ packages: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} + async-mutex@0.4.1: + resolution: {integrity: sha512-WfoBo4E/TbCX1G95XTjbWTE3X2XLG0m1Xbv2cwOtuPdyH9CZvnaA5nCt1ucjaKEgW2A5IF71hxrRhr83Je5xjA==} + async-mutex@0.5.0: resolution: {integrity: sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==} @@ -3553,6 +3583,10 @@ packages: resolution: {integrity: sha512-ERAyNnZOfqM+Ao3RAvIXkYh5joP220yf59gVe2X/cI6SiCxIdi4c9HZKZD8R6q/RDXEje1THBju6iExiSsgJaQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + ora@7.0.1: + resolution: {integrity: sha512-0TUxTiFJWv+JnjWm4o9yvuskpEJLXTcng8MJuKd+SzAzp2o+OP3HWqNhB4OdJRt1Vsd9/mR0oyaEYlOnL7XIRw==} + engines: {node: '>=16'} + ora@8.1.0: resolution: {integrity: sha512-GQEkNkH/GHOhPFXcqZs3IDahXEQcQxsSjEkK4KvEEST4t7eNzoMjxTzef+EZ+JluDEV+Raoi3WQ2CflnRdSVnQ==} engines: {node: '>=18'} @@ -4279,6 +4313,10 @@ packages: resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} engines: {node: '>=12'} + string-width@6.1.0: + resolution: {integrity: sha512-k01swCJAgQmuADB0YIc+7TuatfNvTBVOoaUWJjTB9R4VJzR5vNWzf5t42ESVZFPS8xTySF7CAdV4t/aaIm3UnQ==} + engines: {node: '>=16'} + string-width@7.2.0: resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} engines: {node: '>=18'} @@ -4742,6 +4780,9 @@ packages: resolution: {integrity: sha512-5D11VcjdGkA1/xax5UWL0YeAbDySKHzWFe6EpsoPNUMw5Uk9tKk9p6GUOfcaI5N7sINKfBMZYNsTBiu5dzJB9A==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} + webextension-polyfill@0.10.0: + resolution: {integrity: sha512-c5s35LgVa5tFaHhrZDnr3FpQpjj1BB+RXhLTYUxGqBVN460HkbM8TBtEqdXWbpTKfzwCcjAZVF7zXCYSKtcp9g==} + webextension-polyfill@0.12.0: resolution: {integrity: sha512-97TBmpoWJEE+3nFBQ4VocyCdLKfw54rFaJ6EVQYLBCXqCIpLSZkwGgASpv4oPt9gdKCJ80RJlcmNzNn008Ag6Q==} @@ -4825,6 +4866,10 @@ packages: utf-8-validate: optional: true + wxt@0.18.15: + resolution: {integrity: sha512-rzFLinUyugBsoC+mJZFMIQILrA4OUk/XG0mAW30zF3pqegIwawrEx5JXFXg/D1XW6//uew/FLTm12t3mcpbw+Q==} + hasBin: true + xdg-basedir@5.1.0: resolution: {integrity: sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==} engines: {node: '>=12'} @@ -6173,6 +6218,10 @@ snapshots: assertion-error@2.0.1: {} + async-mutex@0.4.1: + dependencies: + tslib: 2.6.0 + async-mutex@0.5.0: dependencies: tslib: 2.6.0 @@ -8008,6 +8057,18 @@ snapshots: strip-ansi: 7.1.0 wcwidth: 1.0.1 + ora@7.0.1: + dependencies: + chalk: 5.3.0 + cli-cursor: 4.0.0 + cli-spinners: 2.9.2 + is-interactive: 2.0.0 + is-unicode-supported: 1.3.0 + log-symbols: 5.1.0 + stdin-discarder: 0.1.0 + string-width: 6.1.0 + strip-ansi: 7.1.0 + ora@8.1.0: dependencies: chalk: 5.3.0 @@ -8770,6 +8831,12 @@ snapshots: emoji-regex: 9.2.2 strip-ansi: 7.1.0 + string-width@6.1.0: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 10.3.0 + strip-ansi: 7.1.0 + string-width@7.2.0: dependencies: emoji-regex: 10.3.0 @@ -9354,6 +9421,8 @@ snapshots: - supports-color - utf-8-validate + webextension-polyfill@0.10.0: {} + webextension-polyfill@0.12.0: {} webidl-conversions@7.0.0: {} @@ -9436,6 +9505,61 @@ snapshots: ws@8.18.0: {} + wxt@0.18.15(@types/node@20.14.12)(rollup@4.19.0)(sass@1.77.8): + dependencies: + '@aklinker1/rollup-plugin-visualizer': 5.12.0(rollup@4.19.0) + '@types/webextension-polyfill': 0.10.7 + '@webext-core/fake-browser': 1.3.1 + '@webext-core/isolated-element': 1.1.2 + '@webext-core/match-patterns': 1.0.3 + async-mutex: 0.4.1 + c12: 1.11.1(magicast@0.3.4) + cac: 6.7.14 + chokidar: 3.6.0 + ci-info: 4.0.0 + defu: 6.1.4 + dequal: 2.0.3 + esbuild: 0.19.12 + fast-glob: 3.3.2 + filesize: 10.1.4 + fs-extra: 11.2.0 + get-port: 7.1.0 + giget: 1.2.3 + hookable: 5.5.3 + is-wsl: 3.1.0 + jiti: 1.21.6 + json5: 2.2.3 + jszip: 3.10.1 + linkedom: 0.18.4 + magicast: 0.3.4 + minimatch: 9.0.5 + natural-compare: 1.4.0 + normalize-path: 3.0.0 + nypm: 0.3.9 + ohash: 1.1.3 + open: 10.1.0 + ora: 7.0.1 + picocolors: 1.0.1 + prompts: 2.4.2 + publish-browser-extension: 2.1.3 + unimport: 3.11.1(rollup@4.19.0) + vite: 5.3.5(@types/node@20.14.12)(sass@1.77.8) + vite-node: 1.6.0(@types/node@20.14.12)(sass@1.77.8) + web-ext-run: 0.2.1 + webextension-polyfill: 0.10.0 + transitivePeerDependencies: + - '@types/node' + - bufferutil + - less + - lightningcss + - rollup + - sass + - stylus + - sugarss + - supports-color + - terser + - utf-8-validate + xdg-basedir@5.1.0: {} xml2js@0.5.0: