> For the complete documentation index, see [llms.txt](https://docs.webtonative.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.webtonative.com/javascript-apis/native-events-bridge.md).

# Native Events Bridge

The Native Events Bridge lets your web page react to things happening on the device: the app going to the background, a push arriving, the network dropping, the keyboard opening, and more.

Every handler receives an event object `{ type, data }`. Events without a payload arrive as `{ type: "<eventType>" }` (no `data` key). Events fired before the page finishes loading are queued and delivered once it loads.

## Functions

All three functions take one or more event types separated by spaces, plus a handler function. Use `"*"` as the type to receive every event.

### `WTN.on(types, handler)`

Calls `handler` **every time** one of the listed events fires, until you remove it with `WTN.off`. Best for global listeners registered once when your app starts.

Registering the same handler twice makes it run twice per event.

**Example: keep an offline banner in sync with the connection.**

```javascript
WTN.on("connectivity", function (event) {
  document.getElementById("offline-banner").hidden = event.data.online;
});
```

### `WTN.off(types, handler)`

Removes a handler that was added with `WTN.on` or `WTN.once`. Pass the **same function** you registered. An inline anonymous function can't be removed this way, so keep a reference to it.

Calling `WTN.off(types)` without a handler removes **every** handler for those types, including ones registered by other code on the page.

**Example: listen to the keyboard only while a chat screen is open.**

```javascript
function onKeyboard(event) {
  chatInput.style.marginBottom = event.data.visible ? event.data.height + "px" : "0";
}

function openChat() {
  WTN.on("keyboard", onKeyboard);
}

function closeChat() {
  WTN.off("keyboard", onKeyboard);
}
```

### `WTN.once(types, handler)`

Calls `handler` for the **next** matching event only, then removes it automatically. No `off` call needed. Like `on`, it returns a function that cancels it before it fires.

With several types, each type fires once: `WTN.once("deepLink notificationTap", fn)` can call `fn` twice, once for each event.

**Example: show a welcome tour on the very first launch.**

```javascript
WTN.once("firstLaunch", function () {
  startWelcomeTour();
});
```

## Events

* `appState`
* `firstLaunch`
* `pushRecievedInForeground`
* `deepLink`
* `notificationTap`
* `volume`
* `audioRoute`
* `bluetooth`
* `backButton` (Android only)
* `shake`
* `connectivity`
* `networkType`
* `vpn`
* `permissionStatus`
* `battery`
* `lowPowerMode`
* `darkMode`
* `keyboard`
* `screenRotation`
* `screenshotTaken` (iOS only)
* `screenRecording` (iOS only)
* `ruStoreReferrer`

> **Deprecated:** the "App resume callback" setting in app settings is deprecated. Use the `appState` event instead; `state: "foreground"` means the app resumed.
>
> ```javascript
> WTN.on("appState", function (event) {
>   if (event.data.state === "foreground") {
>     // app resumed
>   }
> });
> ```

Listening to all of them at once:

```javascript
WTN.on("appState firstLaunch pushRecievedInForeground deepLink notificationTap volume audioRoute bluetooth backButton shake connectivity networkType vpn permissionStatus battery lowPowerMode darkMode keyboard screenRotation screenshotTaken screenRecording", function (event) {
  switch (event.type) {
    case "appState": /* event.data.state */ break;
    // ...one case per event
  }
});
```

## Responses

| Event (`type`)             | `event.data`                                                       | Notes                                                                                                                                |
| -------------------------- | ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------ |
| `appState`                 | `{ state: "foreground" \| "background" }`                          | Replaces the deprecated "App resume callback" setting                                                                                |
| `firstLaunch`              | —                                                                  |                                                                                                                                      |
| `pushRecievedInForeground` | `{ title: string, body: string, data: object }`                    | `data` holds the push's custom key/values (`{}` when none)                                                                           |
| `deepLink`                 | `{ url: string }`                                                  |                                                                                                                                      |
| `notificationTap`          | `{ title: string, body: string, data: object }`                    | `data` holds the push's custom key/values (`{}` when none)                                                                           |
| `volume`                   | `{ direction: "up" \| "down" }`                                    |                                                                                                                                      |
| `audioRoute`               | `{ route: "bluetooth" \| "headphone" \| "speaker" \| "earpiece" }` | iOS only: `route` can also be `airplay`, `hdmi`, `usb`, `carAudio`, and there's a `portName` field (e.g. `"AirPods Pro"`)            |
| `bluetooth`                | `{ enabled: boolean }`                                             |                                                                                                                                      |
| `backButton`               | —                                                                  | Android only                                                                                                                         |
| `shake`                    | —                                                                  |                                                                                                                                      |
| `connectivity`             | `{ online: boolean }`                                              |                                                                                                                                      |
| `networkType`              | `{ type: "wifi" \| "cellular" \| "offline" }`                      |                                                                                                                                      |
| `vpn`                      | `{ connected: boolean }`                                           |                                                                                                                                      |
| `permissionStatus`         | `{ permission: string, granted: boolean }`                         | iOS only: when the user changes a permission in Settings, a `status` field is added (`"granted"` \| `"denied"` \| `"notDetermined"`) |
| `battery`                  | `{ level: number, charging: boolean }`                             | `level` is 0–100, or `-1` if unknown                                                                                                 |
| `lowPowerMode`             | `{ enabled: boolean }`                                             |                                                                                                                                      |
| `darkMode`                 | `{ isDark: boolean }`                                              |                                                                                                                                      |
| `keyboard`                 | `{ visible: boolean, height: number }`                             | `height` is in physical pixels on Android, points on iOS                                                                             |
| `screenRotation`           | `{ orientation: "portrait" \| "landscape" }`                       |                                                                                                                                      |
| `screenshotTaken`          | `{ timestamp: number }`                                            | iOS only; Unix time in seconds                                                                                                       |
| `screenRecording`          | `{ recording: boolean }`                                           | iOS only                                                                                                                             |
| `ruStoreReferrer`          | `{ installReferrer:string }`                                       |                                                                                                                                      |

Example event: `{ type: "battery", data: { level: 82, charging: true } }`

## Native Share Target

When Share Target is enabled, your app appears in the OS share sheet. Content the user shares into it from another app is delivered to your page as a `contentShared` event.

* **Android:** delivered to the app's main page.
* **iOS:** delivered to the page shown inside the share sheet.

### Response

| Event (`type`)  | `event.data`                                                                                 |
| --------------- | -------------------------------------------------------------------------------------------- |
| `contentShared` | `{ contentType: "url" \| "text" \| "image" \| "audio" \| "video" \| "file", value: string }` |

* For `url` and `text`, `value` is the string itself.
* For `image`, `audio`, `video` and `file`, `value` is a `blob:` URL. Read it with `fetch(value)`.

### Example

```javascript
WTN.on("contentShared", function (event) {
  const { contentType, value } = event.data;

  if (contentType === "url" || contentType === "text") {
    composer.value = value;
  } else {
    fetch(value)
      .then(r => r.blob())
      .then(blob => uploadAttachment(blob, contentType));
  }
});
```

## `ruStoreReferrer` Response

Your handler receives `{ type: "ruStoreReferrer", data: { ... } }`. Everything below is inside `event.data`.

Check `event.data.success` first, then read either the referrer fields or `error`. The event fires only once per install, whether it succeeds or fails.

### Success

```json
{
  "success": true,
  "error": null,
  "installReferrer": "utm_source=vk&utm_campaign=autumn",
  "referrerClickTimestampSeconds": 1730540000,
  "installBeginTimestampSeconds": 1730540060,
  "referrerClickTimestampServerSeconds": 1730540001,
  "installBeginTimestampServerSeconds": 1730540061,
  "isFromRuStore": true
}
```

| Field                                 | Type      | Meaning                                                      |
| ------------------------------------- | --------- | ------------------------------------------------------------ |
| `success`                             | `boolean` | Always `true`                                                |
| `error`                               | `null`    | Always `null`                                                |
| `installReferrer`                     | `string`  | The referrer string from the RuStore install link            |
| `referrerClickTimestampSeconds`       | `number`  | When the user clicked the link (Unix seconds, phone's clock) |
| `installBeginTimestampSeconds`        | `number`  | When the install started (Unix seconds, phone's clock)       |
| `referrerClickTimestampServerSeconds` | `number`  | Click time from RuStore's server clock                       |
| `installBeginTimestampServerSeconds`  | `number`  | Install start time from RuStore's server clock               |
| `isFromRuStore`                       | `boolean` | `true` if RuStore installed the app                          |

### Error

```json
{
  "success": false,
  "error": "RUSTORE_NOT_INSTALLED"
}
```

| Field     | Type      | Meaning                |
| --------- | --------- | ---------------------- |
| `success` | `boolean` | Always `false`         |
| `error`   | `string`  | One of the codes below |

The referrer and timestamp fields are not sent at all on error, so they will be `undefined`.

| `error` value           | Meaning                                               |
| ----------------------- | ----------------------------------------------------- |
| `NO_REFERRER`           | RuStore answered but has no referrer for this install |
| `RUSTORE_NOT_INSTALLED` | The RuStore app isn't on the phone                    |
| `RUSTORE_OUTDATED`      | The RuStore app is too old to support this            |
| `CLIENT_NOT_CREATED`    | The RuStore client couldn't be created                |
| `UNKNOWN`               | Any other failure from the RuStore SDK                |

### Example

```javascript
WTN.on("ruStoreReferrer", function (event) {
  const data = event.data;
  if (data.success) {
    console.log("Referrer:", data.installReferrer);
  } else {
    console.warn("RuStore referrer error:", data.error);
  }
});
```
