UNPKG

@skyway-sdk/sfu-bot

Version:

The official Next Generation JavaScript SDK for SkyWay

286 lines 12.8 kB
"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