Skip to content
Star

Defining a contract ​

The contract is the single source of truth for the messages your bridge exchanges. React Native imports it directly; Swift and Kotlin use native code generated from it.

Basic shape ​

Wrap a map of messages with defineContract, declaring each one with the request / command / event factories.

ts
// shared/bridge-contract.ts
import { command, defineContract, event, request } from 'webview-bridge-kit';

export const contract = defineContract({
  // request: web → native, has a reply
  GET_FCM_TOKEN: request(),
  PING: request(),

  // command: web → native, no reply
  OPEN_CAMERA: command(),

  // event: native → web
  APP_FOREGROUND: event(),
});

export type Contract = typeof contract;

This alone locks message names and kinds into the type system. Calling bridge.request('OPEN_CAMERA') (wrong kind) is a compile error.

Schemas for types + runtime validation ​

Pass a schema with a parse(value): T method to any factory and the payload / response types are inferred, and validated at runtime in both directions. Schemas are optional; without one, payload / response types are void.

ts
import { z } from 'zod';
import { command, defineContract, event, request } from 'webview-bridge-kit';

const TokenSchema = z.object({ token: z.string() });
const KakaoLoginSchema = z.object({ accessToken: z.string() });
const UsernameSchema = z.object({ username: z.string() });
const UriSchema = z.object({ uri: z.string() });

export const contract = defineContract({
  GET_FCM_TOKEN: request({ response: TokenSchema }),
  KAKAO_LOGIN: request({ response: KakaoLoginSchema, timeout: 'none' }),
  PING: request(),

  OPEN_CAMERA: command(),
  OPEN_INSTAGRAM: command({ payload: UsernameSchema }),

  APP_FOREGROUND: event(),
  PHOTO_TAKEN: event({ payload: UriSchema }),
});
  • request({ payload, response, timeout }) — payload is sent web→native, response is what native returns. timeout is this request's default timeout (ms or 'none').
  • command({ payload }) — the sent value only.
  • event({ payload }) — the value native sends.

Schemas aren't tied to any library

Anything shaped like { parse(value: unknown): T } works — zod, valibot, arktype, or your own object. Write validators that return the same output for the same input (avoid transforms with time / randomness).

If you're not in a monorepo ​

If web and React Native live in separate repositories, share the contract package or keep copied files synchronized. For Swift and Kotlin projects, regenerate native code whenever the contract changes.

A native host written in Swift or Kotlin cannot import the contract directly. Use the Swift / Kotlin type generator to generate message names and data type stubs instead.