open-collaboration-server
Version:
Open Collaboration Server implementation, part of the Open Collaboration Tools project
196 lines • 7.37 kB
JavaScript
// ******************************************************************************
// Copyright 2024 TypeFox GmbH
// This program and the accompanying materials are made available under the
// terms of the MIT License, which is available in the project root.
// ******************************************************************************
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import { inject, injectable, postConstruct } from 'inversify';
import { nanoid } from 'nanoid';
import * as protocol from 'open-collaboration-protocol';
import { Channel } from './channel.js';
import { MessageRelay } from './message-relay.js';
import { RoomManager } from './room-manager.js';
import { PeerInfo } from './types.js';
import { Logger } from './utils/logging.js';
import { parse } from 'semver';
export const PeerFactory = Symbol('PeerFactory');
let PeerImpl = class PeerImpl {
logger;
id = nanoid(24);
get jwt() {
return this.peerInfo.jwt;
}
get publicKey() {
return this.peerInfo.publicKey;
}
get client() {
return this.peerInfo.client;
}
get supportedCompression() {
return this.peerInfo.supportedCompression;
}
get user() {
return this.peerInfo.user;
}
get host() {
return this.peerInfo.host;
}
get room() {
const value = this.roomManager.getRoomByPeerId(this.id);
if (!value) {
throw this.logger.createErrorAndLog(`Peer '${this.id}' does not belong to any room`);
}
return value;
}
messageRelay;
peerInfo;
roomManager;
_channel;
disposed = false;
onDisposeEmitter = new protocol.Emitter();
get onDispose() {
return this.onDisposeEmitter.event;
}
get channel() {
if (!this._channel) {
throw new Error('Not initialized');
}
return this._channel;
}
initialize() {
this._channel = new Channel(this.peerInfo.channel);
this._channel.onMessage(message => this.receiveMessage(message));
this._channel.onClose(() => this.dispose());
}
async receiveMessage(message) {
const messageVersion = parse(message.version);
if (!messageVersion) {
this.logger.warn(`Received message from '${this.id}' with invalid version: ${message.version}. Ignoring message.`);
return;
}
if (!protocol.compatibleVersions(messageVersion)) {
this.logger.warn(`Received message from '${this.id}' with incompatible version: ${message.version}; expected: ${protocol.VERSION}. Ignoring message.`);
return;
}
if (protocol.ResponseMessage.isAny(message) || protocol.ResponseErrorMessage.isAny(message)) {
this.messageRelay.pushResponse(this, message);
}
else if (protocol.RequestMessage.isAny(message)) {
if (!protocol.Message.isEncrypted(message)) {
this.logger.warn(`Received unencrypted request from: '${this.id}'`);
return;
}
// Override whatever we know about the origin of the message
message.origin = this.id;
try {
const response = await this.messageRelay.sendRequest(this.getTargetPeer(message.target), message);
// Adjust the response to the original message id
response.id = message.id;
this.channel.sendMessage(response);
}
catch (_error) {
const errorResponseMessage = protocol.ResponseErrorMessage.create(message.id, 'Failed to retrieve the requested data.');
this.channel.sendMessage(errorResponseMessage);
}
}
else if (protocol.NotificationMessage.isAny(message)) {
message.origin = this.id;
if (message.target === '') {
this.handleServerMessage(message);
return;
}
else if (!protocol.Message.isEncrypted(message)) {
this.logger.warn(`Received unencrypted notification from: '${this.id}'`);
return;
}
try {
this.messageRelay.sendNotification(this.getTargetPeer(message.target), message);
}
catch (_error) {
this.logger.error(`Failed sending notification to: ${message.target || '<empty>'}`);
}
}
else if (protocol.BroadcastMessage.isAny(message)) {
if (!protocol.Message.isEncrypted(message)) {
this.logger.warn(`Received unencrypted broadcast from: '${this.id}'`);
return;
}
this.messageRelay.sendBroadcast(this, message);
}
}
async handleServerMessage(notification) {
if (!protocol.NotificationMessage.is(notification)) {
this.logger.error('Received encrypted server message');
return;
}
try {
if (notification.content.method === protocol.Messages.Room.Leave.method) {
this.dispose();
}
else {
throw new Error('Unknown server message method: ' + notification.content.method);
}
}
catch (err) {
this.logger.error('Failed to handle server message', err);
}
}
getTargetPeer(targetId) {
const peer = targetId ? this.room.getPeer(targetId) : undefined;
if (!peer) {
throw this.logger.createErrorAndLog(`Could not find the target peer: ${targetId || '<empty>'}`);
}
return peer;
}
toProtocol() {
return {
id: this.id,
host: this.host,
name: this.user.name,
email: this.user.email,
metadata: {
compression: {
supported: this.peerInfo.supportedCompression
},
encryption: {
publicKey: this.publicKey
}
}
};
}
dispose() {
if (this.disposed) {
return;
}
this.disposed = true;
this.onDisposeEmitter.fire(undefined);
this.onDisposeEmitter.dispose();
this._channel?.close();
this.logger.info(`Peer '${this.id}' disposed`);
}
};
__decorate([
inject(Logger)
], PeerImpl.prototype, "logger", void 0);
__decorate([
inject(MessageRelay)
], PeerImpl.prototype, "messageRelay", void 0);
__decorate([
inject(PeerInfo)
], PeerImpl.prototype, "peerInfo", void 0);
__decorate([
inject(RoomManager)
], PeerImpl.prototype, "roomManager", void 0);
__decorate([
postConstruct()
], PeerImpl.prototype, "initialize", null);
PeerImpl = __decorate([
injectable()
], PeerImpl);
export { PeerImpl };
//# sourceMappingURL=peer.js.map