Web Usage
Using the bridge from web pages — installation, bridge instance, error handling, built-in and custom protocols
Install
pnpm add @vois/webview-bridge
You can also use npm or yarn according to your project setup.
npm install @vois/webview-bridge
# or
yarn add @vois/webview-bridge
The package is ESM-only and is intended to be consumed through a bundler.
Get a Bridge Instance
A usable bridge instance is only delivered through onBridgeReady. There is no synchronous way to obtain it.
import { isSupportBridge, onBridgeReady } from '@vois/webview-bridge'
if (!isSupportBridge()) {
// Not inside a supported Vois / Weila WebView.
// You should degrade UI or show appropriate messaging.
return
}
// Recommended: call early in the page lifecycle
onBridgeReady((bridge) => {
// `bridge` is now safe to use
bridge.send('close-page')
})
Important Semantics
isSupportBridge()only checks the User-Agent. It returnstruefor Android/iOS even before the native bridge object is injected.onBridgeReadystarts the waiting process on first call (page-level singleton).- If the bridge is already ready when you call
onBridgeReady, the callback is invoked immediately with the same instance. - In unsupported environments (desktop browser, etc.), the callback is never invoked.
- There is no timeout. If native never injects the bridge, your callbacks will never run.
Typical Pattern
import { isSupportBridge, onBridgeReady } from '@vois/webview-bridge'
let bridge: WebviewBridge | null = null
if (isSupportBridge()) {
onBridgeReady((b) => {
bridge = b
// You can now safely call native features
})
}
// Later in event handlers:
function closePage() {
if (bridge) {
bridge.send('close-page')
} else {
// fallback behavior
}
}
Error Handling
The library surface is intentionally small regarding errors.
Only One Library Error
BridgeProtocolError is thrown only when the response from native cannot be parsed as JSON:
import { BridgeProtocolError } from '@vois/webview-bridge'
try {
const result = await bridge.request('some-action')
} catch (err) {
if (err instanceof BridgeProtocolError) {
// Native sent malformed JSON
console.error('Bridge protocol violation')
}
}
Business Errors
Business-level errors (authentication failure, validation error, etc.) must be expressed inside the response payload. The promise will still resolve.
const result = await bridge.request('submit-order', orderData)
if (result.errcode !== 0) {
// handle business error
showToast(result.errmsg)
return
}
No Bridge-Level Error Handler
There is deliberately no bridge.onError or similar. Use isSupportBridge() for environment detection and handle business errors in response data.
Built-in Functions
The official Vois / Weila applications expose a small set of commonly used protocols.
To enable them, import the dedicated entry point:
// Side-effect import — registers the protocols via module augmentation
import '@vois/webview-bridge/vois'
After this import, the following protocols become available with full type information:
| Protocol | Mode | Payload Type | Response Type | Description |
|---|---|---|---|---|
close-page |
send | — | — | Close current WebView page |
wechat-app-prepay |
request | WechatPrepay |
WechatPayRes |
WeChat Pay (mainly Android) |
ios-app-prepay |
request | IOSPrepay |
IOSPayRes |
In-app purchase (iOS) |
Usage Example
import '@vois/webview-bridge/vois'
import type { WechatPrepay, IOSPrepay } from '@vois/webview-bridge/vois'
onBridgeReady((bridge) => {
bridge.send('close-page')
void bridge.request('wechat-app-prepay', {
appid: 'wx...',
partnerid: '...',
prepay_id: '...',
noncestr: '...',
timestamp: 1752400000,
sign: '...',
})
void bridge.request('ios-app-prepay', {
product_id: 'vip_monthly',
product_name: 'VIP Monthly',
product_desc: 'Unlock all features',
amount: 1990,
currency: 'CNY',
})
})
You can freely mix built-in protocols with your own custom protocols (see next section).
Custom (Define a Webview Function)
You define your own protocols using TypeScript module augmentation. This is the primary and recommended way to extend the bridge.
Declaring Protocols
Create a declaration file (e.g. src/types/bridge.d.ts) and make sure it is included by your tsconfig.json:
import type { BridgeFn } from '@vois/webview-bridge'
declare module '@vois/webview-bridge' {
interface BridgeProtocolMap {
// send, no payload
'close-page': {}
// send, with payload
'share-text': BridgeFn<{ title: string; text: string }>
// request, with payload and response
'get-user-info': BridgeFn<{ userId: number }, { name: string; avatar: string }>
}
}
Rules:
- Use
BridgeFnhelper when possible for clarity. - You can also write the shape inline:
{},{ data: T }, or{ data: T; response: R }. - The declaration file must participate in type checking.
- Unknown protocol strings are still allowed at runtime (typed as
unknown).
Send
Use send for actions that do not require a response from native.
Signature
bridge.send(type)
bridge.send(type, data)
Examples
// No payload
bridge.send('close-page')
// With payload
bridge.send('share-text', {
title: 'Amazing content',
text: 'Check out this article',
})
// With a custom protocol
bridge.send('log-event', {
name: 'button_click',
params: { button: 'buy' },
})
send is synchronous and fire-and-forget. It will not throw under normal circumstances (the call happens after the bridge is ready).
Request
Use request when you need to receive data or acknowledgement from native.
Signature
const result = await bridge.request(type)
const result = await bridge.request(type, data)
The returned promise resolves with the value parsed from the JSON string sent back by native.
Examples
// No input data, expect response
const user = await bridge.request('get-user-info')
// With input data
const payResult = await bridge.request('create-order', {
productId: 'vip',
amount: 19900,
currency: 'CNY',
})
console.log(payResult) // typed according to your declaration
Unknown Protocols
If you call a protocol that has not been declared, both data and the response are typed as unknown:
const res = await bridge.request('future-protocol-xyz', { foo: 1 })
// res is unknown
This escape hatch allows forward compatibility without losing the ability to call new native features.
Recommendations
- Keep
typestrings stable — they form part of the contract between web and native. - Prefer descriptive names (
open-cameraoveroc). - For breaking changes, version the identifier (e.g.
get-user-v2). - Place all protocol declarations in
.d.tsfiles. - Import
'@vois/webview-bridge/vois'early if you need the built-in protocols.