MV2 MV3 Chrome Firefox Safari
@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> {
...
}
}
import type { ProxyServiceKey } from '@webext-core/proxy-service';
import type { MathService } from './MathService';
// ^^^^ IMPORTANT: do not import the math service's value, just it's type.
// 2. [Optional] Define a key with a branded type to ensure type-safety
export const MATH_SERVICE_KEY = 'math-service' as ProxyServiceKey<MathService>;
import { registerService } from '@webext-core/proxy-service';
import { MathService } from './MathService';
import { MATH_SERVICE_KEY } from './proxy-service-keys';
// 3. Instantiate your service
const mathService = new MathService();
// 4. Register the service BEFORE awaiting anything
registerService(MATH_SERVICE_KEY, mathService);
import { createProxyService } from './MathService';
import { MATH_SERVICE_KEY } from './proxy-service-keys';
// 5. Get a proxy of your service
const mathService = createProxyService(MATH_SERVICE_KEY);
// 6. Call methods like normal, they will execute in the background
await mathService.fibonacci(100);
pnpm i @webext-core/proxy-service
import { createProxyService, registerService } from '@webext-core/proxy-service';
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>
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);
},
};
}
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>;
import { registerService } from '@webext-core/proxy-service';
import { openDB } from 'idb';
import { createTodosRepo } from './todos-repo';
import { TODOS_REPO_KEY } from './proxy-service-keys';
// DO NOT await the promise here. registerService must be called synchronously.
const idbPromise = openDB("todos", ...);
const todosRepo = createTodosRepo(idbPromise);
registerService(TODOS_REPO_KEY, 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>
import { TODOS_REPO_KEY } from './proxy-service-keys';
import { createProxyService } from '@webext-core/proxy-service';
// Inside content scripts
const todosRepo = createProxyService(TODOS_REPO_KEY);
const todos = await todosRepo.getAll();
console.log(todos);