feat(db): mesh data model — meshes, members, invites, audit log

- pgSchema "mesh" with 4 tables isolating the peer mesh domain
- Enums: visibility, transport, tier, role
- audit_log is metadata-only (E2E encryption enforced at broker/client)
- Cascade on mesh delete, soft-delete via archivedAt/revokedAt

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Alejandro Gutiérrez
2026-04-04 21:19:32 +01:00
commit d3163a5bff
1384 changed files with 314925 additions and 0 deletions

View File

@@ -0,0 +1,63 @@
---
title: Background service worker
description: Configure your extension's background service worker.
url: /docs/extension/structure/background
---
# Background service worker
An extension's service worker is a powerful script that runs in the background, separate from other parts of the extension. It's loaded when it is needed, and unloaded when it goes dormant.
Once loaded, an extension service worker generally runs as long as it is actively receiving events, though it [can shut down](https://developer.chrome.com/docs/extensions/develop/concepts/service-workers/lifecycle#idle-shutdown). Like its web counterpart, an extension service worker cannot access the DOM, though you can use it if needed with [offscreen documents](https://developer.chrome.com/docs/extensions/reference/api/offscreen).
Extension service workers are more than network proxies (as web service workers are often described), they run in a separate [service worker context](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers). For example, when in this context, you no longer need to worry about CORS and can fetch resources from any origin.
In addition to the [standard service worker events](https://developer.mozilla.org/docs/Web/API/ServiceWorkerGlobalScope#events), they also respond to extension events such as navigating to a new page, clicking a notification, or closing a tab. They're also registered and updated differently from web service workers.
**It's common to offload heavy computation to the background service worker**, so you should always try to do resouce-expensive operations there and send results using [Messages API](/docs/extension/structure/messaging) to other parts of the extension.
Code for the background service worker is located at `src/app/background` directory - you need to use `defineBackground` within `index.ts` file inside to allow WXT to include your script in the build.
```ts title="src/app/background/index.ts"
import { defineBackground } from "wxt/sandbox";
const main = () => {
console.log(
"Background service worker is running! Edit `src/app/background` and save to reload.",
);
};
export default defineBackground(main);
```
To see the service worker in action, reload the extension, then open its "Service Worker inspector":
![Service Worker inspector](/images/docs/extension/structure/sw-inspector.png)
You should see what we've logged in the console:
![Service Worker console](/images/docs/extension/structure/sw-log.png)
To communicate with the service worker from other parts of the extension, you can use the [Messaging API](/docs/extension/structure/messaging).
## Persisting state
<Callout>
Service workers in `dev` mode always remain in `active` state.
</Callout>
The worker becomes idle after a few seconds of inactivity, and the browser will kill its process entirely after 5 minutes. This means all state (variables, etc.) is lost unless you use a storage engine.
The simplest way to persist your background service worker's state is to use the [storage API](/docs/extension/structure/storage).
The more advanced way is to send the state to a remote database via our [backend API](/docs/extension/api/overview).
<Cards>
<Card title="Using service workers" href="https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers" description="developer.mozilla.org" />
<Card title="Migrate to a service worker" href="https://developer.chrome.com/docs/extensions/develop/migrate/to-service-workers" description="developer.chrome.com" />
<Card title="Extension service worker basics" href="https://developer.chrome.com/docs/extensions/develop/concepts/service-workers/basics" description="developer.chrome.com" />
<Card title="The extension service worker lifecycle" href="https://developer.chrome.com/docs/extensions/develop/concepts/service-workers/lifecycle" description="developer.chrome.com" />
</Cards>

View File

@@ -0,0 +1,94 @@
---
title: Content scripts
description: Learn more about content scripts.
url: /docs/extension/structure/content-scripts
---
# Content scripts
Content scripts run in the context of web pages in an isolated world. This allows multiple content scripts from various extensions to coexist without conflicting with each other's execution and to stay isolated from the page's JavaScript.
A script that ends with `.ts` will not have front-end runtime (e.g. react) bundled with it and won't be treated as a ui script, while a script that ends in `.tsx` will be.
There are many use cases for content scripts:
* Injecting a custom stylesheet into the page
* Scraping data from the current web page
* Selecting, finding, and styling elements from the current web page
* Injecting UI elements into current web page
Code for the content scripts is located in `src/app/content` directory - you need to define `.ts` or `.tsx` file inside and use `defineContentScript` to allow WXT to include your script in the build.
```ts title="src/app/content/index.ts"
export default defineContentScript({
matches: ["<all_urls>"],
async main(ctx) {
console.log(
"Content script is running! Edit `app/content` and save to reload.",
);
},
});
```
Reload your extension, open a web page, then open its inspector:
![Content Script](/images/docs/extension/structure/content-script.png)
To learn more about content scripts, e.g. how to configure only specific pages to load content scripts, how to inject them into `window` object or how to fetch data inside, please check [the official documentation](https://wxt.dev/guide/essentials/content-scripts.html).
## UI scripts
WXT has first-class support for mounting React components into the current webpage. This feature is called content scripts UI (CSUI).
![CSUI](/images/docs/extension/structure/csui.png)
An extension can have as many CSUI as needed, with each CSUI targeting a group of webpages or a specific webpage.
To get started with CSUI, create a `.tsx` file in `src/app/content` directory and use `defineContentScript` allow WXT to include your script in the build and mount your component into the current webpage:
```tsx title="src/app/content/index.tsx"
const ContentScriptUI = () => {
return (
<Button onClick={() => alert("This is injected UI!")}>
Content script UI
</Button>
);
};
export default defineContentScript({
matches: ["<all_urls>"],
cssInjectionMode: "ui",
async main(ctx) {
const ui = await createShadowRootUi(ctx, {
name: "turbostarter-extension",
position: "overlay",
anchor: "body",
onMount: (container) => {
const app = document.createElement("div");
container.append(app);
const root = ReactDOM.createRoot(app);
root.render(<ContentScriptUI />);
return root;
},
onRemove: (root) => {
root?.unmount();
},
});
ui.mount();
},
});
export default ContentScriptUI;
```
<Callout title="File extensions matters!" type="warn">
The `.tsx` extension is essential to differentiate between Content Scripts UI and regular Content Scripts. Make sure to check if you're using appropriate type of content script for your use case.
</Callout>
To learn more about content scripts UI, e.g. how to inject custom styles, fonts or the whole lifecycle of a component, please check [the official documentation](https://wxt.dev/guide/essentials/content-scripts.html#ui).
<Callout title="How does it work?">
Under the hood, the component is wrapped inside the component that implements the Shadow DOM technique, together with many helpful features. This isolation technique prevents the web page's style from affecting your component's styling and vice-versa.
[Read more about the lifecycle of CSUI](https://docs.plasmo.com/framework/content-scripts-ui/life-cycle)
</Callout>

View File

@@ -0,0 +1,95 @@
---
title: Messaging
description: Communicate between your extension's components.
url: /docs/extension/structure/messaging
---
# Messaging
Messaging API makes communication between different parts of your extension easy. To make it simple and scalable, we're leveraging `@webext-core/messaging` library.
It provides a declarative, type-safe, functional, promise-based API for sending, relaying, and receiving messages between your extension components.
## Handling messages
Based on our convention, we implemented a little abstraction on top of `@webext-core/messaging` to make it easier to use. That's why all types and keys are stored inside `lib/messaging` directory:
```ts title="lib/messaging/index.ts"
import { defineExtensionMessaging } from "@webext-core/messaging";
export const Message = {
HELLO: "hello",
} as const;
export type Message = (typeof Message)[keyof typeof Message];
interface Messages {
[Message.HELLO]: (message: string) => string;
}
export const { onMessage, sendMessage } = defineExtensionMessaging<Messages>();
```
There you need to define what will be handled under each key. To make it more secure, only `Message` enum and `onMessage` and `sendMessage` functions are exported from the module.
All message handlers are located in `src/app/background/messaging` directory under respective subdirectories.
To create a message handler, create a TypeScript module in the `background/messaging` directory. Then, include your handlers for all keys related to the message:
```ts title="app/background/messaging/hello.ts"
import { onMessage, Message } from "~/lib/messaging";
onMessage(Message.HELLO, (req) => {
const result = await querySomeApi(req.body.id);
return result;
});
```
<Callout title="Don't forget to import!" type="warn">
To make your handlers available across your extension, you need to import them
in the `background/index.ts` file. That way they could be interpreted by the
build process facilitated by WXT.
</Callout>
## Sending messages
Extension pages, content scripts, or tab pages can send messages to the handlers using the `sendMessage` function. Since we orchestrate your handlers behind the scenes, the message names are typed and will enable autocompletion in your editor:
```tsx title="app/popup/index.tsx"
import { sendMessage, Message } from "~/lib/messaging";
...
const response = await sendMessage(Message.HELLO, "Hello, world!");
console.log(response);
...
```
As it's an asynchronous operation, it's advisable to use [@tanstack/react-query](https://tanstack.com/query/latest/docs/framework/react/overview) integration to handle the response on the client side.
We're already doing it that way when fetching auth session in the `User` component:
```tsx title="hello.tsx"
export const Hello = () => {
const { data, isLoading } = useQuery({
queryKey: [Message.HELLO],
queryFn: () => sendMessage(Message.HELLO, "Hello, world!"),
});
if (isLoading) {
return <p>Loading...</p>;
}
/* do something with the data... */
return <p>{data?.message}</p>;
};
```
<Cards>
<Card href="https://webext-core.aklinker1.io/messaging/installation/" title="Messaging API" description="webext-core.aklinker1.io" />
<Card title="Message passing" description="developer.chrome.com" href="https://developer.chrome.com/docs/extensions/develop/concepts/messaging" />
</Cards>

View File

@@ -0,0 +1,50 @@
---
title: Overview
description: Learn about the structure of the extension app.
url: /docs/extension/structure/overview
---
# Overview
Every browser extension is different and can include different parts, removing the ones that are not needed.
TurboStarter ships with all the things you need to start developing your own extension including:
* **Popup window** - a small window that appears when the user clicks the extension icon.
* **Options page** - a page that appears when user enters extension settings.
* **Side panel** - a panel that appears when the user clicks sidepanel.
* **New tab page** - a page that appears when the user opens a new tab.
* **Devtools page** - a page that appears when the user opens the browser's devtools.
* **Tab pages** - custom pages shipped with the extension.
* **Content scripts** - injected scripts that run in the browser page.
* **Background scripts** - scripts that run in the background.
* **Message passing** - a way to communicate between different parts of the extension.
* **Storage** - a way to store data in the extension.
All the entrypoints are defined in `apps/extension/src/app` directory (it's similar to file-based routing in Next.js and Expo).
This directory acts as a source for WXT framework which is used to build the extension. It has the following structure:
<Files>
<Folder name="app" defaultOpen>
<Folder name="background - Background service worker" />
<Folder name="content - Content scripts" />
<Folder name="devtools - Devtools page with custom panels" />
<Folder name="newtab - New tab page" />
<Folder name="options - Options page" />
<Folder name="popup - Popup window" />
<Folder name="sidepanel - Side panel" />
<Folder name="tabs - Custom pages shipped with the extension" />
</Folder>
</Files>
By structurizing it this way, we can easily add new entrypoints in the future and extend rest of the extension independently from each other.
We'll go through each part and explain the purpose of it, check following sections for more details:

View File

@@ -0,0 +1,77 @@
---
title: Pages
description: Get started with your extension's pages.
url: /docs/extension/structure/pages
---
# Pages
Extension pages are built-in pages recognized by the browser. They include the extension's popup, options, sidepanel and newtab pages.
<Callout>
As WXT is based on Vite, it has very powerful [HMR support](https://vite.dev/guide/features#hot-module-replacement). This means that you don't need to refresh the extension manually when you make changes to the code.
</Callout>
## Popup
The popup page is a small dialog window that opens when a user clicks on the extension's icon in the browser toolbar. It is the most common type of extension page.
![Popup window](/images/docs/extension/structure/popup.png)
<Cards>
<Card title="Add a popup" href="https://developer.chrome.com/docs/extensions/develop/ui/add-popup" description="developer.chrome.com" />
<Card title="Entrypoints" href="https://wxt.dev/guide/essentials/entrypoints.html" description="wxt.dev" />
</Cards>
## Options
The options page is meant to be a dedicated place for the extension's settings and configuration.
![Options page](/images/docs/extension/structure/options.png)
<Card title="Give users options" href="https://developer.chrome.com/docs/extensions/develop/ui/options-page" description="developer.chrome.com" />
## Devtools
The devtools page is a custom page (including panels) that opens when a user opens the extension's devtools panel.
![Devtools page](/images/docs/extension/structure/devtools.png)
<Card title="Extend devtools" href="https://developer.chrome.com/docs/extensions/how-to/devtools/extend-devtools" description="developer.chrome.com" />
## New tab
The new tab page is a custom page that opens when a user opens a new tab in the browser.
![New tab page](/images/docs/extension/structure/newtab.png)
<Card title="Override Chrome pages" href="https://developer.chrome.com/docs/extensions/develop/ui/override-chrome-pages" description="developer.chrome.com" />
## Side panel
The side panel is a custom page that opens when a user clicks on the extension's icon in the browser toolbar.
![Side panel](/images/docs/extension/structure/sidepanel.png)
<Card title="Side panel" href="https://developer.chrome.com/docs/extensions/reference/api/sidePanel" description="developer.chrome.com" />
## Tabs
Unlike traditional extension pages, tab (unlisted) pages are just regular web pages shipped with your extension bundle. Extensions generally redirect to or open these pages programmatically, but you can link to them as well.
They could be useful for following cases:
* when you want to show a some page when user first installs your extension
* when you want to have dedicated pages for authentication
* when you need more advanced routing setup
![Tab page](/images/docs/extension/structure/tabs.png)
Your tab page will be available under the `/tabs` path in the extension bundle. It will be accessible from the browser under the URL:
```
chrome-extension://<your-extension-id>/tabs/your-tab-page.html
```
<Card title="Unlisted pages" href="https://wxt.dev/guide/essentials/entrypoints.html#unlisted-pages" description="wxt.dev" />

View File

@@ -0,0 +1,109 @@
---
title: Storage
description: Learn how to store data in your extension.
url: /docs/extension/structure/storage
---
# Storage
TurboStarter leverages `wxt/storage` library to handle persistent storage for your extension. It's a utility library from that abstracts the persistent storage API available to browser extensions.
It falls back to localStorage when the extension storage API is unavailable, allowing for state sync between extension pages, content scripts, background service workers and web pages.
<Callout>
To use the `wxt/storage` API, the "storage" permission **must** be added to the manifest:
```ts title="wxt.config.ts"
export default defineConfig({
manifest: {
permissions: ["storage"],
},
});
```
</Callout>
## Storing data
The base Storage API is designed to be easy to use. It is usable in every extension runtime such as background service workers, content scripts and extension pages.
TurboStarter ships with predefined storage used to handle [theming](/docs/extension/customization/styling) in your extension, but you can create your own storage as well.
All storage-related methods and types are located in `lib/storage` directory.
```ts title="lib/storage/index.ts"
export const StorageKey = {
THEME: "local:theme",
} as const;
export type StorageKey = (typeof StorageKey)[keyof typeof StorageKey];
```
Then, to make it available around your extension, we're setting it up and providing default values:
```ts title="lib/storage/index.ts"
import { storage as browserStorage } from "wxt/storage";
import { appConfig } from "~/config/app";
import type { ThemeConfig } from "@turbostarter/ui";
const storage = {
[StorageKey.THEME]: browserStorage.defineItem<ThemeConfig>(StorageKey.THEME, {
fallback: appConfig.theme,
}),
} as const;
```
To learn more about customizing your storage, syncing state or setup automatic backups please refer to the [official documentation](https://wxt.dev/storage.html).
## Consuming storage
To consume storage in your extension, you can use the `useStorage` React hook that is automatically provided to every part of the extension. The hook API is designed to streamline the state-syncing workflow between the different pieces of an extension.
Here is an example on how to consume our theme storage in `Layout` component:
```tsx title="modules/common/layout/layout.tsx"
import { StorageKey, useStorage } from "~/lib/storage";
export const Layout = ({ children }: { children: React.ReactNode }) => {
const { data } = useStorage(StorageKey.THEME);
return (
<div id="root" data-theme={data.color}>
{children}
</div>
);
};
```
Congrats! You've just learned how to persist and consume global data in your extension 🎉
For more advanced use cases, please refer to the [official documentation](https://wxt.dev/storage.html).
### Usage with Firefox
To use the storage API on Firefox during development you need to add an addon ID to your manifest, otherwise, you will get this error:
> Error: The storage API will not work with a temporary addon ID. Please add an explicit addon ID to your manifest. For more information see [https://mzl.la/3lPk1aE](https://mzl.la/3lPk1aE)
To add an addon ID to your manifest, add this to your package.json:
```ts title="wxt.config.ts"
export default defineConfig({
manifest: {
browser_specific_settings: {
gecko: {
id: "your-id@example.com",
},
},
},
});
```
During development, you may use any ID. If you have published your extension, you need to use the ID assigned by [Firefox Add-ons](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons).
<Cards>
<Card title="Storage API" href="https://wxt.dev/storage.html" description="wxt.dev" />
<Card title="chrome.storage" href="https://developer.chrome.com/docs/extensions/reference/api/storage" description="developer.chrome.com" />
</Cards>