@skyway-sdk/core
Version:
The official Next Generation JavaScript SDK for SkyWay
288 lines • 13.5 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Peer = void 0;
const common_1 = require("@skyway-sdk/common");
const token_1 = require("@skyway-sdk/token");
const errors_1 = require("../../../../errors");
const util_1 = require("../../../../util");
const util_2 = require("../util");
const log = new common_1.Logger('packages/core/src/plugin/internal/person/connection/peer.ts');
class Peer {
constructor(_context, _iceManager, signaling, analytics, localPerson, endpoint, role) {
var _a;
this._context = _context;
this._iceManager = _iceManager;
this.signaling = signaling;
this.analytics = analytics;
this.localPerson = localPerson;
this.endpoint = endpoint;
this.role = role;
this._pendingCandidates = [];
this.pc = new RTCPeerConnection(Object.assign(Object.assign({}, this._context.config.rtcConfig), { iceTransportPolicy: this._context.config.rtcConfig.turnPolicy === 'turnOnly'
? 'relay'
: undefined, iceServers: this._iceManager.iceServers }));
this.onSignalingStateChanged = new common_1.Event();
this.onPeerConnectionStateChanged = new common_1.Event();
this.onDisconnect = new common_1.Event();
this.connected = false;
this.disconnected = false;
this.rtcPeerConnectionId = (0, token_1.uuidV4)();
this._onICECandidate = (ev) => __awaiter(this, void 0, void 0, function* () {
if (ev.candidate == null ||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-expect-error firefox
ev.candidate === '' ||
this.pc.connectionState === 'closed') {
return;
}
const message = {
kind: 'iceCandidateMessage',
payload: {
candidate: ev.candidate,
role: this.role,
},
};
log.debug('[start] send candidate', {
message,
localPerson: this.localPerson,
});
if (this.localPerson._analytics &&
!this.localPerson._analytics.isClosed()) {
// 再送時に他の処理をブロックしないためにawaitしない
void this.localPerson._analytics.client.sendRtcPeerConnectionEventReport({
rtcPeerConnectionId: this.rtcPeerConnectionId,
type: 'iceCandidate',
data: {
candidate: JSON.stringify(ev.candidate),
},
createdAt: Date.now(),
});
}
try {
yield this.signaling.send(this.endpoint, message);
log.debug(`[end] send candidate`, {
message,
localPerson: this.localPerson,
});
}
catch (error) {
log.warn(`[failed] send candidate`, (0, util_1.createWarnPayload)({
operationName: 'Peer._onICECandidate',
channel: this.localPerson.channel,
detail: '[failed] send candidate',
payload: { message },
}), error);
}
});
this._onICECandidateError = (ev) => __awaiter(this, void 0, void 0, function* () {
if (this.localPerson._analytics &&
!this.localPerson._analytics.isClosed()) {
// 再送時に他の処理をブロックしないためにawaitしない
void this.localPerson._analytics.client.sendRtcPeerConnectionEventReport({
rtcPeerConnectionId: this.rtcPeerConnectionId,
type: 'iceCandidateError',
data: {
event: JSON.stringify(ev),
},
createdAt: Date.now(),
});
}
});
this._onIceGatheringStateChange = () => __awaiter(this, void 0, void 0, function* () {
if (this.localPerson._analytics &&
!this.localPerson._analytics.isClosed()) {
const state = this.pc.iceGatheringState;
// 再送時に他の処理をブロックしないためにawaitしない
void this.localPerson._analytics.client.sendRtcPeerConnectionEventReport({
rtcPeerConnectionId: this.rtcPeerConnectionId,
type: 'iceGatheringStateChange',
data: {
event: state,
},
createdAt: Date.now(),
});
}
});
this._onConnectionStateChange = () => __awaiter(this, void 0, void 0, function* () {
const state = this.pc.connectionState;
if (this.localPerson._analytics &&
!this.localPerson._analytics.isClosed()) {
// 再送時に他の処理をブロックしないためにawaitしない
void this.localPerson._analytics.client.sendRtcPeerConnectionEventReport({
rtcPeerConnectionId: this.rtcPeerConnectionId,
type: 'connectionStateChange',
data: {
connectionState: state,
},
createdAt: Date.now(),
});
}
switch (state) {
case 'connected':
this.connected = true;
this._pendingCandidates = [];
break;
}
this.onPeerConnectionStateChanged.emit(this.pc.connectionState);
});
this._onIceConnectionStateChange = () => __awaiter(this, void 0, void 0, function* () {
if (this.localPerson._analytics &&
!this.localPerson._analytics.isClosed()) {
const state = this.pc.iceConnectionState;
// 再送時に他の処理をブロックしないためにawaitしない
void this.localPerson._analytics.client.sendRtcPeerConnectionEventReport({
rtcPeerConnectionId: this.rtcPeerConnectionId,
type: 'iceConnectionStateChange',
data: {
iceConnectionState: state,
},
createdAt: Date.now(),
});
}
});
this._onSignalingStateChange = () => __awaiter(this, void 0, void 0, function* () {
if (this.localPerson._analytics &&
!this.localPerson._analytics.isClosed()) {
const state = this.pc.signalingState;
// 再送時に他の処理をブロックしないためにawaitしない
void this.localPerson._analytics.client.sendRtcPeerConnectionEventReport({
rtcPeerConnectionId: this.rtcPeerConnectionId,
type: 'signalingStateChange',
data: {
signalingState: state,
},
createdAt: Date.now(),
});
}
});
/**@throws {@link SkyWayError} */
this.waitForSignalingState = (state,
/**ms */
timeout = 10000) => __awaiter(this, void 0, void 0, function* () {
if (this.pc.signalingState === state)
return;
yield this.onSignalingStateChanged
.watch(() => this.pc.signalingState === state, timeout)
.catch((err) => {
throw (0, util_1.createError)({
operationName: 'Peer.waitForSignalingState',
info: Object.assign(Object.assign({}, errors_1.errors.timeout), { detail: 'waitForSignalingState timeout' }),
path: log.prefix,
context: this._context,
channel: this.localPerson.channel,
error: err,
});
});
});
/**@throws {@link SkyWayError} */
this.waitForConnectionState = (state,
/**ms */
timeout = 10000) => __awaiter(this, void 0, void 0, function* () {
if (state === this.pc.connectionState)
return;
yield this.onPeerConnectionStateChanged
.watch(() => state === this.pc.connectionState, timeout)
.catch((err) => {
throw (0, util_1.createError)({
operationName: 'Peer.waitForConnectionState',
info: Object.assign(Object.assign({}, errors_1.errors.timeout), { detail: 'waitForConnectionState timeout' }),
path: log.prefix,
context: this._context,
channel: this.localPerson.channel,
error: err,
});
});
});
/**@throws {@link SkyWayError} */
this.waitForStats = ({ track, cb, interval, timeout, logging, }) => __awaiter(this, void 0, void 0, function* () {
interval !== null && interval !== void 0 ? interval : (interval = 100);
timeout !== null && timeout !== void 0 ? timeout : (timeout = 10000);
for (let elapsed = 0;; elapsed += interval) {
if (elapsed >= timeout) {
throw (0, util_1.createError)({
operationName: 'Peer.waitForStats',
info: Object.assign(Object.assign({}, errors_1.errors.timeout), { detail: 'waitForStats timeout' }),
path: log.prefix,
context: this._context,
channel: this.localPerson.channel,
});
}
const report = yield this.pc.getStats(track);
const stats = (0, util_2.statsToJson)(report);
if (logging) {
log.debug('Peer.waitForStats', stats);
}
if (cb(stats)) {
break;
}
yield new Promise((r) => setTimeout(r, interval));
}
});
log.debug('peerConfig', this.pc.getConfiguration());
this.setPeerConnectionListener();
// suppress firefox [RTCPeerConnection is gone] Exception
const peerIdentity = (_a = this.pc) === null || _a === void 0 ? void 0 : _a.peerIdentity;
if (peerIdentity) {
peerIdentity.catch((err) => {
log.debug('firefox peerIdentity', err);
});
}
}
setPeerConnectionListener() {
this.pc.onicecandidate = this._onICECandidate;
this.pc.onicecandidateerror = this._onICECandidateError;
this.pc.onicegatheringstatechange = this._onIceGatheringStateChange;
this.pc.onconnectionstatechange = this._onConnectionStateChange;
this.pc.oniceconnectionstatechange = this._onIceConnectionStateChange;
this.pc.onsignalingstatechange = () => {
void this._onSignalingStateChange();
this.onSignalingStateChanged.emit(this.pc.signalingState);
};
}
unSetPeerConnectionListener() {
this.pc.onicecandidate = null;
this.pc.onicecandidateerror = null;
this.pc.onicegatheringstatechange = null;
this.pc.onconnectionstatechange = null;
this.pc.oniceconnectionstatechange = null;
this.pc.onsignalingstatechange = null;
}
handleCandidate(candidate) {
return __awaiter(this, void 0, void 0, function* () {
this._pendingCandidates.push(candidate);
if (this.pc.remoteDescription) {
yield this.resolveCandidates();
}
});
}
resolveCandidates() {
return __awaiter(this, void 0, void 0, function* () {
const candidates = [...this._pendingCandidates];
this._pendingCandidates = [];
log.debug('addIceCandidates', candidates);
yield Promise.all(candidates.map((candidate) => {
if (this.pc.signalingState === 'closed')
return Promise.resolve();
return this.pc.addIceCandidate(candidate).catch((err) => {
log.warn('[failed] add ice candidate', (0, util_1.createWarnPayload)({
operationName: 'Peer.resolveCandidates',
channel: this.localPerson.channel,
detail: '[failed] send candidate',
payload: { endpointId: this.endpoint.id },
}), err);
});
}));
});
}
}
exports.Peer = Peer;
//# sourceMappingURL=peer.js.map