MWSE/sdk/Peer.js

200 lines
6.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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);
}
}