200 lines
6.2 KiB
JavaScript
200 lines
6.2 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.mwse.pairs.delete(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;
|
||
}
|
||
// Kabul eden tarafta da pairs haritasını güncelle.
|
||
this.mwse.pairs.set(this.socketId, this);
|
||
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;
|
||
}
|
||
this.mwse.pairs.delete(this.socketId);
|
||
return true;
|
||
}
|
||
|
||
async getPairedList() {
|
||
const { value } = await this.mwse.EventPooling.request({
|
||
type: 'pair/list',
|
||
to: this.socketId
|
||
});
|
||
return value;
|
||
}
|
||
|
||
async send(pack) {
|
||
const serverOpen = this.mwse.server.connected;
|
||
|
||
// WebRTC signaling (:rtcpack:) MUST always travel via the WebSocket relay —
|
||
// never over the DataChannel. This is true even after the p2p connection is
|
||
// established (renegotiation, new-stream ICE candidates, etc.). The DataChannel
|
||
// does not exist yet when the first offer/answer exchange happens, and may not
|
||
// be the right path for out-of-band signaling later either.
|
||
const forceWS = pack.type === ':rtcpack:';
|
||
|
||
const p2pOpen = !forceWS && this.peerConnection && this.rtc?.active;
|
||
|
||
const channel = (p2pOpen && serverOpen)
|
||
? (this.primaryChannel === 'websocket' ? 'websocket' : 'datachannel')
|
||
: 'websocket';
|
||
|
||
if (channel === 'websocket') {
|
||
if (!serverOpen) return;
|
||
if (!this.mwse.writable && !forceWS) {
|
||
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 {
|
||
this.rtc?.sendMessage(pack);
|
||
}
|
||
}
|
||
|
||
forget() {
|
||
this.mwse.peers.delete(this.socketId);
|
||
this.mwse.pairs.delete(this.socketId);
|
||
}
|
||
}
|