A mobile-to-mobile WebRTC connection system for secure, peer-to-peer payment interactions. Built for web applications rendered in mobile WebView containers (iOS and Android) and standard mobile browsers.
- WebRTC Data Channels: Direct peer-to-peer communication
- QR Code Bootstrapping: Quick local connection via QR code scanning
- Remote Bootstrapping: Connect via username lookup with Topic Broadcaster and Lookup Resolver services
- STUN/TURN Support: Automatic NAT traversal with fallback relay
- JSON Message Protocol: Structured message validation and routing
- TypeScript: Full type safety and IntelliSense support
- Mobile-Optimized: Designed for mobile WebView and browser environments
npm install webrtpayOr with yarn:
yarn add webrtpayWebRTPay includes a Docker Compose setup for running a local TURN server:
# Clone the repository
git clone https://github.com/sirdeggen/webrtpay.git
cd webrtpay
# Start TURN server and demo
docker-compose up -d
# Or start just the TURN server
docker-compose up -d coturn
# View logs
docker-compose logs -fThe local TURN server runs on localhost:3478 with credentials:
- Username:
testuser - Password:
testpass
See TURN_SERVER_SETUP.md for detailed configuration.
import { createConnectionManager } from 'webrtpay';
// Initialize connection manager
const manager = createConnectionManager({
connectionTimeout: 30000,
autoRetry: true,
maxRetries: 3
});
// Generate QR code for local connection
const { qrCodeDataUrl, token } = await manager.createQRConnection();
// Display QR code
document.getElementById('qr-image').src = qrCodeDataUrl;import { createConnectionManager, QRBootstrap } from 'webrtpay';
const manager = createConnectionManager();
// Setup camera for scanning
const { video, stream } = await QRBootstrap.setupCamera('environment');
// Scan and join
await manager.scanAndJoin(video);
// Stop camera after successful connection
QRBootstrap.stopCamera(stream);import { PaymentMessageTypes } from 'webrtpay';
// Listen for messages
manager.onMessage(PaymentMessageTypes.PAYMENT_REQUEST, (message) => {
console.log('Payment request:', message.payload);
});
// Send a message
await manager.send(PaymentMessageTypes.PAYMENT_REQUEST, {
amount: 50.00,
currency: 'USD',
recipient: 'user123',
description: 'Coffee payment'
});Best for face-to-face payments:
// Device A: Create QR code
const { qrCodeDataUrl } = await manager.createQRConnection();
// Device B: Scan QR code
const token = await QRBootstrap.scanQRCodeFromVideo(videoElement);
await manager.joinQRConnection(token);Best for remote payments:
// Configure remote services
const manager = createConnectionManager({
remote: {
broadcasterUrl: 'https://broadcaster.example.com',
lookupUrl: 'https://lookup.example.com',
tokenValidityMs: 300000 // 5 minutes
}
});
// Device A: Publish connection
await manager.publishRemoteConnection('alice');
// Device B: Lookup and connect
await manager.joinRemoteConnection('alice');import { PaymentMessageTypes } from 'webrtpay';
// Available types:
PaymentMessageTypes.PAYMENT_REQUEST
PaymentMessageTypes.PAYMENT_RESPONSE
PaymentMessageTypes.PAYMENT_ACKNOWLEDGMENT
PaymentMessageTypes.ERROR
PaymentMessageTypes.HANDSHAKE
PaymentMessageTypes.PING
PaymentMessageTypes.PONGimport { MessageProtocol } from 'webrtpay';
const protocol = manager.getProtocol();
// Register custom schema
protocol.registerSchema({
type: 'custom_payment',
requiredFields: ['amount', 'token'],
validate: (payload) => {
return payload.amount > 0;
}
});
// Send custom message
await manager.send('custom_payment', {
amount: 100,
token: 'abc123'
});Main class for managing WebRTC connections.
const manager = createConnectionManager(config?: ConnectionManagerConfig);ConnectionManagerConfig:
webrtc?: WebRTCConfig- WebRTC configuration (ICE servers, etc.)remote?: RemoteBootstrapConfig- Remote service configurationconnectionTimeout?: number- Connection timeout in ms (default: 30000)autoRetry?: boolean- Enable automatic retry (default: true)maxRetries?: number- Maximum retry attempts (default: 3)
Connection Management:
createQRConnection()- Create connection and generate QR codejoinQRConnection(token)- Join using bootstrap tokenscanAndJoin(video, timeout?)- Scan QR and join automaticallypublishRemoteConnection(username)- Publish to remote broadcasterjoinRemoteConnection(username)- Lookup and join remote connection
Messaging:
send(type, payload)- Create and send messagesendMessage(message)- Send pre-created messageonMessage(type, handler)- Register message handleroffMessage(type, handler)- Unregister message handleronAnyMessage(handler)- Handle all message types
State & Info:
getState()- Get current connection stateisReady()- Check if ready for messaginggetConnectionId()- Get unique connection IDgetMessageHistory(type?)- Get message historygetStats()- Get connection statisticsclose()- Close connection and cleanup
Utilities for QR code generation and scanning.
generateQRCode(token, options?)- Generate QR code as data URLgenerateQRCodeSVG(token, options?)- Generate QR code as SVGscanQRCode(imageData)- Scan QR from ImageDatascanQRCodeFromVideo(video, timeout?)- Continuous video scanningsetupCamera(facingMode?)- Setup camera for scanningstopCamera(stream)- Stop camera streamvalidateToken(token)- Validate token structureisTokenExpired(token, maxAge?)- Check token expiration
enum ConnectionState {
IDLE = 'idle',
CREATING_OFFER = 'creating_offer',
AWAITING_ANSWER = 'awaiting_answer',
CONNECTING = 'connecting',
CONNECTED = 'connected',
DISCONNECTED = 'disconnected',
FAILED = 'failed',
CLOSED = 'closed'
}import { createConnectionManager } from 'webrtpay';
const manager = createConnectionManager({
webrtc: {
iceServers: [
// Public STUN servers
{ urls: 'stun:stun.l.google.com:19302' },
// Your TURN server
{
urls: 'turn:turn.example.com:3478',
username: 'user',
credential: 'password'
}
],
iceTransportPolicy: 'all', // or 'relay' to force TURN
bundlePolicy: 'balanced'
}
});To use remote bootstrapping, you need to implement two services:
Topic Broadcaster API:
POST /publish
Body: { username: string, token: BootstrapToken, ttl: number }
Response: { tokenId: string, expiresAt: number }
DELETE /publish?username=<username>
Response: { success: boolean }
Lookup Resolver API:
GET /lookup?username=<username>
Response: { token: BootstrapToken, username: string, publishedAt: number }
import { WebRTPayError, ErrorType } from 'webrtpay';
try {
await manager.createQRConnection();
} catch (error) {
if (error instanceof WebRTPayError) {
switch (error.type) {
case ErrorType.BOOTSTRAP_GENERATION_FAILED:
console.error('Failed to generate bootstrap token');
break;
case ErrorType.CONNECTION_FAILED:
console.error('WebRTC connection failed');
break;
case ErrorType.ICE_FAILED:
console.error('ICE negotiation failed');
break;
case ErrorType.TIMEOUT:
console.error('Operation timed out');
break;
// ... handle other error types
}
}
}import WebKit
let webView = WKWebView()
let config = webView.configuration
// Enable camera access
config.preferences.setValue(true, forKey: "allowFileAccessFromFileURLs")
config.mediaTypesRequiringUserActionForPlayback = []WebView webView = new WebView(this);
WebSettings settings = webView.getSettings();
settings.setJavaScriptEnabled(true);
settings.setDomStorageEnabled(true);
settings.setMediaPlaybackRequiresUserGesture(false);
// Enable camera permission
webView.setWebChromeClient(new WebChromeClient() {
@Override
public void onPermissionRequest(PermissionRequest request) {
request.grant(request.getResources());
}
});- Chrome/Edge 90+
- Firefox 88+
- Safari 14.1+
- Mobile Chrome (Android)
- Mobile Safari (iOS 14.3+)
WebRTC is supported in all modern browsers. Check compatibility:
import { isWebRTCSupported } from 'webrtpay';
if (!isWebRTCSupported()) {
alert('WebRTC is not supported in this browser');
}- All WebRTC connections use DTLS-SRTP encryption
- Bootstrap tokens should have short TTL (5 minutes recommended)
- Token publication does not authorize payments, only communication
- Implement additional authentication in your payment flow
- Validate all incoming messages before processing
See the /demo directory for a complete React application demonstrating:
- QR code generation and scanning
- Remote username-based connections
- Message exchange
- Connection state management
- Error handling
Run the demo:
cd demo
npm install
npm run dev┌─────────────────────────────────────────┐
│ ConnectionManager │
│ (High-level orchestration) │
└────────────┬────────────────────────────┘
│
┌────────┴─────────┐
│ │
┌───▼──────────┐ ┌───▼─────────────┐
│ WebRTC │ │ MessageProtocol │
│ Connection │ │ │
└──────┬───────┘ └─────────────────┘
│
┌──────┴──────────────────┐
│ │
│ ┌──────────────────┐ │
│ │ QRBootstrap │ │
│ └──────────────────┘ │
│ │
│ ┌──────────────────┐ │
│ │ RemoteBootstrap │ │
│ └──────────────────┘ │
└─────────────────────────┘
- QR code generation: ~100ms
- Connection establishment (STUN): ~1-3 seconds
- Connection establishment (TURN fallback): ~3-5 seconds
- Message latency: <50ms (direct P2P)
- QR code size: ~1-2KB (optimized payload)
- Check STUN/TURN server configuration
- Verify network allows WebRTC traffic
- Check browser WebRTC support
- Ensure camera permissions granted
- Check lighting conditions
- Verify QR code is not too small/large
- Try different camera (front/back)
- Check connection state is CONNECTED
- Verify data channel is open:
manager.isReady() - Check message schema validation
MIT
Contributions welcome! Please read CONTRIBUTING.md for guidelines.
- GitHub Issues: https://github.com/sirdeggen/webrtpay/issues