diff --git a/.github/workflows/publish-packages.yml b/.github/workflows/publish-packages.yml index 4a582ca..fe5a8e8 100644 --- a/.github/workflows/publish-packages.yml +++ b/.github/workflows/publish-packages.yml @@ -93,7 +93,6 @@ jobs: if: ${{ steps.changelog.outputs.skipped == 'false' }} run: | echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > ~/.npmrc - pnpm build:dependencies pnpm build pnpm publish working-directory: packages/${{ matrix.package }} diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index cb3ae88..2f2a733 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -6,35 +6,42 @@ on: workflow_call: jobs: - root: - name: root + checks: runs-on: ubuntu-22.04 steps: - - name: Checkout Repo - uses: actions/checkout@v3 - - - name: Install PNPM - uses: pnpm/action-setup@v2.2.4 - with: - version: 8 - - - name: Install Node 16 - uses: actions/setup-node@v3 - with: - node-version: 16 - cache: 'pnpm' - - - name: Install dependencies - run: pnpm install - - - name: Check Formatting - run: pnpm format:check - - - name: Build - run: pnpm build - - - name: Type Check - run: pnpm -r compile + - uses: actions/checkout@v3 + - uses: oven-sh/setup-bun@v2 + - run: bun install + - run: | + bun format:check + bun run --cwd packages/fake-browser check + bun run --cwd packages/isolated-element check + bun run --cwd packages/isolated-element-demo check + bun run --cwd packages/job-scheduler check + bun run --cwd packages/match-patterns check + bun run --cwd packages/messaging check + bun run --cwd packages/proxy-service check + bun run --cwd packages/storage check + + build: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v3 + - uses: oven-sh/setup-bun@v2 + - run: bun install + - run: bun run build - - name: Run Tests - run: pnpm -r test:coverage + tests: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v3 + - uses: oven-sh/setup-bun@v2 + - run: bun install + - run: | + bun run --cwd packages/fake-browser test:coverage + bun run --cwd packages/isolated-element test:coverage + bun run --cwd packages/job-scheduler test:coverage + bun run --cwd packages/match-patterns test:coverage + bun run --cwd packages/messaging test:coverage + bun run --cwd packages/proxy-service test:coverage + bun run --cwd packages/storage test:coverage diff --git a/.gitignore b/.gitignore index 3f9bbc7..6c27d90 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,6 @@ coverage /docs/.vitepress/cache .DS_Store tsconfig.vitest-temp.json +/.cache +.output +.wxt diff --git a/.husky/pre-commit b/.husky/pre-commit deleted file mode 100755 index f42dc94..0000000 --- a/.husky/pre-commit +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env sh -. "$(dirname -- "$0")/_/husky.sh" - -pnpm pretty-quick --staged diff --git a/.prettierignore b/.prettierignore index 05bbd30..7349e29 100644 --- a/.prettierignore +++ b/.prettierignore @@ -4,3 +4,7 @@ lib/ pnpm-lock.yaml /docs/.vitepress/cache /docs/api/* +.nuxt +.wxt.output +docs/content/index.md +docs/content/*/api.md diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 0000000..c6a0f85 Binary files /dev/null and b/bun.lockb differ diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 0000000..4a7f73a --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,24 @@ +# Nuxt dev/build outputs +.output +.data +.nuxt +.nitro +.cache +dist + +# Node dependencies +node_modules + +# Logs +logs +*.log + +# Misc +.DS_Store +.fleet +.idea + +# Local env files +.env +.env.* +!.env.example diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts deleted file mode 100644 index 385310e..0000000 --- a/docs/.vitepress/config.ts +++ /dev/null @@ -1,181 +0,0 @@ -import { defineConfig } from 'vitepress'; -import { defineTypescriptDocs } from './plugins/typescript-docs'; - -const ogDescription = 'Next Generation Frontend Tooling'; -const ogTitle = 'Web Ext Core'; -const ogUrl = 'https://webext-core.aklinker1.io'; - -const packageDirnames = [ - 'storage', - 'messaging', - 'job-scheduler', - 'match-patterns', - 'proxy-service', - 'isolated-element', - 'fake-browser', -]; - -const packagePages = { - 'fake-browser': [ - { - text: 'Get Started', - link: '/guide/fake-browser/', - }, - { - text: 'Testing Frameworks', - link: '/guide/fake-browser/testing-frameworks', - }, - { - text: 'Reseting State', - link: '/guide/fake-browser/reseting-state', - }, - { - text: 'Triggering Events', - link: '/guide/fake-browser/triggering-events', - }, - { - text: 'Implemented APIs', - link: '/guide/fake-browser/implemented-apis', - }, - ], - 'isolated-element': [ - { - text: 'Get Started', - link: '/guide/isolated-element/', - }, - ], - messaging: [ - { - text: 'Get Started', - link: '/guide/messaging/', - }, - { - text: 'Protocol Maps', - link: '/guide/messaging/protocol-maps', - }, - ], - 'proxy-service': [ - { - text: 'Get Started', - link: '/guide/proxy-service/', - }, - { - text: 'Defining Services', - link: '/guide/proxy-service/defining-services', - }, - ], - storage: [ - { - text: 'Get Started', - link: '/guide/storage/', - }, - { - text: 'Typescript', - link: '/guide/storage/typescript', - }, - ], - 'job-scheduler': [ - { - text: 'Get Started', - link: '/guide/job-scheduler/', - }, - ], - 'match-patterns': [ - { - text: 'Get Started', - link: '/guide/match-patterns/', - }, - ], -}; - -const packagesItemGroup = packageDirnames.map(dirname => ({ - text: dirname, - link: `/guide/${dirname}/`, - items: packagePages[dirname], -})); - -const apiItemGroup = { - text: 'API', - items: packageDirnames.map(dirname => ({ text: dirname, link: `/api/${dirname}` })), -}; - -export default defineConfig({ - ...defineTypescriptDocs(packageDirnames), - - title: `Web Ext Core`, - description: 'Web Extension Development Made Easy', - - head: [ - ['link', { rel: 'icon', type: 'image/svg+xml', href: '/logo.svg' }], - ['meta', { property: 'og:type', content: 'website' }], - ['meta', { property: 'og:title', content: ogTitle }], - // ['meta', { property: 'og:image', content: ogImage }], - ['meta', { property: 'og:url', content: ogUrl }], - ['meta', { property: 'og:description', content: ogDescription }], - [ - 'meta', - { - name: 'twitter:card', - content: - 'https://repository-images.githubusercontent.com/562524328/c0cd6d4b-23ff-4536-97ab-f19a57cc23e3', - }, - ], - ['meta', { name: 'theme-color', content: '#646cff' }], - // - [ - 'script', - { - async: '', - defer: '', - 'data-website-id': '04aff3ed-57d7-4ee0-9faf-e24a39adeafa', - src: 'https://stats.aklinker1.io/umami.js', - }, - ], - ], - - themeConfig: { - logo: '/logo.svg', - - search: { - provider: 'algolia', - options: { - appId: '5YM53OJZKV', - apiKey: 'afa7be8df70add29c036f21548117128', - indexName: 'webext-core-aklinker1', - }, - }, - - editLink: { - pattern: 'https://github.com/aklinker1/webext-core/edit/main/docs/:path', - text: 'Suggest changes to this page', - }, - - socialLinks: [{ icon: 'github', link: 'https://github.com/aklinker1/webext-core' }], - - footer: { - message: `Released under the MIT License.`, - copyright: 'Copyright © 2022-present Aaron Klinker & Web Ext Core Contributors', - }, - - nav: [{ text: 'Guide', link: '/guide/' }, apiItemGroup], - - sidebar: { - '/guide/': [ - { - text: 'Introduction', - link: '/guide/', - }, - { - text: 'Browser Support', - link: '/guide/browser-support', - }, - { - text: 'Contributing', - link: '/guide/contributing', - }, - ...packagesItemGroup, - ], - '/api/': [apiItemGroup], - }, - }, -}); diff --git a/docs/.vitepress/theme/components/Chip.vue b/docs/.vitepress/theme/components/Chip.vue deleted file mode 100644 index 0fea3f0..0000000 --- a/docs/.vitepress/theme/components/Chip.vue +++ /dev/null @@ -1,64 +0,0 @@ - - - - - diff --git a/docs/.vitepress/theme/components/ChipGroup.vue b/docs/.vitepress/theme/components/ChipGroup.vue deleted file mode 100644 index 5d691d8..0000000 --- a/docs/.vitepress/theme/components/ChipGroup.vue +++ /dev/null @@ -1,14 +0,0 @@ - - - diff --git a/docs/.vitepress/theme/components/ContributorList.vue b/docs/.vitepress/theme/components/ContributorList.vue deleted file mode 100644 index f0008bb..0000000 --- a/docs/.vitepress/theme/components/ContributorList.vue +++ /dev/null @@ -1,63 +0,0 @@ - - - - - diff --git a/docs/.vitepress/theme/index.ts b/docs/.vitepress/theme/index.ts deleted file mode 100644 index 0b0ae68..0000000 --- a/docs/.vitepress/theme/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { h } from 'vue'; -import Theme from 'vitepress/theme'; -import './styles/vars.css'; -import Chip from './components/Chip.vue'; -import ChipGroup from './components/ChipGroup.vue'; -import ContributorList from './components/ContributorList.vue'; - -export default { - ...Theme, - Layout() { - return h(Theme.Layout, null, {}); - }, - enhanceApp({ app }) { - app.component('Chip', Chip); - app.component('ChipGroup', ChipGroup); - app.component('ContributorList', ContributorList); - }, -}; diff --git a/docs/.vitepress/theme/styles/vars.css b/docs/.vitepress/theme/styles/vars.css deleted file mode 100644 index f584d71..0000000 --- a/docs/.vitepress/theme/styles/vars.css +++ /dev/null @@ -1,112 +0,0 @@ -/** - * Colors - * -------------------------------------------------------------------------- */ - -:root { - --vp-c-brand: #34c55d; - --vp-c-brand-light: #3de26b; - --vp-c-brand-lighter: #60fa8b; - --vp-c-brand-lightest: #94ffb2; - --vp-c-brand-dark: #26b24d; - --vp-c-brand-darker: #14a43c; - --vp-c-brand-dimm: rgba(52, 197, 93, 0.08); -} - -/** - * Component: Button - * -------------------------------------------------------------------------- */ - -:root { - --vp-button-brand-border: var(--vp-c-brand-light); - --vp-button-brand-text: var(--vp-c-black); - --vp-button-brand-bg: var(--vp-c-brand); - --vp-button-brand-hover-border: var(--vp-c-brand-light); - --vp-button-brand-hover-text: var(--vp-c-black); - --vp-button-brand-hover-bg: var(--vp-c-brand-light); - --vp-button-brand-active-border: var(--vp-c-brand-light); - --vp-button-brand-active-text: var(--vp-c-black); - --vp-button-brand-active-bg: var(--vp-button-brand-bg); -} - -/** - * Component: Home - * -------------------------------------------------------------------------- */ - -:root { - --vp-home-hero-name-color: transparent; - --vp-home-hero-name-background: -webkit-linear-gradient(120deg, #45ccf6 30%, #3de26b 70%); - - --vp-home-hero-image-background-image: linear-gradient( - -45deg, - #45ccf6 50%, - rgba(61, 226, 107, 70%) 50% - ); - --vp-home-hero-image-filter: blur(40px); -} - -@media (min-width: 640px) { - :root { - --vp-home-hero-image-filter: blur(56px); - } -} - -@media (min-width: 960px) { - :root { - --vp-home-hero-image-filter: blur(72px); - } -} - -/** - * Component: Custom Block - * -------------------------------------------------------------------------- */ - -:root { - --vp-custom-block-tip-border: var(--vp-c-brand); - --vp-custom-block-tip-text: var(--vp-c-brand-darker); - --vp-custom-block-tip-bg: var(--vp-c-brand-dimm); -} - -.dark { - --vp-custom-block-tip-border: var(--vp-c-brand); - --vp-custom-block-tip-text: var(--vp-c-brand-lightest); - --vp-custom-block-tip-bg: var(--vp-c-brand-dimm); -} - -/** - * Component: Algolia - * -------------------------------------------------------------------------- */ - -.DocSearch { - --docsearch-primary-color: var(--vp-c-brand) !important; -} - -/** - * VitePress: Custom fix - * -------------------------------------------------------------------------- */ - -/* - Use lighter colors for links in dark mode for a11y. - Also specify some classes twice to have higher specificity - over scoped class data attribute. -*/ -.dark .vp-doc a, -.dark .vp-doc a > code, -.dark .VPNavBarMenuLink.VPNavBarMenuLink:hover, -.dark .VPNavBarMenuLink.VPNavBarMenuLink.active, -.dark .link.link:hover, -.dark .link.link.active, -.dark .edit-link-button.edit-link-button, -.dark .pager-link .title { - color: var(--vp-c-brand-lighter); -} - -.dark .vp-doc a:hover, -.dark .vp-doc a > code:hover { - color: var(--vp-c-brand-lightest); - opacity: 1; -} - -/* Transition by color instead of opacity */ -.dark .vp-doc .custom-block a { - transition: color 0.25s; -} diff --git a/docs/__mocks__/webextension-polyfill.ts b/docs/__mocks__/webextension-polyfill.ts deleted file mode 100644 index 2f62519..0000000 --- a/docs/__mocks__/webextension-polyfill.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { fakeBrowser } from '@webext-core/fake-browser'; - -export default fakeBrowser; diff --git a/docs/_redirects.txt b/docs/_redirects.txt new file mode 100644 index 0000000..d93536f --- /dev/null +++ b/docs/_redirects.txt @@ -0,0 +1,26 @@ +# Vitepress -> Nuxt + +/guide/ /get-started/introduction +/guide/browser-support.html /get-started/browser-support +/guide/contributing.html /get-started/contributing +/guide/fake-browser/ /fake-browser/installation +/guide/fake-browser/testing-frameworks.html /fake-browser/testing-frameworks +/guide/fake-browser/reseting-state.html /fake-browser/triggering-events +/guide/fake-browser/triggering-events.html /fake-browser/reseting-state +/guide/fake-browser/implemented-apis.html /fake-browser/implemented-apis +/guide/isolated-element/ /isolated-element/installation +/guide/job-scheduler/ /job-scheduler/installation +/guide/match-patterns/ /match-patterns/installation +/guide/messaging/ /messaging/installation +/guide/messaging/protocol-maps.html /messaging/protocol-maps +/guide/proxy-service/ /proxy-service/installation +/guide/proxy-service/defining-services.html /proxy-service/defining-services +/guide/storage/ /storage/installation +/guide/storage/typescript.html /storage/typescript +/api/fake-browser.html /fake-browser/api +/api/isolated-element.html /isolated-element/api +/api/job-scheduler.html /job-scheduler/api +/api/match-patterns.html /match-patterns/api +/api/messaging.html /messaging/api +/api/proxy-service.html /proxy-service/api +/api/storage.html /storage/api diff --git a/docs/app.config.ts b/docs/app.config.ts new file mode 100644 index 0000000..f153774 --- /dev/null +++ b/docs/app.config.ts @@ -0,0 +1,17 @@ +export default defineAppConfig({ + docus: { + title: 'Web Ext Core', + description: 'Web Extension Development Made Easy', + url: 'http://webext-core.aklinker1.io', + github: { + dir: 'docs', + owner: 'aklinker1', + repo: 'webext-core', + }, + socials: { + github: 'https://github.com/aklinker1/webext-core', + }, + titleTemplate: '%s - Webext Core', + image: '/favicon.ico', + }, +}); diff --git a/docs/guide/index.md b/docs/content/0.get-started/0.introduction.md similarity index 80% rename from docs/guide/index.md rename to docs/content/0.get-started/0.introduction.md index 481c910..0d78145 100644 --- a/docs/guide/index.md +++ b/docs/content/0.get-started/0.introduction.md @@ -1,31 +1,16 @@ # Introduction -## Overview - All of `@webext-core`'s packages are provided via NPM. Depending on your project's setup, you can consume them in 2 different ways: -1. If your project uses a bundler like Vite or Webpack, see [Bundler Setup](#bundler-setup). +1. If your project uses a bundler or framework (like Vite, Webpack, WXT, or Plasmo), see [Bundler Setup](#bundler-setup). 2. If your project does not use a bundler, see [Non-bundler Setup](#non-bundler-setup) -## List of packages - - - - - ## Bundler Setup -If you haven't setup a bundler yet, I recommend using [`vite`](https://vitejs.dev/) and [`vite-plugin-web-extension`](https://vite-plugin-web-extension.aklinker1.io/) for the best DX and to support all browsers. +If you haven't setup a bundler yet, I recommend using [WXT](https://wxt.dev/) for the best DX and to support all browsers. -```sh PNPM -# Bootstrap a new project -pnpm create vite-plugin-web-extnesion +```sh +pnpm dlx wxt@latest init ``` Install any of the packages and use them normally. Everything will just work :+1: @@ -44,11 +29,15 @@ const value = await localExtStorage.getItem('some-key'); If you're not using a bundler, you'll have to download each package and put it inside your project. -:::details Why download them? +::callout +#summary +Why download them? + +#content With Manifest V3, Google doesn't approve of extensions using CDN URLs directly, considering it "remotely hosted code" and a security risk. So you will need to download each package and ship them with your extension. See the [MV3 overview](https://developer.chrome.com/docs/extensions/mv3/intro/mv3-overview/#remotely-hosted-code) for more details. If you're not on MV3 yet, you could use the CDN, but it's still recommended to download it so it loads faster. -::: +:: All of `@webext-core` NPM packages include a minified, `lib/index.global.js` file that will create a global variable you can use to access the package's APIs. diff --git a/docs/content/0.get-started/1.browser-support.md b/docs/content/0.get-started/1.browser-support.md new file mode 100644 index 0000000..719229a --- /dev/null +++ b/docs/content/0.get-started/1.browser-support.md @@ -0,0 +1,18 @@ +# Browser Support + +## Overview + +The `@webext-core` packages are simple wrappers around [`webextension-polyfill`](https://www.npmjs.com/package/webextension-polyfill) by Mozilla. As such, they will work on: + +| Browser | Supported Versions | +| --------------------- | ------------------ | +| Chrome | >= 87 | +| Firefox | >= 78 | +| Safari _1_ | >= 14 | +| Edge | >= 88 | + +Other Chromium-based browsers are not officially supported, and may not work_2_. See Mozilla's [supported browsers documentation](https://github.com/mozilla/webextension-polyfill#supported-browsers) for more details. + +> _1_ `webextension-polyfill` works on Safari, however Safari does not implement the complete web extension standard. See the [browser compatibliity chart](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Browser_support_for_JavaScript_APIs) for more details. +> +> _2_ In practice, the browsers are close enough to chrome that they work 99% of the time. But make sure to test your extension before assuming it will work. diff --git a/docs/guide/contributing.md b/docs/content/0.get-started/2.contributing.md similarity index 85% rename from docs/guide/contributing.md rename to docs/content/0.get-started/2.contributing.md index 99de21c..07111c7 100644 --- a/docs/guide/contributing.md +++ b/docs/content/0.get-started/2.contributing.md @@ -1,24 +1,26 @@ +--- +toc: true +--- + # Contributing Special thanks to the contributors. I look forward to seeing you in the list! - - - + + + + +::callout +#summary +First time contributing to open source? -:::details First time contributing to open source? +#content It's easy! Here are some resources to get started: - https://www.youtube.com/embed/dSl_qnWO104 - https://docs.github.com/en/get-started/quickstart/contributing-to-projects -::: - -
- -###### Table of Contents - -[[toc]] +:: ## Project Goals @@ -38,18 +40,15 @@ If you want to add a new package or feature, open an issue first. That way we ca ## Development Setup -You'll need to install some tools: - -- [NodeJS](https://nodejs.org/en/), v16 or above -- [PNPM](https://pnpm.io/): Install it via `corepack enable` +You'll need to install [Bun](https://bun.sh) before contributing. Then you can fork the repo, install the dependencies, and build the packages for the first time! ```sh git clone {your-fork} cd webext-core -pnpm i -pnpm build +bun i +bun run build ``` ## Project Layout @@ -58,7 +57,7 @@ The `webext-core` repo is a monorepo containing all the packages under the [`@we Here's an overview of the main directories: -- `docs`: The [`vitepress`](https://vitepress.vuejs.org/) website for +- `docs`: The website for - `packages/*`: Each NPM package has it's own directory - `packages/*-demo`: Some packages have a demo extension @@ -77,17 +76,16 @@ In general, all packages are the same. In the root directory, you can run the following scripts: ```sh -pnpm build # Run the build script for all packages -pnpm docs # Run the docs website locally -pnpm format # Run prettier to format all your files +bun run build # Run the build script for all packages +bun run format # Run prettier to format all your files ``` Or `cd` into a package's directory and run these scripts ```sh -pnpm build # Build the package and it's dependencies -pnpm compile # Check for type errors -pnpm test # Run unit tests in watch mode +bun run build # Build the package and it's dependencies +bun run check # Check for type errors +bun run test # Run unit tests in watch mode ``` Each directory might have additional scripts you can run. See each `package.json` for a complete list. diff --git a/docs/content/fake-browser/0.installation.md b/docs/content/fake-browser/0.installation.md new file mode 100644 index 0000000..bfa880b --- /dev/null +++ b/docs/content/fake-browser/0.installation.md @@ -0,0 +1,25 @@ +--- +toc: true +--- + +# Installation + +:badge[Vitest]{type="success"} :badge[Jest]{type="success"} :badge[Bun]{type="success"} :badge[Mocha]{type="success"} + +## Overview + +An in-memory implementation of [`webextension-polyfill`](https://www.npmjs.com/package/webextension-polyfill) for testing. Supports all test frameworks (Vitest, Jest, etc). + +```sh +pnpm i -D @webext-core/fake-browser +``` + +::alrt{type=warning} +This package only really works with projects using node, so only the NPM install steps are shown. +:: + +See [Testing Frameworks](/fake-browser/testing-frameworks) to setup mocks for your testing framework of choice. + +## Examples + +See [Implemented APIs](/fake-browser/implemented-apis) for example tests and details on how to use each API. diff --git a/docs/guide/fake-browser/testing-frameworks.md b/docs/content/fake-browser/1.testing-frameworks.md similarity index 98% rename from docs/guide/fake-browser/testing-frameworks.md rename to docs/content/fake-browser/1.testing-frameworks.md index 3b2872e..53c1307 100644 --- a/docs/guide/fake-browser/testing-frameworks.md +++ b/docs/content/fake-browser/1.testing-frameworks.md @@ -2,9 +2,9 @@ `@webext-core/fake-browser` works with all frameworks. Setup for only a few of the major testing frameworks is listed below. -[[toc]] - -> Open a PR to add an example for your framework of choice! +::alert +Open a PR to add an example for your framework of choice! +:: ## Vitest diff --git a/docs/guide/fake-browser/triggering-events.md b/docs/content/fake-browser/2.triggering-events.md similarity index 72% rename from docs/guide/fake-browser/triggering-events.md rename to docs/content/fake-browser/2.triggering-events.md index 947ac43..6b3e9fd 100644 --- a/docs/guide/fake-browser/triggering-events.md +++ b/docs/content/fake-browser/2.triggering-events.md @@ -7,9 +7,13 @@ When possible, events are triggered based on other calls to other browser APIs. Some events, like `runtime.onInstalled` or `alarms.onAlarm`, can't be triggered as they would be in a real extension. -> In the case of `onInstalled`, when is an extension "installed" during tests? Never? Or when the tests start? Either way, not useful for testing. +::alert +In the case of `onInstalled`, when is an extension "installed" during tests? Never? Or when the tests start? Either way, not useful for testing. +:: -> In the case of `onAlarm`, alarms are meant to trigger in the far future, usually a much longer timespan than the duration of a unit test. Also, timers in tests are notoriously flakey and difficult to work with. +::alert +In the case of `onAlarm`, alarms are meant to trigger in the far future, usually a much longer timespan than the duration of a unit test. Also, timers in tests are notoriously flakey and difficult to work with. +:: Instead, the `fakeBrowser` provides a `trigger` method on every implemented event that you can call to trigger them manually. Pass in the arguements that the listeners are called with: diff --git a/docs/guide/fake-browser/reseting-state.md b/docs/content/fake-browser/3.reseting-state.md similarity index 97% rename from docs/guide/fake-browser/reseting-state.md rename to docs/content/fake-browser/3.reseting-state.md index 514b5ba..9a2fd7d 100644 --- a/docs/guide/fake-browser/reseting-state.md +++ b/docs/content/fake-browser/3.reseting-state.md @@ -6,9 +6,9 @@ Implemented APIs store state in memory. When unit testing, we often want to rese 2. Reset just one API: `fakeBrowser.{api}.resetState()` 3. Call `fakeBrowser.{api}.on{Event}.removeAllListeners()` to remove all the listeners setup for an event -:::info +::alert All the reset methods are synchronous -::: +:: For example, to clear the in-memory stored values for `browser.storage.local`, you could call any of the following: @@ -17,6 +17,6 @@ For example, to clear the in-memory stored values for `browser.storage.local`, y All these reset methods should show up in your editor's intelisense. -:::info +::alert Generally, you should put a call to `fakeBrowser.reset()` in a `beforeEach` block to cleanup the state before every test. -::: +:: diff --git a/docs/content/fake-browser/4.implemented-apis.md b/docs/content/fake-browser/4.implemented-apis.md new file mode 100644 index 0000000..da99efb --- /dev/null +++ b/docs/content/fake-browser/4.implemented-apis.md @@ -0,0 +1,122 @@ +# Implmeneted APIs + +This file lists all the implemented APIs, their caveots, limitations, and example tests. Example tests are writen with vitest. + +::callout{type=warning} +#summary +Not all APIs are implemented! +#content +For all APIs not listed here, you will have to mock the functions behavior yourself, or you can submit a PR to add support :smile: +:: + +## `alarms` + +- All alarms APIs are implemented as in production, except for `onAlarm`. +- You have to manually call `onAlarm.trigger()` for your event listeners to be executed. + +## `notifications` + +- `create`, `clear`, and `getAll` are fully implemented +- You have to manually trigger all the events (`onClosed`, `onClicked`, `onButtonClicked`, `onShown`) + +### Example Tests + +::code-group + +```ts [ensureNotificationExists.test.ts] +import { describe, it, beforeEach, vi, expect } from 'vitest'; +import browser, { Notifications } from 'webextension-polyfill'; +import { fakeBrowser } from '@webext-core/fake-browser'; + +async function ensureNotificationExists( + id: string, + notification: Notifications.CreateNotificationOptions, +): Promise { + const notifications = await browser.notifications.getAll(); + if (!notifications[id]) await browser.notifications.create(id, notification); +} + +describe('ensureNotificationExists', () => { + const id = 'some-id'; + const notification: Notifications.CreateNotificationOptions = { + type: 'basic', + title: 'Some Title', + message: 'Some message...', + }; + + beforeEach(() => { + fakeBrowser.reset(); + }); + + it('should create a notification if it does not exist', async () => { + const createSpy = vi.spyOn(browser.notifications, 'create'); + + await ensureNotificationExists(id, notification); + + expect(createSpy).toBeCalledTimes(1); + expect(createSpy).toBeCalledWith(id, notification); + }); + + it('should not create the notification if it already exists', async () => { + await fakeBrowser.notifications.create(id, notification); + const createSpy = vi.spyOn(browser.notifications, 'create'); + + await ensureNotificationExists(id, notification); + + expect(createSpy).not.toBeCalled(); + }); +}); +``` + +```ts [setupNotificationShownReports.test.ts] +import { describe, it, beforeEach, vi, expect } from 'vitest'; +import browser from 'webextension-polyfill'; +import { fakeBrowser } from '@webext-core/fake-browser'; + +async function setupNotificationShownReports( + reportEvent: (notificationId: string) => void, +): Promise { + browser.notifications.onShown.addListener(id => reportEvent(id)); +} + +describe('setupNotificationShownReports', () => { + beforeEach(() => { + fakeBrowser.reset(); + }); + + it('should properly report an analytics event when a notification is shown', async () => { + const reportAnalyticsEvent = vi.fn(); + const id = 'notification-id'; + + setupNotificationShownReports(reportAnalyticsEvent); + await fakeBrowser.notifications.onShown.trigger(id); + + expect(reportAnalyticsEvent).toBeCalledTimes(1); + expect(reportAnalyticsEvent).toBeCalledWith(id); + }); +}); +``` + +:: + +## `runtime` + +- All events have been implemented, but all of them other than `onMessage` must be triggered manually. +- `rutime.id` is a hardcoded string. You can set this to whatever you want, but it is reset to the hardcoded value when calling `reset()`. +- Unlike in a real production, `sendMessage` will trigger `onMessage` listeners setup in the same JS context. This allows you to add a listener when setting up your test, then call `sendMessage` to trigger it. + +## `storage` + +- The `local`, `sync`, `session`, and `managed` storages are all stored separately in memory. +- `storage.onChanged`, `storage.{area}.onChanged` events are all triggered when updating values. +- Each storage area can be reset individually. + +## `tabs` and `windows` + +- Fully implemented. +- All methods trigger corresponding `tabs` events AND `windows` events depending on what happened (ie: closing the last tab of a window would trigger both `tabs.onRemoved` and `windows.onRemoved`). + +## `webNavigation` + +- The two functions, `getFrame` and `getAllFrames` are not implemented. You will have to mock their return values yourself. +- All the event listeners are implemented, but none are triggered automatically. They can be triggered manually by calling `browser.webNavigation.{event}.trigger(...)` diff --git a/docs/api/fake-browser.md b/docs/content/fake-browser/api.md similarity index 61% rename from docs/api/fake-browser.md rename to docs/content/fake-browser/api.md index f7bb5f9..d9d5c7c 100644 --- a/docs/api/fake-browser.md +++ b/docs/content/fake-browser/api.md @@ -1,8 +1,12 @@ -# API Reference - `fake-browser` +# API Reference -> [`@webext-core/fake-browser`](/guide/fake-browser/) +::alert + +See [`@webext-core/fake-browser`](/fake-browser/installation/) + +:: ## `FakeBrowser` @@ -24,4 +28,4 @@ An in-memory implementation of the `browser` global. --- -_API reference generated by [`plugins/typescript-docs.ts`](https://github.com/aklinker1/webext-core/blob/main/docs/.vitepress/plugins/typescript-docs.ts)_ \ No newline at end of file +_API reference generated by [`docs/generate-api-references.ts`](https://github.com/aklinker1/webext-core/blob/main/docs/generate-api-references.ts)_ \ No newline at end of file diff --git a/docs/content/index.md b/docs/content/index.md new file mode 100644 index 0000000..065c114 --- /dev/null +++ b/docs/content/index.md @@ -0,0 +1,118 @@ +--- +title: Home +navigation: false +layout: page +main: + fluid: false +--- + +:ellipsis{right=0px width=75% blur=150px} + +::block-hero +--- +cta: + - Get started + - /get-started/introduction +secondary: + - Open on GitHub → + - https://github.com/aklinker1/webext-core +--- + +#title +Web extension development made easy + +#description +Easy-to-use utilities for writing and testing web extensions that work on all browsers. + +#support + ::terminal + --- + content: + - npm i @webext-core/storage + - npm i @webext-core/messaging + - npm i @webext-core/proxy-service + - npm i @webext-core/fake-browser + - npm i @webext-core/job-scheduler + - ... + --- + :: +:: + +::card-grid +#title +All Packages + +#root +:ellipsis{left=0px width=40rem top=10rem blur=140px} + +#default + ::card{icon=noto:optical-disk} + #title + `@webext-core/storage` + #description + An alternative, type-safe API similar to local storage for accessing extension storage. +
+
+ [Go to docs →](/storage/installation) + :: + + ::card{icon=noto:left-speech-bubble} + #title + `@webext-core/messaging` + #description + A simpler, type-safe API for sending and recieving messages. +
+
+ [Go to docs →](/messaging/installation) + :: + + ::card{icon=noto:construction-worker} + #title + `@webext-core/job-scheduler` + #description + Easily schedule and manage reoccuring jobs. +
+
+ [Go to docs →](/job-scheduler/installation) + :: + + ::card{icon=noto:thumbs-up} + #title + `@webext-core/match-patterns` + #description + Utilities for working with match patterns. +
+
+ [Go to docs →](/match-patterns/installation) + :: + + ::card{icon=noto:oncoming-bus} + #title + `@webext-core/proxy-service` + #description + Call a function, but execute in a different JS context, like the background. +
+
+ [Go to docs →](/proxy-service/installation) + :: + + ::card{icon=noto:puzzle-piece} + #title + `@webext-core/isolated-element` + #description + Create a container who's styles are isolated from the page's styles. +
+
+ [Go to docs →](/isolated-element/installation) + :: + + ::card{icon=noto:rocket} + #title + `@webext-core/fake-browser` + #description + An in-memory implementation of webextension-polyfill for testing. +
+
+ [Go to docs →](/fake-browser/installation) + :: +:: diff --git a/docs/guide/isolated-element/index.md b/docs/content/isolated-element/0.installation.md similarity index 85% rename from docs/guide/isolated-element/index.md rename to docs/content/isolated-element/0.installation.md index 541be74..d5a708e 100644 --- a/docs/guide/isolated-element/index.md +++ b/docs/content/isolated-element/0.installation.md @@ -1,19 +1,6 @@ ---- -titleTemplate: '@webext-core/isolated-element' -next: - text: API Reference - link: /api/isolated-element ---- - -# Isolated Element - - - - - - - - +# Installation + +:badge[MV2]{type="success"} :badge[MV3]{type="success"} :badge[Chrome]{type="success"} :badge[Firefox]{type="success"} :badge[Safari]{type="success"} ## Overview diff --git a/docs/api/isolated-element.md b/docs/content/isolated-element/api.md similarity index 60% rename from docs/api/isolated-element.md rename to docs/content/isolated-element/api.md index d8cb072..e4ee17a 100644 --- a/docs/api/isolated-element.md +++ b/docs/content/isolated-element/api.md @@ -1,14 +1,18 @@ -# API Reference - `isolated-element` +# API Reference -> [`@webext-core/isolated-element`](/guide/isolated-element/) +::alert + +See [`@webext-core/isolated-element`](/isolated-element/installation/) + +:: ## `createIsolatedElement` ```ts async function createIsolatedElement( - options: CreateIsolatedElementOptions + options: CreateIsolatedElementOptions, ): Promise<{ parentElement: HTMLElement; isolatedElement: HTMLElement; @@ -36,6 +40,7 @@ Create an HTML element that has isolated styles from the rest of the page. const { isolatedElement, parentElement } = createIsolatedElement({ name: 'example-ui', css: { textContent: "p { color: red }" }, + isolateEvents: true // or ['keydown', 'keyup', 'keypress'] }); // Create and mount your app inside the isolation @@ -54,6 +59,7 @@ interface CreateIsolatedElementOptions { name: string; mode?: "open" | "closed"; css?: { url: string } | { textContent: string }; + isolateEvents?: boolean | string[]; } ``` @@ -61,14 +67,19 @@ Options that can be passed into `createIsolatedElement`. ### Properties -- ***`name: string`***
A unique tag name used when defining the web component used internally. Don't use the same name twice for different UIs. +- ***`name: string`***
A unique HTML tag name (two words, kebab case - [see spec](https://html.spec.whatwg.org/multipage/custom-elements.html#valid-custom-element-name)) used when defining the web component used internally. Don't use the same name twice for different UIs. - ***`mode?: 'open' | 'closed'`*** (default: `'closed'`)
See [`ShadowRoot.mode`](https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot/mode). - ***`css?: { url: string } | { textContent: string }`***
Either the URL to a CSS file or the text contents of a CSS file. The styles will be mounted inside the shadow DOM so they don't effect the rest of the page. +- ***`isolateEvents?: boolean | string[]`***
When enabled, `event.stopPropagation` will be called on events trying to bubble out of the shadow root. + +- Set to `true` to stop the propagation of a default set of events, `["keyup", "keydown", "keypress"]` +- Set to an array of event names to stop the propagation of a custom list of events +

--- -_API reference generated by [`plugins/typescript-docs.ts`](https://github.com/aklinker1/webext-core/blob/main/docs/.vitepress/plugins/typescript-docs.ts)_ \ No newline at end of file +_API reference generated by [`docs/generate-api-references.ts`](https://github.com/aklinker1/webext-core/blob/main/docs/generate-api-references.ts)_ \ No newline at end of file diff --git a/docs/guide/job-scheduler/index.md b/docs/content/job-scheduler/0.installation.md similarity index 85% rename from docs/guide/job-scheduler/index.md rename to docs/content/job-scheduler/0.installation.md index b9392d6..1dcd2aa 100644 --- a/docs/guide/job-scheduler/index.md +++ b/docs/content/job-scheduler/0.installation.md @@ -1,19 +1,6 @@ ---- -titleTemplate: '@webext-core/job-scheduler' -next: - text: API Reference - link: /api/job-scheduler ---- - -# Job Scheduler - - - - - - - - +# Installation + +:badge[MV2]{type="success"} :badge[MV3]{type="success"} :badge[Chrome]{type="success"} :badge[Firefox]{type="success"} :badge[Safari]{type="success"} ## Overview @@ -52,7 +39,7 @@ curl -o job-scheduler.js https://cdn.jsdelivr.net/npm/@webext-core/job-scheduler `defineJobSchduler` should to be executed once in the background. It returns an object that can be used to schedule or remove jobs. -:::code-group +::code-group ```ts [background.ts] import { defineJobScheduler } from '@webext-core/job-scheduler'; @@ -60,11 +47,11 @@ import { defineJobScheduler } from '@webext-core/job-scheduler'; const jobs = defineJobScheduler(); ``` -::: +:: -Once the job scheduler is created, call `scheduleJob`. To see all the options for configuring jobs, see the [API reference](/api/job-scheduler). +Once the job scheduler is created, call `scheduleJob`. To see all the options for configuring jobs, see the [API reference](/job-scheduler/api). -:::code-group +::code-group ```ts [One time] jobs.scheduleJob({ @@ -99,7 +86,7 @@ jobs.scheduleJob({ }); ``` -::: +:: If a job has been created in the past, and nothing has changed, `scheduleJob` will do nothing. If something changed, it will update the job. @@ -109,15 +96,15 @@ To stop running a job, call `removeJob`. job.removeJob('some-old-job'); ``` -:::warning +::warning This is especially important when releasing an update after removing a job that is no longer needed - even if `scheduleJob` isn't called anymore. If you don't call `removeJob`, the alarm managed internally for that job will not be deleted. -::: +:: ## Parameterized Jobs You can't pass parameters into each individual job execution, but you can pass dependencies when scheduling a job by using higher-order functions: -:::code-group +::code-group ```ts [background.ts] import { someJob } from './someJob.ts'; @@ -140,15 +127,15 @@ function someJob(someDependency: SomeDependency) { } ``` -::: +:: ## Other JS Contexts You should only create one scheduler, and it should be created in the background page/service worker. -To schedule jobs from a UI or content script, you can use [`@webext-core/proxy-service`](/guide/proxy-service/). +To schedule jobs from a UI or content script, you can use [`@webext-core/proxy-service`](/proxy-service/installation). -:::code-group +::code-group ```ts [job-scheduler.ts] import { defineProxyService } from '@webext-core/proxy-service'; @@ -179,4 +166,4 @@ jobs.scheduleJob({ }); ``` -::: +:: diff --git a/docs/api/job-scheduler.md b/docs/content/job-scheduler/api.md similarity index 90% rename from docs/api/job-scheduler.md rename to docs/content/job-scheduler/api.md index f4974c4..ab7e640 100644 --- a/docs/api/job-scheduler.md +++ b/docs/content/job-scheduler/api.md @@ -1,8 +1,12 @@ -# API Reference - `job-scheduler` +# API Reference -> [`@webext-core/job-scheduler`](/guide/job-scheduler/) +::alert + +See [`@webext-core/job-scheduler`](/job-scheduler/installation/) + +:: ## `CronJob` @@ -101,11 +105,11 @@ interface JobScheduler { removeJob(jobId: string): Promise; on( event: "success", - callback: (job: Job, result: any) => void + callback: (job: Job, result: any) => void, ): RemoveListenerFn; on( event: "error", - callback: (job: Job, error: unknown) => void + callback: (job: Job, error: unknown) => void, ): RemoveListenerFn; } ``` @@ -164,4 +168,4 @@ Runs a job once, at a specific date/time. --- -_API reference generated by [`plugins/typescript-docs.ts`](https://github.com/aklinker1/webext-core/blob/main/docs/.vitepress/plugins/typescript-docs.ts)_ \ No newline at end of file +_API reference generated by [`docs/generate-api-references.ts`](https://github.com/aklinker1/webext-core/blob/main/docs/generate-api-references.ts)_ \ No newline at end of file diff --git a/docs/guide/match-patterns/index.md b/docs/content/match-patterns/0.installation.md similarity index 78% rename from docs/guide/match-patterns/index.md rename to docs/content/match-patterns/0.installation.md index d07f199..719b1b2 100644 --- a/docs/guide/match-patterns/index.md +++ b/docs/content/match-patterns/0.installation.md @@ -1,19 +1,6 @@ ---- -titleTemplate: '@webext-core/match-patterns' -next: - text: API Reference - link: /api/match-patterns ---- - -# Match Patterns - - - - - - - - +# Installation + +:badge[MV2]{type="success"} :badge[MV3]{type="success"} :badge[Chrome]{type="success"} :badge[Firefox]{type="success"} :badge[Safari]{type="success"} ## Overview diff --git a/docs/api/match-patterns.md b/docs/content/match-patterns/api.md similarity index 72% rename from docs/api/match-patterns.md rename to docs/content/match-patterns/api.md index 8db437b..5d191cb 100644 --- a/docs/api/match-patterns.md +++ b/docs/content/match-patterns/api.md @@ -1,8 +1,12 @@ -# API Reference - `match-patterns` +# API Reference -> [`@webext-core/match-patterns`](/guide/match-patterns/) +::alert + +See [`@webext-core/match-patterns`](/match-patterns/installation/) + +:: ## `InvalidMatchPattern` @@ -42,4 +46,4 @@ pattern.includes("http://youtube.com/watch?v=123") // false --- -_API reference generated by [`plugins/typescript-docs.ts`](https://github.com/aklinker1/webext-core/blob/main/docs/.vitepress/plugins/typescript-docs.ts)_ \ No newline at end of file +_API reference generated by [`docs/generate-api-references.ts`](https://github.com/aklinker1/webext-core/blob/main/docs/generate-api-references.ts)_ \ No newline at end of file diff --git a/docs/guide/messaging/index.md b/docs/content/messaging/0.installation.md similarity index 88% rename from docs/guide/messaging/index.md rename to docs/content/messaging/0.installation.md index b6f5aa4..ea1353d 100644 --- a/docs/guide/messaging/index.md +++ b/docs/content/messaging/0.installation.md @@ -1,22 +1,14 @@ ---- -titleTemplate: '@webext-core/messaging' ---- +# Installation -# Messaging - - - - - - - - +:badge[MV2]{type="success"} :badge[MV3]{type="success"} :badge[Chrome]{type="success"} :badge[Firefox]{type="success"} :badge[Safari]{type="success"} ## Overview `@webext-core/messaging` a simplified, type-safe wrapper around the web extension messaging APIs. It also provides a similar interface for communicating with web pages or injected scripts. -> Don't like lower-level messaging APIs? Try out [`@webext-core/proxy-service`](/guide/proxy-service/) for a more DX-friendly approach to executing code in the background script. +::alert +Don't like lower-level messaging APIs? Try out [`@webext-core/proxy-service`](/proxy-service/installation) for a more DX-friendly approach to executing code in the background script. +:: ## Installation @@ -47,7 +39,7 @@ curl -o messaging.js https://cdn.jsdelivr.net/npm/@webext-core/messaging/lib/ind First, define a protocol map: -:::code-group +::code-group ```ts [messaging.ts] interface ProtocolMap { @@ -55,13 +47,13 @@ interface ProtocolMap { } ``` -::: +:: Then call `defineExtensionMessaging`, passing your `ProtocolMap` as the first type parameter. Export the `sendMessage` and `onMessage` methods. These are what the rest of your extension will use to pass messages around. -:::code-group +::code-group ```ts [messaging.ts] import { defineExtensionMessaging } from '@webext-core/messaging'; @@ -73,11 +65,11 @@ interface ProtocolMap { export const { sendMessage, onMessage } = defineExtensionMessaging(); ``` -::: +:: Usually the `onMessage` function will be used in the background and messages will be sent from other parts of the extension. -:::code-group +::code-group ```ts [background.ts] import { onMessage } from './messaging'; @@ -95,13 +87,13 @@ const length = await sendMessage('getStringLength', 'hello world'); console.log(length); // 11 ``` -::: +:: ### Sending Messages to Tabs You can also send messages from your background script to a tab, but you need to know the `tabId`. -:::code-group +::code-group ```ts [content-script.ts] import { onMessage } from './messaging'; @@ -117,13 +109,13 @@ import { sendMessage } from './messaging'; const length = await sendMessage('getStringLength', 'hello world', tabId); ``` -::: +:: ## Window Messaging Inside a content script, you may need to communicate with a webpage or an injected script running in the page's JS context. In this case, you can use `defineWindowMessenger` or `defineCustomEventMessenger`, which use the `window.postMessage` and `CustomEvent` APIs respectively. -:::code-group +::code-group ```ts [Window] import { defineWindowMessaging } from '@webext-core/messaging/page'; @@ -151,11 +143,14 @@ export const websiteMessenger = defineCustomEventMessaging { }); ``` -::: +:: diff --git a/docs/guide/messaging/protocol-maps.md b/docs/content/messaging/1.protocol-maps.md similarity index 94% rename from docs/guide/messaging/protocol-maps.md rename to docs/content/messaging/1.protocol-maps.md index 8776b95..1ab98d2 100644 --- a/docs/guide/messaging/protocol-maps.md +++ b/docs/content/messaging/1.protocol-maps.md @@ -1,12 +1,8 @@ ---- -next: - text: API Reference - link: /api/messaging ---- - # Protocol Maps -> Only relevant to TypeScript projects. +::alert +Only relevant to TypeScript projects. +:: ## Overview diff --git a/docs/api/messaging.md b/docs/content/messaging/api.md similarity index 95% rename from docs/api/messaging.md rename to docs/content/messaging/api.md index 8e83b9f..f104986 100644 --- a/docs/api/messaging.md +++ b/docs/content/messaging/api.md @@ -1,8 +1,12 @@ -# API Reference - `messaging` +# API Reference -> [`@webext-core/messaging`](/guide/messaging/) +::alert + +See [`@webext-core/messaging`](/messaging/installation/) + +:: ## `BaseMessagingConfig` @@ -53,7 +57,7 @@ Messenger returned by `defineCustomEventMessenger`. ```ts function defineCustomEventMessaging< - TProtocolMap extends Record = Record + TProtocolMap extends Record = Record, >(config: CustomEventMessagingConfig): CustomEventMessenger { // ... } @@ -89,7 +93,7 @@ websiteMessenger.onMessage("initInjectedScript", (...) => { ```ts function defineExtensionMessaging< - TProtocolMap extends Record = Record + TProtocolMap extends Record = Record, >(config?: ExtensionMessagingConfig): ExtensionMessenger { // ... } @@ -104,7 +108,7 @@ It can be used to send messages to and from the background page/service worker. ```ts function defineWindowMessaging< - TProtocolMap extends Record = Record + TProtocolMap extends Record = Record, >(config: WindowMessagingConfig): WindowMessenger { // ... } @@ -183,7 +187,7 @@ You cannot message between tabs directly. It must go through the background scri interface GenericMessenger< TProtocolMap extends Record, TMessageExtension, - TSendMessageArgs extends any[] + TSendMessageArgs extends any[], > { sendMessage( type: TType, @@ -193,8 +197,8 @@ interface GenericMessenger< onMessage( type: TType, onReceived: ( - message: Message & TMessageExtension - ) => void | MaybePromise> + message: Message & TMessageExtension, + ) => void | MaybePromise>, ): RemoveListenerCallback; removeAllListeners(): void; } @@ -216,8 +220,8 @@ type GetDataType = T extends (...args: infer Args) => any ? Args[0] : never : T extends ProtocolWithReturn - ? T["BtVgCTPYZu"] - : T; + ? T["BtVgCTPYZu"] + : T; ``` Given a function declaration, `ProtocolWithReturn`, or a value, return the message's data type. @@ -228,8 +232,8 @@ Given a function declaration, `ProtocolWithReturn`, or a value, return the messa type GetReturnType = T extends (...args: any[]) => infer R ? R : T extends ProtocolWithReturn - ? T["RrhVseLgZW"] - : void; + ? T["RrhVseLgZW"] + : void; ``` Given a function declaration, `ProtocolWithReturn`, or a value, return the message's return type. @@ -261,7 +265,7 @@ async. ```ts interface Message< TProtocolMap extends Record, - TType extends keyof TProtocolMap + TType extends keyof TProtocolMap, > { id: number; data: GetDataType; @@ -401,4 +405,4 @@ details. --- -_API reference generated by [`plugins/typescript-docs.ts`](https://github.com/aklinker1/webext-core/blob/main/docs/.vitepress/plugins/typescript-docs.ts)_ \ No newline at end of file +_API reference generated by [`docs/generate-api-references.ts`](https://github.com/aklinker1/webext-core/blob/main/docs/generate-api-references.ts)_ \ No newline at end of file diff --git a/docs/guide/proxy-service/index.md b/docs/content/proxy-service/0.installation.md similarity index 86% rename from docs/guide/proxy-service/index.md rename to docs/content/proxy-service/0.installation.md index 95764b5..c84f803 100644 --- a/docs/guide/proxy-service/index.md +++ b/docs/content/proxy-service/0.installation.md @@ -1,22 +1,12 @@ ---- -titleTemplate: '@webext-core/proxy-service' ---- +# Installation -# Proxy Service - - - - - - - - +:badge[MV2]{type="success"} :badge[MV3]{type="success"} :badge[Chrome]{type="success"} :badge[Firefox]{type="success"} :badge[Safari]{type="success"} ## Overview `@webext-core/proxy-service` provides a simple, type-safe way to execute code in the extension's background. -:::code-group +::code-group ```ts [MathService.ts] import { defineProxyService } from '@webext-core/proxy-service'; @@ -50,7 +40,7 @@ const mathService = getMathService(); await mathService.fibonacci(100); ``` -::: +:: ## Installation @@ -83,7 +73,7 @@ Lets look at a more realistic example, IndexedDB! Since the same IndexedDB datab First, we need to implementat of our service. In this case, the service will contain CRUD operations for todos in the database: -:::code-group +::code-group ```ts [TodosRepo.ts] import { defineProxyService, flattenPromise } from '@webext-core/proxy-service'; @@ -112,13 +102,15 @@ function createTodosRepo(idbPromise: Promise) { } ``` -::: +:: -> In this example, we're using a plain object instead of a class as the service. See the [Defining Services](./defining-services) docs for examples of all the different ways to create a proxy service. +::alert +In this example, we're using a plain object instead of a class as the service. See the [Defining Services](/proxy-service/defining-services) docs for examples of all the different ways to create a proxy service. +:: In the same file, define a proxy service for our `TodosRepo`: -:::code-group +::code-group ```ts [TodosRepo.ts] // ... @@ -130,7 +122,7 @@ export const [registerTodosRepo, getTodosRepo] = defineProxyService('TodosRepo', Now that you have a service implemented, we need to tell the extension to use it! This needs to happen syncronously when your background script is loaded, so put it as high up as possible. -:::code-group +::code-group ```ts [background.ts] import { openDB } from 'idb'; @@ -141,21 +133,21 @@ const db = openDB("todos", ...); registerTodosRepo(db); ``` -::: +:: You need to call `register` synchronously at the top level of the background script to avoid race conditions between registering and accessing the service for the first time. -:::info +::alert Here, even though `openDB` returns a promise, we're not awaiting the promise until executing the functions inside the service. You can follow the pattern of passing `Promise` into your services and awaiting them internally to stay synchronous. -[`flattenPromise`](/api/proxy-service#flattenpromise) is used to make consuming this promise easier. -::: +[`flattenPromise`](/proxy-service/api#flattenpromise) is used to make consuming this promise easier. +:: And that's it. You can now access your IndexedDB database from any JS context inside your extension: -:::code-group +::code-group ```html [extension-page.html] +