resig.js
Version:
Universal reactive signal library with complete platform features: signals, animations, CRDTs, scheduling, DOM integration. Works identically across React, SolidJS, Svelte, Vue, and Qwik.
306 lines • 24.6 kB
JavaScript
/**
* WebRTC Streaming Signals
* Real-time collaboration with automatic reconnection and peer discovery
*/
import { createStreamingSignal } from './coalgebra';
// WebRTC peer connection wrapper
export class WebRTCPeer {
constructor(peerId, config, signaling) {
this.peerId = peerId;
this.config = config;
this.signaling = signaling;
this.dataChannel = null;
this.reconnectAttempts = 0;
this.heartbeatTimer = null;
this.messageHandlers = new Set();
this.connection = new RTCPeerConnection({
iceServers: config.iceServers || [
{ urls: 'stun:stun.l.google.com:19302' },
],
});
this.setupConnection();
}
setupConnection() {
// Handle ICE candidates
this.connection.onicecandidate = (event) => {
if (event.candidate) {
this.signaling.iceCandidate(this.peerId, event.candidate.toJSON());
}
};
// Handle connection state changes
this.connection.onconnectionstatechange = () => {
const state = this.connection.connectionState;
if (state === 'failed' || state === 'disconnected') {
this.handleReconnection();
}
};
// Handle incoming data channels
this.connection.ondatachannel = (event) => {
this.setupDataChannel(event.channel);
};
}
setupDataChannel(channel) {
this.dataChannel = channel;
channel.onopen = () => {
this.startHeartbeat();
};
channel.onclose = () => {
this.stopHeartbeat();
};
channel.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
if (data.type === 'heartbeat') {
// Respond to heartbeat
this.send({ type: 'heartbeat-response', timestamp: Date.now() });
}
else {
this.messageHandlers.forEach((handler) => handler(data));
}
}
catch (error) {
console.error('Failed to parse WebRTC message:', error);
}
};
channel.onerror = (error) => {
console.error('WebRTC data channel error:', error);
};
}
startHeartbeat() {
if (this.heartbeatTimer)
return;
const interval = this.config.heartbeatInterval || 30000; // 30 seconds
this.heartbeatTimer = window.setInterval(() => {
this.send({ type: 'heartbeat', timestamp: Date.now() });
}, interval);
}
stopHeartbeat() {
if (this.heartbeatTimer) {
clearInterval(this.heartbeatTimer);
this.heartbeatTimer = null;
}
}
async handleReconnection() {
const maxAttempts = this.config.reconnectAttempts || 5;
const delay = this.config.reconnectDelay || 1000;
if (this.reconnectAttempts >= maxAttempts) {
console.error(`Failed to reconnect to peer ${this.peerId} after ${maxAttempts} attempts`);
return;
}
this.reconnectAttempts++;
console.log(`Attempting to reconnect to peer ${this.peerId} (attempt ${this.reconnectAttempts})`);
await new Promise((resolve) => setTimeout(resolve, delay * this.reconnectAttempts));
try {
await this.createOffer();
}
catch (error) {
console.error('Reconnection failed:', error);
this.handleReconnection();
}
}
async createOffer() {
// Create data channel for outgoing connections
if (!this.dataChannel) {
this.dataChannel = this.connection.createDataChannel('data', this.config.dataChannelOptions);
this.setupDataChannel(this.dataChannel);
}
const offer = await this.connection.createOffer();
await this.connection.setLocalDescription(offer);
await this.signaling.offer(this.peerId, offer);
}
async handleOffer(offer) {
await this.connection.setRemoteDescription(offer);
const answer = await this.connection.createAnswer();
await this.connection.setLocalDescription(answer);
await this.signaling.answer(this.peerId, answer);
}
async handleAnswer(answer) {
await this.connection.setRemoteDescription(answer);
this.reconnectAttempts = 0; // Reset on successful connection
}
async handleIceCandidate(candidate) {
await this.connection.addIceCandidate(new RTCIceCandidate(candidate));
}
send(data) {
if (this.dataChannel && this.dataChannel.readyState === 'open') {
try {
this.dataChannel.send(JSON.stringify(data));
return true;
}
catch (error) {
console.error('Failed to send WebRTC message:', error);
return false;
}
}
return false;
}
onMessage(handler) {
this.messageHandlers.add(handler);
return () => this.messageHandlers.delete(handler);
}
getConnectionState() {
return this.connection.connectionState;
}
close() {
this.stopHeartbeat();
if (this.dataChannel) {
this.dataChannel.close();
}
this.connection.close();
}
}
// Simple signaling implementation using BroadcastChannel
export class BroadcastChannelSignaling {
constructor(channelName = 'webrtc-signaling') {
this.offerHandlers = new Set();
this.answerHandlers = new Set();
this.iceCandidateHandlers = new Set();
this.channel = new BroadcastChannel(channelName);
this.channel.onmessage = this.handleMessage.bind(this);
}
handleMessage(event) {
const { type, peerId, data } = event.data;
switch (type) {
case 'offer':
this.offerHandlers.forEach((handler) => handler(peerId, data));
break;
case 'answer':
this.answerHandlers.forEach((handler) => handler(peerId, data));
break;
case 'ice-candidate':
this.iceCandidateHandlers.forEach((handler) => handler(peerId, data));
break;
}
}
async offer(peerId, offer) {
this.channel.postMessage({ type: 'offer', peerId, data: offer });
}
async answer(peerId, answer) {
this.channel.postMessage({ type: 'answer', peerId, data: answer });
}
async iceCandidate(peerId, candidate) {
this.channel.postMessage({
type: 'ice-candidate',
peerId,
data: candidate,
});
}
onOffer(callback) {
this.offerHandlers.add(callback);
}
onAnswer(callback) {
this.answerHandlers.add(callback);
}
onIceCandidate(callback) {
this.iceCandidateHandlers.add(callback);
}
close() {
this.channel.close();
}
}
// Create WebRTC streaming signal
export const createWebRTCStream = (peerId, config = {}, signaling) => {
const stream = createStreamingSignal(null);
const sig = signaling || new BroadcastChannelSignaling();
const peer = new WebRTCPeer(peerId, config, sig);
// Handle incoming messages
peer.onMessage((data) => {
if (typeof data === 'object' &&
data !== null &&
'type' in data &&
data.type === 'stream-data' &&
'value' in data) {
stream._set(data.value);
}
});
// Setup signaling handlers
sig.onOffer(async (fromPeerId, offer) => {
if (fromPeerId === peerId) {
await peer.handleOffer(offer);
}
});
sig.onAnswer(async (fromPeerId, answer) => {
if (fromPeerId === peerId) {
await peer.handleAnswer(answer);
}
});
sig.onIceCandidate(async (fromPeerId, candidate) => {
if (fromPeerId === peerId) {
await peer.handleIceCandidate(candidate);
}
});
// Override _set to broadcast changes
const originalSet = stream
._set;
stream._set = (value) => {
originalSet(value);
peer.send({ type: 'stream-data', value, timestamp: Date.now() });
};
// Add peer management methods
stream.peer = peer;
stream.connect = () => peer.createOffer();
stream.disconnect = () => peer.close();
stream.getConnectionState = () => peer.getConnectionState();
return stream;
};
// Multi-peer WebRTC stream for broadcasting to multiple peers
export const createWebRTCBroadcast = (peerIds, config = {}, signaling) => {
const stream = createStreamingSignal(null);
const sig = signaling || new BroadcastChannelSignaling();
const peers = new Map();
// Create peers for each peer ID
peerIds.forEach((peerId) => {
const peer = new WebRTCPeer(peerId, config, sig);
peers.set(peerId, peer);
// Handle incoming messages from any peer
peer.onMessage((data) => {
if (typeof data === 'object' &&
data !== null &&
'type' in data &&
data.type === 'stream-data' &&
'value' in data) {
stream._set(data.value);
}
});
});
// Setup signaling for all peers
sig.onOffer(async (fromPeerId, offer) => {
const peer = peers.get(fromPeerId);
if (peer) {
await peer.handleOffer(offer);
}
});
sig.onAnswer(async (fromPeerId, answer) => {
const peer = peers.get(fromPeerId);
if (peer) {
await peer.handleAnswer(answer);
}
});
sig.onIceCandidate(async (fromPeerId, candidate) => {
const peer = peers.get(fromPeerId);
if (peer) {
await peer.handleIceCandidate(candidate);
}
});
// Override _set to broadcast to all peers
const originalSet = stream
._set;
stream._set = (value) => {
originalSet(value);
const message = { type: 'stream-data', value, timestamp: Date.now() };
peers.forEach((peer) => peer.send(message));
};
// Add broadcast management methods
stream.peers = peers;
stream.connectAll = async () => {
const peerArray = Array.from(peers.values());
for (const peer of peerArray) {
await peer.createOffer();
}
};
stream.disconnectAll = () => {
peers.forEach((peer) => peer.close());
};
return stream;
};
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoid2VicnRjLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL3N0cmVhbWluZy93ZWJydGMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7OztHQUdHO0FBRUgsT0FBTyxFQUFFLHFCQUFxQixFQUFtQixNQUFNLGFBQWEsQ0FBQztBQW1DckUsaUNBQWlDO0FBQ2pDLE1BQU0sT0FBTyxVQUFVO0lBT3JCLFlBQ1MsTUFBYyxFQUNiLE1BQTBCLEVBQzFCLFNBQXdCO1FBRnpCLFdBQU0sR0FBTixNQUFNLENBQVE7UUFDYixXQUFNLEdBQU4sTUFBTSxDQUFvQjtRQUMxQixjQUFTLEdBQVQsU0FBUyxDQUFlO1FBUjFCLGdCQUFXLEdBQTBCLElBQUksQ0FBQztRQUMxQyxzQkFBaUIsR0FBRyxDQUFDLENBQUM7UUFDdEIsbUJBQWMsR0FBa0IsSUFBSSxDQUFDO1FBQ3JDLG9CQUFlLEdBQUcsSUFBSSxHQUFHLEVBQTJCLENBQUM7UUFPM0QsSUFBSSxDQUFDLFVBQVUsR0FBRyxJQUFJLGlCQUFpQixDQUFDO1lBQ3RDLFVBQVUsRUFBRSxNQUFNLENBQUMsVUFBVSxJQUFJO2dCQUMvQixFQUFFLElBQUksRUFBRSw4QkFBOEIsRUFBRTthQUN6QztTQUNGLENBQUMsQ0FBQztRQUVILElBQUksQ0FBQyxlQUFlLEVBQUUsQ0FBQztJQUN6QixDQUFDO0lBRU8sZUFBZTtRQUNyQix3QkFBd0I7UUFDeEIsSUFBSSxDQUFDLFVBQVUsQ0FBQyxjQUFjLEdBQUcsQ0FBQyxLQUFLLEVBQUUsRUFBRTtZQUN6QyxJQUFJLEtBQUssQ0FBQyxTQUFTLEVBQUUsQ0FBQztnQkFDcEIsSUFBSSxDQUFDLFNBQVMsQ0FBQyxZQUFZLENBQUMsSUFBSSxDQUFDLE1BQU0sRUFBRSxLQUFLLENBQUMsU0FBUyxDQUFDLE1BQU0sRUFBRSxDQUFDLENBQUM7WUFDckUsQ0FBQztRQUNILENBQUMsQ0FBQztRQUVGLGtDQUFrQztRQUNsQyxJQUFJLENBQUMsVUFBVSxDQUFDLHVCQUF1QixHQUFHLEdBQUcsRUFBRTtZQUM3QyxNQUFNLEtBQUssR0FBRyxJQUFJLENBQUMsVUFBVSxDQUFDLGVBQWUsQ0FBQztZQUM5QyxJQUFJLEtBQUssS0FBSyxRQUFRLElBQUksS0FBSyxLQUFLLGNBQWMsRUFBRSxDQUFDO2dCQUNuRCxJQUFJLENBQUMsa0JBQWtCLEVBQUUsQ0FBQztZQUM1QixDQUFDO1FBQ0gsQ0FBQyxDQUFDO1FBRUYsZ0NBQWdDO1FBQ2hDLElBQUksQ0FBQyxVQUFVLENBQUMsYUFBYSxHQUFHLENBQUMsS0FBSyxFQUFFLEVBQUU7WUFDeEMsSUFBSSxDQUFDLGdCQUFnQixDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsQ0FBQztRQUN2QyxDQUFDLENBQUM7SUFDSixDQUFDO0lBRU8sZ0JBQWdCLENBQUMsT0FBdUI7UUFDOUMsSUFBSSxDQUFDLFdBQVcsR0FBRyxPQUFPLENBQUM7UUFFM0IsT0FBTyxDQUFDLE1BQU0sR0FBRyxHQUFHLEVBQUU7WUFDcEIsSUFBSSxDQUFDLGNBQWMsRUFBRSxDQUFDO1FBQ3hCLENBQUMsQ0FBQztRQUVGLE9BQU8sQ0FBQyxPQUFPLEdBQUcsR0FBRyxFQUFFO1lBQ3JCLElBQUksQ0FBQyxhQUFhLEVBQUUsQ0FBQztRQUN2QixDQUFDLENBQUM7UUFFRixPQUFPLENBQUMsU0FBUyxHQUFHLENBQUMsS0FBSyxFQUFFLEVBQUU7WUFDNUIsSUFBSSxDQUFDO2dCQUNILE1BQU0sSUFBSSxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxDQUFDO2dCQUNwQyxJQUFJLElBQUksQ0FBQyxJQUFJLEtBQUssV0FBVyxFQUFFLENBQUM7b0JBQzlCLHVCQUF1QjtvQkFDdkIsSUFBSSxDQUFDLElBQUksQ0FBQyxFQUFFLElBQUksRUFBRSxvQkFBb0IsRUFBRSxTQUFTLEVBQUUsSUFBSSxDQUFDLEdBQUcsRUFBRSxFQUFFLENBQUMsQ0FBQztnQkFDbkUsQ0FBQztxQkFBTSxDQUFDO29CQUNOLElBQUksQ0FBQyxlQUFlLENBQUMsT0FBTyxDQUFDLENBQUMsT0FBTyxFQUFFLEVBQUUsQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQztnQkFDM0QsQ0FBQztZQUNILENBQUM7WUFBQyxPQUFPLEtBQUssRUFBRSxDQUFDO2dCQUNmLE9BQU8sQ0FBQyxLQUFLLENBQUMsaUNBQWlDLEVBQUUsS0FBSyxDQUFDLENBQUM7WUFDMUQsQ0FBQztRQUNILENBQUMsQ0FBQztRQUVGLE9BQU8sQ0FBQyxPQUFPLEdBQUcsQ0FBQyxLQUFLLEVBQUUsRUFBRTtZQUMxQixPQUFPLENBQUMsS0FBSyxDQUFDLDRCQUE0QixFQUFFLEtBQUssQ0FBQyxDQUFDO1FBQ3JELENBQUMsQ0FBQztJQUNKLENBQUM7SUFFTyxjQUFjO1FBQ3BCLElBQUksSUFBSSxDQUFDLGNBQWM7WUFBRSxPQUFPO1FBRWhDLE1BQU0sUUFBUSxHQUFHLElBQUksQ0FBQyxNQUFNLENBQUMsaUJBQWlCLElBQUksS0FBSyxDQUFDLENBQUMsYUFBYTtRQUN0RSxJQUFJLENBQUMsY0FBYyxHQUFHLE1BQU0sQ0FBQyxXQUFXLENBQUMsR0FBRyxFQUFFO1lBQzVDLElBQUksQ0FBQyxJQUFJLENBQUMsRUFBRSxJQUFJLEVBQUUsV0FBVyxFQUFFLFNBQVMsRUFBRSxJQUFJLENBQUMsR0FBRyxFQUFFLEVBQUUsQ0FBQyxDQUFDO1FBQzFELENBQUMsRUFBRSxRQUFRLENBQUMsQ0FBQztJQUNmLENBQUM7SUFFTyxhQUFhO1FBQ25CLElBQUksSUFBSSxDQUFDLGNBQWMsRUFBRSxDQUFDO1lBQ3hCLGFBQWEsQ0FBQyxJQUFJLENBQUMsY0FBYyxDQUFDLENBQUM7WUFDbkMsSUFBSSxDQUFDLGNBQWMsR0FBRyxJQUFJLENBQUM7UUFDN0IsQ0FBQztJQUNILENBQUM7SUFFTyxLQUFLLENBQUMsa0JBQWtCO1FBQzlCLE1BQU0sV0FBVyxHQUFHLElBQUksQ0FBQyxNQUFNLENBQUMsaUJBQWlCLElBQUksQ0FBQyxDQUFDO1FBQ3ZELE1BQU0sS0FBSyxHQUFHLElBQUksQ0FBQyxNQUFNLENBQUMsY0FBYyxJQUFJLElBQUksQ0FBQztRQUVqRCxJQUFJLElBQUksQ0FBQyxpQkFBaUIsSUFBSSxXQUFXLEVBQUUsQ0FBQztZQUMxQyxPQUFPLENBQUMsS0FBSyxDQUNYLCtCQUErQixJQUFJLENBQUMsTUFBTSxVQUFVLFdBQVcsV0FBVyxDQUMzRSxDQUFDO1lBQ0YsT0FBTztRQUNULENBQUM7UUFFRCxJQUFJLENBQUMsaUJBQWlCLEVBQUUsQ0FBQztRQUN6QixPQUFPLENBQUMsR0FBRyxDQUNULG1DQUFtQyxJQUFJLENBQUMsTUFBTSxhQUFhLElBQUksQ0FBQyxpQkFBaUIsR0FBRyxDQUNyRixDQUFDO1FBRUYsTUFBTSxJQUFJLE9BQU8sQ0FBQyxDQUFDLE9BQU8sRUFBRSxFQUFFLENBQzVCLFVBQVUsQ0FBQyxPQUFPLEVBQUUsS0FBSyxHQUFHLElBQUksQ0FBQyxpQkFBaUIsQ0FBQyxDQUNwRCxDQUFDO1FBRUYsSUFBSSxDQUFDO1lBQ0gsTUFBTSxJQUFJLENBQUMsV0FBVyxFQUFFLENBQUM7UUFDM0IsQ0FBQztRQUFDLE9BQU8sS0FBSyxFQUFFLENBQUM7WUFDZixPQUFPLENBQUMsS0FBSyxDQUFDLHNCQUFzQixFQUFFLEtBQUssQ0FBQyxDQUFDO1lBQzdDLElBQUksQ0FBQyxrQkFBa0IsRUFBRSxDQUFDO1FBQzVCLENBQUM7SUFDSCxDQUFDO0lBRUQsS0FBSyxDQUFDLFdBQVc7UUFDZiwrQ0FBK0M7UUFDL0MsSUFBSSxDQUFDLElBQUksQ0FBQyxXQUFXLEVBQUUsQ0FBQztZQUN0QixJQUFJLENBQUMsV0FBVyxHQUFHLElBQUksQ0FBQyxVQUFVLENBQUMsaUJBQWlCLENBQ2xELE1BQU0sRUFDTixJQUFJLENBQUMsTUFBTSxDQUFDLGtCQUFrQixDQUMvQixDQUFDO1lBQ0YsSUFBSSxDQUFDLGdCQUFnQixDQUFDLElBQUksQ0FBQyxXQUFXLENBQUMsQ0FBQztRQUMxQyxDQUFDO1FBRUQsTUFBTSxLQUFLLEdBQUcsTUFBTSxJQUFJLENBQUMsVUFBVSxDQUFDLFdBQVcsRUFBRSxDQUFDO1FBQ2xELE1BQU0sSUFBSSxDQUFDLFVBQVUsQ0FBQyxtQkFBbUIsQ0FBQyxLQUFLLENBQUMsQ0FBQztRQUNqRCxNQUFNLElBQUksQ0FBQyxTQUFTLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxNQUFNLEVBQUUsS0FBSyxDQUFDLENBQUM7SUFDakQsQ0FBQztJQUVELEtBQUssQ0FBQyxXQUFXLENBQUMsS0FBZ0M7UUFDaEQsTUFBTSxJQUFJLENBQUMsVUFBVSxDQUFDLG9CQUFvQixDQUFDLEtBQUssQ0FBQyxDQUFDO1FBQ2xELE1BQU0sTUFBTSxHQUFHLE1BQU0sSUFBSSxDQUFDLFVBQVUsQ0FBQyxZQUFZLEVBQUUsQ0FBQztRQUNwRCxNQUFNLElBQUksQ0FBQyxVQUFVLENBQUMsbUJBQW1CLENBQUMsTUFBTSxDQUFDLENBQUM7UUFDbEQsTUFBTSxJQUFJLENBQUMsU0FBUyxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsTUFBTSxFQUFFLE1BQU0sQ0FBQyxDQUFDO0lBQ25ELENBQUM7SUFFRCxLQUFLLENBQUMsWUFBWSxDQUFDLE1BQWlDO1FBQ2xELE1BQU0sSUFBSSxDQUFDLFVBQVUsQ0FBQyxvQkFBb0IsQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUNuRCxJQUFJLENBQUMsaUJBQWlCLEdBQUcsQ0FBQyxDQUFDLENBQUMsaUNBQWlDO0lBQy9ELENBQUM7SUFFRCxLQUFLLENBQUMsa0JBQWtCLENBQUMsU0FBOEI7UUFDckQsTUFBTSxJQUFJLENBQUMsVUFBVSxDQUFDLGVBQWUsQ0FBQyxJQUFJLGVBQWUsQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDO0lBQ3hFLENBQUM7SUFFRCxJQUFJLENBQUMsSUFBYTtRQUNoQixJQUFJLElBQUksQ0FBQyxXQUFXLElBQUksSUFBSSxDQUFDLFdBQVcsQ0FBQyxVQUFVLEtBQUssTUFBTSxFQUFFLENBQUM7WUFDL0QsSUFBSSxDQUFDO2dCQUNILElBQUksQ0FBQyxXQUFXLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQztnQkFDNUMsT0FBTyxJQUFJLENBQUM7WUFDZCxDQUFDO1lBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQztnQkFDZixPQUFPLENBQUMsS0FBSyxDQUFDLGdDQUFnQyxFQUFFLEtBQUssQ0FBQyxDQUFDO2dCQUN2RCxPQUFPLEtBQUssQ0FBQztZQUNmLENBQUM7UUFDSCxDQUFDO1FBQ0QsT0FBTyxLQUFLLENBQUM7SUFDZixDQUFDO0lBRUQsU0FBUyxDQUFDLE9BQWdDO1FBQ3hDLElBQUksQ0FBQyxlQUFlLENBQUMsR0FBRyxDQUFDLE9BQU8sQ0FBQyxDQUFDO1FBQ2xDLE9BQU8sR0FBRyxFQUFFLENBQUMsSUFBSSxDQUFDLGVBQWUsQ0FBQyxNQUFNLENBQUMsT0FBTyxDQUFDLENBQUM7SUFDcEQsQ0FBQztJQUVELGtCQUFrQjtRQUNoQixPQUFPLElBQUksQ0FBQyxVQUFVLENBQUMsZUFBcUMsQ0FBQztJQUMvRCxDQUFDO0lBRUQsS0FBSztRQUNILElBQUksQ0FBQyxhQUFhLEVBQUUsQ0FBQztRQUNyQixJQUFJLElBQUksQ0FBQyxXQUFXLEVBQUUsQ0FBQztZQUNyQixJQUFJLENBQUMsV0FBVyxDQUFDLEtBQUssRUFBRSxDQUFDO1FBQzNCLENBQUM7UUFDRCxJQUFJLENBQUMsVUFBVSxDQUFDLEtBQUssRUFBRSxDQUFDO0lBQzFCLENBQUM7Q0FDRjtBQUVELHlEQUF5RDtBQUN6RCxNQUFNLE9BQU8seUJBQXlCO0lBWXBDLFlBQVksY0FBc0Isa0JBQWtCO1FBVjVDLGtCQUFhLEdBQUcsSUFBSSxHQUFHLEVBRTVCLENBQUM7UUFDSSxtQkFBYyxHQUFHLElBQUksR0FBRyxFQUU3QixDQUFDO1FBQ0kseUJBQW9CLEdBQUcsSUFBSSxHQUFHLEVBRW5DLENBQUM7UUFHRixJQUFJLENBQUMsT0FBTyxHQUFHLElBQUksZ0JBQWdCLENBQUMsV0FBVyxDQUFDLENBQUM7UUFDakQsSUFBSSxDQUFDLE9BQU8sQ0FBQyxTQUFTLEdBQUcsSUFBSSxDQUFDLGFBQWEsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUM7SUFDekQsQ0FBQztJQUVPLGFBQWEsQ0FBQyxLQUFtQjtRQUN2QyxNQUFNLEVBQUUsSUFBSSxFQUFFLE1BQU0sRUFBRSxJQUFJLEVBQUUsR0FBRyxLQUFLLENBQUMsSUFBSSxDQUFDO1FBRTFDLFFBQVEsSUFBSSxFQUFFLENBQUM7WUFDYixLQUFLLE9BQU87Z0JBQ1YsSUFBSSxDQUFDLGFBQWEsQ0FBQyxPQUFPLENBQUMsQ0FBQyxPQUFPLEVBQUUsRUFBRSxDQUFDLE9BQU8sQ0FBQyxNQUFNLEVBQUUsSUFBSSxDQUFDLENBQUMsQ0FBQztnQkFDL0QsTUFBTTtZQUNSLEtBQUssUUFBUTtnQkFDWCxJQUFJLENBQUMsY0FBYyxDQUFDLE9BQU8sQ0FBQyxDQUFDLE9BQU8sRUFBRSxFQUFFLENBQUMsT0FBTyxDQUFDLE1BQU0sRUFBRSxJQUFJLENBQUMsQ0FBQyxDQUFDO2dCQUNoRSxNQUFNO1lBQ1IsS0FBSyxlQUFlO2dCQUNsQixJQUFJLENBQUMsb0JBQW9CLENBQUMsT0FBTyxDQUFDLENBQUMsT0FBTyxFQUFFLEVBQUUsQ0FBQyxPQUFPLENBQUMsTUFBTSxFQUFFLElBQUksQ0FBQyxDQUFDLENBQUM7Z0JBQ3RFLE1BQU07UUFDVixDQUFDO0lBQ0gsQ0FBQztJQUVELEtBQUssQ0FBQyxLQUFLLENBQUMsTUFBYyxFQUFFLEtBQWdDO1FBQzFELElBQUksQ0FBQyxPQUFPLENBQUMsV0FBVyxDQUFDLEVBQUUsSUFBSSxFQUFFLE9BQU8sRUFBRSxNQUFNLEVBQUUsSUFBSSxFQUFFLEtBQUssRUFBRSxDQUFDLENBQUM7SUFDbkUsQ0FBQztJQUVELEtBQUssQ0FBQyxNQUFNLENBQ1YsTUFBYyxFQUNkLE1BQWlDO1FBRWpDLElBQUksQ0FBQyxPQUFPLENBQUMsV0FBVyxDQUFDLEVBQUUsSUFBSSxFQUFFLFFBQVEsRUFBRSxNQUFNLEVBQUUsSUFBSSxFQUFFLE1BQU0sRUFBRSxDQUFDLENBQUM7SUFDckUsQ0FBQztJQUVELEtBQUssQ0FBQyxZQUFZLENBQ2hCLE1BQWMsRUFDZCxTQUE4QjtRQUU5QixJQUFJLENBQUMsT0FBTyxDQUFDLFdBQVcsQ0FBQztZQUN2QixJQUFJLEVBQUUsZUFBZTtZQUNyQixNQUFNO1lBQ04sSUFBSSxFQUFFLFNBQVM7U0FDaEIsQ0FBQyxDQUFDO0lBQ0wsQ0FBQztJQUVELE9BQU8sQ0FDTCxRQUFvRTtRQUVwRSxJQUFJLENBQUMsYUFBYSxDQUFDLEdBQUcsQ0FBQyxRQUFRLENBQUMsQ0FBQztJQUNuQyxDQUFDO0lBRUQsUUFBUSxDQUNOLFFBQXFFO1FBRXJFLElBQUksQ0FBQyxjQUFjLENBQUMsR0FBRyxDQUFDLFFBQVEsQ0FBQyxDQUFDO0lBQ3BDLENBQUM7SUFFRCxjQUFjLENBQ1osUUFBa0U7UUFFbEUsSUFBSSxDQUFDLG9CQUFvQixDQUFDLEdBQUcsQ0FBQyxRQUFRLENBQUMsQ0FBQztJQUMxQyxDQUFDO0lBRUQsS0FBSztRQUNILElBQUksQ0FBQyxPQUFPLENBQUMsS0FBSyxFQUFFLENBQUM7SUFDdkIsQ0FBQztDQUNGO0FBRUQsaUNBQWlDO0FBQ2pDLE1BQU0sQ0FBQyxNQUFNLGtCQUFrQixHQUFHLENBQ2hDLE1BQWMsRUFDZCxTQUE2QixFQUFFLEVBQy9CLFNBQXlCLEVBQ0wsRUFBRTtJQUN0QixNQUFNLE1BQU0sR0FBRyxxQkFBcUIsQ0FBSSxJQUFXLENBQUMsQ0FBQztJQUNyRCxNQUFNLEdBQUcsR0FBRyxTQUFTLElBQUksSUFBSSx5QkFBeUIsRUFBRSxDQUFDO0lBQ3pELE1BQU0sSUFBSSxHQUFHLElBQUksVUFBVSxDQUFDLE1BQU0sRUFBRSxNQUFNLEVBQUUsR0FBRyxDQUFDLENBQUM7SUFFakQsMkJBQTJCO0lBQzNCLElBQUksQ0FBQyxTQUFTLENBQUMsQ0FBQyxJQUFJLEVBQUUsRUFBRTtRQUN0QixJQUNFLE9BQU8sSUFBSSxLQUFLLFFBQVE7WUFDeEIsSUFBSSxLQUFLLElBQUk7WUFDYixNQUFNLElBQUksSUFBSTtZQUNkLElBQUksQ0FBQyxJQUFJLEtBQUssYUFBYTtZQUMzQixPQUFPLElBQUksSUFBSSxFQUNmLENBQUM7WUFDQSxNQUF3RCxDQUFDLElBQUksQ0FDM0QsSUFBcUIsQ0FBQyxLQUFLLENBQzdCLENBQUM7UUFDSixDQUFDO0lBQ0gsQ0FBQyxDQUFDLENBQUM7SUFFSCwyQkFBMkI7SUFDM0IsR0FBRyxDQUFDLE9BQU8sQ0FBQyxLQUFLLEVBQUUsVUFBVSxFQUFFLEtBQUssRUFBRSxFQUFFO1FBQ3RDLElBQUksVUFBVSxLQUFLLE1BQU0sRUFBRSxDQUFDO1lBQzFCLE1BQU0sSUFBSSxDQUFDLFdBQVcsQ0FBQyxLQUFLLENBQUMsQ0FBQztRQUNoQyxDQUFDO0lBQ0gsQ0FBQyxDQUFDLENBQUM7SUFFSCxHQUFHLENBQUMsUUFBUSxDQUFDLEtBQUssRUFBRSxVQUFVLEVBQUUsTUFBTSxFQUFFLEVBQUU7UUFDeEMsSUFBSSxVQUFVLEtBQUssTUFBTSxFQUFFLENBQUM7WUFDMUIsTUFBTSxJQUFJLENBQUMsWUFBWSxDQUFDLE1BQU0sQ0FBQyxDQUFDO1FBQ2xDLENBQUM7SUFDSCxDQUFDLENBQUMsQ0FBQztJQUVILEdBQUcsQ0FBQyxjQUFjLENBQUMsS0FBSyxFQUFFLFVBQVUsRUFBRSxTQUFTLEVBQUUsRUFBRTtRQUNqRCxJQUFJLFVBQVUsS0FBSyxNQUFNLEVBQUUsQ0FBQztZQUMxQixNQUFNLElBQUksQ0FBQyxrQkFBa0IsQ0FBQyxTQUFTLENBQUMsQ0FBQztRQUMzQyxDQUFDO0lBQ0gsQ0FBQyxDQUFDLENBQUM7SUFFSCxxQ0FBcUM7SUFDckMsTUFBTSxXQUFXLEdBQUksTUFBd0Q7U0FDMUUsSUFBSSxDQUFDO0lBQ1AsTUFBd0QsQ0FBQyxJQUFJLEdBQUcsQ0FDL0QsS0FBUSxFQUNSLEVBQUU7UUFDRixXQUFXLENBQUMsS0FBSyxDQUFDLENBQUM7UUFDbkIsSUFBSSxDQUFDLElBQUksQ0FBQyxFQUFFLElBQUksRUFBRSxhQUFhLEVBQUUsS0FBSyxFQUFFLFNBQVMsRUFBRSxJQUFJLENBQUMsR0FBRyxFQUFFLEVBQUUsQ0FBQyxDQUFDO0lBQ25FLENBQUMsQ0FBQztJQUVGLDhCQUE4QjtJQUM3QixNQUFjLENBQUMsSUFBSSxHQUFHLElBQUksQ0FBQztJQUMzQixNQUFjLENBQUMsT0FBTyxHQUFHLEdBQUcsRUFBRSxDQUFDLElBQUksQ0FBQyxXQUFXLEVBQUUsQ0FBQztJQUNsRCxNQUFjLENBQUMsVUFBVSxHQUFHLEdBQUcsRUFBRSxDQUFDLElBQUksQ0FBQyxLQUFLLEVBQUUsQ0FBQztJQUMvQyxNQUFjLENBQUMsa0JBQWtCLEdBQUcsR0FBRyxFQUFFLENBQUMsSUFBSSxDQUFDLGtCQUFrQixFQUFFLENBQUM7SUFFckUsT0FBTyxNQUFNLENBQUM7QUFDaEIsQ0FBQyxDQUFDO0FBRUYsOERBQThEO0FBQzlELE1BQU0sQ0FBQyxNQUFNLHFCQUFxQixHQUFHLENBQ25DLE9BQWlCLEVBQ2pCLFNBQTZCLEVBQUUsRUFDL0IsU0FBeUIsRUFDTCxFQUFFO0lBQ3RCLE1BQU0sTUFBTSxHQUFHLHFCQUFxQixDQUFJLElBQVcsQ0FBQyxDQUFDO0lBQ3JELE1BQU0sR0FBRyxHQUFHLFNBQVMsSUFBSSxJQUFJLHlCQUF5QixFQUFFLENBQUM7SUFDekQsTUFBTSxLQUFLLEdBQUcsSUFBSSxHQUFHLEVBQXNCLENBQUM7SUFFNUMsZ0NBQWdDO0lBQ2hDLE9BQU8sQ0FBQyxPQUFPLENBQUMsQ0FBQyxNQUFNLEVBQUUsRUFBRTtRQUN6QixNQUFNLElBQUksR0FBRyxJQUFJLFVBQVUsQ0FBQyxNQUFNLEVBQUUsTUFBTSxFQUFFLEdBQUcsQ0FBQyxDQUFDO1FBQ2pELEtBQUssQ0FBQyxHQUFHLENBQUMsTUFBTSxFQUFFLElBQUksQ0FBQyxDQUFDO1FBRXhCLHlDQUF5QztRQUN6QyxJQUFJLENBQUMsU0FBUyxDQUFDLENBQUMsSUFBSSxFQUFFLEVBQUU7WUFDdEIsSUFDRSxPQUFPLElBQUksS0FBSyxRQUFRO2dCQUN4QixJQUFJLEtBQUssSUFBSTtnQkFDYixNQUFNLElBQUksSUFBSTtnQkFDZCxJQUFJLENBQUMsSUFBSSxLQUFLLGFBQWE7Z0JBQzNCLE9BQU8sSUFBSSxJQUFJLEVBQ2YsQ0FBQztnQkFDQSxNQUF3RCxDQUFDLElBQUksQ0FDM0QsSUFBcUIsQ0FBQyxLQUFLLENBQzdCLENBQUM7WUFDSixDQUFDO1FBQ0gsQ0FBQyxDQUFDLENBQUM7SUFDTCxDQUFDLENBQUMsQ0FBQztJQUVILGdDQUFnQztJQUNoQyxHQUFHLENBQUMsT0FBTyxDQUFDLEtBQUssRUFBRSxVQUFVLEVBQUUsS0FBSyxFQUFFLEVBQUU7UUFDdEMsTUFBTSxJQUFJLEdBQUcsS0FBSyxDQUFDLEdBQUcsQ0FBQyxVQUFVLENBQUMsQ0FBQztRQUNuQyxJQUFJLElBQUksRUFBRSxDQUFDO1lBQ1QsTUFBTSxJQUFJLENBQUMsV0FBVyxDQUFDLEtBQUssQ0FBQyxDQUFDO1FBQ2hDLENBQUM7SUFDSCxDQUFDLENBQUMsQ0FBQztJQUVILEdBQUcsQ0FBQyxRQUFRLENBQUMsS0FBSyxFQUFFLFVBQVUsRUFBRSxNQUFNLEVBQUUsRUFBRTtRQUN4QyxNQUFNLElBQUksR0FBRyxLQUFLLENBQUMsR0FBRyxDQUFDLFVBQVUsQ0FBQyxDQUFDO1FBQ25DLElBQUksSUFBSSxFQUFFLENBQUM7WUFDVCxNQUFNLElBQUksQ0FBQyxZQUFZLENBQUMsTUFBTSxDQUFDLENBQUM7UUFDbEMsQ0FBQztJQUNILENBQUMsQ0FBQyxDQUFDO0lBRUgsR0FBRyxDQUFDLGNBQWMsQ0FBQyxLQUFLLEVBQUUsVUFBVSxFQUFFLFNBQVMsRUFBRSxFQUFFO1FBQ2pELE1BQU0sSUFBSSxHQUFHLEtBQUssQ0FBQyxHQUFHLENBQUMsVUFBVSxDQUFDLENBQUM7UUFDbkMsSUFBSSxJQUFJLEVBQUUsQ0FBQztZQUNULE1BQU0sSUFBSSxDQUFDLGtCQUFrQixDQUFDLFNBQVMsQ0FBQyxDQUFDO1FBQzNDLENBQUM7SUFDSCxDQUFDLENBQUMsQ0FBQztJQUVILDBDQUEwQztJQUMxQyxNQUFNLFdBQVcsR0FBSSxNQUF3RDtTQUMxRSxJQUFJLENBQUM7SUFDUCxNQUF3RCxDQUFDLElBQUksR0FBRyxDQUMvRCxLQUFRLEVBQ1IsRUFBRTtRQUNGLFdBQVcsQ0FBQyxLQUFLLENBQUMsQ0FBQztRQUNuQixNQUFNLE9BQU8sR0FBRyxFQUFFLElBQUksRUFBRSxhQUFhLEVBQUUsS0FBSyxFQUFFLFNBQVMsRUFBRSxJQUFJLENBQUMsR0FBRyxFQUFFLEVBQUUsQ0FBQztRQUN0RSxLQUFLLENBQUMsT0FBTyxDQUFDLENBQUMsSUFBSSxFQUFFLEVBQUUsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUM7SUFDOUMsQ0FBQyxDQUFDO0lBRUYsbUNBQW1DO0lBQ2xDLE1BQWMsQ0FBQyxLQUFLLEdBQUcsS0FBSyxDQUFDO0lBQzdCLE1BQWMsQ0FBQyxVQUFVLEdBQUcsS0FBSyxJQUFJLEVBQUU7UUFDdEMsTUFBTSxTQUFTLEdBQUcsS0FBSyxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsTUFBTSxFQUFFLENBQUMsQ0FBQztRQUM3QyxLQUFLLE1BQU0sSUFBSSxJQUFJLFNBQVMsRUFBRSxDQUFDO1lBQzdCLE1BQU0sSUFBSSxDQUFDLFdBQVcsRUFBRSxDQUFDO1FBQzNCLENBQUM7SUFDSCxDQUFDLENBQUM7SUFDRCxNQUFjLENBQUMsYUFBYSxHQUFHLEdBQUcsRUFBRTtRQUNuQyxLQUFLLENBQUMsT0FBTyxDQUFDLENBQUMsSUFBSSxFQUFFLEVBQUUsQ0FBQyxJQUFJLENBQUMsS0FBSyxFQUFFLENBQUMsQ0FBQztJQUN4QyxDQUFDLENBQUM7SUFFRixPQUFPLE1BQU0sQ0FBQztBQUNoQixDQUFDLENBQUMifQ==