2016-10-22 00:27:47 +08:00
|
|
|
import { Meteor } from 'meteor/meteor';
|
2019-03-29 23:47:07 +08:00
|
|
|
import { WebAppInternals } from 'meteor/webapp';
|
2017-12-21 00:10:01 +08:00
|
|
|
import Langmap from 'langmap';
|
2018-01-10 20:43:45 +08:00
|
|
|
import fs from 'fs';
|
2019-07-02 03:00:27 +08:00
|
|
|
import Users from '/imports/api/users';
|
2018-09-25 20:37:45 +08:00
|
|
|
import './settings';
|
2019-09-10 02:45:54 +08:00
|
|
|
import { check } from 'meteor/check';
|
2016-10-18 20:03:51 +08:00
|
|
|
import Logger from './logger';
|
|
|
|
import Redis from './redis';
|
2020-05-26 22:15:17 +08:00
|
|
|
|
2019-05-02 08:33:03 +08:00
|
|
|
import setMinBrowserVersions from './minBrowserVersion';
|
2021-08-24 04:39:53 +08:00
|
|
|
import { PrometheusAgent, METRIC_NAMES } from './prom-metrics/index.js'
|
2016-08-17 23:48:03 +08:00
|
|
|
|
2020-07-03 04:14:49 +08:00
|
|
|
let guestWaitHtml = '';
|
2021-03-11 19:42:41 +08:00
|
|
|
|
|
|
|
const env = Meteor.isDevelopment ? 'development' : 'production';
|
|
|
|
|
|
|
|
const meteorRoot = fs.realpathSync(`${process.cwd()}/../`);
|
|
|
|
|
|
|
|
const applicationRoot = (env === 'development')
|
|
|
|
? fs.realpathSync(`${meteorRoot}'/../../../../public/locales/`)
|
|
|
|
: fs.realpathSync(`${meteorRoot}/../programs/web.browser/app/locales/`);
|
|
|
|
|
|
|
|
const AVAILABLE_LOCALES = fs.readdirSync(`${applicationRoot}`);
|
2020-05-26 22:15:17 +08:00
|
|
|
const FALLBACK_LOCALES = JSON.parse(Assets.getText('config/fallbackLocales.json'));
|
2018-05-30 20:23:05 +08:00
|
|
|
|
2020-12-03 03:06:42 +08:00
|
|
|
process.on('uncaughtException', (err) => {
|
|
|
|
Logger.error(`uncaughtException: ${err}`);
|
|
|
|
process.exit(1);
|
|
|
|
});
|
|
|
|
|
2022-09-16 03:56:00 +08:00
|
|
|
const formatMemoryUsage = (data) => `${Math.round(data / 1024 / 1024 * 100) / 100} MB`
|
|
|
|
|
|
|
|
const serverHealth = () => {
|
|
|
|
const memoryData = process.memoryUsage();
|
|
|
|
const memoryUsage = {
|
|
|
|
rss: formatMemoryUsage(memoryData.rss),
|
|
|
|
heapTotal: formatMemoryUsage(memoryData.heapTotal),
|
|
|
|
heapUsed: formatMemoryUsage(memoryData.heapUsed),
|
|
|
|
external: formatMemoryUsage(memoryData.external),
|
|
|
|
}
|
|
|
|
|
|
|
|
const cpuData = process.cpuUsage();
|
|
|
|
const cpuUsage = {
|
|
|
|
system: formatMemoryUsage(cpuData.system),
|
|
|
|
user: formatMemoryUsage(cpuData.user),
|
|
|
|
}
|
|
|
|
|
2022-10-04 00:48:00 +08:00
|
|
|
Logger.info('Server health', {memoryUsage, cpuUsage});
|
2022-09-16 03:56:00 +08:00
|
|
|
};
|
|
|
|
|
2016-10-18 20:03:51 +08:00
|
|
|
Meteor.startup(() => {
|
2016-08-17 23:48:03 +08:00
|
|
|
const APP_CONFIG = Meteor.settings.public.app;
|
2019-03-29 23:47:07 +08:00
|
|
|
const CDN_URL = APP_CONFIG.cdn;
|
2021-02-16 11:19:31 +08:00
|
|
|
const instanceId = parseInt(process.env.INSTANCE_ID, 10) || 1;
|
2020-12-12 05:36:06 +08:00
|
|
|
|
2021-02-03 02:44:58 +08:00
|
|
|
Logger.warn(`Started bbb-html5 process with instanceId=${instanceId}`);
|
2019-12-04 04:26:45 +08:00
|
|
|
|
2022-09-16 22:56:56 +08:00
|
|
|
const LOG_CONFIG = Meteor.settings.private.serverLog;
|
|
|
|
const { healthChecker } = LOG_CONFIG;
|
|
|
|
const { enable: enableHealthCheck, intervalMs: healthCheckInterval } = healthChecker;
|
|
|
|
|
|
|
|
if (enableHealthCheck) {
|
|
|
|
Meteor.setInterval(() => {
|
|
|
|
serverHealth();
|
|
|
|
}, healthCheckInterval);
|
|
|
|
}
|
2022-09-16 03:56:00 +08:00
|
|
|
|
2024-01-23 00:10:41 +08:00
|
|
|
const { customHeartbeat, customHeartbeatUseDataFrames } = APP_CONFIG;
|
2020-11-14 05:41:56 +08:00
|
|
|
|
2020-11-26 22:19:59 +08:00
|
|
|
if (customHeartbeat) {
|
|
|
|
Logger.warn('Custom heartbeat functions are enabled');
|
|
|
|
// https://github.com/sockjs/sockjs-node/blob/1ef08901f045aae7b4df0f91ef598d7a11e82897/lib/transport/websocket.js#L74-L82
|
2024-01-23 00:10:41 +08:00
|
|
|
const heartbeatFactory = function ({ heartbeatTimeoutCallback }) {
|
|
|
|
return function () {
|
|
|
|
const currentTime = new Date().getTime();
|
|
|
|
|
|
|
|
if (customHeartbeatUseDataFrames) {
|
|
|
|
// Skipping heartbeat, because websocket is sending data
|
|
|
|
if (currentTime - this.ws.lastSentFrameTimestamp < 10000) {
|
|
|
|
try {
|
|
|
|
Logger.debug('Skipping heartbeat, because websocket is sending data', {
|
|
|
|
currentTime,
|
|
|
|
lastSentFrameTimestamp: this.ws.lastSentFrameTimestamp,
|
|
|
|
userId: this.session?.connection?._meteorSession?.userId,
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
} catch (err) {
|
|
|
|
Logger.error(`Skipping heartbeat error: ${err}`);
|
|
|
|
}
|
|
|
|
}
|
2020-12-03 03:43:34 +08:00
|
|
|
}
|
2020-11-21 01:31:24 +08:00
|
|
|
|
2024-01-23 00:10:41 +08:00
|
|
|
const supportsHeartbeats = this.ws.ping(null, () => {
|
|
|
|
clearTimeout(this.hto_ref);
|
|
|
|
});
|
|
|
|
|
|
|
|
if (supportsHeartbeats) {
|
|
|
|
this.hto_ref = setTimeout(() => {
|
|
|
|
try {
|
|
|
|
Logger.warn('Heartbeat timeout', { userId: this.session?.connection?._meteorSession?.userId, sentAt: currentTime, now: new Date().getTime() });
|
|
|
|
} catch (err) {
|
|
|
|
Logger.error(`Heartbeat timeout error: ${err}`);
|
|
|
|
} finally {
|
|
|
|
if (typeof heartbeatTimeoutCallback === 'function') {
|
|
|
|
heartbeatTimeoutCallback();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}, Meteor.server.options.heartbeatTimeout);
|
|
|
|
} else {
|
|
|
|
Logger.error('Unexpected error supportsHeartbeats=false');
|
|
|
|
}
|
|
|
|
};
|
2020-11-26 22:19:59 +08:00
|
|
|
};
|
2020-11-21 01:31:24 +08:00
|
|
|
|
2020-11-26 22:19:59 +08:00
|
|
|
// https://github.com/davhani/hagty/blob/6a5c78e9ae5a5e4ade03e747fb4cc8ea2df4be0c/faye-websocket/lib/faye/websocket/api.js#L84-L88
|
|
|
|
const newSend = function send(data) {
|
2020-12-04 02:07:24 +08:00
|
|
|
try {
|
|
|
|
this.lastSentFrameTimestamp = new Date().getTime();
|
2020-11-21 01:31:24 +08:00
|
|
|
|
2020-12-04 02:07:24 +08:00
|
|
|
if (this.meteorHeartbeat) {
|
|
|
|
// Call https://github.com/meteor/meteor/blob/1e7e56eec8414093cd0c1c70750b894069fc972a/packages/ddp-common/heartbeat.js#L80-L88
|
|
|
|
this.meteorHeartbeat._seenPacket = true;
|
|
|
|
if (this.meteorHeartbeat._heartbeatTimeoutHandle) {
|
|
|
|
this.meteorHeartbeat._clearHeartbeatTimeoutTimer();
|
|
|
|
}
|
|
|
|
}
|
2020-11-21 01:31:24 +08:00
|
|
|
|
2020-12-04 02:07:24 +08:00
|
|
|
if (this.readyState > 1/* API.OPEN = 1 */) return false;
|
|
|
|
if (!(data instanceof Buffer)) data = String(data);
|
|
|
|
return this._driver.messages.write(data);
|
|
|
|
} catch (err) {
|
|
|
|
console.error('Error on send data', err);
|
|
|
|
return false;
|
|
|
|
}
|
2020-11-26 22:19:59 +08:00
|
|
|
};
|
2019-12-04 04:26:45 +08:00
|
|
|
|
2020-11-26 22:19:59 +08:00
|
|
|
Meteor.setInterval(() => {
|
|
|
|
for (const session of Meteor.server.sessions.values()) {
|
|
|
|
const { socket } = session;
|
|
|
|
const recv = socket._session.recv;
|
2019-03-18 16:57:43 +08:00
|
|
|
|
2020-11-26 22:19:59 +08:00
|
|
|
if (session.bbbFixApplied || !recv || !recv.ws) {
|
|
|
|
continue;
|
|
|
|
}
|
2019-09-25 05:50:37 +08:00
|
|
|
|
2020-11-26 22:19:59 +08:00
|
|
|
recv.ws.meteorHeartbeat = session.heartbeat;
|
2024-01-23 00:10:41 +08:00
|
|
|
recv.heartbeat = heartbeatFactory({
|
|
|
|
heartbeatTimeoutCallback: recv.heartbeat_cb
|
|
|
|
});
|
|
|
|
recv.ws.send = newSend;
|
2020-11-26 22:19:59 +08:00
|
|
|
session.bbbFixApplied = true;
|
2019-09-25 05:50:37 +08:00
|
|
|
}
|
2020-11-26 22:19:59 +08:00
|
|
|
}, 5000);
|
2019-03-29 23:47:07 +08:00
|
|
|
}
|
2021-03-11 02:14:25 +08:00
|
|
|
if (CDN_URL.trim()) {
|
|
|
|
// Add CDN
|
|
|
|
BrowserPolicy.content.disallowEval();
|
|
|
|
BrowserPolicy.content.allowInlineScripts();
|
|
|
|
BrowserPolicy.content.allowInlineStyles();
|
|
|
|
BrowserPolicy.content.allowImageDataUrl(CDN_URL);
|
|
|
|
BrowserPolicy.content.allowFontDataUrl(CDN_URL);
|
|
|
|
BrowserPolicy.content.allowOriginForAll(CDN_URL);
|
|
|
|
WebAppInternals.setBundledJsCssPrefix(CDN_URL + APP_CONFIG.basename + Meteor.settings.public.app.instanceId);
|
|
|
|
|
|
|
|
const fontRegExp = /\.(eot|ttf|otf|woff|woff2)$/;
|
|
|
|
|
|
|
|
WebApp.rawConnectHandlers.use('/', (req, res, next) => {
|
|
|
|
if (fontRegExp.test(req._parsedUrl.pathname)) {
|
|
|
|
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
|
|
res.setHeader('Vary', 'Origin');
|
|
|
|
res.setHeader('Pragma', 'public');
|
|
|
|
res.setHeader('Cache-Control', '"public"');
|
|
|
|
}
|
|
|
|
return next();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
setMinBrowserVersions();
|
|
|
|
|
2021-08-24 04:39:53 +08:00
|
|
|
Meteor.onMessage(event => {
|
|
|
|
const { method } = event;
|
|
|
|
if (method) {
|
2022-02-22 00:06:21 +08:00
|
|
|
const methodName = method.includes('stream-cursor') ? 'stream-cursor' : method;
|
|
|
|
PrometheusAgent.increment(METRIC_NAMES.METEOR_METHODS, { methodName });
|
2021-08-24 04:39:53 +08:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2021-03-11 02:14:25 +08:00
|
|
|
Logger.warn(`SERVER STARTED.
|
|
|
|
ENV=${env}
|
|
|
|
nodejs version=${process.version}
|
|
|
|
BBB_HTML5_ROLE=${process.env.BBB_HTML5_ROLE}
|
|
|
|
INSTANCE_ID=${instanceId}
|
|
|
|
PORT=${process.env.PORT}
|
|
|
|
CDN=${CDN_URL}\n`, APP_CONFIG);
|
2016-05-05 04:25:34 +08:00
|
|
|
});
|
2016-05-05 01:00:57 +08:00
|
|
|
|
2020-12-12 05:36:06 +08:00
|
|
|
|
|
|
|
const generateLocaleOptions = () => {
|
|
|
|
try {
|
|
|
|
Logger.warn('Calculating aggregateLocales (heavy)');
|
2021-02-03 02:44:58 +08:00
|
|
|
|
|
|
|
|
|
|
|
// remove duplicated locales (always remove more generic if same name)
|
2020-12-12 05:36:06 +08:00
|
|
|
const tempAggregateLocales = AVAILABLE_LOCALES
|
|
|
|
.map(file => file.replace('.json', ''))
|
|
|
|
.map(file => file.replace('_', '-'))
|
|
|
|
.map((locale) => {
|
|
|
|
const localeName = (Langmap[locale] || {}).nativeName
|
|
|
|
|| (FALLBACK_LOCALES[locale] || {}).nativeName
|
|
|
|
|| locale;
|
|
|
|
return {
|
|
|
|
locale,
|
|
|
|
name: localeName,
|
|
|
|
};
|
2021-02-03 02:44:58 +08:00
|
|
|
}).reverse()
|
|
|
|
.filter((item, index, self) => index === self.findIndex(i => (
|
|
|
|
i.name === item.name
|
|
|
|
)))
|
|
|
|
.reverse();
|
|
|
|
|
2020-12-12 05:36:06 +08:00
|
|
|
Logger.warn(`Total locales: ${tempAggregateLocales.length}`, tempAggregateLocales);
|
2021-02-03 02:44:58 +08:00
|
|
|
|
2020-12-12 05:36:06 +08:00
|
|
|
return tempAggregateLocales;
|
|
|
|
} catch (e) {
|
|
|
|
Logger.error(`'Could not process locales error: ${e}`);
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
let avaibleLocalesNamesJSON = JSON.stringify(generateLocaleOptions());
|
|
|
|
|
2017-10-06 20:50:01 +08:00
|
|
|
WebApp.connectHandlers.use('/check', (req, res) => {
|
2017-06-03 03:25:02 +08:00
|
|
|
const payload = { html5clientStatus: 'running' };
|
2016-06-14 01:56:41 +08:00
|
|
|
|
|
|
|
res.setHeader('Content-Type', 'application/json');
|
|
|
|
res.writeHead(200);
|
|
|
|
res.end(JSON.stringify(payload));
|
|
|
|
});
|
|
|
|
|
2016-10-08 00:36:27 +08:00
|
|
|
WebApp.connectHandlers.use('/locale', (req, res) => {
|
2017-03-10 03:50:21 +08:00
|
|
|
const APP_CONFIG = Meteor.settings.public.app;
|
2018-05-23 03:57:43 +08:00
|
|
|
const fallback = APP_CONFIG.defaultSettings.application.fallbackLocale;
|
2019-07-10 21:19:00 +08:00
|
|
|
const override = APP_CONFIG.defaultSettings.application.overrideLocale;
|
2020-03-11 21:55:41 +08:00
|
|
|
const browserLocale = override && req.query.init === 'true'
|
|
|
|
? override.split(/[-_]/g) : req.query.locale.split(/[-_]/g);
|
|
|
|
|
2021-03-11 19:42:41 +08:00
|
|
|
let localeFile = fallback;
|
2018-05-28 19:35:24 +08:00
|
|
|
|
2018-05-30 20:39:02 +08:00
|
|
|
const usableLocales = AVAILABLE_LOCALES
|
2018-05-28 19:35:24 +08:00
|
|
|
.map(file => file.replace('.json', ''))
|
2019-03-09 00:57:40 +08:00
|
|
|
.reduce((locales, locale) => (locale.match(browserLocale[0])
|
|
|
|
? [...locales, locale]
|
|
|
|
: locales), []);
|
2018-05-30 20:39:02 +08:00
|
|
|
|
2018-05-28 19:35:24 +08:00
|
|
|
let normalizedLocale;
|
2017-10-27 19:36:27 +08:00
|
|
|
|
2021-05-21 01:01:58 +08:00
|
|
|
const regionDefault = usableLocales.find(locale => browserLocale[0] === locale);
|
|
|
|
|
2018-05-28 19:35:24 +08:00
|
|
|
if (browserLocale.length > 1) {
|
2021-05-21 01:01:58 +08:00
|
|
|
// browser asks for specific locale
|
2021-11-22 08:55:51 +08:00
|
|
|
normalizedLocale = `${browserLocale[0]}_${browserLocale[1]?.toUpperCase()}`;
|
2021-03-11 19:42:41 +08:00
|
|
|
|
|
|
|
const normDefault = usableLocales.find(locale => normalizedLocale === locale);
|
2021-05-21 01:01:58 +08:00
|
|
|
if (normDefault) {
|
|
|
|
localeFile = normDefault;
|
2021-05-21 02:14:19 +08:00
|
|
|
} else {
|
2021-05-21 01:01:58 +08:00
|
|
|
if (regionDefault) {
|
|
|
|
localeFile = regionDefault;
|
|
|
|
} else {
|
|
|
|
const specFallback = usableLocales.find(locale => browserLocale[0] === locale.split("_")[0]);
|
|
|
|
if (specFallback) localeFile = specFallback;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// browser asks for region default locale
|
|
|
|
if (regionDefault && localeFile === fallback && regionDefault !== localeFile) {
|
|
|
|
localeFile = regionDefault;
|
|
|
|
} else {
|
|
|
|
const normFallback = usableLocales.find(locale => browserLocale[0] === locale.split("_")[0]);
|
|
|
|
if (normFallback) localeFile = normFallback;
|
|
|
|
}
|
2021-03-11 19:42:41 +08:00
|
|
|
}
|
2016-10-08 00:36:27 +08:00
|
|
|
|
|
|
|
res.setHeader('Content-Type', 'application/json');
|
2021-03-11 19:42:41 +08:00
|
|
|
res.end(JSON.stringify({
|
|
|
|
normalizedLocale: localeFile,
|
|
|
|
regionDefaultLocale: (regionDefault && regionDefault !== localeFile) ? regionDefault : '',
|
|
|
|
}));
|
2016-10-08 00:36:27 +08:00
|
|
|
});
|
|
|
|
|
2021-03-11 19:42:41 +08:00
|
|
|
WebApp.connectHandlers.use('/locale-list', (req, res) => {
|
2020-09-10 22:32:42 +08:00
|
|
|
if (!avaibleLocalesNamesJSON) {
|
|
|
|
avaibleLocalesNamesJSON = JSON.stringify(generateLocaleOptions());
|
2017-04-06 20:36:59 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
res.setHeader('Content-Type', 'application/json');
|
|
|
|
res.writeHead(200);
|
2020-09-10 22:32:42 +08:00
|
|
|
res.end(avaibleLocalesNamesJSON);
|
2017-04-06 20:36:59 +08:00
|
|
|
});
|
|
|
|
|
2018-05-10 02:07:40 +08:00
|
|
|
WebApp.connectHandlers.use('/feedback', (req, res) => {
|
|
|
|
req.on('data', Meteor.bindEnvironment((data) => {
|
|
|
|
const body = JSON.parse(data);
|
|
|
|
const {
|
|
|
|
meetingId,
|
|
|
|
userId,
|
2018-05-11 04:23:17 +08:00
|
|
|
authToken,
|
2019-07-12 04:51:09 +08:00
|
|
|
userName: reqUserName,
|
|
|
|
comment,
|
|
|
|
rating,
|
2018-05-10 02:07:40 +08:00
|
|
|
} = body;
|
|
|
|
|
2019-07-12 04:51:09 +08:00
|
|
|
check(meetingId, String);
|
|
|
|
check(userId, String);
|
|
|
|
check(authToken, String);
|
|
|
|
check(reqUserName, String);
|
|
|
|
check(comment, String);
|
|
|
|
check(rating, Number);
|
|
|
|
|
2018-05-11 04:23:17 +08:00
|
|
|
const user = Users.findOne({
|
|
|
|
meetingId,
|
|
|
|
userId,
|
|
|
|
authToken,
|
|
|
|
});
|
2018-05-10 02:07:40 +08:00
|
|
|
|
2019-07-07 06:48:33 +08:00
|
|
|
if (!user) {
|
2019-07-23 03:32:14 +08:00
|
|
|
Logger.warn('Couldn\'t find user for feedback');
|
2019-07-07 06:48:33 +08:00
|
|
|
}
|
|
|
|
|
2019-07-12 04:51:09 +08:00
|
|
|
res.setHeader('Content-Type', 'application/json');
|
|
|
|
res.writeHead(200);
|
|
|
|
res.end(JSON.stringify({ status: 'ok' }));
|
|
|
|
|
|
|
|
body.userName = user ? user.name : `[unconfirmed] ${reqUserName}`;
|
|
|
|
|
2018-05-10 02:07:40 +08:00
|
|
|
const feedback = {
|
|
|
|
...body,
|
|
|
|
};
|
|
|
|
Logger.info('FEEDBACK LOG:', feedback);
|
|
|
|
}));
|
|
|
|
});
|
|
|
|
|
2020-07-03 04:14:49 +08:00
|
|
|
WebApp.connectHandlers.use('/guestWait', (req, res) => {
|
|
|
|
if (!guestWaitHtml) {
|
|
|
|
try {
|
|
|
|
guestWaitHtml = Assets.getText('static/guest-wait/guest-wait.html');
|
|
|
|
} catch (e) {
|
2020-08-13 04:15:16 +08:00
|
|
|
Logger.warn(`Could not process guest wait html file: ${e}`);
|
2020-07-03 04:14:49 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
res.setHeader('Content-Type', 'text/html');
|
|
|
|
res.writeHead(200);
|
|
|
|
res.end(guestWaitHtml);
|
|
|
|
});
|
|
|
|
|
2016-10-18 20:03:51 +08:00
|
|
|
export const eventEmitter = Redis.emitter;
|
2016-05-05 01:00:57 +08:00
|
|
|
|
2017-06-03 03:25:02 +08:00
|
|
|
export const redisPubSub = Redis;
|