@skyway-sdk/sfu-bot
Version:
The official Next Generation JavaScript SDK for SkyWay
286 lines • 12.8 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.SfuTransport = void 0;
const common_1 = require("@skyway-sdk/common");
const core_1 = require("@skyway-sdk/core");
const errors_1 = require("../../errors");
const plugin_1 = require("../../plugin");
const util_1 = require("../../util");
const log = new core_1.Logger('packages/sfu-bot/src/connection/transport/transport.ts');
class SfuTransport {
constructor(msTransport, _bot, _iceManager, _sfuApi, _context, _analyticsSession) {
var _a, _b, _c;
this.msTransport = msTransport;
this._bot = _bot;
this._iceManager = _iceManager;
this._sfuApi = _sfuApi;
this._context = _context;
this._analyticsSession = _analyticsSession;
this._backoffIceRestart = new common_1.BackOff({
times: 8,
interval: 100,
jitter: 100,
});
this._connectionState = 'new';
this.onProduce = new common_1.Event();
this.onProduceData = new common_1.Event();
this.onMediasoupConnectionStateChanged = new common_1.Event();
this.onConnectionStateChanged = new common_1.Event();
this.restartIce = () => __awaiter(this, void 0, void 0, function* () {
if (this._backoffIceRestart.exceeded) {
log.error('_iceRestartedCount exceeded', (0, core_1.createError)({
operationName: 'SfuTransport.restartIce',
context: this._context,
info: errors_1.errors.netWorkError,
path: log.prefix,
}));
this._setConnectionState('disconnected');
return;
}
log.warn('[start] restartIce', (0, util_1.createWarnPayload)({
bot: this._bot,
detail: 'start restartIce',
operationName: 'SfuTransport.restartIce',
payload: { count: this._backoffIceRestart.count, transport: this },
}));
const checkNeedEnd = () => {
if (this._bot.state === 'left') {
log.debug('bot already left', this);
this._setConnectionState('disconnected');
log.warn('[end] restartIce', (0, util_1.createWarnPayload)({
bot: this._bot,
detail: 'end restartIce',
operationName: 'SfuTransport.restartIce',
payload: { count: this._backoffIceRestart.count, transport: this },
}));
return true;
}
if (this.msTransport.connectionState === 'connected') {
this._backoffIceRestart.reset();
this._setConnectionState('connected');
log.warn('[end] restartIce', (0, util_1.createWarnPayload)({
bot: this._bot,
detail: 'end restartIce',
operationName: 'SfuTransport.restartIce',
payload: { count: this._backoffIceRestart.count, transport: this },
}));
if (this._analyticsSession && !this._analyticsSession.isClosed()) {
// 再送時に他の処理をブロックしないためにawaitしない
void this._analyticsSession.client.sendRtcPeerConnectionEventReport({
rtcPeerConnectionId: this.id,
type: 'restartIce',
data: undefined,
createdAt: Date.now(),
});
}
return true;
}
};
this._setConnectionState('reconnecting');
yield this._backoffIceRestart.wait();
if (checkNeedEnd()) {
return;
}
let e = yield this._iceManager.updateIceParams().catch((e) => e);
if (e) {
log.warn('updateIceParams failed', (0, util_1.createWarnPayload)({
operationName: 'SfuTransport.restartIce',
detail: 'updateIceParams failed',
bot: this._bot,
payload: { transport: this },
}), e);
yield this.restartIce();
return;
}
yield this.msTransport.updateIceServers({
iceServers: this._iceManager.iceServers,
});
if (checkNeedEnd()) {
return;
}
const iceParameters = yield this._mediasoupRestartIce();
e = yield this._waitForMsConnectionState('connected', this._context.config.rtcConfig.iceDisconnectBufferTimeout).catch((e) => e);
if (!e && checkNeedEnd()) {
return iceParameters;
}
yield this.restartIce();
});
this._waitForMsConnectionState = (state,
/**ms */
timeout = 10000) => __awaiter(this, void 0, void 0, function* () {
if (state === this.msTransport.connectionState)
return;
yield this.onMediasoupConnectionStateChanged
.watch(() => state === this.msTransport.connectionState, timeout)
.catch((err) => {
throw (0, core_1.createError)({
operationName: 'SfuTransport._waitForMsConnectionState',
context: this._context,
info: Object.assign(Object.assign({}, errors_1.errors.timeout), { detail: 'waitForConnectionState timeout' }),
error: err,
path: log.prefix,
});
});
});
this._onConnect = (transportId) => ({ dtlsParameters, }, callback, errback) => __awaiter(this, void 0, void 0, function* () {
try {
log.debug('[start] transport connect', { transportId });
yield this._sfuApi.connect({ transportId, dtlsParameters });
log.debug('[end] transport connect', { transportId });
callback();
}
catch (error) {
log.error('[failed] transport connect', {
error,
transportId,
});
errback(error);
}
});
const sfuPlugin = _context.plugins.find((p) => p.subtype === plugin_1.SfuBotPlugin.subtype);
this._options = sfuPlugin.options;
log.debug('peerConfig', (_c = (_b = (_a = this.pc) === null || _a === void 0 ? void 0 : _a.getConfiguration) === null || _b === void 0 ? void 0 : _b.call(_a)) !== null && _c !== void 0 ? _c : {});
msTransport.on('connect', (params, callback, errback) => this._onConnect(msTransport.id)(params, callback, errback));
msTransport.on('connectionstatechange', (e) => {
this.onMediasoupConnectionStateChanged.emit(e);
if (this._analyticsSession && !this._analyticsSession.isClosed()) {
// 再送時に他の処理をブロックしないためにawaitしない
void this._analyticsSession.client.sendRtcPeerConnectionEventReport({
rtcPeerConnectionId: this.id,
type: 'connectionStateChange',
data: {
connectionState: e,
},
createdAt: Date.now(),
});
}
});
msTransport.on('produce', (producerOptions, callback, errback) => {
this.onProduce.emit({
producerOptions,
callback: callback,
errback: errback,
});
});
msTransport.on('producedata', (producerOptions, callback, errback) => {
this.onProduceData.emit({
producerOptions,
callback: callback,
errback: errback,
});
});
this.onMediasoupConnectionStateChanged.add((state) => __awaiter(this, void 0, void 0, function* () {
(0, core_1.createLogPayload)({
operationName: 'onMediasoupConnectionStateChanged',
channel: this._bot.channel,
})
.then((debug) => {
log.debug(debug, { state, transportId: this.id, bot: _bot });
})
.catch(() => { });
switch (state) {
case 'disconnected':
case 'failed':
{
if (this._connectionState === 'reconnecting') {
return;
}
const e = yield this._waitForMsConnectionState('connected', _context.config.rtcConfig.iceDisconnectBufferTimeout).catch((e) => e);
if (e &&
this._connectionState !==
'reconnecting' &&
_bot.options.disableRestartIce !== true) {
yield this.restartIce();
}
}
break;
case 'connecting':
case 'connected':
this._setConnectionState(state);
break;
case 'closed':
this._setConnectionState('disconnected');
break;
}
log.debug('onMediasoupConnectionStateChanged', this);
}));
}
get pc() {
var _a, _b, _c;
return (_c = (_b = (_a = this.msTransport) === null || _a === void 0 ? void 0 : _a._handler) === null || _b === void 0 ? void 0 : _b._pc) !== null && _c !== void 0 ? _c : {};
}
get id() {
return this.msTransport.id;
}
get connectionState() {
return this._connectionState;
}
toJSON() {
return {
id: this.id,
direction: this.msTransport.direction,
connectionState: this._connectionState,
};
}
close() {
var _a;
log.debug('close', this.id);
// suppress firefox [RTCPeerConnection is gone] Exception
if ((_a = this.pc) === null || _a === void 0 ? void 0 : _a.peerIdentity) {
this.pc.peerIdentity.catch(() => { });
}
this.msTransport.close();
this._setConnectionState('disconnected');
}
_setConnectionState(state) {
if (this._connectionState === state) {
return;
}
log.debug('onConnectionStateChanged', this._connectionState, state, this);
this._connectionState = state;
this.onConnectionStateChanged.emit(state);
if (this._analyticsSession && !this._analyticsSession.isClosed()) {
void this._analyticsSession.client.sendRtcPeerConnectionEventReport({
rtcPeerConnectionId: this.id,
type: 'skywayConnectionStateChange',
data: {
skywayConnectionState: state,
},
createdAt: Date.now(),
});
}
}
/**@private */
_mediasoupRestartIce() {
return __awaiter(this, void 0, void 0, function* () {
const iceParameters = yield this._sfuApi
.iceRestart({
transportId: this.id,
})
.catch((e) => e);
if (iceParameters instanceof Error) {
log.warn('iceRestart failed', (0, util_1.createWarnPayload)({
operationName: 'SfuTransport._mediasoupRestartIce',
detail: 'iceRestart failed',
bot: this._bot,
payload: { transport: this },
}), iceParameters);
yield this.restartIce();
return;
}
yield this.msTransport.restartIce({ iceParameters });
return iceParameters;
});
}
}
exports.SfuTransport = SfuTransport;
//# sourceMappingURL=transport.js.map