@skyway-sdk/core
Version:
The official Next Generation JavaScript SDK for SkyWay
545 lines • 24 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.SkyWayChannel = exports.SkyWayChannelImpl = void 0;
const common_1 = require("@skyway-sdk/common");
const errors_1 = require("../errors");
const localPerson_1 = require("../member/localPerson");
const factory_1 = require("../publication/factory");
const factory_2 = require("../subscription/factory");
const util_1 = require("../util");
const validation_1 = require("../validation");
const log = new common_1.Logger('packages/core/src/channel/index.ts');
/**@internal */
class SkyWayChannelImpl {
constructor(
/**@private */
_context,
/**@private */
_channelImpl) {
this._context = _context;
this._channelImpl = _channelImpl;
this.id = this._channelImpl.id;
this.name = this._channelImpl.name;
this.appId = this._context.appId;
this.disposed = false;
this.config = this._context.config;
this._state = 'opened';
this._api = this._context._api;
this._members = {};
/**@private */
this._getMember = (id) => this._members[id];
this._publications = {};
/**@private */
this._getPublication = (id) => this._publications[id];
this._subscriptions = {};
/**@private */
this._getSubscription = (id) => this._subscriptions[id];
// 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.onPublicationListChanged = this._events.make();
this.onStreamPublished = this._events.make();
this.onStreamUnpublished = this._events.make();
this.onPublicationMetadataUpdated = this._events.make();
this.onPublicationEnabled = this._events.make();
this.onPublicationDisabled = this._events.make();
this.onSubscriptionListChanged = this._events.make();
this.onPublicationSubscribed = this._events.make();
this.onPublicationUnsubscribed = this._events.make();
/**@private */
this._onDisposed = this._events.make();
this.leave = (member) => __awaiter(this, void 0, void 0, function* () { return this._channelImpl.leave(this.id, member.id); });
this.updateMetadata = (metadata) => this._channelImpl.updateChannelMetadata(metadata);
this.close = () => new Promise((r, f) => {
if (this.state === 'closed') {
f((0, util_1.createError)({
operationName: 'SkyWayChannelImpl.close',
path: log.prefix,
info: errors_1.errors.alreadyChannelClosed,
channel: this,
context: this._context,
payload: this.toJSON(),
}));
return;
}
const executeClose = () => __awaiter(this, void 0, void 0, function* () {
const timestamp = log.info('[start] close channel', yield (0, util_1.createLogPayload)({
operationName: 'SkyWayChannelImpl.close',
channel: this,
}));
try {
yield this._channelImpl.close().catch((e) => {
const error = (0, util_1.createError)({
operationName: 'SkyWayChannelImpl.close',
context: this._context,
info: Object.assign(Object.assign({}, errors_1.errors.internal), { detail: '_api.deleteChannel failed' }),
error: e,
path: log.prefix,
channel: this,
});
throw error;
});
if (this._state !== 'closed') {
yield this.onClosed
.asPromise(this._context.config.rtcApi.timeout)
.catch((e) => {
const error = (0, util_1.createError)({
operationName: 'SkyWayChannelImpl.close',
context: this._context,
info: Object.assign(Object.assign({}, errors_1.errors.timeout), { detail: 'channel.onClosed' }),
error: e,
path: log.prefix,
channel: this,
});
throw error;
});
}
}
catch (error) {
log.error(error.message, error);
f(error);
return;
}
log.elapsed(timestamp, '[end] close channel', yield (0, util_1.createLogPayload)({
operationName: 'SkyWayChannelImpl.close',
channel: this,
}));
r();
});
executeClose().catch(f);
});
/**@private */
this._updateMemberTtl = (memberId, ttlSec) => this._channelImpl.updateMemberTtl(memberId, ttlSec);
/**@private */
this._updateMemberMetadata = (memberId, metadata) => this._channelImpl.updateMemberMetadata(memberId, metadata);
/**@private */
/**@throws {SkyWayError} */
this._publish = (init) => this._channelImpl.publish(init);
/**@private */
this._unpublish = (publicationId) => __awaiter(this, void 0, void 0, function* () { return this._channelImpl.unpublish(publicationId); });
/**@private
* @throws {@link SkyWayError}
*/
this._subscribe = (subscriberId, publicationId) => {
const publication = this._getPublication(publicationId);
const subscriber = this._getMember(subscriberId);
if (subscriber === undefined || subscriber === null) {
throw (0, util_1.createError)({
operationName: 'SkyWayChannelImpl._subscribe',
path: log.prefix,
info: Object.assign(Object.assign({}, errors_1.errors.internal), { detail: 'subscriber not found' }),
channel: this,
context: this._context,
payload: { subscriberId, publicationId },
});
}
return this._channelImpl.subscribe({
publication: publication.toJSON(),
subscriber: subscriber.toJSON(),
});
};
/**@private */
this._unsubscribe = (subscriptionId) => __awaiter(this, void 0, void 0, function* () {
if (!this._getSubscription(subscriptionId)) {
throw (0, util_1.createError)({
operationName: 'SkyWayChannelImpl._unsubscribe',
path: log.prefix,
info: Object.assign(Object.assign({}, errors_1.errors.internal), { detail: "can't unsubscribe not exist subscription" }),
channel: this,
context: this._context,
payload: { subscriptionId },
});
}
yield this._channelImpl.unsubscribe(subscriptionId);
});
/**@private */
this._updatePublicationMetadata = (publicationId, metadata) => this._channelImpl.updatePublicationMetadata(publicationId, metadata);
/**@private */
this._disablePublication = (publicationId) => this._channelImpl.disablePublication(publicationId);
/**@private */
this._enablePublication = (publicationId) => this._channelImpl.enablePublication(publicationId);
this._setupPropertiesFromChannel();
this._setupListenChannelEvent();
_context.onDisposed.once(() => {
this.dispose();
});
log.debug('channel spawned', this.toJSON());
}
_addLocalPerson(member, config) {
return __awaiter(this, void 0, void 0, function* () {
const person = yield (0, localPerson_1.createLocalPerson)(this._context, this, member, config);
this._localPerson = person;
this._members[this._localPerson.id] = person;
return person;
});
}
_addRemoteMember(memberDto) {
const exist = this._getMember(memberDto.id);
if (exist) {
if (exist.side === 'local') {
throw (0, util_1.createError)({
operationName: 'SkyWayChannelImpl._addRemoteMember',
path: log.prefix,
context: this._context,
channel: this,
info: errors_1.errors.internal,
});
}
return exist;
}
const member = this._context._createRemoteMember(this, memberDto);
this._members[member.id] = member;
return member;
}
_removeMember(memberId) {
delete this._members[memberId];
const isLocalPerson = this._localPerson && this._localPerson.id === memberId;
if (isLocalPerson) {
this._localPerson = undefined;
}
}
/**@private */
_addPublication(p) {
const exist = this._getPublication(p.id);
if (exist) {
return exist;
}
const publication = (0, factory_1.createPublication)(this, p);
this._publications[p.id] = publication;
return publication;
}
_removePublication(publicationId) {
delete this._publications[publicationId];
}
/**@private */
_addSubscription(s) {
const exist = this._getSubscription(s.id);
if (exist) {
return exist;
}
const subscription = (0, factory_2.createSubscription)(this, s);
this._subscriptions[s.id] = subscription;
return subscription;
}
_removeSubscription(subscriptionId) {
delete this._subscriptions[subscriptionId];
}
get localPerson() {
return this._localPerson;
}
get members() {
return Object.values(this._members);
}
get bots() {
return this.members.filter((m) => m.type === 'bot');
}
get publications() {
return Object.values(this._publications);
}
get subscriptions() {
return Object.values(this._subscriptions);
}
get metadata() {
return this._channelImpl.metadata;
}
get state() {
return this._state;
}
toJSON() {
return {
id: this.id,
name: this.name,
appId: this.appId,
metadata: this.metadata,
members: this.members,
publications: this.publications,
subscriptions: this.subscriptions,
};
}
_setupPropertiesFromChannel() {
this._channelImpl.members.forEach((memberDto) => {
this._addRemoteMember(memberDto);
});
this._channelImpl.publications.forEach((publicationDto) => {
this._addPublication(publicationDto);
});
this._channelImpl.subscriptions.forEach((subscriptionDto) => {
this._addSubscription(subscriptionDto);
});
}
_setupListenChannelEvent() {
this._channelImpl.onClosed.add(() => this._handleOnChannelClose());
this._channelImpl.onMetadataUpdated.add(({ channel }) => this._handleOnChannelMetadataUpdate(channel.metadata));
this._channelImpl.onMemberJoined.add(({ member }) => {
this._handleOnMemberJoin(member);
});
this._channelImpl.onMemberLeft.add(({ member }) => {
this._handleOnMemberLeft(member);
});
this._channelImpl.onMemberListChanged.pipe(this.onMemberListChanged);
this._channelImpl.onMemberMetadataUpdated.add(({ member }) => {
this._handleOnMemberMetadataUpdate(member, member.metadata);
});
this._channelImpl.onStreamPublished.add(({ publication }) => {
this._handleOnStreamPublish(publication);
});
this._channelImpl.onStreamUnpublished.add(({ publication }) => {
this._handleOnStreamUnpublish(publication);
});
this._channelImpl.onPublicationListChanged.pipe(this.onPublicationListChanged);
this._channelImpl.onPublicationMetadataUpdated.add(({ publication }) => {
this._handleOnPublicationMetadataUpdate(publication, publication.metadata);
});
this._channelImpl.onPublicationEnabled.add(({ publication }) => __awaiter(this, void 0, void 0, function* () { return yield this._handleOnPublicationEnabled(publication); }));
this._channelImpl.onPublicationDisabled.add(({ publication }) => __awaiter(this, void 0, void 0, function* () { return yield this._handleOnPublicationDisabled(publication); }));
this._channelImpl.onPublicationSubscribed.add(({ subscription }) => {
this._handleOnStreamSubscribe(subscription);
});
this._channelImpl.onPublicationUnsubscribed.add(({ subscription }) => {
this._handleOnStreamUnsubscribe(subscription);
});
this._channelImpl.onSubscriptionListChanged.pipe(this.onSubscriptionListChanged);
}
_handleOnChannelClose() {
this._state = 'closed';
this.onClosed.emit({});
this.dispose();
}
_handleOnChannelMetadataUpdate(metadata) {
this.onMetadataUpdated.emit({ metadata });
}
_handleOnMemberJoin(memberDto) {
const member = this._addRemoteMember(memberDto);
this.onMemberJoined.emit({ member });
}
_handleOnMemberLeft(memberDto) {
const member = this._getMember(memberDto.id);
this._removeMember(member.id);
member._left();
this.onMemberLeft.emit({ member });
}
_handleOnMemberMetadataUpdate(memberDto, metadata) {
var _a;
const member = this._getMember(memberDto.id);
member._metadataUpdated(metadata);
if (((_a = this.localPerson) === null || _a === void 0 ? void 0 : _a.id) === memberDto.id) {
this.localPerson._metadataUpdated(metadata);
}
this.onMemberMetadataUpdated.emit({ member, metadata });
}
_handleOnStreamPublish(publicationDto) {
const publication = this._addPublication(publicationDto);
this.onStreamPublished.emit({ publication });
}
_handleOnStreamUnpublish(publicationDto) {
const publication = this._getPublication(publicationDto.id);
this._removePublication(publication.id);
publication._unpublished();
this.onStreamUnpublished.emit({ publication });
}
_handleOnPublicationMetadataUpdate(publicationDto, metadata) {
const publication = this._getPublication(publicationDto.id);
publication._updateMetadata(metadata);
this.onPublicationMetadataUpdated.emit({ publication, metadata });
}
_handleOnPublicationEnabled(publicationDto) {
return __awaiter(this, void 0, void 0, function* () {
const publication = this._getPublication(publicationDto.id);
publication._enable();
this.onPublicationEnabled.emit({ publication });
});
}
_handleOnPublicationDisabled(publicationDto) {
return __awaiter(this, void 0, void 0, function* () {
const publication = this._getPublication(publicationDto.id);
yield publication._disable();
this.onPublicationDisabled.emit({ publication });
});
}
_handleOnStreamSubscribe(subscriptionDto) {
const subscription = this._addSubscription(subscriptionDto);
const publication = this._getPublication(subscription.publication.id);
publication._subscribed(subscription);
this.onPublicationSubscribed.emit({ subscription });
}
_handleOnStreamUnsubscribe(subscriptionDto) {
const subscription = this._getSubscription(subscriptionDto.id);
this._removeSubscription(subscription.id);
subscription._canceled();
const publication = this._getPublication(subscription.publication.id);
publication._unsubscribed(subscription);
this.onPublicationUnsubscribed.emit({ subscription });
}
join(options = {}) {
var _a, _b, _c;
return __awaiter(this, void 0, void 0, function* () {
const timestamp = log.info('[start] join', yield (0, util_1.createLogPayload)({
operationName: 'SkyWayChannelImpl.join',
channel: this,
}));
if (this._localPerson) {
throw (0, util_1.createError)({
operationName: 'SkyWayChannelImpl.join',
path: log.prefix,
info: errors_1.errors.alreadyLocalPersonExist,
channel: this,
context: this._context,
});
}
if (!(0, validation_1.isValidName)(options.name)) {
throw (0, util_1.createError)({
operationName: 'SkyWayChannelImpl.join',
path: log.prefix,
info: errors_1.errors.invalidRequestParameter,
channel: this,
context: this._context,
payload: options,
});
}
if (options.name !== undefined && options.name !== null) {
const exist = this.members.find((m) => m.name === options.name);
if (exist) {
throw (0, util_1.createError)({
operationName: 'SkyWayChannelImpl.join',
path: log.prefix,
info: errors_1.errors.alreadySameNameMemberExist,
channel: this,
context: this._context,
payload: options,
});
}
}
(_a = options.keepaliveIntervalSec) !== null && _a !== void 0 ? _a : (options.keepaliveIntervalSec = this.config.member.keepaliveIntervalSec);
(_b = options.keepaliveIntervalGapSec) !== null && _b !== void 0 ? _b : (options.keepaliveIntervalGapSec = this.config.member.keepaliveIntervalGapSec);
(_c = options.preventAutoLeaveOnBeforeUnload) !== null && _c !== void 0 ? _c : (options.preventAutoLeaveOnBeforeUnload = this.config.member.preventAutoLeaveOnBeforeUnload);
const init = Object.assign(Object.assign({}, options), { type: 'person', subtype: 'person' });
if (options.keepaliveIntervalSec !== null) {
init.ttlSec =
(yield this._context._api.getServerUnixtimeInSec()) +
options.keepaliveIntervalSec;
}
const member = yield this._channelImpl.joinChannel(init).catch((e) => {
log.error('[failed] join', e);
throw e;
});
log.elapsed(timestamp, '[elapsed] join / channelImpl.joinChannel', {
member,
});
const person = yield this._addLocalPerson(member, options);
const adapter = new localPerson_1.LocalPersonAdapter(person);
log.elapsed(timestamp, '[end] join', { person });
return adapter;
});
}
dispose() {
if (this.disposed) {
return;
}
this.disposed = true;
log.debug('disposed', this.toJSON());
this._channelImpl.dispose();
this._onDisposed.emit();
this._events.dispose();
}
}
exports.SkyWayChannelImpl = SkyWayChannelImpl;
class SkyWayChannel {
/**
* @description [japanese] Channelの作成
*/
static Create(context, init = {}) {
return __awaiter(this, void 0, void 0, function* () {
const timestamp = log.info('[start] createChannel', {
operationName: 'SkyWayChannel.Create',
});
if (!(0, validation_1.isValidName)(init.name)) {
throw (0, util_1.createError)({
operationName: 'SkyWayChannel.Create',
info: errors_1.errors.invalidRequestParameter,
path: log.prefix,
context,
payload: init,
});
}
const channelImpl = yield context._api.createChannel(init).catch((e) => {
log.error('[failed] createChannel', e);
throw e;
});
const channel = new SkyWayChannelImpl(context, channelImpl);
log.elapsed(timestamp, '[end] createChannel');
return channel;
});
}
/**
* @description [japanese] 既存のChannelの取得
*/
static Find(context, query) {
return __awaiter(this, void 0, void 0, function* () {
const timestamp = log.info('[start] findChannel', {
operationName: 'SkyWayChannel.Find',
});
// id が指定されていない場合に channelName の validation を行う
if (query.id === undefined && !(0, validation_1.isValidName)(query.name)) {
throw (0, util_1.createError)({
operationName: 'SkyWayChannel.Find',
info: errors_1.errors.invalidRequestParameter,
path: log.prefix,
context,
payload: query,
});
}
const channelImpl = yield context._api.findChannel(query).catch((e) => {
log.error('[failed] findChannel', e);
throw e;
});
const channel = new SkyWayChannelImpl(context, channelImpl);
log.elapsed(timestamp, '[end] findChannel');
return channel;
});
}
/**
* @description [japanese] Channelの取得を試み、存在しなければ作成する
*/
static FindOrCreate(context, query) {
return __awaiter(this, void 0, void 0, function* () {
const timestamp = log.info('[start] findOrCreateChannel', {
operationName: 'SkyWayChannel.FindOrCreate',
});
if (!(0, validation_1.isValidName)(query.name)) {
throw (0, util_1.createError)({
operationName: 'SkyWayChannel.Create',
info: errors_1.errors.invalidRequestParameter,
path: log.prefix,
context,
payload: query,
});
}
const channelImpl = yield context._api
.findOrCreateChannel(query)
.catch((e) => {
log.error('[failed] findOrCreateChannel', e);
throw e;
});
const channel = new SkyWayChannelImpl(context, channelImpl);
log.elapsed(timestamp, '[end] findOrCreateChannel');
return channel;
});
}
constructor() { }
}
exports.SkyWayChannel = SkyWayChannel;
//# sourceMappingURL=index.js.map