@ntrip/caster
Version:
NTRIP caster
167 lines (166 loc) • 6.91 kB
JavaScript
"use strict";
/*
* This file is part of the @ntrip/caster distribution (https://github.com/node-ntrip/caster).
* Copyright (c) 2020 Nebojsa Cvetkovic.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Mountpoint = void 0;
const stream = require("stream");
const sourcetable_1 = require("./sourcetable");
const events_1 = __importDefault(require("events"));
const auto_source_entry_1 = require("./util/auto-source-entry");
const MOUNTPOINT_NAME_REGEX = /^[A-Za-z0-9-_.]{1,100}$/;
const STATS_RATE_RUNNING_AVERAGE_ALPHA = 0.90;
const CONNECTION_TIMEOUT_DEFAULT = 10000;
const SILENCE_WARNING_TIMEOUT_DEFAULT = 10000;
const SILENCE_TIMEOUT_DEFAULT = 30000;
const DEFAULT_OPTIONS = {
connectionTimeout: CONNECTION_TIMEOUT_DEFAULT,
silenceTimeout: SILENCE_TIMEOUT_DEFAULT,
silenceWarningTimeout: SILENCE_WARNING_TIMEOUT_DEFAULT,
autoSourceEntry: true,
serverSourceEntry: false
};
class Mountpoint extends events_1.default.EventEmitter {
constructor(name, options) {
var _a;
super();
this.hidden = false;
this._server = null;
this._clients = new Set();
this.connectionTimeout = null;
this.silenceTimeout = null;
this.silenceWarningTimeout = null;
this.stats = {
in: 0,
out: 0,
rate: 0
};
this.statsStream = new stream.Writable({
write: (chunk, encoding, callback) => {
this.stats.in += chunk.length;
this.stats.out += chunk.length * this._clients.size;
callback();
this.resetTimeouts();
}
});
this.statsRateCalculator = (old) => {
const current = this.stats.in;
const rate = current - old;
this.stats.rate = Math.round(this.stats.rate * STATS_RATE_RUNNING_AVERAGE_ALPHA
+ rate * (1.0 - STATS_RATE_RUNNING_AVERAGE_ALPHA));
setTimeout(() => this.statsRateCalculator(current), 1000);
};
this.options = options = { ...DEFAULT_OPTIONS, ...options };
if (!MOUNTPOINT_NAME_REGEX.test(name))
throw new Error(`Mountpoint name (${name}) contains invalid characters: must be 1..100 characters, A-Za-z0-9.-_`);
this.name = name;
this.sourceEntry = new sourcetable_1.Sourcetable.SourceEntry(name);
if (options === null || options === void 0 ? void 0 : options.autoSourceEntry)
this.autoSourceEntry = new auto_source_entry_1.AutoSourceEntry(this, (_a = this.options) === null || _a === void 0 ? void 0 : _a.autoSourceEntryOptions);
this.statsRateCalculator(0);
this.resetTimeouts();
}
get server() { return this._server; }
get clients() { return this._clients; }
on(event, listener) {
return super.on(event, listener);
}
resetTimeouts() {
var _a, _b, _c;
if (this.connectionTimeout !== null)
clearTimeout(this.connectionTimeout);
if (this.silenceTimeout !== null)
clearTimeout(this.silenceTimeout);
if (this.silenceWarningTimeout !== null)
clearTimeout(this.silenceWarningTimeout);
this.connectionTimeout = this.silenceTimeout = this.silenceWarningTimeout = null;
if (this.active) {
this.silenceTimeout = setTimeout(() => {
this.emit('inactivity');
this.clearServer();
}, (_a = this.options) === null || _a === void 0 ? void 0 : _a.silenceTimeout);
this.silenceWarningTimeout = setTimeout(() => {
this.emit('silence');
}, (_b = this.options) === null || _b === void 0 ? void 0 : _b.silenceWarningTimeout);
}
else {
this.connectionTimeout = setTimeout(() => {
this.emit('timeout');
this.close();
}, (_c = this.options) === null || _c === void 0 ? void 0 : _c.connectionTimeout);
}
}
get active() {
return this._server != null;
}
setServer(server) {
var _a, _b;
if (this._server === server)
return;
if (this._server !== null)
throw new Error(`Another server is already connected to mountpoint ${this.name}`);
this._server = server;
this.emit('server', server);
server.once('close', () => this.clearServer(server));
this.resetTimeouts();
server.input.pipe(this.statsStream, { end: false });
this._clients.forEach((client) => server.pipe(client));
if ((_a = this.options) === null || _a === void 0 ? void 0 : _a.serverSourceEntry)
server.on('str', (str) => this.sourceEntry.fromSourcetableLine('STR;;' + str));
if ((_b = this.options) === null || _b === void 0 ? void 0 : _b.autoSourceEntry)
server.rtcm.pipe(this.autoSourceEntry, { end: false });
}
clearServer(server = this._server) {
if (this._server === null)
return;
if (this._server !== server)
return;
this._server = null;
this.resetTimeouts();
server.input.unpipe(this.statsStream);
this._clients.forEach((client) => server.unpipe(client));
if (this.autoSourceEntry !== undefined)
server.rtcm.unpipe(this.autoSourceEntry);
server.close();
}
addClient(client) {
var _a;
if (this._clients.has(client))
return;
this._clients.add(client);
this.emit('client', client);
client.on('close', () => this.removeClient(client));
(_a = this._server) === null || _a === void 0 ? void 0 : _a.pipe(client);
}
removeClient(client) {
var _a;
if (!this._clients.has(client))
return;
this._clients.delete(client);
(_a = this._server) === null || _a === void 0 ? void 0 : _a.unpipe(client);
client.close();
}
close() {
this.clearServer();
this._clients.forEach((client) => this.removeClient(client));
this.emit('close');
}
}
exports.Mountpoint = Mountpoint;