Skip to content

End-to-End Quickstart

This guide is the consumer path: you have the npm package and the Android AAR, and you want an app running. The framework repo’s own docs describe its internal structure; this is the “make it mine” path.

Want it running instead of described? The repo ships a minimal runnable consumer at examples/consumer-basic/ (private workspace package): its own worklet entry, an app.basic plugin (one invoke + one backend → web push), the web layer, and a real-stack e2e. It is the copy-paste starting point for everything on this page:

Terminal window
npm run build:pkg # compile @ekrooh/bare to dist/ (prereq)
CONSUMER_BASIC_PORT=8091 npm run test:e2e:consumer # real-stack e2e
npm --prefix examples/consumer-basic run dev # dev loop (Vite + bare)
your-app/
├── web/ # Vite app (frontend) — imports @ekrooh/bare/transports
├── worklet/ # Bare worklet entry — imports @ekrooh/bare/runtime
└── android/ # Android app — consumes io.github.anderscan.ekrooh:bare-host

The frontend is a normal Vite page. The backend is a Bare worklet that runs your entry. The host (Android shell) starts the worklet, injects the one-time bootstrap nonce, and loads the loopback-served page.

Terminal window
npm install @ekrooh/bare

The package ships compiled ESM + types through its exports map. No build step, no TypeScript source.

Your entry is “register plugins + wire p2p + start”. It can be one file:

import {
createWorkletRuntime,
resolveWorkletConfig,
} from '@ekrooh/bare/runtime';
const runtime = createWorkletRuntime(resolveWorkletConfig());
// Register your own plugins (see "Authoring a Plugin").
// runtime.pluginRegistry.register(myPlugin);
const { origin, port, token, bootstrap } = await runtime.start();

createWorkletRuntime is synchronous and throws on auth: false unless deviceMode: false is set too. resolveWorkletConfig() reads the host-provided config from Bare.argv ([webAssets, storage, cache]). Under the bare CLI it parses labeled tokens; dev mode (auth off) needs an explicit mode=dev.

Bundle it for the Bare runtime:

Terminal window
esbuild worklet/index.ts --bundle --packages=external \
--platform=node --format=esm --outfile=worklet/app.core.js

3. Point the frontend at the protocol socket

Section titled “3. Point the frontend at the protocol socket”
import { createWebSocketTransport } from '@ekrooh/bare/transports';
const transport = createWebSocketTransport();

On device the page is served by the loopback server, so the WebSocket defaults to the page’s own origin (ws://<location.host>) and the injected window.__ekrooh = { bootstrap } (a one-time nonce) boots the bare_session_<port> cookie automatically — the raw token is never given to page JS. In browser dev, pass the backend URL explicitly:

import { createWebSocketTransport } from '@ekrooh/bare/transports';
const transport = createWebSocketTransport({ url: 'ws://localhost:8080' });
import {
MessageType,
createPluginBus,
createProtocolMessenger,
} from '@ekrooh/bare/core';
import { healthEvents } from '@ekrooh/bare/plugins/health/events';
import type { MessageTransport } from '@ekrooh/bare/transports';
declare const transport: MessageTransport; // from step 3
const messenger = createProtocolMessenger((request, payload) => {
transport.send(MessageType.ENVELOPE, request, payload);
});
transport.subscribe((message) => messenger.handleIncoming(message.header));
const bus = createPluginBus(messenger);
const [err, pong] = await bus.invoke(healthEvents.health.ping());

bus.invoke resolves to [error, null] or [null, result]; it never throws.

PieceSource
Runtime + loopback servercore/runtime.ts, core/server/static-file-server.ts
Protocol + plugin kernelcore/messages/
Reference worklet entrycore/main.core.ts
Reference web appexamples/web/