Skip to content

Writing a module ​

Every built-in module uses the same public interface, so you can write your own. A module has a name, reports capabilities in the device's hello, and talks to the UI with messages in its own namespace, module.<name>.*.

ts
import {defineModule, ModuleError} from '@himmetkaan/remote-devtools';

export function featureTourModule(tour: Tour) {
    return defineModule({
        name: 'tour',

        // Sent with `hello` and again after ctx.updateCapabilities().
        capabilities: () => ({steps: tour.steps.map((s) => s.id)}),

        // Called once when the SDK is enabled; the returned function runs on destroy().
        setup(ctx) {
            // Request / response: the return value (or thrown error) becomes the reply.
            ctx.handle<{stepId: string}>('module.tour.show', ({stepId}) => {
                if (!tour.has(stepId)) throw new ModuleError('UNKNOWN_STEP', `No step "${stepId}"`);
                return tour.show(stepId);
            });

            // Fire-and-forget messages from the UI.
            ctx.on('module.tour.reset', () => tour.reset());

            // Events to the UI. Buffered in `history` while disconnected unless {buffer: false}.
            const off = tour.onStep((step) => ctx.emit('module.tour.step', {stepId: step.id, at: Date.now()}));

            return off;
        },
    });
}

The module context ​

MemberPurpose
ctx.emit(type, payload, {buffer?})Send an event to the UI
ctx.handle(type, handler)Answer a UI request; the result is sent as module.<name>.result with replyTo
ctx.on(type, listener)Listen for UI messages that need no answer
ctx.updateCapabilities()Re-send capabilities() (for example after registering something)
ctx.storagePersistent key-value storage, namespaced to the module
ctx.isConnected() / ctx.deviceIdConnection info
ctx.logThe SDK logger

A module can only use its own module.<name>. message types. Payloads are serialised safely before sending.

Showing it in the UI ​

The relay forwards module.* messages without looking at them, so a new module needs no relay changes. The UI only draws tabs for modules it knows; a module it does not know is listed on the device card. To get a tab without changing the UI, build it with custom panels instead, or add a UI module to packages/ui/src/modules (see Development setup).

Released under the MIT License.