196 lines
5.8 KiB
JavaScript
196 lines
5.8 KiB
JavaScript
import MWSEEventTarget from './EventTarget.js';
|
|
import { PeerInfo } from './PeerInfo.js';
|
|
import WebRTC from './WebRTC.js';
|
|
|
|
export default class Peer extends MWSEEventTarget {
|
|
constructor(mwse) {
|
|
super();
|
|
this.mwse = mwse;
|
|
this.options = {};
|
|
this.socketId = undefined;
|
|
this.selfSocket = false;
|
|
this.active = false;
|
|
this.peerConnection = false;
|
|
this.primaryChannel = 'datachannel';
|
|
|
|
this.info = new PeerInfo(this);
|
|
this.rtc = this._createRTC();
|
|
|
|
// Route incoming packs: RTC signaling goes to the RTC engine,
|
|
// everything else surfaces as a 'message' event.
|
|
this.on('pack', data => {
|
|
if (data.type === ':rtcpack:') {
|
|
this.rtc.emit('input', data.payload);
|
|
} else {
|
|
this.emit('message', data);
|
|
}
|
|
});
|
|
}
|
|
|
|
_createRTC(rtcConfig, rtcServers) {
|
|
const rtc = new WebRTC(rtcConfig, rtcServers);
|
|
rtc.peer = this;
|
|
|
|
rtc.on('connected', () => { this.peerConnection = true; });
|
|
rtc.on('disconnected', () => { this.peerConnection = false; });
|
|
|
|
// RTC output → relay opaque `:rtcpack:` to the paired peer via server.
|
|
rtc.on('output', payload => {
|
|
this.send({ type: ':rtcpack:', payload });
|
|
});
|
|
|
|
// RTC data-channel message → surface as peer 'pack' event.
|
|
rtc.on('message', payload => {
|
|
this.emit('pack', payload);
|
|
});
|
|
|
|
this.rtc = rtc;
|
|
return rtc;
|
|
}
|
|
|
|
// createRTC allows callers to (re)create the RTC object with custom config.
|
|
createRTC(rtcConfig, rtcServers) {
|
|
return this._createRTC(rtcConfig, rtcServers);
|
|
}
|
|
|
|
setPeerOptions(options) {
|
|
if (typeof options === 'string') {
|
|
this.setSocketId(options);
|
|
} else {
|
|
this.options = options;
|
|
}
|
|
}
|
|
|
|
setSocketId(uuid) {
|
|
this.socketId = uuid;
|
|
}
|
|
|
|
async metadata() {
|
|
if (this.socketId === 'me') {
|
|
const result = await this.mwse.EventPooling.request({ type: 'my/socketid' });
|
|
this.selfSocket = true;
|
|
this.active = this.active || true;
|
|
this.socketId = result;
|
|
this.emit('scope');
|
|
this.activeScope = true;
|
|
return result;
|
|
}
|
|
}
|
|
|
|
async request(pack) {
|
|
if (this.active) {
|
|
return this.mwse.request(this.socketId, pack);
|
|
}
|
|
}
|
|
|
|
equalTo(peer) {
|
|
return this.socketId === peer.socketId;
|
|
}
|
|
|
|
async isReachable() {
|
|
return this.mwse.EventPooling.request({ type: 'is/reachable', to: this.socketId });
|
|
}
|
|
|
|
async enablePairAuth() {
|
|
await this.mwse.EventPooling.request({ type: 'auth/pair-system', value: 'everybody' });
|
|
}
|
|
|
|
async disablePairAuth() {
|
|
await this.mwse.EventPooling.request({ type: 'auth/pair-system', value: 'disable' });
|
|
}
|
|
|
|
async enablePairInfo() {
|
|
await this.mwse.EventPooling.request({ type: 'connection/pairinfo', value: true });
|
|
}
|
|
|
|
async disablePairInfo() {
|
|
await this.mwse.EventPooling.request({ type: 'connection/pairinfo', value: false });
|
|
}
|
|
|
|
async requestPair() {
|
|
const { message, status } = await this.mwse.EventPooling.request({
|
|
type: 'request/pair',
|
|
to: this.socketId
|
|
});
|
|
if (message === 'ALREADY-PAIRED' || message === 'ALREADY-REQUESTED') {
|
|
console.warn('MWSE: already paired or pair requested');
|
|
}
|
|
if (status === 'fail') {
|
|
console.error('MWSE: requestPair failed', status, message);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
async endPair() {
|
|
await this.mwse.EventPooling.request({ type: 'end/pair', to: this.socketId });
|
|
this.forget();
|
|
}
|
|
|
|
async acceptPair() {
|
|
const { message, status } = await this.mwse.EventPooling.request({
|
|
type: 'accept/pair',
|
|
to: this.socketId
|
|
});
|
|
if (status === 'fail') {
|
|
console.error('MWSE: acceptPair failed', status, message);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
async rejectPair() {
|
|
const { message, status } = await this.mwse.EventPooling.request({
|
|
type: 'reject/pair',
|
|
to: this.socketId
|
|
});
|
|
if (status === 'fail') {
|
|
console.error('MWSE: rejectPair failed', status, message);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
async getPairedList() {
|
|
const { value } = await this.mwse.EventPooling.request({
|
|
type: 'pair/list',
|
|
to: this.socketId
|
|
});
|
|
return value;
|
|
}
|
|
|
|
async send(pack) {
|
|
const p2pOpen = this.peerConnection && this.rtc?.active;
|
|
const serverOpen = this.mwse.server.connected;
|
|
|
|
let channel;
|
|
if (p2pOpen && serverOpen) {
|
|
channel = this.primaryChannel === 'websocket' ? 'websocket' : 'datachannel';
|
|
} else if (serverOpen) {
|
|
channel = 'websocket';
|
|
} else {
|
|
channel = 'datachannel';
|
|
}
|
|
|
|
if (channel === 'websocket') {
|
|
if (!this.mwse.writable) {
|
|
console.warn('MWSE: socket is not writable');
|
|
return;
|
|
}
|
|
// WOM — no waiter registered; the engine returns nil for pack/to (#33).
|
|
this.mwse.EventPooling.only({ type: 'pack/to', pack, to: this.socketId });
|
|
} else {
|
|
if (pack.type !== ':rtcpack:') {
|
|
this.rtc?.sendMessage(pack);
|
|
} else {
|
|
console.warn('MWSE: cannot send :rtcpack: over data channel');
|
|
}
|
|
}
|
|
}
|
|
|
|
forget() {
|
|
this.mwse.peers.delete(this.socketId);
|
|
this.mwse.pairs.delete(this.socketId);
|
|
}
|
|
}
|