npm package: @kookapp/web-bridge
Description: PostMessage-based bridge for iframe communication
Latest version: 0.0.6

# WebBridge

A PostMessage-based bridge for iframe communication, providing symmetric callHandler and registerHandler APIs similar to jsbridge but for web environments.

## Features

- **Symmetric API**: Both parent and iframe can call handlers and register responders
- **PostMessage-based**: Uses native Web APIs for cross-origin communication
- **Origin Validation**: Strict domain whitelist verification
- **Type-Safe**: Full TypeScript support with comprehensive type definitions
- **Promise & Callback**: Supports both async/await and callback patterns
- **No Exceptions**: Errors returned via result values, never throws exceptions (like globalBridge)
- **Error Handling**: Comprehensive error reporting through return values

## Installation

```bash
npm install @kookapp/web-bridge
```

## Usage

### Parent Side (iframe外部)

```typescript
import { ParentBridge } from '@kookapp/web-bridge'

// Initialize the bridge
const bridge = new ParentBridge({
  allowedOrigins: ['https://iframe.example.com']
})

// Register a handler that can be called from iframe
bridge.registerHandler('getUserInfo', (data, callback) => {
  const userInfo = { id: 1, name: 'John' }
  callback(userInfo)
})

// Create a communication channel with an iframe
const iframeElement = document.getElementById('myIframe') as HTMLIFrameElement
const channel = bridge.createChannel(iframeElement)

// Call a handler inside iframe (using callback)
channel.callHandler('getIframeData', { type: 'user' }, (result) => {
  if (result.error) {
    console.error('Error:', result.error)
  } else {
    console.log('Result:', result)
  }
})

// Or using async/await (without callback)
const result = await channel.callHandler('getIframeData', { type: 'user' })
if (result.error) {
  console.error('Error:', result.error)
} else {
  console.log('Result:', result)
}

// Clean up
// bridge.destroy()
```

### Child Side (iframe内部)

```typescript
import { ChildBridge } from '@kookapp/web-bridge'

// Initialize the bridge
const bridge = new ChildBridge({
  allowedOrigins: ['https://parent.example.com']
})

// Register a handler that can be called from parent
bridge.registerHandler('getIframeData', (data, callback) => {
  const response = { data: 'from iframe', type: data.type }
  callback(response)
})

// Call a handler on parent (using callback)
bridge.callHandler('getUserInfo', {}, (userInfo) => {
  if (userInfo.error) {
    console.error('Error:', userInfo.error)
  } else {
    console.log('User:', userInfo)
  }
})

// Or using async/await
const userInfo = await bridge.callHandler('getUserInfo', {})
if (userInfo.error) {
  console.error('Error:', userInfo.error)
} else {
  console.log('User:', userInfo)
}
```

## API Reference

### ParentBridge

```typescript
class ParentBridge {
  constructor(config: BridgeConfig)
  registerHandler(handlerName: string, handler: Handler): void
  createChannel(iframe: HTMLIFrameElement, options?: { origin: string }): IframeChannel
  unregisterHandler(handlerName: string): void
  destroy(): void
}
```

### IframeChannel

```typescript
interface IframeChannel {
  callHandler(handlerName: string, data?: any, callback?: (result: any) => void): Promise<any>
  getIframe(): HTMLIFrameElement
  isConnected(): boolean
  destroy(): void
}
```

### ChildBridge

```typescript
class ChildBridge {
  constructor(config: BridgeConfig)
  callHandler(handlerName: string, data?: any, callback?: (result: any) => void): Promise<any>
  registerHandler(handlerName: string, handler: Handler): void
  unregisterHandler(handlerName: string): void
  destroy(): void
  isConnected(): boolean
}
```

### BridgeConfig

```typescript
interface BridgeConfig {
  allowedOrigins: string[]    // Domain whitelist (supports exact match, regex, and wildcard)
  timeout?: number            // Message timeout in ms (default: 5000)
  debug?: boolean             // Enable debug logging
  maxRetries?: number         // Maximum retry attempts (default: 0)
}
```

## Error Handling

**WebBridge never throws exceptions** - all errors are returned through the result value, keeping behavior consistent with globalBridge.

All errors are returned as objects with an `error` property:

```typescript
// Using callback
bridge.callHandler('someHandler', {}, (result) => {
  if (result.error) {
    // Handle error cases:
    // - Handler not found
    // - Request timeout
    // - Invalid message
    // - Connection issues
    console.error('Error:', result.error)
  } else {
    console.log('Success:', result)
  }
})

// Using Promise (never rejects!)
const result = await bridge.callHandler('someHandler', {})
if (result.error) {
  console.error('Error:', result.error)
} else {
  console.log('Success:', result)
}
```

**Common error types:**
- Handler not found: `{ error: 'Handler "handlerName" not found' }`
- Timeout: `{ error: 'Timeout: no response after 5000ms' }`
- Connection issues: `{ error: 'iframe contentWindow is not available' }`

See [ERROR_HANDLING.md](./ERROR_HANDLING.md) for complete documentation.

## Security

### Origin Validation

- Exact match: `'https://example.com'`
- Regex pattern: `'^https://.*\\.example\\.com$'`
- Wildcard (dev only): `'*'`

Origins are validated for both incoming and outgoing messages.

## License

MIT