Contract 정의
Contract는 브릿지가 주고받을 메시지의 단일 진실 공급원(single source of truth) 입니다. 웹과 네이티브가 같은 contract를 import 해서, 양쪽의 타입이 자동으로 맞춰집니다.
기본 형태
defineContract로 메시지 맵을 감싸고, 각 메시지를 request / command / event factory로 선언합니다.
ts
// shared/bridge-contract.ts
import { command, defineContract, event, request } from 'webview-bridge-kit';
export const contract = defineContract({
// request: web → native, 응답 있음
GET_FCM_TOKEN: request(),
PING: request(),
// command: web → native, 응답 없음
OPEN_CAMERA: command(),
// event: native → web
APP_FOREGROUND: event(),
});
export type Contract = typeof contract;이렇게만 해도 메시지 이름과 종류가 타입으로 고정됩니다. bridge.request('OPEN_CAMERA')처럼 종류가 안 맞으면 컴파일 에러입니다.
스키마로 타입 + 런타임 검증
각 factory에 parse(value): T를 가진 스키마를 넘기면 payload / response 타입이 추론되고, 런타임에서도 양방향으로 검증됩니다. 스키마는 선택 사항이며, 없으면 payload / response 타입은 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는 web→native로 보내는 값,response는 native가 돌려주는 값.timeout은 이 request의 기본 타임아웃(ms 또는'none').command({ payload })— 보내는 값만.event({ payload })— native가 보내는 값만.
스키마는 라이브러리에 매이지 않습니다
{ parse(value: unknown): T } 모양이면 무엇이든 됩니다 — zod, valibot, arktype, 또는 직접 만든 객체. 검증은 같은 입력에 같은 출력을 내도록 작성하세요(시간·랜덤이 섞인 transform은 피하는 게 안전합니다).
모노레포가 아니라면
웹과 네이티브가 별도 저장소라면, 동일한 contract 파일을 양쪽에 각각 두어야 합니다. 한쪽만 바꾸면 타입은 맞아 보여도 런타임에서 메시지가 어긋납니다. contract를 공유 패키지로 빼거나, 한 파일을 복사해 동기화하세요. 이 내용은 React · React Native 페이지에서 다시 짚습니다.