WebExt Core
Proxy Service

Installation

MV2 MV3 Chrome Firefox Safari

Overview

@webext-core/proxy-service provides a simple, type-safe way to execute code in the extension's background.

// 1. Define your service
export class MathService {
  async fibonacci(number: number): Promise<number> {
    ...
  }
}

Installation

NPM
pnpm i @webext-core/proxy-service
import { createProxyService, registerService } from '@webext-core/proxy-service';
CDN
curl -o proxy-service.js https://cdn.jsdelivr.net/npm/@webext-core/proxy-service/lib/index.global.js
<script src="/proxy-service.js"></script>
<script>
  const { createProxyService, registerService } = webExtCoreProxyService;
</script>

Usage

Lets look at a more realistic example, IndexedDB! Since the same IndexedDB database is not available in every JS context of an extension, it's common to use the IndexedDB instance in the background script as a database for web extensions.

First, we need to implementat of our service. In this case, the service will contain CRUD operations for todos in the database:

import { IDBPDatabase } from 'idb';

export function createTodosRepo(idbPromise: Promise<IDBPDatabase>) {
  return {
    async create(todo: Todo): Promise<void> {
      const idb = await idbPromise;
      await idb.add('todos', todo);
    },
    async getOne(id: Pick<Todo, 'id'>): Promise<Todo> {
      const idb = await idbPromise;
      return await idb.get('todos', id);
    },
    async getAll(): Promise<Todo[]> {
      const idb = await idbPromise;
      return await idb.getAll('todos');
    },
    async update(todo: Todo): Promise<void> {
      const idb = await idbPromise;
      await idb.put('todos', todo);
    },
    async delete(todo: Todo): Promise<void> {
      const idb = await idbPromise;
      await idb.delete('todos', todo.id);
    },
  };
}
In this example, we're using a plain object instead of a class as the service. See the Defining Services docs for examples of all the different ways to create a proxy service.

Now that you have a service implemented, we need to register it in the background so it starts listening for messages from other parts of the extension.

import type { ProxyServiceKey } from '@webext-core/proxy-service';
import type { TodosRepo } from './todos-repo';

export const TODOS_REPO_KEY = 'todos-repo' as ProxyServiceKey<TodosRepo>;

Here, even though openDB returns a promise, we're not awaiting it because registerService must be called synchronously on service worker/background script startup. Otherwise, the message listeners might not be setup by the time a content script tries to proxy a function call.

You can follow the pattern of passing Promise<Dependency> into your services and awaiting them internally to stay synchronous.

And that's it. You can now access your IndexedDB database from any JS context inside your extension:

<script type="module">
  import { TODOS_REPO_KEY } from './proxy-service-keys';
  import { createProxyService } from '@webext-core/proxy-service';

  // On your UIs
  const todosRepo = createProxyService(TODOS_REPO_KEY);
  const todos = await todosRepo.getAll();
  console.log(todos);
</script>