Messaging and browser startup

Use process-wide channels, register Service Worker listeners, and coordinate the optional browser-window startup gate.

Entitlement: messaging. API: chrovia.messaging. Use a Provision Service Worker; page access additionally needs unrestrictedApi.

Publish and subscribe

const messaging = globalThis.chrovia?.messaging;
if (messaging) {
  const receive = data => console.log('Received status:', data);
  messaging.on('my-plugin.status', receive);
  messaging.emit('my-plugin.status', { ready: true });
  setTimeout(() => messaging.off('my-plugin.status', receive), 1000);
}
MethodBehavior
on(channel, callback)Register a callback receiving the message data
emit(channel, data)Send JSON-compatible data to the named channel
off(channel, callback)Remove the same callback reference

All methods return undefined. emit() does not return a response Promise. For RPC, define a request ID and reply channel in your protocol. Do not use an anonymous replacement function with off and expect it to remove the original callback.

Channels are shared across the browser process, not private to an extension. Validate payloads and namespace application channels. Register on every worker script execution. The browser can retain early messages and wake subscribed Service Workers, but messages are not durable across browser exits and the first subscriber may consume previously unhandled pending messages.

Browser invocation events

chrovia.browser.invoked describes process cold-start or command-line re-invocation of an existing instance. It is not a tab/window-open event and does not repeat merely because a worker wakes or the macOS Dock opens a new window.

Payload fields are initialInvocation (boolean), commandLine (string), argv and args (string arrays), switches (object), and currentDirectory (string, empty on initial startup). Treat command-line data as input, not trusted instructions to execute.

Defer the first window workflow

To perform plugin initialization before the normal startup/open-window path proceeds, merge this EP configuration and sign if required:

{
  "internal": {
    "browser_invocation": {
      "defer_window": {
        "enabled": true,
        "timeout_seconds": 30
      }
    }
  }
}

Register one responsible handler in your Provision Service Worker. This example validates local application configuration before allowing the normal startup path and additionally requires extendedPrefs:

const sdk = globalThis.chrovia;
if (sdk?.messaging) {
  sdk.messaging.on('chrovia.browser.defer_window', data => {
    const channel = data?.callback_channel;
    if (typeof channel !== 'string'
        || !channel.startsWith('chrovia.browser.defer_window.reply.')) {
      return;
    }
    (async () => {
      let success = false;
      try {
        const config = await sdk.prefs?.get('my_plugin');
        success = config?.ready === true;
      } finally {
        sdk.messaging.emit(channel, { success });
      }
    })().catch(console.error);
  });
}

Set unsigned application data my_plugin: {"ready": true} at the EP root for this example. The actual request carries the invocation fields plus `callback_channel`, using snake_case. Reply to that exact channel with {success: true} to proceed or {success: false} to skip the scheduled startup path; false is not itself a process-exit call.

Order is invocation event, optional defer event/reply, then the normal startup path. Missing/false enabled, or missing messaging, means no wait. Missing or non-positive integer timeout uses 30 seconds. Timeout is fail-open and proceeds as success; malformed replies also default to success. This is workflow coordination, not a security barrier.

Do not install multiple plugins that race to answer the same defer event. Do not confuse it with network registration readiness: intercept returns void, so awaiting it in this handler does not provide a browser-side readiness acknowledgment.

Other built-in channels

ChannelPurpose
chrovia.process.supervisor_exitedSupervisor termination notification in non-exit mode
chrovia.passwords.changedPassword-store change signal after passwords.watch()
chrovia.software_authenticator.credential.saveSoftware credential registration save RPC
chrovia.software_authenticator.counter.claimExternal signature-counter RPC

Their payloads and reply rules belong to supervision, passwords, and software authenticator. Do not emit synthetic kernel lifecycle events from application code.