Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Binary file modified bun.lockb
Binary file not shown.
24 changes: 15 additions & 9 deletions docs/content/messaging/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ interface BaseMessagingConfig {

Shared configuration between all the different messengers.

### Properties
### Properties

- ***`logger?: Logger`*** (default: `console`)<br/>The logger to use when logging messages. Set to `null` to disable logging.

Expand All @@ -35,7 +35,7 @@ interface CustomEventMessage {

Additional fields available on the `Message` from a `CustomEventMessenger`.

### Properties
### Properties

- ***`event: CustomEvent`***<br/>The event that was fired, resulting in the message being passed.

Expand Down Expand Up @@ -153,7 +153,7 @@ interface ExtensionMessage {

Additional fields available on the `Message` from an `ExtensionMessenger`.

### Properties
### Properties

- ***`sender: Runtime.MessageSender`***<br/>Information about where the message came from. See
[`Runtime.MessageSender`](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/MessageSender).
Expand Down Expand Up @@ -196,6 +196,12 @@ interface GenericMessenger<
TMessageExtension,
TSendMessageArgs extends any[],
> {
sendMessage<TType extends keyof TProtocolMap>(
type: TType,
...args: GetDataType<TProtocolMap[TType]> extends undefined
? [data?: undefined, ...args: TSendMessageArgs]
: never
): Promise<GetReturnType<TProtocolMap[TType]>>;
sendMessage<TType extends keyof TProtocolMap>(
type: TType,
data: GetDataType<TProtocolMap[TType]>,
Expand Down Expand Up @@ -283,7 +289,7 @@ interface Message<

Contains information about the message received.

### Properties
### Properties

- ***`id: number`***<br/>A semi-unique, auto-incrementing number used to trace messages being sent.

Expand All @@ -306,7 +312,7 @@ interface MessageSender {

An object containing information about the script context that sent a message or request.

### Properties
### Properties

- ***`tab?: Tabs.Tab`***<br/>The $(ref:tabs.Tab) which opened the connection, if any. This property will <strong>only</strong>
be present when the connection was opened from a tab (including content scripts), and <strong>only</strong>
Expand All @@ -332,7 +338,7 @@ interface NamespaceMessagingConfig extends BaseMessagingConfig {
}
```

### Properties
### Properties

- ***`namespace: string`***<br/>A string used to ensure the messenger only sends messages to and listens for messages from
other messengers of the same type, with the same namespace.
Expand All @@ -354,7 +360,7 @@ Used to add a return type to a message in the protocol map.

> Internally, this is just an object with random keys for the data and return types.

### Properties
### Properties

- ***`BtVgCTPYZu: TData`***<br/>Stores the data type. Randomly named so that it isn't accidentally implemented.

Expand Down Expand Up @@ -392,7 +398,7 @@ interface SendMessageOptions {

Options for sending a message to a specific tab/frame

### Properties
### Properties

- ***`tabId: number`***<br/>The tab to send a message to

Expand Down Expand Up @@ -429,4 +435,4 @@ details.

---

_API reference generated by [`docs/generate-api-references.ts`](https://github.com/aklinker1/webext-core/blob/main/docs/generate-api-references.ts)_
_API reference generated by [`docs/generate-api-references.ts`](https://github.com/aklinker1/webext-core/blob/main/docs/generate-api-references.ts)_
12 changes: 7 additions & 5 deletions docs/content/proxy-service/0.installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,19 @@ class MathService {
...
}
}
export const [registerMathService, getMathService] = defineProxyService(
'MathService',
() => new MathService(),
);
export const [registerMathService, getMathService] = defineServiceProxy<MathService>('MathService');
```

```ts [background.ts]
import { registerMathService } from './MathService';

// 2. Register the service at the beginning of the background script
registerMathService();
export default defineBackground(() => {
registerMathService(async () => {
const { MathService } = await import('../utils/math-service');
return new MathService();
});
});
Comment on lines -30 to +32
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is something I'm confused about. I saw in the PR description something about dynamic imports and top-level awaits being required... but that's not true?

You could just import the math-service module here like normal, right? You just can't import it in the entrypoints that use proxies instead.

Here's what I'm thinking, might be a nicer API:

// math-service.ts
export class MathService {}

// background.ts
import { MathService } from '../math-service'; // import the real thing where it is needed
import { registerProxyService } from '@webext-core/proxy-service';

registerProxyService<MathSerivce>('math-service', new MathService())

// content-script.ts
import type { MathService } from '../math-service'; // Import just the types so it's not bundled
import { getServiceProxy } from '@webext-core/proxy-service';

const mathService = getServiceProxy<MathService>('math-service')

We could even do something like what Vue does with provide/inject keys and support a custom branded type that let's us infer the service type from the key

// service-proxy-keys.ts
import type { MathService } from '../math-service'; // Important to only import type here

export const ServiceProxyKey = {
  MathService: createProxyServiceKey<MathService>('math-service'),
  // ...
}

// background.ts
import { MathService } from '../math-service';
import { ServiceProxyKey } from '../service-proxy-keys';
import { registerProxyService } from '@webext-core/proxy-service';

registerProxyService(ServiceProxyKey.MathService, new MathService())

// content-script.ts
import { ServiceProxyKey } from '../service-proxy-keys';
import { getServiceProxy } from '@webext-core/proxy-service';

const mathService = getServiceProxy(ServiceProxyKey.MathService)

That way the key and type are tied together.

Copy link
Owner

@aklinker1 aklinker1 Oct 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow, I was very inconsistent in the naming in my suggestions lol. Since the package is called "proxy service", we should stick with that when naming things. I'm OK releasing a major version and straight up dropping the defineProxyService function without any deprecation notice.

So I'm proposing 3 functions:

  • registerProxyService - takes in a key and actual service implementation, registering messaging listeners for it
  • createProxyService - takes in just a key and returns the proxy object
  • createProxyServiceKey - Helper for defining a branded type that the above 2 functions can infer the service type from

This is still in the brainstorming phase, so I'm open for naming changes to any of those APIs. I'm just trying to summarize my suggestions from the last comment.

```

```ts [anywhere-else.ts]
Expand Down
68 changes: 31 additions & 37 deletions docs/content/proxy-service/1.defining-services.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,23 @@ Define a class whose methods are available in other JS contexts:

```ts
import { openDB, IDBPDatabase } from 'idb';
import { defineProxyService } from '@webext-core/proxy-service';
import { defineServiceProxy } from '@webext-core/proxy-service';

class TodosRepo {
export class TodosRepo {
constructor(private db: Promise<IDBPDatabase>) {}

async getAll(): Promise<Todo[]> {
return (await this.db).getAll('todos');
}
}

export const [registerTodosRepo, getTodosRepo] = defineProxyService(
'TodosRepo',
(idb: Promise<IDBPDatabase>) => new TodosRepo(idb),
);
export const [registerTodosRepo, getTodosRepo] = defineServiceProxy<TodosRepo>('TodosRepo');
```

```ts
// Register
const db = openDB('todos');
registerTodosRepo(db);
registerTodosRepo((idb: Promise<IDBPDatabase>) => new TodosRepo(idb), db);
```

```ts
Expand All @@ -42,24 +39,24 @@ Objects can be used as services as well. All functions defined on the object are

```ts
import { openDB, IDBPDatabase } from 'idb';
import { defineProxyService } from '@webext-core/proxy-service';
import { defineServiceProxy } from '@webext-core/proxy-service';

export const [registerTodosRepo, getTodosRepo] = defineServiceProxy<TodosRepo>('TodosRepo');
```

export const [registerTodosRepo, getTodosRepo] = defineProxyService(
'TodosRepo',
```ts
// Register
const db = openDB('todos');
await registerTodosRepo(
(db: Promise<IDBPDatabase>) => ({
async getAll(): Promise<Todo[]> {
return (await this.db).getAll('todos');
},
}),
db,
);
```

```ts
// Register
const db = openDB('todos');
registerTodosRepo(db);
```

```ts
// Get an instance
const todosRepo = getTodosRepo();
Expand All @@ -72,21 +69,20 @@ If you only need to define a single function, you can!

```ts
import { openDB, IDBPDatabase } from 'idb';
import { defineProxyService } from '@webext-core/proxy-service';
import { defineServiceProxy } from '@webext-core/proxy-service';

export const [registerGetAllTodos, getGetAllTodos] = defineProxyService(
'TodosRepo',
(db: Promise<IDBPDatabase>) =>
function getAllTodos() {
return (await this.db).getAll('todos');
},
);
export async function getAllTodos(db: Promise<IDBPDatabase>) {
return (await db).getAll('todos');
}

export const [registerGetAllTodos, getGetAllTodos] =
defineServiceProxy<typeof getAllTodos>('TodosRepo');
```

```ts
// Register
const db = openDB('todos');
registerGetAllTodos(db);
await registerGetAllTodos((db: Promise<IDBPDatabase>) => getAllTodos, db);
```

```ts
Expand All @@ -101,36 +97,34 @@ If you need to register "deep" objects containing multiple services, you can do

```ts
import { openDB, IDBPDatabase } from 'idb';
import { defineProxyService } from '@webext-core/proxy-service';
import { defineServiceProxy } from '@webext-core/proxy-service';

class TodosRepo {
export class TodosRepo {
constructor(private db: Promise<IDBPDatabase>) {}

async getAll(): Promise<Todo[]> {
return (await this.db).getAll('todos');
}
}

const createAuthorsRepo = (db: Promise<IDBPDatabase>) => ({
export const createAuthorsRepo = (db: Promise<IDBPDatabase>) => ({
async getOne(id: string): Promise<Todo[]> {
return (await this.db).getAll('authors', id);
},
});

function createApi(db: Promise<IDBPDatabase>) {
return {
todos: new TodosRepo(db),
authors: createAuthorsRepo(db),
};
}

export const [registerApi, getApi] = defineProxyService('Api', createApi);
export const [registerApi, getApi] = defineServiceProxy<TodosRepo>('Api');
```

```ts
// Register
const db = openDB('todos');
registerApi(db);
await registerApi((db: Promise<IDBPDatabase>) => {
return {
todos: new TodosRepo(db),
authors: reateAuthorsRepo(db),
};
}, db);
```

```ts
Expand Down
35 changes: 35 additions & 0 deletions docs/content/proxy-service/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ A recursive type that deeply converts all methods in `TService` to be async.

## `defineProxyService`

:::danger Deprecated
Use {@link defineServiceProxy } instead.
:::

```ts
function defineProxyService<TService extends Service, TArgs extends any[]>(
name: string,
Expand Down Expand Up @@ -54,6 +58,37 @@ of the JS context the they are called from.
- `registerService`: Used to register your service in the background
- `getService`: Used to get an instance of the service anywhere in the extension.

## `defineServiceProxy`

```ts
function defineServiceProxy<TService extends Service>(
name: string,
config?: ProxyServiceConfig,
): [
registerService: (
init: (...args: any[]) => TService | Promise<TService>,
...args: any[]
) => Promise<TService>,
getService: () => ProxyService<TService>,
] {
// ...
}
```

Utility for creating a service whose functions are executed in the background script regardless
of the JS context they are called from.

### Parameters

- ***`name: string`***<br/>A unique name for the service. Used to identify which service is being executed.

- ***`config?: ProxyServiceConfig`***<br/>An object that allows configuration of the underlying messaging service

### Returns

- `registerService`: Used to register your service in the background. Requires an `init()` callback used to create the actual service object.
- `getService`: Used to get an instance of the service anywhere in the extension.

## `flattenPromise`

```ts
Expand Down
5 changes: 4 additions & 1 deletion packages/proxy-service-demo/src/entrypoints/background.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
export default defineBackground(() => {
registerMathService();
registerMathService(async () => {
const { MathService } = await import('../utils/math-service');
return new MathService();
});
});
2 changes: 2 additions & 0 deletions packages/proxy-service-demo/src/entrypoints/popup/main.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { getMathService } from '../../utils/math-service';

function getMathServiceElements(id: string) {
const parent = document.getElementById(id)!;
return [
Expand Down
9 changes: 3 additions & 6 deletions packages/proxy-service-demo/src/utils/math-service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { defineProxyService } from '@webext-core/proxy-service';
import { defineServiceProxy } from '@webext-core/proxy-service';

class MathService {
export class MathService {
add(x: number, y: number): number {
console.log(`MathService.add(${x}, ${y})`);
return x + y;
Expand All @@ -25,7 +25,4 @@ class MathService {
}
}

export const [registerMathService, getMathService] = defineProxyService(
'MathService',
() => new MathService(),
);
export const [registerMathService, getMathService] = defineServiceProxy<MathService>('MathService');
3 changes: 3 additions & 0 deletions packages/proxy-service/src/defineProxyService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ import get from 'get-value';
* @returns
* - `registerService`: Used to register your service in the background
* - `getService`: Used to get an instance of the service anywhere in the extension.
*
* @deprecated
* Use {@link defineServiceProxy} instead.
*/
export function defineProxyService<TService extends Service, TArgs extends any[]>(
name: string,
Expand Down
Loading