# JS <--> Native Bridge

Use the JS <--> Native Bridge to call OnsideKit from JavaScript running inside a `WKWebView`. This integration fits apps built as a web wrapper, where most product logic lives in a website but the app still needs native OnsideKit features such as in-app purchases, attribution, and event tracking.

The bridge is only a transport layer between JavaScript and native code. It does not add new capabilities and does not change the semantics, lifecycle, or error handling of existing OnsideKit APIs. The behavior described in [Installation Guide](/sdk/installation-guide.md), [User account](/sdk/user-account.md), [Attribution](/sdk/attribution.md), [Building funnels with event tracking](/sdk/attribution/building-funnels-with-event-tracking.md), and [Purchasing & Restoring Purchases](/sdk/purchasing-and-restoring-purchases.md) still applies. This page focuses on `WKWebView` configuration and the JavaScript API exposed by the bridge.

### Initialize OnsideKit first

Start with the same OnsideKit setup used for a native integration.

Complete the installation, callback URL scheme setup, and SDK initialization exactly as described in [Installation Guide](/sdk/installation-guide.md).

The only difference is delegate and observer setup.

{% hint style="warning" %}
If you plan to use OnsideKit from JavaScript, do not assign OnsideKit delegates or transaction observers in native code. During `WKWebView` configuration, `OnsideJS` registers itself implicitly as the required delegate and observer layer.
{% endhint %}

### Configure WKWebView

After OnsideKit is installed and initialized, configure your `WKWebView` by passing its instance to `OnsideJS.configureOnsideForWebView(_:)`.

This is the only required `WKWebView` setup step.

```swift
import WebKit
import OnsideKit

let webView = WKWebView(frame: .zero)
OnsideJS.configureOnsideForWebView(webView)
```

### Load your web content

After configuration, use the `WKWebView` as usual and load your web content in the normal way.

If configuration succeeds, JavaScript running inside the page gets access to `window.onside`.\
Conventions

* **Setters and "fire-and-forget" methods** (`setAppearance`, `setShouldForceLocalLoginMethods`, `setDefaultCountryCodeAssumption`, `logout`, `trackEvent`) return synchronously and never throw.
* **Everything else returns a `Promise`.** Resolves with the success payload, rejects with a typed error envelope.
* **Error envelope shape.** All native `enum: Error` cases without associated values arrive as `{ <caseName>: {} }`. Discriminate with `'caseName' in error`, or pattern-match against the union types in `onside.d.ts`.

```js
try {
  const { products, invalidProductIdentifiers } = await window.onside.loadProducts({
    productIdentifiers: ['my.product.id'],
  });
} catch (error) {
  if ('connectionError' in error) { /* ... */ }
}
```

### API Reference

#### Configuration

**`setAppearance(theme)`**

```ts
setAppearance(theme: 'light' | 'dark' | 'system'): void;
```

Override the theme used for native UI presented from JS (login, payment methods, etc.). `'system'` follows the OS-level appearance.

**`setShouldForceLocalLoginMethods(force)`**

```ts
setShouldForceLocalLoginMethods(force: boolean): void;
```

When `true`, the login screen exposes only local login methods (SMS / e-mail) and hides federated providers. Useful for environments where federated login is not desired.

**`setDefaultCountryCodeAssumption(countryCode)`**

```ts
setDefaultCountryCodeAssumption(countryCode: string): void;
```

Default country code assumed when the system region is unknown. Pass an empty string to clear the assumption.

#### Analytics

**`trackEvent(input)`**

```ts
trackEvent(input: {
  name: string;
  parameters?: Record<string, JSEventParameterValue>;
}): void;
```

Send a single analytics event. `name` may be a predefined Onside event (e.g. `purchaseCompleted`, `subscriptionStarted`) or any custom identifier. Validation is enforced natively:

* name and parameter keys must match `^[a-zA-Z][a-zA-Z0-9_]*$` and be ≤ 40 chars (longer names are truncated; reserved prefixes `onside_` and `$$` are rejected);
* at most 25 effective parameters per event;
* string values longer than 100 chars are truncated.

#### Auth

**`requestLogin()`**

```ts
requestLogin(): Promise<void>;
```

Show the login flow. Resolves on successful authentication. Rejects with `OnsideLoginError`.

**`logout()`**

```ts
logout(): void;
```

Log out the current user.

#### UI

**`presentPaymentMethodsManager()`**

```ts
presentPaymentMethodsManager(): Promise<void>;
```

Show the payment-methods management screen. Triggers a login flow first if the user is not authenticated. Rejects with `OnsidePaymentMethodsManagerError`.

#### Storefront

**`getCurrentStorefront()`**

```ts
getCurrentStorefront(): Promise<JSStorefront | null>;
```

Currently selected storefront, or `null` if none is selected yet. The selection can change at any time — subscribe to `onStorefrontChanged` for updates.

#### Products

**`loadProducts(input)`**

```ts
loadProducts(input: { productIdentifiers: string[] }): Promise<JSProductsResponse>;
```

Load products by identifiers. The response contains:

* `products` — products that resolved successfully;
* `invalidProductIdentifiers` — identifiers that did not resolve (unknown, malformed, etc.).

Returned products are also cached natively so that `purchase` can look them up by `productIdentifier`.

Rejects with `OnsideProductsRequestError`.

#### Payments

**`purchase(input)`**

```ts
purchase(input: {
  productIdentifier: string;
  appAccountToken?: string;
}): Promise<void>;
```

Initiate a purchase. The product identifier must refer to a product previously returned by `loadProducts`; otherwise the promise rejects with `{ unknownProduct: {} }`.

The promise itself only reports the **pre-flight** outcome (login outcome, lookup, etc.). Subsequent transaction state changes are delivered via `onTransactionsUpdated`; JS is responsible for calling `finishTransaction` once a transaction reaches a terminal state.

Rejects with `JSPurchaseError`.

**`restoreCompletedTransactions()`**

```ts
restoreCompletedTransactions(): Promise<void>;
```

Restore previously completed transactions. The promise only reports the pre-flight outcome (e.g. `loginDiscarded`). The completion of the restore flow itself is delivered via `onRestoreCompletedTransactionsFinished` or `onRestoreCompletedTransactionsFailedWithError`. Restored transactions arrive through `onTransactionsUpdated`.

Rejects with `OnsidePaymentQueueRequestRestoreError`.

**`getTransactions()`**

```ts
getTransactions(): Promise<JSPaymentTransaction[]>;
```

Snapshot of currently alive transactions in the payment queue. Useful on page reload to rebuild UI state without waiting for the next event.

**`finishTransaction(id)`**

```ts
finishTransaction(id: string): Promise<void>;
```

Finish a transaction by its opaque id (the `id` field of `JSPaymentTransaction`). Removes the transaction from the queue — `onTransactionsRemoved` will fire.

Rejects with `JSFinishTransactionError`.

#### Attribution

**`getAttributionMetadata()`**

```ts
getAttributionMetadata(): Promise<JSAttributionMetadata>;
```

Fetch attribution metadata for the current install/user (e.g. the referer URL that brought the user to the install).

Rejects with `OnsideAttributionMetadataError`.

#### History

**`getSignedInAppsHistory()`**

```ts
getSignedInAppsHistory(): Promise<JSSignedInAppsHistory>;
```

Fetch the signed in-apps history JWT for the current user. The response carries both the raw bytes (base64-encoded) and a UTF-8 representation, when applicable.

Rejects with `OnsideSignedInAppsHistoryRequestError`.

### Native → JS Callbacks

Assign a function to the corresponding property on `window.onside` to start receiving the event. All callbacks are optional; a missing handler is a no-op (with one exception, see `shouldContinueTransaction`).

**`onTransactionsUpdated(transactions)`**

```ts
onTransactionsUpdated(transactions: JSPaymentTransaction[]): void | Promise<void>;
```

Transactions added to the queue or transitioned to a new state. Inspect `transactionState` and react accordingly. Once the state reaches `purchased`, `restored` or `failed`, JS must call `finishTransaction` to remove it from the queue.

**`onTransactionsRemoved(transactions)`**

```ts
onTransactionsRemoved(transactions: JSPaymentTransaction[]): void | Promise<void>;
```

Transactions that have been removed from the queue (typically after `finishTransaction`).

**`onStorefrontChanged(storefront)`**

```ts
onStorefrontChanged(storefront: JSStorefront | null): void | Promise<void>;
```

Storefront selection changed; `null` means the storefront is no longer available.

**`onRestoreCompletedTransactionsFinished()`**

```ts
onRestoreCompletedTransactionsFinished(): void | Promise<void>;
```

The `restoreCompletedTransactions` flow finished successfully.

**`onRestoreCompletedTransactionsFailedWithError(error)`**

```ts
onRestoreCompletedTransactionsFailedWithError(
  error: OnsideTransactionsRestoreError
): void | Promise<void>;
```

The `restoreCompletedTransactions` flow failed.

**`shouldContinueTransaction(input)`**

```ts
shouldContinueTransaction(input: {
  transaction: JSPaymentTransaction;
  storefront: JSStorefront;
}): boolean | Promise<boolean>;
```

Async pre-flight gate the queue invokes before processing a transaction in a particular storefront. Return `false` to cancel. If no handler is registered, native defaults to `true`.

### Domain Types

**`JSPrice`**

```ts
{ value: number; currencyCode: string }
```

Numeric monetary value with an ISO-4217 currency code (e.g. `"EUR"`).

**`JSPeriod`**

```ts
{ unit: 'day' | 'week' | 'month' | 'year'; numberOfUnits: number }
```

Subscription/discount period — analogous to StoreKit's `SKProductSubscriptionPeriod`.

**`JSPricePeriod`**

```ts
{ price: JSPrice; period: JSPeriod }
```

A price valid for the given period (introductory price, discounted price, etc.).

**`JSProduct`**

```ts
{
  productIdentifier: string;
  localizedTitle: string;
  localizedDescription: string;
  iconUrl?: string;
  subscriptionGroupIdentifier?: string;
  subscriptionPeriod?: JSPeriod;
  price: JSPrice;
  introductoryPrice?: JSPricePeriod;
  discountedPrice?: JSPricePeriod;
}
```

A product as returned by `loadProducts`. `subscriptionGroupIdentifier` and `subscriptionPeriod` are present only for subscriptions.

**`JSStorefront`**

```ts
{ id: string; countryCode: string }
```

The selected storefront — opaque storefront `id` plus its ISO-3166 `countryCode`.

**`JSPayment`**

```ts
{ product: JSProduct; appAccountToken?: string }
```

The payment that produced a transaction, embedded inside `JSPaymentTransaction.payment`. `appAccountToken` is the optional opaque token passed to `purchase`.

**`JSPaymentTransactionState`**

```ts
'purchasing' | 'purchased' | 'restored' | 'failed'
```

Lifecycle states of a transaction:

| State        | Meaning                                                                      |
| ------------ | ---------------------------------------------------------------------------- |
| `purchasing` | Still in flight: created, awaiting payment, etc.                             |
| `purchased`  | Completed successfully. Call `finishTransaction(id)`.                        |
| `restored`   | Returned by `restoreCompletedTransactions`. Call `finishTransaction(id)`.    |
| `failed`     | Final, failed. `error` is populated. Call `finishTransaction(id)` to remove. |

**`JSPaymentTransaction`**

```ts
{
  id: string;
  transactionIdentifier?: string;
  originalTransactionIdentifier?: string;
  payment: JSPayment;
  transactionState: JSPaymentTransactionState;
  storefront: JSStorefront;
  error?: OnsidePaymentTransactionError;
}
```

* `id` — opaque UUID string. The only stable handle for this transaction; pass it to `finishTransaction`.
* `transactionIdentifier` — server-side transaction id (may be absent in `purchasing`).
* `originalTransactionIdentifier` — id of the original transaction for restored/renewed cases.
* `error` — populated only for `failed` transactions.

**`JSAttributionMetadata`**

```ts
{ refererUrl?: string }
```

Currently exposes only the referer URL. May be extended in the future.

**`JSSignedInAppsHistory`**

```ts
{ dataBase64: string; string?: string }
```

* `dataBase64` — raw payload bytes, base64-encoded.
* `string` — UTF-8 decoded payload, when applicable (the payload is typically a JWT and decodes to a printable string).

**`JSProductsResponse`**

```ts
{ products: JSProduct[]; invalidProductIdentifiers: string[] }
```

Result of `loadProducts`.

**`JSEventParameterValue`**

```ts
string | number | boolean | JSEventArrayParameterValue[]
```

Value type for `trackEvent` parameters: a primitive or an array of primitives. Array elements (`JSEventArrayParameterValue`) follow the same primitive set without further nesting.

### Errors

All native `enum: Error` cases without associated values arrive on the JS side as `{ <caseName>: {} }`. The bridge also defines a few JS-specific error types listed at the end.

**`OnsideLoginError`**

| Case             | Meaning                                      |
| ---------------- | -------------------------------------------- |
| `loginDiscarded` | The user dismissed/cancelled the login flow. |

**`OnsideProductsRequestError`**

| Case                       | Meaning                                        |
| -------------------------- | ---------------------------------------------- |
| `cancelled`                | The request was cancelled.                     |
| `connectionError`          | Network connectivity error.                    |
| `appNotRegistered`         | The current app is not registered with Onside. |
| `invalidProductIdentifier` | Server rejected the product identifier(s).     |
| `serviceUnavailable`       | Server returned a 5xx.                         |
| `internalError`            | Other unexpected error.                        |

**`OnsideSignedInAppsHistoryRequestError`**

| Case                         | Meaning                                                     |
| ---------------------------- | ----------------------------------------------------------- |
| `notLoggedIn`                | Operation requires an authenticated user.                   |
| `notSupportedInLocalTesting` | Not supported when running with a local-testing storefront. |
| `cancelled`                  | The request was cancelled.                                  |
| `connectionError`            | Network connectivity error.                                 |
| `appNotRegistered`           | The current app is not registered with Onside.              |
| `serviceUnavailable`         | Server returned a 5xx.                                      |
| `internalError`              | Other unexpected error.                                     |

**`OnsidePaymentMethodsManagerError`**

| Case                         | Meaning                                                     |
| ---------------------------- | ----------------------------------------------------------- |
| `loginDiscarded`             | The user dismissed the implicit login flow.                 |
| `notSupportedInLocalTesting` | Not supported when running with a local-testing storefront. |

**`OnsidePaymentQueueRequestRestoreError`**

| Case             | Meaning                                     |
| ---------------- | ------------------------------------------- |
| `loginDiscarded` | The user dismissed the implicit login flow. |

**`OnsideTransactionsRestoreError`**

| Case                 | Meaning                                        |
| -------------------- | ---------------------------------------------- |
| `cancelled`          | The restore was cancelled.                     |
| `connectionError`    | Network connectivity error.                    |
| `appNotRegistered`   | The current app is not registered with Onside. |
| `serviceUnavailable` | Server returned a 5xx.                         |
| `internalError`      | Other unexpected error.                        |

**`OnsidePaymentTransactionError`**

Populated on `JSPaymentTransaction.error` for `failed` transactions.

| Case        | Meaning                        |
| ----------- | ------------------------------ |
| `cancelled` | The transaction was cancelled. |

**`OnsideAttributionMetadataError`**

| Case                 | Meaning                                        |
| -------------------- | ---------------------------------------------- |
| `connectionError`    | Network connectivity error.                    |
| `appNotRegistered`   | The current app is not registered with Onside. |
| `serviceUnavailable` | Server returned a 5xx.                         |
| `internalError`      | Other unexpected error.                        |

**`JSPurchaseError`**

JS-bridge specific error for `purchase`.

| Case             | Meaning                                                                       |
| ---------------- | ----------------------------------------------------------------------------- |
| `unknownProduct` | The `productIdentifier` is not in the local cache. Call `loadProducts` first. |
| `loginDiscarded` | The user dismissed the implicit login flow.                                   |

**`JSFinishTransactionError`**

JS-bridge specific error for `finishTransaction`.

| Case                 | Meaning                                                 |
| -------------------- | ------------------------------------------------------- |
| `unknownTransaction` | No transaction with the given `id` exists in the queue. |

### Reference

The full machine-readable contract is available in [`onside.d.ts`](https://github.com/onside-io/OnsideKit-iOS/blob/main/onside.d.ts).


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.onside.io/sdk/js-less-than-greater-than-native-bridge.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
