@skyway-sdk/rtc-api-client
Version:
The official Next Generation JavaScript SDK for SkyWay
670 lines • 28.5 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
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());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.channelFactory = exports.ChannelImpl = void 0;
const common_1 = require("@skyway-sdk/common");
const model_1 = __importDefault(require("@skyway-sdk/model"));
const errors_1 = require("../errors");
const event = __importStar(require("../model/event"));
const util_1 = require("../util");
const log = new common_1.Logger('packages/rtc-api-client/src/domain/channel.ts');
class ChannelImpl {
constructor(appId, { id, name, members, metadata, publications, subscriptions, version, }, eventObserver, apiClient, config) {
this.appId = appId;
this.eventObserver = eventObserver;
this.apiClient = apiClient;
this.config = config;
this.disposed = false;
// events
this._events = new common_1.Events();
this.onClosed = this._events.make();
this.onMetadataUpdated = this._events.make();
this.onMemberListChanged = this._events.make();
this.onMemberJoined = this._events.make();
this.onMemberLeft = this._events.make();
this.onMemberMetadataUpdated = this._events.make();
this.onPublicationDisabled = this._events.make();
this.onPublicationEnabled = this._events.make();
this.onPublicationListChanged = this._events.make();
this.onStreamPublished = this._events.make();
this.onStreamUnpublished = this._events.make();
this.onPublicationMetadataUpdated = this._events.make();
this.onSubscriptionListChanged = this._events.make();
this.onPublicationSubscribed = this._events.make();
this.onPublicationUnsubscribed = this._events.make();
this.updateChannelMetadata = (metadata) => new Promise((r, f) => {
let failed = false;
this.apiClient
.updateChannelMetadata(this.appId, this.id, metadata)
.catch((e) => {
failed = true;
f(e);
});
this.onMetadataUpdated
.watch((e) => e.channel.metadata === metadata)
.then(() => r())
.catch((error) => {
if (!failed)
f((0, util_1.createError)({
operationName: 'ChannelImpl.updateChannelMetadata',
info: Object.assign(Object.assign({}, errors_1.errors.timeout), { detail: 'onMetadataUpdated' }),
path: log.prefix,
error,
appId: this.appId,
channelId: this.id,
}));
});
});
this.leave = (channelId, memberId) => new Promise((r, f) => {
let failed = false;
this.apiClient.leave(this.appId, channelId, memberId).catch((e) => {
failed = true;
f(e);
});
this.onMemberLeft
.watch((e) => e.member.id === memberId, this.config.rtcApi.timeout)
.then(() => r())
.catch((error) => {
if (!failed)
f((0, util_1.createError)({
operationName: 'ChannelImpl.leave',
info: Object.assign(Object.assign({}, errors_1.errors.timeout), { detail: 'onMemberLeft' }),
path: log.prefix,
error,
appId: this.appId,
channelId: this.id,
}));
});
});
this.updateMemberMetadata = (memberId, metadata) => new Promise((r, f) => {
let failed = false;
this.apiClient
.updateMemberMetadata(this.appId, this.id, memberId, metadata)
.catch((e) => {
failed = true;
f(e);
});
this.onMemberMetadataUpdated
.watch((e) => e.member.id === memberId && e.member.metadata === metadata)
.then(() => r())
.catch((error) => {
if (!failed)
f((0, util_1.createError)({
operationName: 'ChannelImpl.updateMemberMetadata',
info: Object.assign(Object.assign({}, errors_1.errors.timeout), { detail: 'onMemberMetadataUpdated' }),
path: log.prefix,
error,
appId: this.appId,
channelId: this.id,
}));
});
});
this.unpublish = (publicationId) => new Promise((r, f) => {
let failed = false;
this.apiClient
.unpublish(this.appId, this.id, publicationId)
.catch((e) => {
failed = true;
f(e);
});
this.onStreamUnpublished
.watch((e) => e.publication.id === publicationId)
.then(() => r())
.catch((error) => {
if (!failed)
f((0, util_1.createError)({
operationName: 'ChannelImpl.unpublish',
info: Object.assign(Object.assign({}, errors_1.errors.timeout), { detail: 'onStreamUnpublished' }),
path: log.prefix,
error,
payload: { publicationId },
appId: this.appId,
channelId: this.id,
}));
});
});
this.updatePublicationMetadata = (publicationId, metadata) => new Promise((r, f) => {
let failed = false;
this.apiClient
.updatePublicationMetadata(this.appId, this.id, publicationId, metadata)
.catch((e) => {
failed = true;
f(e);
});
this.onPublicationMetadataUpdated
.watch((e) => e.publication.id === publicationId &&
e.publication.metadata === metadata)
.then(() => r())
.catch((error) => {
if (!failed)
f((0, util_1.createError)({
operationName: 'ChannelImpl.updatePublicationMetadata',
info: Object.assign(Object.assign({}, errors_1.errors.timeout), { detail: 'onPublicationMetadataUpdated' }),
path: log.prefix,
error,
payload: { publicationId },
appId: this.appId,
channelId: this.id,
}));
});
});
this.disablePublication = (publicationId) => new Promise((r, f) => {
let failed = false;
this.apiClient
.disablePublication(this.appId, this.id, publicationId)
.catch((e) => {
failed = true;
f(e);
});
this.onPublicationDisabled
.watch((e) => e.publication.id === publicationId)
.then(() => r())
.catch((error) => {
if (!failed)
f((0, util_1.createError)({
operationName: 'ChannelImpl.disablePublication',
info: Object.assign(Object.assign({}, errors_1.errors.timeout), { detail: 'onPublicationDisabled' }),
path: log.prefix,
error,
payload: { publicationId },
appId: this.appId,
channelId: this.id,
}));
});
});
this.enablePublication = (publicationId) => new Promise((r, f) => {
let failed = false;
this.apiClient
.enablePublication(this.appId, this.id, publicationId)
.catch((e) => {
failed = true;
f(e);
});
this.onPublicationEnabled
.watch((e) => e.publication.id === publicationId)
.then(() => r())
.catch((error) => {
if (!failed)
f((0, util_1.createError)({
operationName: 'ChannelImpl.enablePublication',
info: Object.assign(Object.assign({}, errors_1.errors.timeout), { detail: 'onPublicationEnabled' }),
path: log.prefix,
error,
payload: { publicationId },
appId: this.appId,
channelId: this.id,
}));
});
});
this.unsubscribe = (subscriptionId) => new Promise((r, f) => {
let failed = false;
this.apiClient
.unsubscribe(this.appId, this.id, subscriptionId)
.catch((e) => {
failed = true;
f(e);
});
this.onPublicationUnsubscribed
.watch((e) => e.subscription.id === subscriptionId)
.then(() => r())
.catch((error) => {
if (!failed)
f((0, util_1.createError)({
operationName: 'ChannelImpl.unsubscribe',
info: Object.assign(Object.assign({}, errors_1.errors.timeout), { detail: 'onPublicationUnsubscribed' }),
path: log.prefix,
error,
payload: { subscriptionId },
appId: this.appId,
channelId: this.id,
}));
});
});
this.id = id;
this.name = name;
this.metadata = metadata;
this.members = members;
this.publications = publications;
this.subscriptions = subscriptions;
this.version = version;
eventObserver.onEvent.add((event) => {
log.debug('received event: ', event);
this.version = event.data.channel.version;
try {
switch (event.type) {
case 'ChannelDeleted':
{
this._channelClosed();
}
break;
case 'ChannelMetadataUpdated':
{
this._channelMetadataUpdated(event.data);
}
break;
case 'MemberAdded':
{
this._memberJoined(event.data);
}
break;
case 'MemberRemoved':
{
this._memberLeft(event.data);
}
break;
case 'MemberMetadataUpdated':
{
this._memberMetadataUpdated(event.data);
}
break;
case 'StreamPublished':
{
this._streamPublished(event.data);
}
break;
case 'StreamUnpublished':
{
this._streamUnpublished(event.data);
}
break;
case 'PublicationMetadataUpdated':
{
this._publicationMetadataUpdated(event.data);
}
break;
case 'PublicationDisabled':
{
this._publicationDisabled(event.data);
}
break;
case 'PublicationEnabled':
{
this._publicationEnabled(event.data);
}
break;
case 'StreamSubscribed':
{
this._streamSubscribed(event.data);
}
break;
case 'StreamUnsubscribed':
{
this._streamUnsubscribed(event.data);
}
break;
}
}
catch (error) {
log.error(error);
}
});
apiClient.onClose.once(() => {
this.dispose();
});
}
getMember(id) {
return this.members.find((s) => s.id === id);
}
addMember(member) {
const exist = this.getMember(member.id);
if (exist) {
return exist;
}
this.members.push(member);
return member;
}
deleteMember(id) {
this.members = this.members.filter((m) => m.id !== id);
}
getPublication(id) {
return this.publications.find((s) => s.id === id);
}
addPublication(summary) {
var _a, _b;
const exist = this.getPublication(summary.id);
if (exist) {
return exist;
}
const publication = Object.assign(Object.assign({}, summary), { channelId: this.id, codecCapabilities: (_a = summary.codecCapabilities) !== null && _a !== void 0 ? _a : [], encodings: (_b = summary.encodings) !== null && _b !== void 0 ? _b : [] });
this.publications.push(publication);
return publication;
}
deletePublication(publicationId) {
this.publications = this.publications.filter((p) => p.id !== publicationId);
}
getSubscription(id) {
return this.subscriptions.find((s) => s.id === id);
}
addSubscription(summary) {
const exist = this.getSubscription(summary.id);
if (exist) {
return exist;
}
const publication = this.getPublication(summary.publicationId);
const subscription = Object.assign(Object.assign({}, summary), { channelId: this.id, publisherId: publication.publisherId, contentType: publication.contentType });
this.subscriptions.push(subscription);
return subscription;
}
deleteSubscription(subscriptionId) {
this.subscriptions = this.subscriptions.filter((s) => s.id !== subscriptionId);
}
_channelClosed() {
this.onClosed.emit({});
}
_channelMetadataUpdated(event) {
this.metadata = event.channel.metadata;
this.onMetadataUpdated.emit(event);
}
_memberJoined(event) {
this.addMember(event.member);
this.onMemberJoined.emit(event);
this.onMemberListChanged.emit({});
}
_memberLeft(event) {
const member = this.getMember(event.member.id);
if (!member) {
throw (0, util_1.createError)({
operationName: 'ChannelImpl._memberLeft',
info: errors_1.errors.memberNotFound,
path: log.prefix,
payload: { event },
appId: this.appId,
channelId: this.id,
});
}
this.deleteMember(member.id);
this.onMemberLeft.emit({ member });
this.onMemberListChanged.emit({});
}
_memberMetadataUpdated(event) {
const member = this.getMember(event.member.id);
if (!member) {
throw (0, util_1.createError)({
operationName: 'ChannelImpl._memberMetadataUpdated',
info: errors_1.errors.memberNotFound,
path: log.prefix,
payload: { event },
appId: this.appId,
channelId: this.id,
});
}
member.metadata = event.member.metadata;
this.onMemberMetadataUpdated.emit(event);
}
_streamPublished(event) {
const publication = this.addPublication(event.publication);
const outgoing = Object.assign(Object.assign({}, event), { publication });
this.onStreamPublished.emit(outgoing);
this.onPublicationListChanged.emit({});
}
_streamUnpublished(event) {
const publication = this.getPublication(event.publication.id);
if (!publication) {
throw (0, util_1.createError)({
operationName: 'ChannelImpl._streamUnpublished',
info: errors_1.errors.publicationNotFound,
path: log.prefix,
payload: { event },
appId: this.appId,
channelId: this.id,
});
}
this.deletePublication(publication.id);
const outgoing = Object.assign(Object.assign({}, event), { publication });
this.onStreamUnpublished.emit(outgoing);
this.onPublicationListChanged.emit({});
}
_publicationMetadataUpdated(event) {
const publication = this.getPublication(event.publication.id);
if (!publication) {
throw (0, util_1.createError)({
operationName: 'ChannelImpl._publicationMetadataUpdated',
info: errors_1.errors.publicationNotFound,
path: log.prefix,
payload: { event },
appId: this.appId,
channelId: this.id,
});
}
publication.metadata = event.publication.metadata;
const outgoing = Object.assign(Object.assign({}, event), { publication });
this.onPublicationMetadataUpdated.emit(outgoing);
}
_publicationDisabled(event) {
const publication = this.getPublication(event.publication.id);
if (!publication) {
throw (0, util_1.createError)({
operationName: 'ChannelImpl._publicationDisabled',
info: errors_1.errors.publicationNotFound,
path: log.prefix,
payload: { event },
appId: this.appId,
channelId: this.id,
});
}
publication.isEnabled = event.publication.isEnabled;
const outgoing = {
publication,
};
this.onPublicationDisabled.emit(outgoing);
}
_publicationEnabled(incoming) {
const publication = this.getPublication(incoming.publication.id);
if (!publication) {
throw (0, util_1.createError)({
operationName: 'ChannelImpl._publicationEnabled',
info: errors_1.errors.publicationNotFound,
path: log.prefix,
payload: { event },
appId: this.appId,
channelId: this.id,
});
}
publication.isEnabled = incoming.publication.isEnabled;
const outgoing = {
publication,
};
this.onPublicationEnabled.emit(outgoing);
}
_streamSubscribed(incoming) {
const subscription = this.addSubscription(incoming.subscription);
const outgoing = Object.assign(Object.assign({}, incoming), { subscription });
this.onPublicationSubscribed.emit(outgoing);
this.onSubscriptionListChanged.emit({});
}
_streamUnsubscribed(event) {
const subscription = this.getSubscription(event.subscription.id);
if (!subscription) {
throw (0, util_1.createError)({
operationName: 'ChannelImpl._streamUnsubscribed',
info: errors_1.errors.subscriptionNotFound,
path: log.prefix,
payload: { event },
appId: this.appId,
channelId: this.id,
});
}
this.deleteSubscription(subscription.id);
const outgoing = Object.assign(Object.assign({}, event), { subscription });
this.onPublicationUnsubscribed.emit(outgoing);
this.onSubscriptionListChanged.emit({});
}
joinChannel(memberInit) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
if (memberInit.type) {
memberInit.type = (memberInit.type[0].toUpperCase() +
memberInit.type.slice(1));
}
if (memberInit.subtype) {
memberInit.subtype = (memberInit.subtype[0].toUpperCase() +
memberInit.subtype.slice(1));
}
log.debug('[start] joinChannel', { memberInit });
const res = yield this.apiClient.join(this.appId, this.id, Object.assign({}, memberInit));
const member = (_a = this.getMember(res.id)) !== null && _a !== void 0 ? _a : (yield this.onMemberJoined
.watch((e) => e.member.id === res.id, this.config.rtcApi.timeout)
.catch((error) => {
throw (0, util_1.createError)({
operationName: 'ChannelImpl.joinChannel',
info: Object.assign(Object.assign({}, errors_1.errors.timeout), { detail: 'onMemberJoined' }),
path: log.prefix,
error,
appId: this.appId,
channelId: this.id,
});
})).member;
log.debug('[end] joinChannel', { member });
return member;
});
}
updateMemberTtl(memberId, ttlSec) {
return this.apiClient.updateMemberTtl(this.appId, this.id, memberId, ttlSec);
}
/**@throws {SkyWayError} */
publish(init) {
var _a, _b, _c, _d;
return __awaiter(this, void 0, void 0, function* () {
const ts = log.debug('[start] apiClient.publish', { init });
const allowedTypes = model_1.default.PublicationType.filter((t) => t !== null);
if (init.type && !allowedTypes.includes(init.type)) {
throw (0, util_1.createError)({
operationName: 'ChannelImpl.publish',
error: new Error('The type in PublicationOptions is invalid.'),
info: Object.assign({}, errors_1.errors.invalidPublicationType),
path: log.prefix,
payload: { init },
appId: this.appId,
channelId: this.id,
});
}
const channelId = this.id;
const publicationId = yield this.apiClient.publish(this.appId, Object.assign(Object.assign({}, init), { channel: channelId }));
const publicationDto = {
id: publicationId,
channelId,
publisherId: init.publisher,
origin: init.origin,
contentType: init.contentType,
metadata: init.metadata,
codecCapabilities: (_a = init.codecCapabilities) !== null && _a !== void 0 ? _a : [],
encodings: (_b = init.encodings) !== null && _b !== void 0 ? _b : [],
isEnabled: (_c = init.isEnabled) !== null && _c !== void 0 ? _c : true,
type: (_d = init.type) !== null && _d !== void 0 ? _d : 'p2p',
};
log.elapsed(ts, '[ongoing] apiClient.publish', { publicationDto });
const exist = this.getPublication(publicationId);
if (exist) {
return exist;
}
const { publication } = yield this.onStreamPublished
.watch((e) => e.publication.id === publicationId, this.config.rtcApi.timeout)
.catch((error) => {
throw (0, util_1.createError)({
operationName: 'ChannelImpl.publish',
info: Object.assign(Object.assign({}, errors_1.errors.timeout), { detail: 'onStreamPublished' }),
path: log.prefix,
error,
payload: { publicationDto },
appId: this.appId,
channelId: this.id,
});
});
log.elapsed(ts, '[end] apiClient.publish', { publicationDto });
return publication;
});
}
/**@throws {@link SkyWayError} */
subscribe(init) {
return __awaiter(this, void 0, void 0, function* () {
const ts = log.debug('[start] apiClient.subscribe', { init });
const subscriptionId = yield this.apiClient.subscribe(this.appId, Object.assign(Object.assign({}, init), { channel: this }));
const subscriptionDto = {
id: subscriptionId,
publicationId: init.publication.id,
channelId: this.id,
publisherId: init.publication.publisherId,
subscriberId: init.subscriber.id,
contentType: init.publication.contentType,
};
log.elapsed(ts, '[ongoing] apiClient.subscribe', { subscriptionDto });
const exist = this.getSubscription(subscriptionId);
if (exist) {
log.elapsed(ts, '[end] apiClient.subscribe', { subscriptionDto });
return exist;
}
const { subscription } = yield this.onPublicationSubscribed
.watch((e) => e.subscription.id === subscriptionId, this.config.rtcApi.timeout)
.catch((error) => {
log.elapsed(ts, '[fail] apiClient.subscribe', error);
throw (0, util_1.createError)({
operationName: 'ChannelImpl.subscribe',
info: Object.assign(Object.assign({}, errors_1.errors.timeout), { detail: 'onPublicationSubscribed' }),
path: log.prefix,
error,
payload: { subscriptionDto },
});
});
log.elapsed(ts, '[end] apiClient.subscribe', { subscriptionDto });
return subscription;
});
}
close() {
return this.apiClient.deleteChannel(this.appId, this.id);
}
/**
* リソースの解放
* - Channelイベントの購読停止
* - イベントリスナー
*/
dispose() {
if (this.disposed) {
return;
}
this.disposed = true;
log.debug('disposed', { id: this.id });
this.eventObserver.dispose();
this._events.dispose();
}
}
exports.ChannelImpl = ChannelImpl;
function channelFactory(appId, eventObserver, api, channelDto, config) {
const channel = new ChannelImpl(appId, channelDto, eventObserver, api, config);
return channel;
}
exports.channelFactory = channelFactory;
//# sourceMappingURL=channel.js.map