# Messaging and browser startup

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

Source: https://console.getchrovia.com/en/docs/sdk-messaging

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

## Publish and subscribe

```js
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);
}
```

| Method | Behavior |
| --- | --- |
| `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:

```json
{
  "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`:

```js
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](https://console.getchrovia.com/en/docs/sdk-network-intercept), so awaiting it in this handler does not provide a browser-side readiness acknowledgment.

## Other built-in channels

| Channel | Purpose |
| --- | --- |
| `chrovia.process.supervisor_exited` | Supervisor termination notification in non-exit mode |
| `chrovia.passwords.changed` | Password-store change signal after `passwords.watch()` |
| `chrovia.software_authenticator.credential.save` | Software credential registration save RPC |
| `chrovia.software_authenticator.counter.claim` | External signature-counter RPC |

Their payloads and reply rules belong to [supervision](https://console.getchrovia.com/en/docs/sdk-supervisor-process), [passwords](https://console.getchrovia.com/en/docs/sdk-passwords), and [software authenticator](https://console.getchrovia.com/en/docs/sdk-software-authenticator). Do not emit synthetic kernel lifecycle events from application code.
