Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
f166b3d
feat(omnium): Add controller architecture and CapletController
rekmarks Jan 8, 2026
83c867d
refactor(omnium): Simplify CapletController state structure
rekmarks Jan 8, 2026
93705fc
refactor(omnium): Add abstract Controller base class
rekmarks Jan 9, 2026
3f498e6
refactor(omnium): Refactor ControllerStorage with debounced persistence
rekmarks Jan 9, 2026
c755a35
refactor(omnium): Simplify CapletId validation to allow any ASCII string
rekmarks Jan 10, 2026
e8bc97c
chore: Update coverage
rekmarks Jan 15, 2026
e359423
refactor: Fix omnium background globals
rekmarks Jan 15, 2026
1ab49c9
refactor: Always close the offscreen stream on startup failure
rekmarks Jan 20, 2026
9583876
Merge branch 'main' into rekm/controller-architecture
rekmarks Jan 20, 2026
1fcacb5
refactor: Address bugbot review
rekmarks Jan 21, 2026
a43c7f2
refactor: Remove caplet services
rekmarks Jan 21, 2026
ebdbb3d
fix: Decrease wait times in logger ipc test
rekmarks Jan 21, 2026
b6e2f19
refactor: Clean up some controller code
rekmarks Jan 21, 2026
f3b866b
test: Improve controller test reliability and add error handling tests
rekmarks Jan 21, 2026
e015435
refactor(controllers): Simplify test setup and remove facet abstraction
rekmarks Jan 22, 2026
60c609e
Merge branch 'main' into rekm/controller-architecture
rekmarks Jan 22, 2026
bd6bfea
docs: Add TODO for caplet manifest validation
rekmarks Jan 22, 2026
94e4677
fix: Fix omnium.caplet types
rekmarks Jan 22, 2026
0e330b6
fix: Forbid ASCII control characters in caplet ids
rekmarks Jan 22, 2026
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
8 changes: 6 additions & 2 deletions packages/extension/src/background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,12 @@ async function main(): Promise<void> {
});
drainPromise.catch(logger.error);

await ping(); // Wait for the kernel to be ready
await startDefaultSubcluster(kernelP);
try {
await ping(); // Wait for the kernel to be ready
await startDefaultSubcluster(kernelP);
} catch (error) {
offscreenStream.throw(error as Error).catch(logger.error);
}

try {
await drainPromise;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { Kernel } from '@metamask/ocap-kernel';
import { describe, it, expect, vi, beforeEach } from 'vitest';

import { makeKernelCapTP } from './kernel-captp.ts';
import type { CapTPMessage } from './kernel-captp.ts';
import type { CapTPMessage } from '../../types.ts';

describe('makeKernelCapTP', () => {
const mockKernel: Kernel = {} as unknown as Kernel;
Expand Down
1 change: 1 addition & 0 deletions packages/kernel-utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export type {
JsonRpcCall,
JsonRpcMessage,
PromiseCallbacks,
Promisified,
TypeGuard,
} from './types.ts';
export {
Expand Down
12 changes: 12 additions & 0 deletions packages/kernel-utils/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,18 @@ export type PromiseCallbacks<Resolve = unknown> = Omit<
'promise'
>;

/**
* Utility type that wraps all method return types in Promise.
* Methods already returning Promise<T> remain Promise<T>.
*/
export type Promisified<T> = {
[K in keyof T]: T[K] extends (...args: infer A) => Promise<infer R>
? (...args: A) => Promise<R>
: T[K] extends (...args: infer A) => infer R
? (...args: A) => Promise<R>
: T[K];
};

export const EmptyJsonArray = empty(array(UnsafeJsonStruct));

export type EmptyJsonArray = Infer<typeof EmptyJsonArray>;
Expand Down
4 changes: 2 additions & 2 deletions packages/logger/test/ipc.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ describe('ipc', () => {
const logger = new Logger({ transports: [mockTransport] });
logger.injectStream(stream as unknown as DuplexStream<LogMessage>);

await waitUntilQuiescent(1000);
await waitUntilQuiescent(10);
Copy link
Member Author

Choose a reason for hiding this comment

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

Incidental change after the root test:dev was failing due to these timeouts being needlessly long.

expect(mockTransport).toHaveBeenCalledWith({
level: 'debug',
message: 'Hello, world!',
Expand All @@ -53,7 +53,7 @@ describe('ipc', () => {
const logger = new Logger({ transports: [mockTransport] });
logger.injectStream(loggerStream as unknown as DuplexStream<LogMessage>);

await waitUntilQuiescent(1000);
await waitUntilQuiescent(10);
expect(mockTransport).toHaveBeenCalledWith({
level: 'debug',
message: 'Hello, world!',
Expand Down
7 changes: 7 additions & 0 deletions packages/omnium-gatherum/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,20 @@
},
"dependencies": {
"@endo/eventual-send": "^1.3.4",
"@endo/exo": "^1.5.12",
"@metamask/kernel-browser-runtime": "workspace:^",
"@metamask/kernel-shims": "workspace:^",
"@metamask/kernel-ui": "workspace:^",
"@metamask/kernel-utils": "workspace:^",
"@metamask/logger": "workspace:^",
"@metamask/ocap-kernel": "workspace:^",
"@metamask/streams": "workspace:^",
"@metamask/superstruct": "^3.2.1",
"@metamask/utils": "^11.9.0",
"immer": "^10.1.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"semver": "^7.7.1",
"ses": "^1.14.0"
},
"devDependencies": {
Expand All @@ -70,6 +76,7 @@
"@types/chrome": "^0.0.313",
"@types/react": "^18.3.18",
"@types/react-dom": "^18.3.5",
"@types/semver": "^7.7.1",
"@types/webextension-polyfill": "^0",
"@typescript-eslint/eslint-plugin": "^8.29.0",
"@typescript-eslint/parser": "^8.29.0",
Expand Down
76 changes: 62 additions & 14 deletions packages/omnium-gatherum/src/background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,20 @@ import type { JsonRpcMessage } from '@metamask/kernel-utils';
import { Logger } from '@metamask/logger';
import { ChromeRuntimeDuplexStream } from '@metamask/streams/browser';

defineGlobals();
import { initializeControllers } from './controllers/index.ts';
import type {
CapletControllerFacet,
CapletManifest,
} from './controllers/index.ts';

const OFFSCREEN_DOCUMENT_PATH = '/offscreen.html';
const logger = new Logger('background');
const globals = defineGlobals();
let bootPromise: Promise<void> | null = null;
let kernelP: Promise<KernelFacade>;
let ping: () => Promise<void>;

// With this we can click the extension action button to wake up the service worker.
chrome.action.onClicked.addListener(() => {
ping?.().catch(logger.error);
omnium.ping?.().catch(logger.error);
});

// Install/update
Expand Down Expand Up @@ -104,12 +107,23 @@ async function main(): Promise<void> {
},
});

kernelP = backgroundCapTP.getKernel();
const kernelP = backgroundCapTP.getKernel();
globals.setKernelP(kernelP);

ping = async (): Promise<void> => {
globals.setPing(async (): Promise<void> => {
const result = await E(kernelP).ping();
logger.info(result);
};
});

try {
const controllers = await initializeControllers({
logger,
kernel: kernelP,
});
globals.setCapletController(controllers.caplet);
} catch (error) {
offscreenStream.throw(error as Error).catch(logger.error);
}

try {
await offscreenStream.drain((message) => {
Expand All @@ -129,31 +143,65 @@ async function main(): Promise<void> {
}
}

type GlobalSetters = {
setKernelP: (value: Promise<KernelFacade>) => void;
setPing: (value: () => Promise<void>) => void;
setCapletController: (value: CapletControllerFacet) => void;
};

/**
* Define globals accessible via the background console.
*
* @returns A device for setting the global values.
*/
function defineGlobals(): void {
function defineGlobals(): GlobalSetters {
Object.defineProperty(globalThis, 'E', {
configurable: false,
enumerable: true,
writable: false,
value: E,
});

Object.defineProperty(globalThis, 'omnium', {
configurable: false,
enumerable: true,
writable: false,
value: {},
});

let kernelP: Promise<KernelFacade>;
let ping: (() => Promise<void>) | undefined;
let capletController: CapletControllerFacet;

Object.defineProperties(globalThis.omnium, {
ping: {
get: () => ping,
},
getKernel: {
value: async () => kernelP,
},
caplet: {
value: harden({
install: async (manifest: CapletManifest) =>
E(capletController).install(manifest),
uninstall: async (capletId: string) =>
E(capletController).uninstall(capletId),
list: async () => E(capletController).list(),
get: async (capletId: string) => E(capletController).get(capletId),
}),
},
});
harden(globalThis.omnium);

Object.defineProperty(globalThis, 'E', {
configurable: false,
enumerable: true,
writable: false,
value: E,
});
return {
setKernelP: (value) => {
kernelP = value;
},
setPing: (value) => {
ping = value;
},
setCapletController: (value) => {
capletController = value;
},
};
}
Loading
Loading