Sovereignbase P2P Module Demo
Open this page on two devices and follow the instructions to try a peer to peer messenger demo.
Step 1 (Device A)
Displays an RTCPeerConnection offer as a QR code.
Step 2 (Device B)
Starts a QR scanner, processes the RTCPeerConnection offer, and displays an answer as a QR code.
Step 3 (Device A)
Scans the QR code and completes the RTCPeerConnection setup.
Chat
After pairing, you can exchange messages.
Call
You can even share microphone, camera, or screen.
Local Mic
Remote Mic
Local Camera
Remote Camera
Local Screen
Remote Screen
Code
All these features implemented in 350 lines of TypeScript.
import {
CRList,
type CRListDelta,
type CRListSnapshot,
} from '@sovereignbase/convergent-replicated-list'
import { QR } from '@sovereignbase/qr'
import { KVStore } from '@sovereignbase/offline-kv-store'
import { P2PConnection, type Offer, type OfferorCopy } from '@sovereignbase/peer2peer'
type ChatMessage = {
name: string
text: string
}
type PeerMessage =
| { kind: 'snapshot'; payload: CRListSnapshot<ChatMessage> }
| { kind: 'delta'; payload: CRListDelta<ChatMessage> }
| { kind: 'camera-shared' }
| { kind: 'microphone-shared' }
| { kind: 'screen-shared' }
| { kind: 'camera-muted' }
| { kind: 'microphone-muted' }
| { kind: 'screen-muted' }
/** Pointers */
let peer: P2PConnection<PeerMessage> | undefined
const profileStore = new KVStore<string>('profile')
const messagesStore = new KVStore<CRListSnapshot<ChatMessage>>('messages')
const snapshot = (await messagesStore.get('messages')) ?? undefined
const messages = new CRList<ChatMessage>(snapshot)
const nameInput = document.getElementById('name')
const makeOfferButton = document.getElementById('makeOffer')
const acceptOfferButton = document.getElementById('acceptOffer')
const finishOfferButton = document.getElementById('finishOffer')
const messagesElement = document.getElementById('messages')
const messageInput = document.getElementById('message-input')
const sendMessageButton = document.getElementById('sendMessage')
const shareMicrophoneButton = document.getElementById('shareMicrophone')
const stopSharingMicrophoneButton = document.getElementById(
'stopSharingMicrophone'
)
const unmuteRemoteMicrophoneButton = document.getElementById(
'unmuteRemoteMicrophone'
)
const muteRemoteMicrophoneButton = document.getElementById(
'muteRemoteMicrophone'
)
const localCameraMount = document.getElementById('localCameraMount')
const remoteCameraMount = document.getElementById('remoteCameraMount')
const shareCameraButton = document.getElementById('shareCamera')
const stopSharingCameraButton = document.getElementById('stopSharingCamera')
const showCameraButton = document.getElementById('showCamera')
const hideCameraButton = document.getElementById('hideCamera')
const shareScreenButton = document.getElementById('shareScreen')
const stopSharingScreenButton = document.getElementById('stopSharingScreen')
const showScreenButton = document.getElementById('showScreen')
const hideScreenButton = document.getElementById('hideScreen')
const localScreenMount = document.getElementById('localScreenMount')
const remoteScreenMount = document.getElementById('remoteScreenMount')
/** Helpers */
function appendMessage(message: ChatMessage): void {
if (!messagesElement) return
void messagesElement.append(
document.createTextNode(`${message.name}: ${message.text}`)
)
void messagesElement.append(document.createElement('br'))
}
function renderMessages(messages: CRList<ChatMessage>): void {
if (!messagesElement) return
messagesElement.textContent = ''
for (const message of messages) void appendMessage(message)
}
function setupWire(
connection: P2PConnection<PeerMessage>,
messages: CRList<ChatMessage>
): void {
peer = connection
remoteCameraMount?.replaceChildren()
remoteScreenMount?.replaceChildren()
void connection.addEventListener('camera', ({ detail }) => {
remoteCameraMount?.replaceChildren(detail)
})
void connection.addEventListener('screen', ({ detail }) => {
remoteScreenMount?.replaceChildren(detail)
})
void connection.addEventListener('message', ({ detail }) => {
switch (detail.kind) {
case 'snapshot': {
void window.dispatchEvent(new PointerEvent('pointerup'))
void messages.merge(detail.payload)
void setTimeout(() => void renderMessages(messages), 10)
break
}
case 'delta': {
void messages.merge(detail.payload)
break
}
case 'microphone-shared': {
if (peer?.remoteCameraVideoElement) {
peer.remoteCameraVideoElement.muted = false
}
break
}
case 'camera-shared': {
if (remoteCameraMount && peer?.remoteCameraVideoElement) {
void remoteCameraMount.replaceChildren(peer.remoteCameraVideoElement)
}
break
}
case 'screen-shared': {
if (remoteScreenMount && peer?.remoteScreenVideoElement) {
void remoteScreenMount.replaceChildren(peer.remoteScreenVideoElement)
}
break
}
case 'microphone-muted': {
if (peer?.remoteCameraVideoElement) {
peer.remoteCameraVideoElement.muted = true
}
break
}
case 'camera-muted': {
if (peer?.remoteCameraVideoElement) {
void document.head.append(peer.remoteCameraVideoElement)
} else {
void remoteCameraMount?.replaceChildren()
}
break
}
case 'screen-muted': {
void remoteScreenMount?.replaceChildren()
break
}
}
})
}
/** Script */
if (nameInput instanceof HTMLInputElement) {
nameInput.value = (await profileStore.get('name')) ?? ''
void nameInput.addEventListener('change', () => {
void profileStore.put('name', nameInput.value.trim())
})
}
if (makeOfferButton instanceof HTMLButtonElement) {
void makeOfferButton.addEventListener('click', async () => {
const offer = await P2PConnection.makeOffer()
const optimized = await QR.optimizeEncoding(JSON.stringify(offer))
void QR.display(optimized)
})
}
if (acceptOfferButton instanceof HTMLButtonElement) {
void acceptOfferButton.addEventListener('click', async () => {
const signal = await QR.scan()
const offer = JSON.parse(await QR.restoreEncoding(signal)) as Offer
const { offeror, offeree } = await P2PConnection.acceptOffer(offer)
void setupWire(new P2PConnection(offeree), messages)
const optimized = await QR.optimizeEncoding(JSON.stringify(offeror))
void QR.display(optimized)
if (!peer) return
void (await peer.ready())
void peer.sendMessage({ kind: 'snapshot', payload: messages.toJSON() })
})
}
if (finishOfferButton instanceof HTMLButtonElement) {
void finishOfferButton.addEventListener('click', async () => {
const signal = await QR.scan()
const offeror = JSON.parse(await QR.restoreEncoding(signal)) as OfferorCopy
void setupWire(new P2PConnection(offeror), messages)
if (!peer) return
void (await peer.ready())
void peer.sendMessage({ kind: 'snapshot', payload: messages.toJSON() })
})
}
if (
sendMessageButton instanceof HTMLButtonElement &&
messageInput instanceof HTMLInputElement &&
nameInput instanceof HTMLInputElement
) {
void sendMessageButton.addEventListener('click', () => {
const text = messageInput.value.trim()
if (!text) return
void messages.append({
name: nameInput.value.trim() || 'Anonymous',
text,
})
messageInput.value = ''
})
}
void messages.addEventListener('delta', ({ detail }) => {
if (peer) void peer.sendMessage({ kind: 'delta', payload: detail })
void messages.snapshot()
})
void messages.addEventListener('change', ({ detail }) => {
for (const value of Object.values(detail)) {
if (value) void appendMessage(value)
}
})
void messages.addEventListener('snapshot', ({ detail }) => {
void messagesStore.put('messages', detail)
})
void renderMessages(messages)
if (shareMicrophoneButton instanceof HTMLButtonElement) {
void shareMicrophoneButton.addEventListener('click', async () => {
if (!peer) return
void (await peer.shareMicrophone())
void peer.sendMessage({ kind: 'microphone-shared' })
})
}
if (stopSharingMicrophoneButton instanceof HTMLButtonElement) {
void stopSharingMicrophoneButton.addEventListener('click', () => {
if (!peer) return
void peer.stopSharingMicrophone()
void peer.sendMessage({ kind: 'microphone-muted' })
})
}
if (unmuteRemoteMicrophoneButton instanceof HTMLButtonElement) {
void unmuteRemoteMicrophoneButton.addEventListener('click', () => {
if (peer?.remoteCameraVideoElement) {
peer.remoteCameraVideoElement.muted = false
}
})
}
if (muteRemoteMicrophoneButton instanceof HTMLButtonElement) {
void muteRemoteMicrophoneButton.addEventListener('click', () => {
if (peer?.remoteCameraVideoElement) {
peer.remoteCameraVideoElement.muted = true
}
})
}
if (shareCameraButton instanceof HTMLButtonElement) {
void shareCameraButton.addEventListener('click', async () => {
if (!peer) return
void (await peer.shareCamera())
if (localCameraMount && P2PConnection.localCameraVideoElement) {
void localCameraMount.replaceChildren(
P2PConnection.localCameraVideoElement
)
}
void peer.sendMessage({ kind: 'camera-shared' })
})
}
if (stopSharingCameraButton instanceof HTMLButtonElement) {
void stopSharingCameraButton.addEventListener('click', () => {
if (!peer) return
void peer.stopSharingCamera()
void localCameraMount?.replaceChildren()
void peer.sendMessage({ kind: 'camera-muted' })
})
}
if (showCameraButton instanceof HTMLButtonElement) {
void showCameraButton.addEventListener('click', () => {
if (remoteCameraMount && peer?.remoteCameraVideoElement) {
void remoteCameraMount.replaceChildren(peer.remoteCameraVideoElement)
}
})
}
if (hideCameraButton instanceof HTMLButtonElement) {
void hideCameraButton.addEventListener('click', () => {
if (peer?.remoteCameraVideoElement) {
void document.head.append(peer.remoteCameraVideoElement)
} else {
void remoteCameraMount?.replaceChildren()
}
})
}
if (shareScreenButton instanceof HTMLButtonElement) {
void shareScreenButton.addEventListener('click', async () => {
if (!peer) return
void (await peer.shareScreen())
if (localScreenMount && P2PConnection.localScreenVideoElement) {
void localScreenMount.replaceChildren(
P2PConnection.localScreenVideoElement
)
}
void peer.sendMessage({ kind: 'screen-shared' })
})
}
if (stopSharingScreenButton instanceof HTMLButtonElement) {
void stopSharingScreenButton.addEventListener('click', () => {
if (!peer) return
void peer.stopSharingScreen()
void localScreenMount?.replaceChildren()
void peer.sendMessage({ kind: 'screen-muted' })
})
}
if (showScreenButton instanceof HTMLButtonElement) {
void showScreenButton.addEventListener('click', () => {
if (remoteScreenMount && peer?.remoteScreenVideoElement) {
void remoteScreenMount.replaceChildren(peer.remoteScreenVideoElement)
}
})
}
if (hideScreenButton instanceof HTMLButtonElement) {
void hideScreenButton.addEventListener('click', () => {
if (peer?.remoteScreenVideoElement) {
void document.head.append(peer.remoteScreenVideoElement)
} else {
void remoteScreenMount?.replaceChildren()
}
})
}