From 670233e0def12002a6175ac0d6fd9a208cf4c0c8 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Thu, 3 Sep 2020 18:41:35 -0300 Subject: [PATCH 001/198] Implement base for Api services --- ee/app/license/server/bundles.ts | 1 + ee/server/broker.ts | 54 +++++++++++++ ee/server/index.js | 2 + package-lock.json | 133 ++++++++++++++++++++++++++++++- package.json | 1 + sdk/api.ts | 3 + sdk/index.ts | 5 ++ sdk/lib/Api.ts | 34 ++++++++ sdk/lib/BaseBroker.ts | 20 +++++ sdk/lib/LocalBroker.ts | 29 +++++++ sdk/lib/proxify.ts | 19 +++++ sdk/services/authorization.ts | 17 ++++ sdk/startup.ts | 7 ++ sdk/types/IAuthorization.ts | 5 ++ sdk/types/IBroker.ts | 6 ++ sdk/types/ServiceClass.ts | 7 ++ server/main.js | 2 + server/routes/avatar/index.js | 10 +++ 18 files changed, 353 insertions(+), 2 deletions(-) create mode 100644 ee/server/broker.ts create mode 100644 sdk/api.ts create mode 100644 sdk/index.ts create mode 100644 sdk/lib/Api.ts create mode 100644 sdk/lib/BaseBroker.ts create mode 100644 sdk/lib/LocalBroker.ts create mode 100644 sdk/lib/proxify.ts create mode 100644 sdk/services/authorization.ts create mode 100644 sdk/startup.ts create mode 100644 sdk/types/IAuthorization.ts create mode 100644 sdk/types/IBroker.ts create mode 100644 sdk/types/ServiceClass.ts diff --git a/ee/app/license/server/bundles.ts b/ee/app/license/server/bundles.ts index 84b55a6f31b28..67ea0be970a61 100644 --- a/ee/app/license/server/bundles.ts +++ b/ee/app/license/server/bundles.ts @@ -11,6 +11,7 @@ const bundles: IBundle = { 'omnichannel-mobile-enterprise', 'engagement-dashboard', 'push-privacy', + 'scalability', ], pro: [ ], diff --git a/ee/server/broker.ts b/ee/server/broker.ts new file mode 100644 index 0000000000000..f6227e44b3385 --- /dev/null +++ b/ee/server/broker.ts @@ -0,0 +1,54 @@ +import { ServiceBroker } from 'moleculer'; + +import { api } from '../../sdk/api'; +import { onLicense } from '../app/license/server'; +import { IBroker } from '../../sdk/types/IBroker'; +import { ServiceClass } from '../../sdk/types/ServiceClass'; + +class NetworkBroker implements IBroker { + private broker: ServiceBroker; + + constructor(broker: ServiceBroker) { + this.broker = broker; + } + + async call(method: string, data: any): Promise { + return this.broker.call(method, data); + } + + createService(instance: ServiceClass): void { + const name = instance.getName(); + + const service = { + name, + actions: {} as any, + }; + + const methods = instance.constructor?.name === 'Object' ? Object.getOwnPropertyNames(instance) : Object.getOwnPropertyNames(Object.getPrototypeOf(instance)); + for (const method of methods) { + if (method === 'constructor') { + continue; + } + const i = instance as any; + + service.actions[method] = (ctx: any): any => i[method](...ctx.params); + } + + this.broker.createService(service); + } +} + +onLicense('scalability', async () => { + const network = new ServiceBroker({ + transporter: 'TCP', + logLevel: 'debug', + registry: { + strategy: 'RoundRobin', + preferLocal: false, + }, + }); + + await network.start(); + + api.setBroker(new NetworkBroker(network)); +}); diff --git a/ee/server/index.js b/ee/server/index.js index b7aa74f4c07c9..08141919ad56b 100644 --- a/ee/server/index.js +++ b/ee/server/index.js @@ -1,3 +1,5 @@ +import './broker'; + import '../app/models'; import '../app/api-enterprise/server/index'; import '../app/auditing/server/index'; diff --git a/package-lock.json b/package-lock.json index de3fb6b6b2641..9c3e3f0907a25 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9596,6 +9596,55 @@ } } }, + "args": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/args/-/args-5.0.1.tgz", + "integrity": "sha512-1kqmFCFsPffavQFGt8OxJdIcETti99kySRUPMpOhaGjL6mRJn8HFU1OxKY5bMqfZKUwTQc1mZkAjmGYaVOHFtQ==", + "requires": { + "camelcase": "5.0.0", + "chalk": "2.4.2", + "leven": "2.1.0", + "mri": "1.1.4" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "camelcase": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz", + "integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==" + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "leven": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz", + "integrity": "sha1-wuep93IJTe6dNCAq6KzORoeHVYA=" + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, "arr-diff": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", @@ -16140,6 +16189,11 @@ "integrity": "sha512-7SwlpL+2JpymWTt8sNLuC2zdhhc+wrfe5cMPI2j0o6WsPdfAiPwmFy2f0AocPB4RQVBOZ9kNTgi5YF7TdhkvEg==", "dev": true }, + "es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==" + }, "es6-promise": { "version": "4.2.8", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", @@ -16729,8 +16783,7 @@ "eventemitter2": { "version": "6.4.3", "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.3.tgz", - "integrity": "sha512-t0A2msp6BzOf+QAcI6z9XMktLj52OjGQg+8SJH6v5+3uxNpWYRR3wQmfA+6xtMU9kOC59qk9licus5dYcrYkMQ==", - "dev": true + "integrity": "sha512-t0A2msp6BzOf+QAcI6z9XMktLj52OjGQg+8SJH6v5+3uxNpWYRR3wQmfA+6xtMU9kOC59qk9licus5dYcrYkMQ==" }, "eventemitter3": { "version": "3.1.2", @@ -17155,6 +17208,11 @@ "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.3.tgz", "integrity": "sha512-dtm4QZH9nZtcDt8qJiOH9fcQd1NAgi+K1O2DbE6GG1PPCK/BWfOH3idCTRQ4ImXRUOyopDEgDEnVEE7Y/2Wrig==" }, + "fastest-validator": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/fastest-validator/-/fastest-validator-1.7.0.tgz", + "integrity": "sha512-g8G9gNYHVH2EdsIOAnQ60xTeRtf+yitBudR1/5ZpGCIUyXpYv4H9oVT4mwocUOAi/JH9X3kC/R3P6tiukItbvg==" + }, "fastq": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.8.0.tgz", @@ -17560,6 +17618,11 @@ "resolved": "https://registry.npmjs.org/flushwritable/-/flushwritable-1.0.0.tgz", "integrity": "sha1-PjKNj95BKtR+c44751C00pAENJg=" }, + "fn-args": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/fn-args/-/fn-args-5.0.0.tgz", + "integrity": "sha512-CtbfI3oFFc3nbdIoHycrfbrxiGgxXBXXuyOl49h47JawM1mYrqpiRqnH5CB2mBatdXvHHOUO6a+RiAuuvKt0lw==" + }, "focus-lock": { "version": "0.6.8", "resolved": "https://registry.npmjs.org/focus-lock/-/focus-lock-0.6.8.tgz", @@ -21957,6 +22020,11 @@ "graceful-fs": "^4.1.9" } }, + "kleur": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.1.tgz", + "integrity": "sha512-BsNhM6T/yTWFG580CRnYhT3LfUuPK7Hwrm+W2H0G8lK/nogalP5Nsrh/cHjxVVkzl0sFm7z8b8rNcZCfKxeoxA==" + }, "known-css-properties": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.19.0.tgz", @@ -24651,6 +24719,62 @@ "integrity": "sha1-z4tP9PKWQGdNbN0CsOO8UjwrvcA=", "dev": true }, + "moleculer": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/moleculer/-/moleculer-0.14.10.tgz", + "integrity": "sha512-3HS8cBAIzhCX8x+R/9yS31NlNRp9u9iyN3lWZbmVv1MrlXqkqPCZXLNNCyaB+iSC6Ncuh97MCsv0dUosA7qpKA==", + "requires": { + "args": "^5.0.1", + "es6-error": "^4.1.1", + "eventemitter2": "^6.4.3", + "fastest-validator": "^1.6.1", + "fn-args": "^5.0.0", + "glob": "^7.1.6", + "ipaddr.js": "^2.0.0", + "kleur": "^4.1.1", + "lodash": "^4.17.20", + "lru-cache": "^6.0.0", + "node-fetch": "^2.6.0" + }, + "dependencies": { + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "ipaddr.js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.0.tgz", + "integrity": "sha512-S54H9mIj0rbxRIyrDMEuuER86LdlgUg9FSeZ8duQb6CUG2iRrA36MYVQBSprTF/ZeAwvyQ5mDGuNvIPM0BIl3w==" + }, + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } + }, "moment": { "version": "2.27.0", "resolved": "https://registry.npmjs.org/moment/-/moment-2.27.0.tgz", @@ -25014,6 +25138,11 @@ } } }, + "mri": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.1.4.tgz", + "integrity": "sha512-6y7IjGPm8AzlvoUrwAaw1tLnUBudaS3752vcd8JtrpGGQn+rXIe63LFVHm/YMwtqAuh+LJPCFdlLYPWM1nYn6w==" + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", diff --git a/package.json b/package.json index 5818410a827a9..9fe1fc3b3edcb 100644 --- a/package.json +++ b/package.json @@ -208,6 +208,7 @@ "mime-db": "^1.44.0", "mime-type": "^3.1.0", "mkdirp": "^0.5.5", + "moleculer": "^0.14.10", "moment": "^2.27.0", "moment-timezone": "^0.5.31", "mongodb": "^3.6.0", diff --git a/sdk/api.ts b/sdk/api.ts new file mode 100644 index 0000000000000..7255bd074d844 --- /dev/null +++ b/sdk/api.ts @@ -0,0 +1,3 @@ +import { Api } from './lib/Api'; + +export const api = new Api(); diff --git a/sdk/index.ts b/sdk/index.ts new file mode 100644 index 0000000000000..c6c5638583018 --- /dev/null +++ b/sdk/index.ts @@ -0,0 +1,5 @@ +import { proxify } from './lib/proxify'; +import { IAuthorization } from './types/IAuthorization'; + +// TODO try not having to duplicate the namespace 'authorization' here +export const Authorization = proxify('authorization'); diff --git a/sdk/lib/Api.ts b/sdk/lib/Api.ts new file mode 100644 index 0000000000000..1a8d643851b63 --- /dev/null +++ b/sdk/lib/Api.ts @@ -0,0 +1,34 @@ +// import { BaseBroker } from './BaseBroker'; +import { IBroker } from '../types/IBroker'; +import { ServiceClass } from '../types/ServiceClass'; + +export class Api { + private services: ServiceClass[] = []; + + private broker: IBroker; + + protected log(...args: Array): void { + console.log('BROKER:', ...args); + } + + // set a broker for the API and registers all services in the broker + setBroker(broker: IBroker): void { + this.broker = broker; + + this.services.forEach((service) => this.broker.createService(service)); + } + + registerService(instance: ServiceClass): void { + this.services.push(instance); + + if (this.broker) { + this.broker.createService(instance); + } + } + + async call(method: string, data: any): Promise { + this.log('BROKER.call', method); + + return this.broker.call(method, data); + } +} diff --git a/sdk/lib/BaseBroker.ts b/sdk/lib/BaseBroker.ts new file mode 100644 index 0000000000000..543b875e4336b --- /dev/null +++ b/sdk/lib/BaseBroker.ts @@ -0,0 +1,20 @@ +export abstract class BaseBroker { + protected log(...args: Array): void { + console.log('BROKER:', ...args); + } + + abstract register(name: string, method: Function): void; + + registerInstance(instance: object): void { + const methods = instance.constructor?.name === 'Object' ? Object.getOwnPropertyNames(instance) : Object.getOwnPropertyNames(Object.getPrototypeOf(instance)); + for (const method of methods) { + if (method === 'constructor') { + continue; + } + const i = instance as any; + this.register(method, i[method]); + } + } + + abstract async call(method: string, data: any): Promise; +} diff --git a/sdk/lib/LocalBroker.ts b/sdk/lib/LocalBroker.ts new file mode 100644 index 0000000000000..adc26ad803e93 --- /dev/null +++ b/sdk/lib/LocalBroker.ts @@ -0,0 +1,29 @@ +import { IBroker } from '../types/IBroker'; +import { ServiceClass } from '../types/ServiceClass'; + +const delay = (ms: number): Promise => new Promise((resolve) => setTimeout(resolve, ms)); + +export class LocalBroker implements IBroker { + private methods = new Map(); + + async call(method: string, data: any): Promise { + await delay(500); + const result = this.methods.get(method)?.(...data); + await delay(500); + return result; + } + + createService(instance: ServiceClass): void { + const namespace = instance.getName(); + + const methods = instance.constructor?.name === 'Object' ? Object.getOwnPropertyNames(instance) : Object.getOwnPropertyNames(Object.getPrototypeOf(instance)); + for (const method of methods) { + if (method === 'constructor') { + continue; + } + const i = instance as any; + + this.methods.set(`${ namespace }.${ method }`, i[method]); + } + } +} diff --git a/sdk/lib/proxify.ts b/sdk/lib/proxify.ts new file mode 100644 index 0000000000000..f9fa306a720fe --- /dev/null +++ b/sdk/lib/proxify.ts @@ -0,0 +1,19 @@ +import { api } from '../api'; + +type FunctionPropertyNames = { + [K in keyof T]: T[K] extends Function ? K : never; +}[keyof T]; + +type Prom = { + [K in FunctionPropertyNames]: ReturnType extends Promise ? T[K] : (...params: Parameters) => Promise>; +} + +function handler(namespace: string): ProxyHandler { + return { + get: (_target: T, prop: string): any => (...params: any): Promise => api.call(`${ namespace }.${ prop }`, params), + }; +} + +export function proxify(namespace: string): Prom { + return new Proxy({}, handler(namespace)) as unknown as Prom; +} diff --git a/sdk/services/authorization.ts b/sdk/services/authorization.ts new file mode 100644 index 0000000000000..6fd82a4825ce9 --- /dev/null +++ b/sdk/services/authorization.ts @@ -0,0 +1,17 @@ +import { IAuthorization } from '../types/IAuthorization'; +import { ServiceClass } from '../types/ServiceClass'; + +// Register as class +export class Authorization extends ServiceClass implements IAuthorization { + protected name = 'authorization'; + + hasPermission(permission: string, user: string): boolean { + console.log('hasPermission called'); + return permission === 'createUser' && user != null; + } + + hasPermission2(permission: string, user: string, bla: number): number { + console.log('hasPermission2 called'); + return permission === 'createUser' && user != null && bla === 1 ? 1 : 0; + } +} diff --git a/sdk/startup.ts b/sdk/startup.ts new file mode 100644 index 0000000000000..67b6e126f0911 --- /dev/null +++ b/sdk/startup.ts @@ -0,0 +1,7 @@ +import { Authorization } from './services/authorization'; +import { api } from './api'; +import { LocalBroker } from './lib/LocalBroker'; + +api.setBroker(new LocalBroker()); + +api.registerService(new Authorization()); diff --git a/sdk/types/IAuthorization.ts b/sdk/types/IAuthorization.ts new file mode 100644 index 0000000000000..74be052c9af06 --- /dev/null +++ b/sdk/types/IAuthorization.ts @@ -0,0 +1,5 @@ +export interface IAuthorization { + hasPermission(permission: string, user: string): boolean; + hasPermission2(permission: string, user: string, bla: number): number; + prop?: string; +} diff --git a/sdk/types/IBroker.ts b/sdk/types/IBroker.ts new file mode 100644 index 0000000000000..981257d29d64e --- /dev/null +++ b/sdk/types/IBroker.ts @@ -0,0 +1,6 @@ +import { ServiceClass } from './ServiceClass'; + +export interface IBroker { + createService(service: ServiceClass): void; + call(method: string, data: any): Promise; +} diff --git a/sdk/types/ServiceClass.ts b/sdk/types/ServiceClass.ts new file mode 100644 index 0000000000000..1374b6e63e3e9 --- /dev/null +++ b/sdk/types/ServiceClass.ts @@ -0,0 +1,7 @@ +export abstract class ServiceClass { + protected name: string; + + getName(): string { + return this.name; + } +} diff --git a/server/main.js b/server/main.js index d24acf4731614..3bb43672ec8a6 100644 --- a/server/main.js +++ b/server/main.js @@ -3,6 +3,8 @@ import '../imports/startup/server'; import '../lib/RegExp'; +import '../sdk/startup'; + import '../ee/server'; import './lib/pushConfig'; import './lib/roomFiles'; diff --git a/server/routes/avatar/index.js b/server/routes/avatar/index.js index 994fad6821e17..031ef3364876e 100644 --- a/server/routes/avatar/index.js +++ b/server/routes/avatar/index.js @@ -2,8 +2,18 @@ import { WebApp } from 'meteor/webapp'; import { roomAvatar } from './room'; import { userAvatar } from './user'; +import { Authorization } from '../../../sdk'; import './middlewares'; WebApp.connectHandlers.use('/avatar/room/', roomAvatar); WebApp.connectHandlers.use('/avatar/', userAvatar); + +// TODO: remove - testing purposes only +WebApp.connectHandlers.use('/sdk/', async (req, res) => { + const result = await Authorization.hasPermission(req.query.name, 'lero'); + + console.log('result ->', result); + + return res.end('done'); +}); From 9f34c42da5787d1edb97cbc4b09b1f60251be05a Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Fri, 4 Sep 2020 11:50:46 -0300 Subject: [PATCH 002/198] Internal authorization service --- app/authorization/lib/AuthorizationUtils.ts | 10 +-- .../server/functions/hasPermission.js | 61 ++----------- sdk/lib/Api.ts | 6 -- sdk/services/authorization.ts | 88 +++++++++++++++++-- sdk/startup.ts | 4 +- sdk/types/IAuthorization.ts | 5 +- server/main.d.ts | 14 +++ 7 files changed, 111 insertions(+), 77 deletions(-) diff --git a/app/authorization/lib/AuthorizationUtils.ts b/app/authorization/lib/AuthorizationUtils.ts index daa19828305c0..6b6aae866139e 100644 --- a/app/authorization/lib/AuthorizationUtils.ts +++ b/app/authorization/lib/AuthorizationUtils.ts @@ -1,15 +1,13 @@ -import { Meteor } from 'meteor/meteor'; - const restrictedRolePermissions = new Map(); export const AuthorizationUtils = class { static addRolePermissionWhiteList(roleId: string, list: [string]): void { if (!roleId) { - throw new Meteor.Error('invalid-param'); + throw new Error('invalid-param'); } if (!list) { - throw new Meteor.Error('invalid-param'); + throw new Error('invalid-param'); } if (!restrictedRolePermissions.has(roleId)) { @@ -25,7 +23,7 @@ export const AuthorizationUtils = class { static isPermissionRestrictedForRole(permissionId: string, roleId: string): boolean { if (!roleId || !permissionId) { - throw new Meteor.Error('invalid-param'); + throw new Error('invalid-param'); } if (!restrictedRolePermissions.has(roleId)) { @@ -42,7 +40,7 @@ export const AuthorizationUtils = class { static isPermissionRestrictedForRoleList(permissionId: string, roleList: [string]): boolean { if (!roleList || !permissionId) { - throw new Meteor.Error('invalid-param'); + throw new Error('invalid-param'); } for (const roleId of roleList) { diff --git a/app/authorization/server/functions/hasPermission.js b/app/authorization/server/functions/hasPermission.js index eb3d901916090..262fa689c3103 100644 --- a/app/authorization/server/functions/hasPermission.js +++ b/app/authorization/server/functions/hasPermission.js @@ -1,63 +1,12 @@ -import mem from 'mem'; - -import { Permissions, Users, Subscriptions } from '../../../models/server/raw'; -import { AuthorizationUtils } from '../../lib/AuthorizationUtils'; - -const rolesHasPermission = mem(async (permission, roles) => { - if (AuthorizationUtils.isPermissionRestrictedForRoleList(permission, roles)) { - return false; - } - - const result = await Permissions.findOne({ _id: permission, roles: { $in: roles } }, { projection: { _id: 1 } }); - return !!result; -}, { - cacheKey: JSON.stringify, - ...process.env.TEST_MODE === 'true' && { maxAge: 1 }, -}); - -const getRoles = mem(async (uid, scope) => { - const { roles: userRoles = [] } = await Users.findOne({ _id: uid }, { projection: { roles: 1 } }); - const { roles: subscriptionsRoles = [] } = (scope && await Subscriptions.findOne({ rid: scope, 'u._id': uid }, { projection: { roles: 1 } })) || {}; - return [...userRoles, ...subscriptionsRoles].sort((a, b) => a.localeCompare(b)); -}, { maxAge: 1000, cacheKey: JSON.stringify }); +import { Authorization } from '../../../../sdk'; export const clearCache = () => { - mem.clear(getRoles); - mem.clear(rolesHasPermission); + // TODO need to implement a way to tell authorization service to clear its cache or not use cache at all }; -async function atLeastOne(uid, permissions = [], scope) { - const sortedRoles = await getRoles(uid, scope); - for (const permission of permissions) { - if (await rolesHasPermission(permission, sortedRoles)) { // eslint-disable-line - return true; - } - } - - return false; -} - -async function all(uid, permissions = [], scope) { - const sortedRoles = await getRoles(uid, scope); - for (const permission of permissions) { - if (!await rolesHasPermission(permission, sortedRoles)) { // eslint-disable-line - return false; - } - } - - return true; -} - -function _hasPermission(userId, permissions, scope, strategy) { - if (!userId) { - return false; - } - return strategy(userId, [].concat(permissions), scope); -} - -export const hasAllPermissionAsync = async (userId, permissions, scope) => _hasPermission(userId, permissions, scope, all); -export const hasPermissionAsync = async (userId, permissionId, scope) => _hasPermission(userId, permissionId, scope, all); -export const hasAtLeastOnePermissionAsync = async (userId, permissions, scope) => _hasPermission(userId, permissions, scope, atLeastOne); +export const hasAllPermissionAsync = async (userId, permissions, scope) => Authorization.hasAllPermission(userId, permissions, scope); +export const hasPermissionAsync = async (userId, permissionId, scope) => Authorization.hasPermission(userId, permissionId, scope); +export const hasAtLeastOnePermissionAsync = async (userId, permissions, scope) => Authorization.hasAtLeastOnePermission(userId, permissions, scope); export const hasAllPermission = (userId, permissions, scope) => Promise.await(hasAllPermissionAsync(userId, permissions, scope)); export const hasPermission = (userId, permissionId, scope) => Promise.await(hasPermissionAsync(userId, permissionId, scope)); diff --git a/sdk/lib/Api.ts b/sdk/lib/Api.ts index 1a8d643851b63..843f19468bde9 100644 --- a/sdk/lib/Api.ts +++ b/sdk/lib/Api.ts @@ -7,10 +7,6 @@ export class Api { private broker: IBroker; - protected log(...args: Array): void { - console.log('BROKER:', ...args); - } - // set a broker for the API and registers all services in the broker setBroker(broker: IBroker): void { this.broker = broker; @@ -27,8 +23,6 @@ export class Api { } async call(method: string, data: any): Promise { - this.log('BROKER.call', method); - return this.broker.call(method, data); } } diff --git a/sdk/services/authorization.ts b/sdk/services/authorization.ts index 6fd82a4825ce9..faf0490ac4e69 100644 --- a/sdk/services/authorization.ts +++ b/sdk/services/authorization.ts @@ -1,17 +1,93 @@ +import mem from 'mem'; +import { Db, Collection } from 'mongodb'; + import { IAuthorization } from '../types/IAuthorization'; import { ServiceClass } from '../types/ServiceClass'; +import { AuthorizationUtils } from '../../app/authorization/lib/AuthorizationUtils'; +import { IUser } from '../../definition/IUser'; // Register as class export class Authorization extends ServiceClass implements IAuthorization { protected name = 'authorization'; - hasPermission(permission: string, user: string): boolean { - console.log('hasPermission called'); - return permission === 'createUser' && user != null; + private Permissions: Collection; + + private Users: Collection; + + private Subscriptions: Collection; + + constructor(db: Db) { + super(); + + this.Permissions = db.collection('rocketchat_permissions'); + this.Subscriptions = db.collection('rocketchat_subscriptions'); + this.Users = db.collection('users'); } - hasPermission2(permission: string, user: string, bla: number): number { - console.log('hasPermission2 called'); - return permission === 'createUser' && user != null && bla === 1 ? 1 : 0; + async hasAllPermission(userId: string, permissions: string[], scope?: string): Promise { + if (!userId) { + return false; + } + return this.all(userId, permissions, scope); + } + + async hasPermission(userId: string, permissionId: string, scope?: string): Promise { + if (!userId) { + return false; + } + return this.all(userId, [permissionId], scope); + } + + async hasAtLeastOnePermission(userId: string, permissions: string[], scope?: string): Promise { + if (!userId) { + return false; + } + return this.atLeastOne(userId, permissions, scope); + } + + private rolesHasPermission = mem(async (permission, roles) => { + // TODO this AuthorizationUtils should be brought to this service. currently its state is kept on the application only, but it needs to kept here + if (AuthorizationUtils.isPermissionRestrictedForRoleList(permission, roles)) { + return false; + } + + const result = await this.Permissions.findOne({ _id: permission, roles: { $in: roles } }, { projection: { _id: 1 } }); + return !!result; + }, { + cacheKey: JSON.stringify, + ...process.env.TEST_MODE === 'true' && { maxAge: 1 }, + }); + + private getRoles = mem(async (uid, scope) => { + const { roles: userRoles = [] } = await this.Users.findOne({ _id: uid }, { projection: { roles: 1 } }) || {}; + const { roles: subscriptionsRoles = [] } = (scope && await this.Subscriptions.findOne<{ roles: string[] }>({ rid: scope, 'u._id': uid }, { projection: { roles: 1 } })) || {}; + return [...userRoles, ...subscriptionsRoles].sort((a, b) => a.localeCompare(b)); + }, { maxAge: 1000, cacheKey: JSON.stringify }); + + // private clearCache = (): void => { + // mem.clear(getRoles); + // mem.clear(rolesHasPermission); + // } + + private async atLeastOne(uid: string, permissions: string[] = [], scope?: string): Promise { + const sortedRoles = await this.getRoles(uid, scope); + for (const permission of permissions) { + if (await this.rolesHasPermission(permission, sortedRoles)) { // eslint-disable-line + return true; + } + } + + return false; + } + + private async all(uid: string, permissions: string[] = [], scope?: string): Promise { + const sortedRoles = await this.getRoles(uid, scope); + for (const permission of permissions) { + if (!await this.rolesHasPermission(permission, sortedRoles)) { // eslint-disable-line + return false; + } + } + + return true; } } diff --git a/sdk/startup.ts b/sdk/startup.ts index 67b6e126f0911..28bcf0023908b 100644 --- a/sdk/startup.ts +++ b/sdk/startup.ts @@ -1,7 +1,9 @@ +import { MongoInternals } from 'meteor/mongo'; + import { Authorization } from './services/authorization'; import { api } from './api'; import { LocalBroker } from './lib/LocalBroker'; api.setBroker(new LocalBroker()); -api.registerService(new Authorization()); +api.registerService(new Authorization(MongoInternals.defaultRemoteCollectionDriver().mongo.db)); diff --git a/sdk/types/IAuthorization.ts b/sdk/types/IAuthorization.ts index 74be052c9af06..74c7fe3c50aae 100644 --- a/sdk/types/IAuthorization.ts +++ b/sdk/types/IAuthorization.ts @@ -1,5 +1,6 @@ export interface IAuthorization { - hasPermission(permission: string, user: string): boolean; - hasPermission2(permission: string, user: string, bla: number): number; + hasAllPermission(userId: string, permissions: string[], scope?: string): Promise; + hasPermission(userId: string, permissionId: string, scope?: string): Promise; + hasAtLeastOnePermission(userId: string, permissions: string[], scope?: string): Promise; prop?: string; } diff --git a/server/main.d.ts b/server/main.d.ts index d1d7791954db1..4ad189f8bb477 100644 --- a/server/main.d.ts +++ b/server/main.d.ts @@ -1,4 +1,5 @@ import { EJSON } from 'meteor/ejson'; +import { Db } from 'mongodb'; /* eslint-disable @typescript-eslint/interface-name-prefix */ declare module 'meteor/random' { @@ -75,3 +76,16 @@ declare module 'meteor/littledata:synced-cron' { function remove(name: string): string; } } + +declare module 'meteor/mongo' { + interface RemoteCollectionDriver { + mongo: MongoConnection; + } + interface MongoConnection { + db: Db; + } + + namespace MongoInternals { + function defaultRemoteCollectionDriver(): RemoteCollectionDriver; + } +} From 2194a92ba9663aa52e862117a1c02919a3282a14 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Fri, 4 Sep 2020 13:58:42 -0300 Subject: [PATCH 003/198] Remove cache --- app/authorization/lib/AuthorizationUtils.ts | 4 ++-- sdk/services/authorization.ts | 18 ++++++++++-------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/app/authorization/lib/AuthorizationUtils.ts b/app/authorization/lib/AuthorizationUtils.ts index 6b6aae866139e..41c96bd6fd3ae 100644 --- a/app/authorization/lib/AuthorizationUtils.ts +++ b/app/authorization/lib/AuthorizationUtils.ts @@ -1,7 +1,7 @@ const restrictedRolePermissions = new Map(); export const AuthorizationUtils = class { - static addRolePermissionWhiteList(roleId: string, list: [string]): void { + static addRolePermissionWhiteList(roleId: string, list: string[]): void { if (!roleId) { throw new Error('invalid-param'); } @@ -38,7 +38,7 @@ export const AuthorizationUtils = class { return !rules.has(permissionId); } - static isPermissionRestrictedForRoleList(permissionId: string, roleList: [string]): boolean { + static isPermissionRestrictedForRoleList(permissionId: string, roleList: string[]): boolean { if (!roleList || !permissionId) { throw new Error('invalid-param'); } diff --git a/sdk/services/authorization.ts b/sdk/services/authorization.ts index faf0490ac4e69..6cb6cd624ed79 100644 --- a/sdk/services/authorization.ts +++ b/sdk/services/authorization.ts @@ -1,4 +1,4 @@ -import mem from 'mem'; +// import mem from 'mem'; import { Db, Collection } from 'mongodb'; import { IAuthorization } from '../types/IAuthorization'; @@ -45,7 +45,7 @@ export class Authorization extends ServiceClass implements IAuthorization { return this.atLeastOne(userId, permissions, scope); } - private rolesHasPermission = mem(async (permission, roles) => { + private async rolesHasPermission(permission: string, roles: string[]): Promise { // TODO this AuthorizationUtils should be brought to this service. currently its state is kept on the application only, but it needs to kept here if (AuthorizationUtils.isPermissionRestrictedForRoleList(permission, roles)) { return false; @@ -53,16 +53,18 @@ export class Authorization extends ServiceClass implements IAuthorization { const result = await this.Permissions.findOne({ _id: permission, roles: { $in: roles } }, { projection: { _id: 1 } }); return !!result; - }, { - cacheKey: JSON.stringify, - ...process.env.TEST_MODE === 'true' && { maxAge: 1 }, - }); + } + // , { + // cacheKey: JSON.stringify, + // ...process.env.TEST_MODE === 'true' && { maxAge: 1 }, + // }); - private getRoles = mem(async (uid, scope) => { + private async getRoles(uid: string, scope?: string): Promise { const { roles: userRoles = [] } = await this.Users.findOne({ _id: uid }, { projection: { roles: 1 } }) || {}; const { roles: subscriptionsRoles = [] } = (scope && await this.Subscriptions.findOne<{ roles: string[] }>({ rid: scope, 'u._id': uid }, { projection: { roles: 1 } })) || {}; return [...userRoles, ...subscriptionsRoles].sort((a, b) => a.localeCompare(b)); - }, { maxAge: 1000, cacheKey: JSON.stringify }); + } + // , { maxAge: 1000, cacheKey: JSON.stringify }); // private clearCache = (): void => { // mem.clear(getRoles); From 341e3bbbdc8183391076528ffc7b52267358c066 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Fri, 4 Sep 2020 14:53:09 -0300 Subject: [PATCH 004/198] Bind instance to method on local broker --- sdk/lib/LocalBroker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/lib/LocalBroker.ts b/sdk/lib/LocalBroker.ts index adc26ad803e93..62d7633cac36c 100644 --- a/sdk/lib/LocalBroker.ts +++ b/sdk/lib/LocalBroker.ts @@ -23,7 +23,7 @@ export class LocalBroker implements IBroker { } const i = instance as any; - this.methods.set(`${ namespace }.${ method }`, i[method]); + this.methods.set(`${ namespace }.${ method }`, i[method].bind(i)); } } } From 3e8e866f92c308bb07c9b928c2fcda816d48976b Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Fri, 4 Sep 2020 15:44:35 -0300 Subject: [PATCH 005/198] Fix spotlight permission check --- server/publications/spotlight.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/publications/spotlight.js b/server/publications/spotlight.js index fdcb4b559c523..9e48b1e1d05f3 100644 --- a/server/publications/spotlight.js +++ b/server/publications/spotlight.js @@ -125,7 +125,7 @@ function searchUsers({ userId, rid, text, usernames }) { return users; } - const canListOutsiders = hasAllPermission(userId, 'view-outside-room', 'view-d-room'); + const canListOutsiders = hasAllPermission(userId, ['view-outside-room', 'view-d-room']); const canListInsiders = canListOutsiders || (rid && canAccessRoom(room, { _id: userId })); // If can't list outsiders and, wither, the rid was not passed or the user has no access to the room, return From 14345d691e08aab3fb7673899c58e34d642408ba Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Fri, 4 Sep 2020 16:08:27 -0300 Subject: [PATCH 006/198] Compile typescript --- .../index.ts} | 8 +- sdk/services/authorization/package-lock.json | 161 ++++++++++++++++++ sdk/services/authorization/package.json | 22 +++ sdk/services/authorization/tsconfig.json | 44 +++++ 4 files changed, 231 insertions(+), 4 deletions(-) rename sdk/services/{authorization.ts => authorization/index.ts} (92%) create mode 100644 sdk/services/authorization/package-lock.json create mode 100644 sdk/services/authorization/package.json create mode 100644 sdk/services/authorization/tsconfig.json diff --git a/sdk/services/authorization.ts b/sdk/services/authorization/index.ts similarity index 92% rename from sdk/services/authorization.ts rename to sdk/services/authorization/index.ts index 6cb6cd624ed79..c7e9042882db2 100644 --- a/sdk/services/authorization.ts +++ b/sdk/services/authorization/index.ts @@ -1,10 +1,10 @@ // import mem from 'mem'; import { Db, Collection } from 'mongodb'; -import { IAuthorization } from '../types/IAuthorization'; -import { ServiceClass } from '../types/ServiceClass'; -import { AuthorizationUtils } from '../../app/authorization/lib/AuthorizationUtils'; -import { IUser } from '../../definition/IUser'; +import { IAuthorization } from '../../types/IAuthorization'; +import { ServiceClass } from '../../types/ServiceClass'; +import { AuthorizationUtils } from '../../../app/authorization/lib/AuthorizationUtils'; +import { IUser } from '../../../definition/IUser'; // Register as class export class Authorization extends ServiceClass implements IAuthorization { diff --git a/sdk/services/authorization/package-lock.json b/sdk/services/authorization/package-lock.json new file mode 100644 index 0000000000000..0db23658e7a5c --- /dev/null +++ b/sdk/services/authorization/package-lock.json @@ -0,0 +1,161 @@ +{ + "name": "rocketchat-authorization", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@types/node": { + "version": "14.6.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.6.4.tgz", + "integrity": "sha512-Wk7nG1JSaMfMpoMJDKUsWYugliB2Vy55pdjLpmLixeyMi7HizW2I/9QoxsPCkXl3dO+ZOVqPumKaDUv5zJu2uQ==", + "dev": true + }, + "bl": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz", + "integrity": "sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==", + "requires": { + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.1" + } + }, + "bson": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.5.tgz", + "integrity": "sha512-kDuEzldR21lHciPQAIulLs1LZlCXdLziXI6Mb/TDkwXhb//UORJNPXgcRs2CuO4H0DcMkpfT3/ySsP3unoZjBg==" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "denque": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/denque/-/denque-1.4.1.tgz", + "integrity": "sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ==" + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", + "optional": true + }, + "mongodb": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.6.1.tgz", + "integrity": "sha512-uH76Zzr5wPptnjEKJRQnwTsomtFOU/kQEU8a9hKHr2M7y9qVk7Q4Pkv0EQVp88742z9+RwvsdTw6dRjDZCNu1g==", + "requires": { + "bl": "^2.2.0", + "bson": "^1.1.4", + "denque": "^1.4.1", + "require_optional": "^1.0.1", + "safe-buffer": "^5.1.2", + "saslprep": "^1.0.0" + } + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + } + } + }, + "require_optional": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz", + "integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==", + "requires": { + "resolve-from": "^2.0.0", + "semver": "^5.1.0" + } + }, + "resolve-from": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz", + "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=" + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "saslprep": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", + "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==", + "optional": true, + "requires": { + "sparse-bitfield": "^3.0.3" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + }, + "sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=", + "optional": true, + "requires": { + "memory-pager": "^1.0.2" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + } + } + }, + "typescript": { + "version": "3.9.7", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz", + "integrity": "sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==", + "dev": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + } + } +} diff --git a/sdk/services/authorization/package.json b/sdk/services/authorization/package.json new file mode 100644 index 0000000000000..263eadc7e1bc6 --- /dev/null +++ b/sdk/services/authorization/package.json @@ -0,0 +1,22 @@ +{ + "name": "rocketchat-authorization", + "version": "1.0.0", + "description": "Rocket.Chat Authorization service", + "main": "index.js", + "scripts": { + "build": "tsc --project . --outDir ./dist", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [ + "rocketchat" + ], + "author": "Rocket.Chat", + "license": "MIT", + "dependencies": { + "mongodb": "^3.6.1" + }, + "devDependencies": { + "@types/node": "^14.6.4", + "typescript": "^3.9.7" + } +} diff --git a/sdk/services/authorization/tsconfig.json b/sdk/services/authorization/tsconfig.json new file mode 100644 index 0000000000000..76870c8f3de3b --- /dev/null +++ b/sdk/services/authorization/tsconfig.json @@ -0,0 +1,44 @@ +{ + "compilerOptions": { + "module": "CommonJS", + "target": "es2018", + "lib": ["esnext", "dom"], + + "allowJs": true, + "checkJs": false, + "jsx": "react", + // "incremental": true, + "noEmit": true, + + /* Strict Type-Checking Options */ + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "strictPropertyInitialization": false, + + /* Additional Checks */ + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": false, + "noFallthroughCasesInSwitch": false, + + /* Module Resolution Options */ + "baseUrl": ".", + "paths": { + /* Support absolute /imports/* with a leading '/' */ + "/*": ["*"] + }, + "moduleResolution": "node", + "resolveJsonModule": true, + "esModuleInterop": true, + "preserveSymlinks": true, + + // "sourceMap": true, + // "declaration": true, + // "removeComments": false, + // "emitDecoratorMetadata": true, + // "experimentalDecorators": true, + }, + "exclude": [ + ] +} From 5c4760b87e520d4b6f8ff351027fe43240a56a23 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Fri, 4 Sep 2020 16:20:31 -0300 Subject: [PATCH 007/198] Remove delay from broker calls --- sdk/lib/LocalBroker.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/sdk/lib/LocalBroker.ts b/sdk/lib/LocalBroker.ts index 62d7633cac36c..6aadb6ba82c8b 100644 --- a/sdk/lib/LocalBroker.ts +++ b/sdk/lib/LocalBroker.ts @@ -1,15 +1,11 @@ import { IBroker } from '../types/IBroker'; import { ServiceClass } from '../types/ServiceClass'; -const delay = (ms: number): Promise => new Promise((resolve) => setTimeout(resolve, ms)); - export class LocalBroker implements IBroker { private methods = new Map(); async call(method: string, data: any): Promise { - await delay(500); const result = this.methods.get(method)?.(...data); - await delay(500); return result; } From af70caa09c784fce843c13170d43e3a7fd5b5cdd Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Fri, 4 Sep 2020 16:28:42 -0300 Subject: [PATCH 008/198] Remove github actions --- .github/workflows/build_and_test.yml | 107 -------------------------- .github/workflows/codeql-analysis.yml | 52 ------------- 2 files changed, 159 deletions(-) delete mode 100644 .github/workflows/codeql-analysis.yml diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 59e1fd89d8168..223c5bc62d5e7 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -256,113 +256,6 @@ jobs: # commit: true # token: ${{ secrets.GITHUB_TOKEN }} - build-image-pr: - runs-on: ubuntu-latest - if: github.event.pull_request.head.repo.full_name == github.repository - - steps: - - uses: actions/checkout@v2 - - - name: Free disk space - run: | - sudo swapoff -a - sudo rm -f /swapfile - sudo apt clean - docker rmi $(docker image ls -aq) - df -h - - - name: Cache node modules - id: cache-nodemodules - uses: actions/cache@v1 - with: - path: node_modules - key: ${{ runner.OS }}-node_modules-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('.github/workflows/build_and_test.yml') }} - - - name: Cache meteor local - uses: actions/cache@v1 - with: - path: ./.meteor/local - key: ${{ runner.OS }}-meteor_cache-${{ hashFiles('.meteor/versions') }}-${{ hashFiles('.github/workflows/build_and_test.yml') }} - - - name: Cache meteor - uses: actions/cache@v1 - with: - path: ~/.meteor - key: ${{ runner.OS }}-meteor-${{ hashFiles('.meteor/release') }}-${{ hashFiles('.github/workflows/build_and_test.yml') }} - - - name: Use Node.js 12.18.3 - uses: actions/setup-node@v1 - with: - node-version: "12.18.3" - - - name: Install Meteor - run: | - # Restore bin from cache - set +e - METEOR_SYMLINK_TARGET=$(readlink ~/.meteor/meteor) - METEOR_TOOL_DIRECTORY=$(dirname "$METEOR_SYMLINK_TARGET") - set -e - LAUNCHER=$HOME/.meteor/$METEOR_TOOL_DIRECTORY/scripts/admin/launch-meteor - if [ -e $LAUNCHER ] - then - echo "Cached Meteor bin found, restoring it" - sudo cp "$LAUNCHER" "/usr/local/bin/meteor" - else - echo "No cached Meteor bin found." - fi - - # only install meteor if bin isn't found - command -v meteor >/dev/null 2>&1 || curl https://install.meteor.com | sed s/--progress-bar/-sL/g | /bin/sh - - - name: Versions - run: | - npm --versions - node -v - meteor --version - meteor npm --versions - meteor node -v - git version - echo $GITHUB_REF - - - name: npm install - if: steps.cache-nodemodules.outputs.cache-hit != 'true' - run: | - meteor npm install - - # To reduce memory need during actual build, build the packages solely first - # - name: Build a Meteor cache - # run: | - # # to do this we can clear the main files and it build the rest - # echo "" > server/main.js - # echo "" > client/main.js - # sed -i.backup 's/rocketchat:livechat/#rocketchat:livechat/' .meteor/packages - # meteor build --server-only --debug --directory /tmp/build-temp - # git checkout -- server/main.js client/main.js .meteor/packages - - - name: Build Rocket.Chat - run: | - meteor build --server-only --directory /tmp/build-pr - - - name: Build Docker image for PRs - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - VERSION: pr-${{ github.event.number }} - run: | - cd /tmp/build-pr - - docker login docker.pkg.github.com -u "${GITHUB_ACTOR}" -p "${GITHUB_TOKEN}" - - cp $GITHUB_WORKSPACE/.docker/Dockerfile . - - export LOWERCASE_REPOSITORY=$(echo "$GITHUB_REPOSITORY" | tr "[:upper:]" "[:lower:]") - - export IMAGE_NAME="docker.pkg.github.com/${LOWERCASE_REPOSITORY}/rocket.chat:${VERSION}" - - echo "Build official Docker image ${IMAGE_NAME}" - - docker build -t $IMAGE_NAME . - docker push $IMAGE_NAME - deploy: runs-on: ubuntu-latest if: github.event_name == 'release' || github.ref == 'refs/heads/develop' diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml deleted file mode 100644 index da9570060ed49..0000000000000 --- a/.github/workflows/codeql-analysis.yml +++ /dev/null @@ -1,52 +0,0 @@ -name: "Code scanning - action" - -on: - push: - pull_request: - schedule: - - cron: '0 13 * * *' - -jobs: - CodeQL-Build: - - # CodeQL runs on ubuntu-latest and windows-latest - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v2 - with: - # We must fetch at least the immediate parents so that if this is - # a pull request then we can checkout the head. - fetch-depth: 2 - - # If this run was triggered by a pull request event, then checkout - # the head of the pull request instead of the merge commit. - - run: git checkout HEAD^2 - if: ${{ github.event_name == 'pull_request' }} - - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v1 - # Override language selection by uncommenting this and choosing your languages - with: - languages: javascript - - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v1 - - # â„šī¸ Command-line programs to run using the OS shell. - # 📚 https://git.io/JvXDl - - # âœī¸ If the Autobuild fails above, remove it and uncomment the following three lines - # and modify them (or add more) to build your code if your project - # uses a compiled language - - #- run: | - # make bootstrap - # make release - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 From ad5cfae1509bcad6f87ee2e641c7c19f49ec58dc Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Fri, 4 Sep 2020 16:52:26 -0300 Subject: [PATCH 009/198] Fix compilation errors --- sdk/services/authorization/.gitignore | 1 + sdk/services/authorization/tsconfig.json | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 sdk/services/authorization/.gitignore diff --git a/sdk/services/authorization/.gitignore b/sdk/services/authorization/.gitignore new file mode 100644 index 0000000000000..1521c8b7652b1 --- /dev/null +++ b/sdk/services/authorization/.gitignore @@ -0,0 +1 @@ +dist diff --git a/sdk/services/authorization/tsconfig.json b/sdk/services/authorization/tsconfig.json index 76870c8f3de3b..bde9fe44c4529 100644 --- a/sdk/services/authorization/tsconfig.json +++ b/sdk/services/authorization/tsconfig.json @@ -3,12 +3,13 @@ "module": "CommonJS", "target": "es2018", "lib": ["esnext", "dom"], + "types" : ["node"], "allowJs": true, "checkJs": false, "jsx": "react", // "incremental": true, - "noEmit": true, + // "noEmit": true, /* Strict Type-Checking Options */ "strict": true, @@ -40,5 +41,6 @@ // "experimentalDecorators": true, }, "exclude": [ + "./dist" ] } From 61033c9a3e05716126e97b04031c6758134b5dcc Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Fri, 4 Sep 2020 20:25:41 -0300 Subject: [PATCH 010/198] Move everything to /server and services out from skd --- .../server/functions/hasPermission.js | 2 +- ee/server/broker.ts | 8 +++---- sdk/services/authorization/package.json | 22 ------------------- sdk/startup.ts | 9 -------- server/main.js | 2 +- server/routes/avatar/index.js | 2 +- {sdk => server/sdk}/api.ts | 0 {sdk => server/sdk}/index.ts | 0 {sdk => server/sdk}/lib/Api.ts | 0 {sdk => server/sdk}/lib/BaseBroker.ts | 0 {sdk => server/sdk}/lib/LocalBroker.ts | 0 {sdk => server/sdk}/lib/proxify.ts | 0 {sdk => server/sdk}/types/IAuthorization.ts | 0 {sdk => server/sdk}/types/IBroker.ts | 0 {sdk => server/sdk}/types/ServiceClass.ts | 0 .../services/authorization/.gitignore | 0 .../services/authorization/package-lock.json | 0 .../services/authorization/service.ts | 4 ++-- .../services/authorization/tsconfig.json | 0 server/services/startup.ts | 9 ++++++++ 20 files changed, 18 insertions(+), 40 deletions(-) delete mode 100644 sdk/services/authorization/package.json delete mode 100644 sdk/startup.ts rename {sdk => server/sdk}/api.ts (100%) rename {sdk => server/sdk}/index.ts (100%) rename {sdk => server/sdk}/lib/Api.ts (100%) rename {sdk => server/sdk}/lib/BaseBroker.ts (100%) rename {sdk => server/sdk}/lib/LocalBroker.ts (100%) rename {sdk => server/sdk}/lib/proxify.ts (100%) rename {sdk => server/sdk}/types/IAuthorization.ts (100%) rename {sdk => server/sdk}/types/IBroker.ts (100%) rename {sdk => server/sdk}/types/ServiceClass.ts (100%) rename {sdk => server}/services/authorization/.gitignore (100%) rename {sdk => server}/services/authorization/package-lock.json (100%) rename sdk/services/authorization/index.ts => server/services/authorization/service.ts (96%) rename {sdk => server}/services/authorization/tsconfig.json (100%) create mode 100644 server/services/startup.ts diff --git a/app/authorization/server/functions/hasPermission.js b/app/authorization/server/functions/hasPermission.js index 262fa689c3103..0694f00aac6c5 100644 --- a/app/authorization/server/functions/hasPermission.js +++ b/app/authorization/server/functions/hasPermission.js @@ -1,4 +1,4 @@ -import { Authorization } from '../../../../sdk'; +import { Authorization } from '../../../../server/sdk'; export const clearCache = () => { // TODO need to implement a way to tell authorization service to clear its cache or not use cache at all diff --git a/ee/server/broker.ts b/ee/server/broker.ts index f6227e44b3385..6ffc13e110808 100644 --- a/ee/server/broker.ts +++ b/ee/server/broker.ts @@ -1,9 +1,9 @@ import { ServiceBroker } from 'moleculer'; -import { api } from '../../sdk/api'; -import { onLicense } from '../app/license/server'; -import { IBroker } from '../../sdk/types/IBroker'; -import { ServiceClass } from '../../sdk/types/ServiceClass'; +import { api } from '../../server/sdk/api'; +import { IBroker } from '../../server/sdk/types/IBroker'; +import { ServiceClass } from '../../server/sdk/types/ServiceClass'; +// import { onLicense } from '../app/license/server'; class NetworkBroker implements IBroker { private broker: ServiceBroker; diff --git a/sdk/services/authorization/package.json b/sdk/services/authorization/package.json deleted file mode 100644 index 263eadc7e1bc6..0000000000000 --- a/sdk/services/authorization/package.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "rocketchat-authorization", - "version": "1.0.0", - "description": "Rocket.Chat Authorization service", - "main": "index.js", - "scripts": { - "build": "tsc --project . --outDir ./dist", - "test": "echo \"Error: no test specified\" && exit 1" - }, - "keywords": [ - "rocketchat" - ], - "author": "Rocket.Chat", - "license": "MIT", - "dependencies": { - "mongodb": "^3.6.1" - }, - "devDependencies": { - "@types/node": "^14.6.4", - "typescript": "^3.9.7" - } -} diff --git a/sdk/startup.ts b/sdk/startup.ts deleted file mode 100644 index 28bcf0023908b..0000000000000 --- a/sdk/startup.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { MongoInternals } from 'meteor/mongo'; - -import { Authorization } from './services/authorization'; -import { api } from './api'; -import { LocalBroker } from './lib/LocalBroker'; - -api.setBroker(new LocalBroker()); - -api.registerService(new Authorization(MongoInternals.defaultRemoteCollectionDriver().mongo.db)); diff --git a/server/main.js b/server/main.js index 3bb43672ec8a6..ff5e955502f55 100644 --- a/server/main.js +++ b/server/main.js @@ -3,7 +3,7 @@ import '../imports/startup/server'; import '../lib/RegExp'; -import '../sdk/startup'; +import './services/startup'; import '../ee/server'; import './lib/pushConfig'; diff --git a/server/routes/avatar/index.js b/server/routes/avatar/index.js index 031ef3364876e..8383aedf9af2f 100644 --- a/server/routes/avatar/index.js +++ b/server/routes/avatar/index.js @@ -2,7 +2,7 @@ import { WebApp } from 'meteor/webapp'; import { roomAvatar } from './room'; import { userAvatar } from './user'; -import { Authorization } from '../../../sdk'; +import { Authorization } from '../../sdk'; import './middlewares'; diff --git a/sdk/api.ts b/server/sdk/api.ts similarity index 100% rename from sdk/api.ts rename to server/sdk/api.ts diff --git a/sdk/index.ts b/server/sdk/index.ts similarity index 100% rename from sdk/index.ts rename to server/sdk/index.ts diff --git a/sdk/lib/Api.ts b/server/sdk/lib/Api.ts similarity index 100% rename from sdk/lib/Api.ts rename to server/sdk/lib/Api.ts diff --git a/sdk/lib/BaseBroker.ts b/server/sdk/lib/BaseBroker.ts similarity index 100% rename from sdk/lib/BaseBroker.ts rename to server/sdk/lib/BaseBroker.ts diff --git a/sdk/lib/LocalBroker.ts b/server/sdk/lib/LocalBroker.ts similarity index 100% rename from sdk/lib/LocalBroker.ts rename to server/sdk/lib/LocalBroker.ts diff --git a/sdk/lib/proxify.ts b/server/sdk/lib/proxify.ts similarity index 100% rename from sdk/lib/proxify.ts rename to server/sdk/lib/proxify.ts diff --git a/sdk/types/IAuthorization.ts b/server/sdk/types/IAuthorization.ts similarity index 100% rename from sdk/types/IAuthorization.ts rename to server/sdk/types/IAuthorization.ts diff --git a/sdk/types/IBroker.ts b/server/sdk/types/IBroker.ts similarity index 100% rename from sdk/types/IBroker.ts rename to server/sdk/types/IBroker.ts diff --git a/sdk/types/ServiceClass.ts b/server/sdk/types/ServiceClass.ts similarity index 100% rename from sdk/types/ServiceClass.ts rename to server/sdk/types/ServiceClass.ts diff --git a/sdk/services/authorization/.gitignore b/server/services/authorization/.gitignore similarity index 100% rename from sdk/services/authorization/.gitignore rename to server/services/authorization/.gitignore diff --git a/sdk/services/authorization/package-lock.json b/server/services/authorization/package-lock.json similarity index 100% rename from sdk/services/authorization/package-lock.json rename to server/services/authorization/package-lock.json diff --git a/sdk/services/authorization/index.ts b/server/services/authorization/service.ts similarity index 96% rename from sdk/services/authorization/index.ts rename to server/services/authorization/service.ts index c7e9042882db2..dedae21743d7e 100644 --- a/sdk/services/authorization/index.ts +++ b/server/services/authorization/service.ts @@ -1,8 +1,8 @@ // import mem from 'mem'; import { Db, Collection } from 'mongodb'; -import { IAuthorization } from '../../types/IAuthorization'; -import { ServiceClass } from '../../types/ServiceClass'; +import { IAuthorization } from '../../sdk/types/IAuthorization'; +import { ServiceClass } from '../../sdk/types/ServiceClass'; import { AuthorizationUtils } from '../../../app/authorization/lib/AuthorizationUtils'; import { IUser } from '../../../definition/IUser'; diff --git a/sdk/services/authorization/tsconfig.json b/server/services/authorization/tsconfig.json similarity index 100% rename from sdk/services/authorization/tsconfig.json rename to server/services/authorization/tsconfig.json diff --git a/server/services/startup.ts b/server/services/startup.ts new file mode 100644 index 0000000000000..5c314bd79e8f1 --- /dev/null +++ b/server/services/startup.ts @@ -0,0 +1,9 @@ +import { MongoInternals } from 'meteor/mongo'; + +import { api } from '../sdk/api'; +// import { LocalBroker } from '../sdk/lib/LocalBroker'; +import { Authorization } from './authorization/service'; + +// api.setBroker(new LocalBroker()); + +api.registerService(new Authorization(MongoInternals.defaultRemoteCollectionDriver().mongo.db)); From 5359a88ab844de3d3341c11473c5a56194c8f1f8 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Fri, 4 Sep 2020 20:26:44 -0300 Subject: [PATCH 011/198] Initial version with MS running --- ee/server/broker.ts | 21 +++++++++++++++++--- server/services/authorization/index.ts | 10 ++++++++++ server/services/authorization/package.json | 23 ++++++++++++++++++++++ server/services/mongodb.ts | 22 +++++++++++++++++++++ 4 files changed, 73 insertions(+), 3 deletions(-) create mode 100644 server/services/authorization/index.ts create mode 100644 server/services/authorization/package.json create mode 100644 server/services/mongodb.ts diff --git a/ee/server/broker.ts b/ee/server/broker.ts index 6ffc13e110808..666a40bb1b9eb 100644 --- a/ee/server/broker.ts +++ b/ee/server/broker.ts @@ -38,10 +38,24 @@ class NetworkBroker implements IBroker { } } -onLicense('scalability', async () => { +// onLicense('scalability', async () => { +(async (): Promise => { const network = new ServiceBroker({ transporter: 'TCP', - logLevel: 'debug', + // logLevel: 'debug', + logLevel: { + // "TRACING": "trace", + // "TRANS*": "warn", + BROKER: 'debug', + TRANSIT: 'debug', + '**': 'info', + }, + logger: { + type: 'Console', + options: { + formatter: 'short', // or `null` + }, + }, registry: { strategy: 'RoundRobin', preferLocal: false, @@ -51,4 +65,5 @@ onLicense('scalability', async () => { await network.start(); api.setBroker(new NetworkBroker(network)); -}); +})(); +// }); diff --git a/server/services/authorization/index.ts b/server/services/authorization/index.ts new file mode 100644 index 0000000000000..d31320fddc169 --- /dev/null +++ b/server/services/authorization/index.ts @@ -0,0 +1,10 @@ +import { api } from '../../sdk/api'; +import { Authorization } from './service'; +import { getConnection } from '../mongodb'; + +import '../../../ee/server/broker'; + +getConnection().then((db) => { + console.log('registering'); + api.registerService(new Authorization(db)); +}); diff --git a/server/services/authorization/package.json b/server/services/authorization/package.json new file mode 100644 index 0000000000000..e27fcab69523f --- /dev/null +++ b/server/services/authorization/package.json @@ -0,0 +1,23 @@ +{ + "name": "rocketchat-authorization", + "version": "1.0.0", + "description": "Rocket.Chat Authorization service", + "main": "index.js", + "scripts": { + "start": "ts-node index.ts", + "build": "tsc --project . --outDir ./dist", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [ + "rocketchat" + ], + "author": "Rocket.Chat", + "license": "MIT", + "dependencies": { + "mongodb": "^3.6.1" + }, + "devDependencies": { + "@types/node": "^14.6.4", + "typescript": "^3.9.7" + } +} diff --git a/server/services/mongodb.ts b/server/services/mongodb.ts new file mode 100644 index 0000000000000..42935a44bd815 --- /dev/null +++ b/server/services/mongodb.ts @@ -0,0 +1,22 @@ + +import { MongoClient, Db } from 'mongodb'; + +const { + MONGO_URL = 'mongodb://localhost:27017/rocketchat', +} = process.env; + +const name = /^mongodb:\/\/.*?(?::[0-9]+)?\/([^?]*)/.exec(MONGO_URL)?.[1]; + +const client = new MongoClient(MONGO_URL, { + useNewUrlParser: true, +}); + +let db: Db; +export async function getConnection(): Promise { + if (!db) { + await client.connect(); + db = client.db(name); + } + + return db; +} From 742413bdc2bbc48991e4dd751448f48d98e19576 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Fri, 4 Sep 2020 21:42:29 -0300 Subject: [PATCH 012/198] Move microservices intances to ee folder --- .meteorignore | 1 + ee/server/broker.ts | 2 +- .../authorization => ee/server/services}/.gitignore | 0 ee/server/services/Authorization.ts | 9 +++++++++ .../services/mongodb.ts => ee/server/services/mongo.ts | 0 .../server/services}/package-lock.json | 0 .../authorization => ee/server/services}/package.json | 2 +- .../authorization => ee/server/services}/tsconfig.json | 0 server/services/authorization/index.ts | 10 ---------- 9 files changed, 12 insertions(+), 12 deletions(-) create mode 100644 .meteorignore rename {server/services/authorization => ee/server/services}/.gitignore (100%) create mode 100644 ee/server/services/Authorization.ts rename server/services/mongodb.ts => ee/server/services/mongo.ts (100%) rename {server/services/authorization => ee/server/services}/package-lock.json (100%) rename {server/services/authorization => ee/server/services}/package.json (90%) rename {server/services/authorization => ee/server/services}/tsconfig.json (100%) delete mode 100644 server/services/authorization/index.ts diff --git a/.meteorignore b/.meteorignore new file mode 100644 index 0000000000000..35dd55fee165e --- /dev/null +++ b/.meteorignore @@ -0,0 +1 @@ +ee/server/services diff --git a/ee/server/broker.ts b/ee/server/broker.ts index 666a40bb1b9eb..e0d7ea05ceb28 100644 --- a/ee/server/broker.ts +++ b/ee/server/broker.ts @@ -53,7 +53,7 @@ class NetworkBroker implements IBroker { logger: { type: 'Console', options: { - formatter: 'short', // or `null` + formatter: 'short', }, }, registry: { diff --git a/server/services/authorization/.gitignore b/ee/server/services/.gitignore similarity index 100% rename from server/services/authorization/.gitignore rename to ee/server/services/.gitignore diff --git a/ee/server/services/Authorization.ts b/ee/server/services/Authorization.ts new file mode 100644 index 0000000000000..f3b15bb495668 --- /dev/null +++ b/ee/server/services/Authorization.ts @@ -0,0 +1,9 @@ +import { api } from '../../../server/sdk/api'; +import { Authorization } from '../../../server/services/authorization/service'; +import { getConnection } from './mongo'; + +import '../broker'; + +getConnection().then((db) => { + api.registerService(new Authorization(db)); +}); diff --git a/server/services/mongodb.ts b/ee/server/services/mongo.ts similarity index 100% rename from server/services/mongodb.ts rename to ee/server/services/mongo.ts diff --git a/server/services/authorization/package-lock.json b/ee/server/services/package-lock.json similarity index 100% rename from server/services/authorization/package-lock.json rename to ee/server/services/package-lock.json diff --git a/server/services/authorization/package.json b/ee/server/services/package.json similarity index 90% rename from server/services/authorization/package.json rename to ee/server/services/package.json index e27fcab69523f..e7bb657ae47dd 100644 --- a/server/services/authorization/package.json +++ b/ee/server/services/package.json @@ -5,7 +5,7 @@ "main": "index.js", "scripts": { "start": "ts-node index.ts", - "build": "tsc --project . --outDir ./dist", + "build": "tsc --outDir ./dist", "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [ diff --git a/server/services/authorization/tsconfig.json b/ee/server/services/tsconfig.json similarity index 100% rename from server/services/authorization/tsconfig.json rename to ee/server/services/tsconfig.json diff --git a/server/services/authorization/index.ts b/server/services/authorization/index.ts deleted file mode 100644 index d31320fddc169..0000000000000 --- a/server/services/authorization/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { api } from '../../sdk/api'; -import { Authorization } from './service'; -import { getConnection } from '../mongodb'; - -import '../../../ee/server/broker'; - -getConnection().then((db) => { - console.log('registering'); - api.registerService(new Authorization(db)); -}); From 9d6c12130a85c54c40d57f99c66fb0367f882b61 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Fri, 4 Sep 2020 22:31:05 -0300 Subject: [PATCH 013/198] Add PM2 options --- ee/server/services/ecosystem.config.js | 9 + ee/server/services/package-lock.json | 1556 ++++++++++++++++++++++++ ee/server/services/package.json | 4 + 3 files changed, 1569 insertions(+) create mode 100644 ee/server/services/ecosystem.config.js diff --git a/ee/server/services/ecosystem.config.js b/ee/server/services/ecosystem.config.js new file mode 100644 index 0000000000000..8155fd5c530d5 --- /dev/null +++ b/ee/server/services/ecosystem.config.js @@ -0,0 +1,9 @@ +module.exports = { + apps: [{ + name: 'Authorization', + script: 'ts-node Authorization.ts', + watch: true, + instances: 2, + // interpreter: '', + }], +}; diff --git a/ee/server/services/package-lock.json b/ee/server/services/package-lock.json index 0db23658e7a5c..00c3b26e8f205 100644 --- a/ee/server/services/package-lock.json +++ b/ee/server/services/package-lock.json @@ -4,12 +4,337 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@opencensus/core": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/@opencensus/core/-/core-0.0.9.tgz", + "integrity": "sha512-31Q4VWtbzXpVUd2m9JS6HEaPjlKvNMOiF7lWKNmXF84yUcgfAFL5re7/hjDmdyQbOp32oGc+RFV78jXIldVz6Q==", + "dev": true, + "requires": { + "continuation-local-storage": "^3.2.1", + "log-driver": "^1.2.7", + "semver": "^5.5.0", + "shimmer": "^1.2.0", + "uuid": "^3.2.1" + } + }, + "@opencensus/propagation-b3": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/@opencensus/propagation-b3/-/propagation-b3-0.0.8.tgz", + "integrity": "sha512-PffXX2AL8Sh0VHQ52jJC4u3T0H6wDK6N/4bg7xh4ngMYOIi13aR1kzVvX1sVDBgfGwDOkMbl4c54Xm3tlPx/+A==", + "dev": true, + "requires": { + "@opencensus/core": "^0.0.8", + "uuid": "^3.2.1" + }, + "dependencies": { + "@opencensus/core": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/@opencensus/core/-/core-0.0.8.tgz", + "integrity": "sha512-yUFT59SFhGMYQgX0PhoTR0LBff2BEhPrD9io1jWfF/VDbakRfs6Pq60rjv0Z7iaTav5gQlttJCX2+VPxFWCuoQ==", + "dev": true, + "requires": { + "continuation-local-storage": "^3.2.1", + "log-driver": "^1.2.7", + "semver": "^5.5.0", + "shimmer": "^1.2.0", + "uuid": "^3.2.1" + } + } + } + }, + "@pm2/agent": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@pm2/agent/-/agent-1.0.4.tgz", + "integrity": "sha512-cZLwaoLa45FRuetKCcoI3kHnnQ7VMLpZnmVom04MoK0cpY/RxcSarkCHSCu9V+pdARwxx96QrWdrtAJdw97dng==", + "dev": true, + "requires": { + "async": "~3.2.0", + "chalk": "~3.0.0", + "dayjs": "~1.8.24", + "debug": "~4.1.1", + "eventemitter2": "~5.0.1", + "fclone": "~1.0.11", + "nssocket": "0.6.0", + "pm2-axon": "^3.2.0", + "pm2-axon-rpc": "^0.5.0", + "proxy-agent": "~3.1.1", + "semver": "~7.2.0", + "ws": "~7.2.0" + }, + "dependencies": { + "semver": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.2.3.tgz", + "integrity": "sha512-utbW9Z7ZxVvwiIWkdOMLOR9G/NFXh2aRucghkVrEMJWuC++r3lCkBC3LwqBinyHzGMAJxY5tn6VakZGHObq5ig==", + "dev": true + } + } + }, + "@pm2/agent-node": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@pm2/agent-node/-/agent-node-1.1.10.tgz", + "integrity": "sha512-xRcrk7OEwhS3d/227/kKGvxgmbIi6Yyp27FzGlFNermEKhgddmFaRnmd7GRLIsBM/KB28NrwflBZulzk/mma6g==", + "dev": true, + "requires": { + "debug": "^3.1.0", + "eventemitter2": "^5.0.1", + "proxy-agent": "^3.0.3", + "ws": "^6.0.0" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ws": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz", + "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==", + "dev": true, + "requires": { + "async-limiter": "~1.0.0" + } + } + } + }, + "@pm2/io": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/@pm2/io/-/io-4.3.5.tgz", + "integrity": "sha512-CY/a6Nw72vrlp/FPx38l4jfEHp4gNEbo8i+WlSJ2cnWO6VE6CKmnC1zb4yQLvdP8f3EuzzoOBZVq6aGN20M82Q==", + "dev": true, + "requires": { + "@opencensus/core": "0.0.9", + "@opencensus/propagation-b3": "0.0.8", + "@pm2/agent-node": "^1.1.10", + "async": "~2.6.1", + "debug": "4.1.1", + "eventemitter2": "^6.3.1", + "require-in-the-middle": "^5.0.0", + "semver": "6.3.0", + "shimmer": "^1.2.0", + "signal-exit": "^3.0.3", + "tslib": "1.9.3" + }, + "dependencies": { + "async": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", + "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "dev": true, + "requires": { + "lodash": "^4.17.14" + } + }, + "eventemitter2": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.3.tgz", + "integrity": "sha512-t0A2msp6BzOf+QAcI6z9XMktLj52OjGQg+8SJH6v5+3uxNpWYRR3wQmfA+6xtMU9kOC59qk9licus5dYcrYkMQ==", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "tslib": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", + "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==", + "dev": true + } + } + }, + "@pm2/js-api": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@pm2/js-api/-/js-api-0.6.0.tgz", + "integrity": "sha512-ZgM/0yI8s3FRyxP01wI5UzDrVTecS/SmD98z25C9fsHo2Wz3JB1DtS4uIBlPopq2/R5HIQynTUJPDNn4qo1d/Q==", + "dev": true, + "requires": { + "async": "^2.6.3", + "axios": "^0.19.0", + "debug": "~3.2.6", + "eventemitter2": "^6.3.1", + "ws": "^7.0.0" + }, + "dependencies": { + "async": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", + "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "dev": true, + "requires": { + "lodash": "^4.17.14" + } + }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "eventemitter2": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.3.tgz", + "integrity": "sha512-t0A2msp6BzOf+QAcI6z9XMktLj52OjGQg+8SJH6v5+3uxNpWYRR3wQmfA+6xtMU9kOC59qk9licus5dYcrYkMQ==", + "dev": true + } + } + }, + "@pm2/pm2-version-check": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@pm2/pm2-version-check/-/pm2-version-check-1.0.3.tgz", + "integrity": "sha512-SBuYsh+o35knItbRW97vl5/5nEc5c5DYP7PxjyPLOfmm9bMaDsVeATXjXMBy6+KLlyrYWHZxGbfXe003NnHClg==", + "dev": true, + "requires": { + "debug": "^4.1.1" + } + }, + "@types/color-name": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", + "dev": true + }, "@types/node": { "version": "14.6.4", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.6.4.tgz", "integrity": "sha512-Wk7nG1JSaMfMpoMJDKUsWYugliB2Vy55pdjLpmLixeyMi7HizW2I/9QoxsPCkXl3dO+ZOVqPumKaDUv5zJu2uQ==", "dev": true }, + "agent-base": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", + "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", + "dev": true, + "requires": { + "es6-promisify": "^5.0.0" + } + }, + "amp": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/amp/-/amp-0.3.1.tgz", + "integrity": "sha1-at+NWKdPNh6CwfqNOJwHnhOfxH0=", + "dev": true + }, + "amp-message": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/amp-message/-/amp-message-0.1.2.tgz", + "integrity": "sha1-p48cmJlQh602GSpBKY5NtJ49/EU=", + "dev": true, + "requires": { + "amp": "0.3.1" + } + }, + "ansi-colors": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", + "integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==", + "dev": true + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "anymatch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + }, + "dependencies": { + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + } + } + }, + "ast-types": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.14.1.tgz", + "integrity": "sha512-pfSiukbt23P1qMhNnsozLzhMLBs7EEeXqPyvPmnuZM+RMfwfqwDbSVKYflgGuVI7/VehR4oMks0igzdNAg4VeQ==", + "dev": true, + "requires": { + "tslib": "^2.0.1" + } + }, + "async": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz", + "integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==", + "dev": true + }, + "async-limiter": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", + "dev": true + }, + "async-listener": { + "version": "0.6.10", + "resolved": "https://registry.npmjs.org/async-listener/-/async-listener-0.6.10.tgz", + "integrity": "sha512-gpuo6xOyF4D5DE5WvyqZdPA3NGhiT6Qf07l7DCB0wwDEsLvDIbCr6j9S5aj5Ch96dLace5tXVzWBZkxU/c5ohw==", + "dev": true, + "requires": { + "semver": "^5.3.0", + "shimmer": "^1.1.0" + } + }, + "axios": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz", + "integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==", + "dev": true, + "requires": { + "follow-redirects": "1.5.10" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "binary-extensions": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", + "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", + "dev": true + }, "bl": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz", @@ -19,37 +344,664 @@ "safe-buffer": "^5.1.1" } }, + "blessed": { + "version": "0.1.81", + "resolved": "https://registry.npmjs.org/blessed/-/blessed-0.1.81.tgz", + "integrity": "sha1-+WLWh+wsNpVwrnGvhDJW5tDKESk=", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, "bson": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.5.tgz", "integrity": "sha512-kDuEzldR21lHciPQAIulLs1LZlCXdLziXI6Mb/TDkwXhb//UORJNPXgcRs2CuO4H0DcMkpfT3/ySsP3unoZjBg==" }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "dev": true + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "charm": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/charm/-/charm-0.1.2.tgz", + "integrity": "sha1-BsIe7RobBq62dVPNxT4jJ0usIpY=", + "dev": true + }, + "chokidar": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.2.tgz", + "integrity": "sha512-IZHaDeBeI+sZJRX7lGcXsdzgvZqKv6sECqsbErJA4mHWfpRrD8B97kSFN4cQz6nGBGiuFia1MKR4d6c1o8Cv7A==", + "dev": true, + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.1.2", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.4.0" + } + }, + "cli-tableau": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/cli-tableau/-/cli-tableau-2.0.1.tgz", + "integrity": "sha512-he+WTicka9cl0Fg/y+YyxcN6/bfQ/1O3QmgxRXDhABKqLzvoOSM4fMzp39uMyLBulAFuywD2N7UaoQE7WaADxQ==", + "dev": true, + "requires": { + "chalk": "3.0.0" + } + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "commander": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "continuation-local-storage": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/continuation-local-storage/-/continuation-local-storage-3.2.1.tgz", + "integrity": "sha512-jx44cconVqkCEEyLSKWwkvUXwO561jXMa3LPjTPsm5QR22PA0/mhe33FT4Xb5y74JDvt/Cq+5lm8S8rskLv9ZA==", + "dev": true, + "requires": { + "async-listener": "^0.6.0", + "emitter-listener": "^1.1.1" + } + }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, + "cron": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/cron/-/cron-1.8.2.tgz", + "integrity": "sha512-Gk2c4y6xKEO8FSAUTklqtfSr7oTq0CiPQeLBG5Fl0qoXpZyMcj1SG59YL+hqq04bu6/IuEA7lMkYDAplQNKkyg==", + "dev": true, + "requires": { + "moment-timezone": "^0.5.x" + } + }, + "data-uri-to-buffer": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-1.2.0.tgz", + "integrity": "sha512-vKQ9DTQPN1FLYiiEEOQ6IBGFqvjCa5rSK3cWMy/Nespm5d/x3dGFT9UBZnkLxCwua/IXBi2TYnwTEpsOvhC4UQ==", + "dev": true + }, + "dayjs": { + "version": "1.8.35", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.8.35.tgz", + "integrity": "sha512-isAbIEenO4ilm6f8cpqvgjZCsuerDAz2Kb7ri201AiNn58aqXuaLJEnCtfIMdCvERZHNGRY5lDMTr/jdAnKSWQ==", + "dev": true + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "degenerator": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-1.0.4.tgz", + "integrity": "sha1-/PSQo37OJmRk2cxDGrmMWBnO0JU=", + "dev": true, + "requires": { + "ast-types": "0.x.x", + "escodegen": "1.x.x", + "esprima": "3.x.x" + } + }, "denque": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/denque/-/denque-1.4.1.tgz", "integrity": "sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ==" }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "dev": true + }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "emitter-listener": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/emitter-listener/-/emitter-listener-1.1.2.tgz", + "integrity": "sha512-Bt1sBAGFHY9DKY+4/2cV6izcKJUf5T7/gkdmkxzX/qv9CcGH8xSwVRW5mtX03SWJtRTWSOpzCuWN9rBFYZepZQ==", + "dev": true, + "requires": { + "shimmer": "^1.2.0" + } + }, + "enquirer": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.5.tgz", + "integrity": "sha512-BNT1C08P9XD0vNg3J475yIUG+mVdp9T6towYFHUv897X0KoHBjB1shyrNmhmtHWKP17iSWgo7Gqh7BBuzLZMSA==", + "dev": true, + "requires": { + "ansi-colors": "^3.2.1" + } + }, + "es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", + "dev": true + }, + "es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", + "dev": true, + "requires": { + "es6-promise": "^4.0.3" + } + }, + "escape-regexp": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/escape-regexp/-/escape-regexp-0.0.1.tgz", + "integrity": "sha1-9EvaEtRbvfnLf4Yu5+SCez3TIlQ=", + "dev": true + }, + "escodegen": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", + "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", + "dev": true, + "requires": { + "esprima": "^4.0.1", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.6.1" + }, + "dependencies": { + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + } + } + }, + "esprima": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", + "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=", + "dev": true + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "eventemitter2": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-5.0.1.tgz", + "integrity": "sha1-YZegldX7a1folC9v1+qtY6CclFI=", + "dev": true + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "fclone": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/fclone/-/fclone-1.0.11.tgz", + "integrity": "sha1-EOhdo4v+p/xZk0HClu4ddyZu5kA=", + "dev": true + }, + "file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "dev": true + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "follow-redirects": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", + "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", + "dev": true, + "requires": { + "debug": "=3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "dev": true, + "optional": true + }, + "ftp": { + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/ftp/-/ftp-0.3.10.tgz", + "integrity": "sha1-kZfYYa2BQvPmPVqDv+TFn3MwiF0=", + "dev": true, + "requires": { + "readable-stream": "1.1.x", + "xregexp": "2.0.0" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + } + } + }, + "get-uri": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-2.0.4.tgz", + "integrity": "sha512-v7LT/s8kVjs+Tx0ykk1I+H/rbpzkHvuIq87LmeXptcf5sNWm9uQiwjNAt94SJPA1zOlCntmnOlJvVWKmzsxG8Q==", + "dev": true, + "requires": { + "data-uri-to-buffer": "1", + "debug": "2", + "extend": "~3.0.2", + "file-uri-to-path": "1", + "ftp": "~0.3.10", + "readable-stream": "2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "http-errors": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", + "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", + "dev": true, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + } + }, + "http-proxy-agent": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz", + "integrity": "sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==", + "dev": true, + "requires": { + "agent-base": "4", + "debug": "3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "https-proxy-agent": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-3.0.1.tgz", + "integrity": "sha512-+ML2Rbh6DAuee7d07tYGEKOEi2voWPUGan+ExdPbPW6Z3svq+JCqr0v8WmKPOkz1vOVykPCBSuobe7G8GJUtVg==", + "dev": true, + "requires": { + "agent-base": "^4.3.0", + "debug": "^3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "ip": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", + "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", + "dev": true + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, + "lazy": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/lazy/-/lazy-1.0.11.tgz", + "integrity": "sha1-2qBoIGKCVCwIgojpdcKXwa53tpA=", + "dev": true + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", + "dev": true + }, + "log-driver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz", + "integrity": "sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==", + "dev": true + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, "memory-pager": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", "optional": true }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + }, + "module-details-from-path": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.3.tgz", + "integrity": "sha1-EUyUlnPiqKNenTV4hSeqN7Z52is=", + "dev": true + }, + "moment": { + "version": "2.27.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.27.0.tgz", + "integrity": "sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ==", + "dev": true + }, + "moment-timezone": { + "version": "0.5.31", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.31.tgz", + "integrity": "sha512-+GgHNg8xRhMXfEbv81iDtrVeTcWt0kWmTEY1XQK14dICTXnWJnT0dxdlPspwqF3keKMVPXwayEsk1DI0AA/jdA==", + "dev": true, + "requires": { + "moment": ">= 2.9.0" + } + }, "mongodb": { "version": "3.6.1", "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.6.1.tgz", @@ -63,11 +1015,327 @@ "saslprep": "^1.0.0" } }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true + }, + "needle": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/needle/-/needle-2.4.0.tgz", + "integrity": "sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg==", + "dev": true, + "requires": { + "debug": "^3.2.6", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "netmask": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-1.0.6.tgz", + "integrity": "sha1-ICl+idhvb2QA8lDZ9Pa0wZRfzTU=", + "dev": true + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "nssocket": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/nssocket/-/nssocket-0.6.0.tgz", + "integrity": "sha1-Wflvb/MhVm8zxw99vu7N/cBxVPo=", + "dev": true, + "requires": { + "eventemitter2": "~0.4.14", + "lazy": "~1.0.11" + }, + "dependencies": { + "eventemitter2": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz", + "integrity": "sha1-j2G3XN4BKy6esoTUVFWDtWQ7Yas=", + "dev": true + } + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + } + }, + "pac-proxy-agent": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-3.0.1.tgz", + "integrity": "sha512-44DUg21G/liUZ48dJpUSjZnFfZro/0K5JTyFYLBcmh9+T6Ooi4/i4efwUiEy0+4oQusCBqWdhv16XohIj1GqnQ==", + "dev": true, + "requires": { + "agent-base": "^4.2.0", + "debug": "^4.1.1", + "get-uri": "^2.0.0", + "http-proxy-agent": "^2.1.0", + "https-proxy-agent": "^3.0.0", + "pac-resolver": "^3.0.0", + "raw-body": "^2.2.0", + "socks-proxy-agent": "^4.0.1" + } + }, + "pac-resolver": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-3.0.0.tgz", + "integrity": "sha512-tcc38bsjuE3XZ5+4vP96OfhOugrX+JcnpUbhfuc4LuXBLQhoTthOstZeoQJBDnQUDYzYmdImKsbz0xSl1/9qeA==", + "dev": true, + "requires": { + "co": "^4.6.0", + "degenerator": "^1.0.4", + "ip": "^1.1.5", + "netmask": "^1.0.6", + "thunkify": "^2.1.2" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "picomatch": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", + "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", + "dev": true + }, + "pidusage": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/pidusage/-/pidusage-2.0.18.tgz", + "integrity": "sha512-Y/VfKfh3poHjMEINxU+gJTeVOBjiThQeFAmzR7z56HSNiMx+etl+yBhk42nRPciPYt/VZl8DQLVXNC6P5vH11A==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.2" + } + }, + "pm2": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/pm2/-/pm2-4.4.1.tgz", + "integrity": "sha512-ece2zqVvSg29tIGdznKqk07IwVaO4mX7zLBMVxbJQMfvxpQUotLLERDW0v/RYMHtzj1bX8MLsDUsmPiIbEszKg==", + "dev": true, + "requires": { + "@pm2/agent": "~1.0.2", + "@pm2/io": "~4.3.5", + "@pm2/js-api": "~0.6.0", + "@pm2/pm2-version-check": "^1.0.3", + "async": "~3.2.0", + "blessed": "0.1.81", + "chalk": "3.0.0", + "chokidar": "^3.3.0", + "cli-tableau": "^2.0.0", + "commander": "2.15.1", + "cron": "1.8.2", + "dayjs": "~1.8.25", + "debug": "4.1.1", + "enquirer": "2.3.5", + "eventemitter2": "5.0.1", + "fclone": "1.0.11", + "mkdirp": "1.0.4", + "needle": "2.4.0", + "pidusage": "2.0.18", + "pm2-axon": "3.3.0", + "pm2-axon-rpc": "0.5.1", + "pm2-deploy": "~1.0.2", + "pm2-multimeter": "^0.1.2", + "promptly": "^2", + "ps-list": "6.3.0", + "semver": "^7.2", + "source-map-support": "0.5.16", + "sprintf-js": "1.1.2", + "systeminformation": "^4.23.3", + "vizion": "0.2.13", + "yamljs": "0.3.0" + }, + "dependencies": { + "semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", + "dev": true + } + } + }, + "pm2-axon": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/pm2-axon/-/pm2-axon-3.3.0.tgz", + "integrity": "sha512-dAFlFYRuFbFjX7oAk41zT+dx86EuaFX/TgOp5QpUKRKwxb946IM6ydnoH5sSTkdI2pHSVZ+3Am8n/l0ocr7jdQ==", + "dev": true, + "requires": { + "amp": "~0.3.1", + "amp-message": "~0.1.1", + "debug": "^3.0", + "escape-regexp": "0.0.1" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "pm2-axon-rpc": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/pm2-axon-rpc/-/pm2-axon-rpc-0.5.1.tgz", + "integrity": "sha512-hT8gN3/j05895QLXpwg+Ws8PjO4AVID6Uf9StWpud9HB2homjc1KKCcI0vg9BNOt56FmrqKDT1NQgheIz35+sA==", + "dev": true, + "requires": { + "debug": "^3.0" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "pm2-deploy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pm2-deploy/-/pm2-deploy-1.0.2.tgz", + "integrity": "sha512-YJx6RXKrVrWaphEYf++EdOOx9EH18vM8RSZN/P1Y+NokTKqYAca/ejXwVLyiEpNju4HPZEk3Y2uZouwMqUlcgg==", + "dev": true, + "requires": { + "run-series": "^1.1.8", + "tv4": "^1.3.0" + } + }, + "pm2-multimeter": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/pm2-multimeter/-/pm2-multimeter-0.1.2.tgz", + "integrity": "sha1-Gh5VFT1BoFU0zqI8/oYKuqDrSs4=", + "dev": true, + "requires": { + "charm": "~0.1.1" + } + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, + "promptly": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/promptly/-/promptly-2.2.0.tgz", + "integrity": "sha1-KhP6BjaIoqWYOxYf/wEIoH0m/HQ=", + "dev": true, + "requires": { + "read": "^1.0.4" + } + }, + "proxy-agent": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-3.1.1.tgz", + "integrity": "sha512-WudaR0eTsDx33O3EJE16PjBRZWcX8GqCEeERw1W3hZJgH/F2a46g7jty6UGty6NeJ4CKQy8ds2CJPMiyeqaTvw==", + "dev": true, + "requires": { + "agent-base": "^4.2.0", + "debug": "4", + "http-proxy-agent": "^2.1.0", + "https-proxy-agent": "^3.0.0", + "lru-cache": "^5.1.1", + "pac-proxy-agent": "^3.0.1", + "proxy-from-env": "^1.0.0", + "socks-proxy-agent": "^4.0.1" + } + }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true + }, + "ps-list": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/ps-list/-/ps-list-6.3.0.tgz", + "integrity": "sha512-qau0czUSB0fzSlBOQt0bo+I2v6R+xiQdj78e1BR/Qjfl5OHWJ/urXi8+ilw1eHe+5hSeDI1wrwVTgDp2wst4oA==", + "dev": true + }, + "raw-body": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.1.tgz", + "integrity": "sha512-9WmIKF6mkvA0SLmA2Knm9+qj89e+j1zqgyn8aXGd7+nAduPoqgI9lO57SAZNn/Byzo5P7JhXTyg9PzaJbH73bA==", + "dev": true, + "requires": { + "bytes": "3.1.0", + "http-errors": "1.7.3", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, + "read": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", + "integrity": "sha1-s9oZvQUkMal2cdRKQmNK33ELQMQ=", + "dev": true, + "requires": { + "mute-stream": "~0.0.4" + } + }, "readable-stream": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", @@ -89,6 +1357,26 @@ } } }, + "readdirp": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz", + "integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "require-in-the-middle": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-5.0.3.tgz", + "integrity": "sha512-p/ICV8uMlqC4tjOYabLMxAWCIKa0YUQgZZ6KDM0xgXJNgdGQ1WmL2A07TwmrZw+wi6ITUFKzH5v3n+ENEyXVkA==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "module-details-from-path": "^1.0.3", + "resolve": "^1.12.0" + } + }, "require_optional": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz", @@ -98,16 +1386,37 @@ "semver": "^5.1.0" } }, + "resolve": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, "resolve-from": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz", "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=" }, + "run-series": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/run-series/-/run-series-1.1.8.tgz", + "integrity": "sha512-+GztYEPRpIsQoCSraWHDBs9WVy4eVME16zhOtDB4H9J4xN0XRhknnmLOl+4gRgZtu8dpp9N/utSPjKH/xmDzXg==", + "dev": true + }, "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, "saslprep": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", @@ -117,11 +1426,88 @@ "sparse-bitfield": "^3.0.3" } }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "dev": true + }, "semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" }, + "setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", + "dev": true + }, + "shimmer": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.1.tgz", + "integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==", + "dev": true + }, + "signal-exit": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", + "dev": true + }, + "smart-buffer": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.1.0.tgz", + "integrity": "sha512-iVICrxOzCynf/SNaBQCw34eM9jROU/s5rzIhpOvzhzuYHfJR/DhZfDkXiZSgKXfgv26HT3Yni3AV/DGw0cGnnw==", + "dev": true + }, + "socks": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.3.3.tgz", + "integrity": "sha512-o5t52PCNtVdiOvzMry7wU4aOqYWL0PeCXRWBEiJow4/i/wr+wpsJQ9awEu1EonLIqsfGd5qSgDdxEOvCdmBEpA==", + "dev": true, + "requires": { + "ip": "1.1.5", + "smart-buffer": "^4.1.0" + } + }, + "socks-proxy-agent": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-4.0.2.tgz", + "integrity": "sha512-NT6syHhI9LmuEMSK6Kd2V7gNv5KFZoLE7V5udWmn0de+3Mkj3UMA/AJPLyeNUVmElCurSHtUdM3ETpR3z770Wg==", + "dev": true, + "requires": { + "agent-base": "~4.2.1", + "socks": "~2.3.2" + }, + "dependencies": { + "agent-base": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz", + "integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==", + "dev": true, + "requires": { + "es6-promisify": "^5.0.0" + } + } + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.16.tgz", + "integrity": "sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, "sparse-bitfield": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", @@ -131,6 +1517,18 @@ "memory-pager": "^1.0.2" } }, + "sprintf-js": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", + "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==", + "dev": true + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "dev": true + }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -146,16 +1544,174 @@ } } }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "systeminformation": { + "version": "4.27.3", + "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-4.27.3.tgz", + "integrity": "sha512-0Nc8AYEK818h7FI+bbe/kj7xXsMD5zOHvO9alUqQH/G4MHXu5tHQfWqC/bzWOk4JtoQPhnyLgxMYncDA2eeSBw==", + "dev": true, + "optional": true + }, + "thunkify": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/thunkify/-/thunkify-2.1.2.tgz", + "integrity": "sha1-+qDp0jDFGsyVyhOjYawFyn4EVT0=", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "dev": true + }, + "ts-node": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.0.0.tgz", + "integrity": "sha512-/TqB4SnererCDR/vb4S/QvSZvzQMJN8daAslg7MeaiHvD8rDZsSfXmNeNumyZZzMned72Xoq/isQljYSt8Ynfg==", + "dev": true, + "requires": { + "arg": "^4.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "source-map-support": "^0.5.17", + "yn": "3.1.1" + }, + "dependencies": { + "source-map-support": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + } + } + }, + "tslib": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.1.tgz", + "integrity": "sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ==", + "dev": true + }, + "tv4": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/tv4/-/tv4-1.3.0.tgz", + "integrity": "sha1-0CDIRvrdUMhVq7JeuuzGj8EPeWM=", + "dev": true + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2" + } + }, "typescript": { "version": "3.9.7", "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz", "integrity": "sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==", "dev": true }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "dev": true + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "dev": true + }, + "vizion": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/vizion/-/vizion-0.2.13.tgz", + "integrity": "sha1-ExTN7is0EW+fWxJIU2+V2/zW718=", + "dev": true, + "requires": { + "async": "1.5" + }, + "dependencies": { + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + } + } + }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "ws": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.2.5.tgz", + "integrity": "sha512-C34cIU4+DB2vMyAbmEKossWq2ZQDr6QEyuuCzWrM9zfw1sGc0mYiJ0UnG9zzNykt49C2Fi34hvr2vssFQRS6EA==", + "dev": true + }, + "xregexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-2.0.0.tgz", + "integrity": "sha1-UqY+VsoLhKfzpfPWGHLxJq16WUM=", + "dev": true + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "yamljs": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/yamljs/-/yamljs-0.3.0.tgz", + "integrity": "sha512-C/FsVVhht4iPQYXOInoxUM/1ELSf9EsgKH34FofQOp6hwCPrW4vG4w5++TED3xRUo8gD7l0P1J1dLlDYzODsTQ==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "glob": "^7.0.5" + } + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true } } } diff --git a/ee/server/services/package.json b/ee/server/services/package.json index e7bb657ae47dd..efe027b192842 100644 --- a/ee/server/services/package.json +++ b/ee/server/services/package.json @@ -4,6 +4,8 @@ "description": "Rocket.Chat Authorization service", "main": "index.js", "scripts": { + "dev": "pm2 start ecosystem.config.js", + "pm2": "pm2", "start": "ts-node index.ts", "build": "tsc --outDir ./dist", "test": "echo \"Error: no test specified\" && exit 1" @@ -18,6 +20,8 @@ }, "devDependencies": { "@types/node": "^14.6.4", + "pm2": "^4.4.1", + "ts-node": "^9.0.0", "typescript": "^3.9.7" } } From 2989b2797670f06fe5978dfb4a2290260056d6a0 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Sat, 5 Sep 2020 12:16:50 -0300 Subject: [PATCH 014/198] Implements async local storage for context --- ee/server/broker.ts | 10 ++++++++-- package-lock.json | 6 +++--- package.json | 1 + server/sdk/index.ts | 5 +++++ server/sdk/lib/LocalBroker.ts | 9 ++++++++- server/sdk/types/ServiceClass.ts | 26 ++++++++++++++++++++++++++ 6 files changed, 51 insertions(+), 6 deletions(-) diff --git a/ee/server/broker.ts b/ee/server/broker.ts index e0d7ea05ceb28..c6bae3160ecc3 100644 --- a/ee/server/broker.ts +++ b/ee/server/broker.ts @@ -1,5 +1,6 @@ -import { ServiceBroker } from 'moleculer'; +import { ServiceBroker, Context } from 'moleculer'; +import { asyncLocalStorage } from '../../server/sdk'; import { api } from '../../server/sdk/api'; import { IBroker } from '../../server/sdk/types/IBroker'; import { ServiceClass } from '../../server/sdk/types/ServiceClass'; @@ -31,7 +32,12 @@ class NetworkBroker implements IBroker { } const i = instance as any; - service.actions[method] = (ctx: any): any => i[method](...ctx.params); + service.actions[method] = async (ctx: Context<[]>): Promise => asyncLocalStorage.run({ + id: ctx.id, + nodeID: ctx.nodeID, + requestID: ctx.requestID, + broker: this, + }, (): any => i[method](...ctx.params)); } this.broker.createService(service); diff --git a/package-lock.json b/package-lock.json index 9c3e3f0907a25..5e93451c52789 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8426,9 +8426,9 @@ } }, "@types/node": { - "version": "9.6.40", - "resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.40.tgz", - "integrity": "sha512-M3HHoXXndsho/sTbQML2BJr7/uwNhMg8P0D4lb+UsM65JQZx268faiz9hKpY4FpocWqpwlLwa8vevw8hLtKjOw==" + "version": "14.6.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.6.4.tgz", + "integrity": "sha512-Wk7nG1JSaMfMpoMJDKUsWYugliB2Vy55pdjLpmLixeyMi7HizW2I/9QoxsPCkXl3dO+ZOVqPumKaDUv5zJu2uQ==" }, "@types/normalize-package-data": { "version": "2.4.0", diff --git a/package.json b/package.json index 9fe1fc3b3edcb..f3d6a6e4fc56a 100644 --- a/package.json +++ b/package.json @@ -69,6 +69,7 @@ "@types/mock-require": "^2.0.0", "@types/moment-timezone": "^0.5.30", "@types/mongodb": "^3.5.26", + "@types/node": "^14.6.4", "@types/react-dom": "^16.9.8", "@types/toastr": "^2.1.38", "@typescript-eslint/eslint-plugin": "^2.34.0", diff --git a/server/sdk/index.ts b/server/sdk/index.ts index c6c5638583018..349bb63e7565e 100644 --- a/server/sdk/index.ts +++ b/server/sdk/index.ts @@ -1,5 +1,10 @@ +import { AsyncLocalStorage } from 'async_hooks'; + import { proxify } from './lib/proxify'; import { IAuthorization } from './types/IAuthorization'; +import { IServiceContext } from './types/ServiceClass'; // TODO try not having to duplicate the namespace 'authorization' here export const Authorization = proxify('authorization'); + +export const asyncLocalStorage = new AsyncLocalStorage(); diff --git a/server/sdk/lib/LocalBroker.ts b/server/sdk/lib/LocalBroker.ts index 6aadb6ba82c8b..1049bd83bd186 100644 --- a/server/sdk/lib/LocalBroker.ts +++ b/server/sdk/lib/LocalBroker.ts @@ -1,11 +1,18 @@ import { IBroker } from '../types/IBroker'; import { ServiceClass } from '../types/ServiceClass'; +import { asyncLocalStorage } from '..'; export class LocalBroker implements IBroker { private methods = new Map(); async call(method: string, data: any): Promise { - const result = this.methods.get(method)?.(...data); + const result = await asyncLocalStorage.run({ + id: 'ctx.id', + nodeID: 'ctx.nodeID', + requestID: 'ctx.requestID', + broker: this, + }, (): any => this.methods.get(method)?.(...data)); + return result; } diff --git a/server/sdk/types/ServiceClass.ts b/server/sdk/types/ServiceClass.ts index 1374b6e63e3e9..f9fd648073754 100644 --- a/server/sdk/types/ServiceClass.ts +++ b/server/sdk/types/ServiceClass.ts @@ -1,7 +1,33 @@ +import { asyncLocalStorage } from '..'; +import { IBroker } from './IBroker'; + +export interface IServiceContext { + id: string; // Context ID + broker: IBroker; // Instance of the broker. + nodeID: string | null; // The caller or target Node ID. + // action: Object; // Instance of action definition. + // event: Object; // Instance of event definition. + // eventName: Object; // The emitted event name. + // eventType: String; // Type of event (“emit” or “broadcast”). + // eventGroups: Array; // String> Groups of event. + // caller: String; // Service full name of the caller. E.g.: v3.myService + requestID: string | null; // Request ID. If you make nested-calls, it will be the same ID. + // parentID: String; // Parent context ID (in nested-calls). + // params: Any; // Request params. Second argument from broker.call. + // meta: Any; // Request metadata. It will be also transferred to nested-calls. + // locals: any; // Local data. + // level: Number; // Request level (in nested-calls). The first level is 1. + // span: Span; // Current active span. +} + export abstract class ServiceClass { protected name: string; getName(): string { return this.name; } + + get context(): IServiceContext | undefined { + return asyncLocalStorage.getStore(); + } } From 00d97e391b7c844e059f63c982918bcc68fdb10b Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Sat, 5 Sep 2020 12:17:31 -0300 Subject: [PATCH 015/198] Move ee Accounts service to a folder --- ee/server/services/Authorization.ts | 9 --------- ee/server/services/Authorization/Authorization.ts | 9 +++++++++ ee/server/services/ecosystem.config.js | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) delete mode 100644 ee/server/services/Authorization.ts create mode 100644 ee/server/services/Authorization/Authorization.ts diff --git a/ee/server/services/Authorization.ts b/ee/server/services/Authorization.ts deleted file mode 100644 index f3b15bb495668..0000000000000 --- a/ee/server/services/Authorization.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { api } from '../../../server/sdk/api'; -import { Authorization } from '../../../server/services/authorization/service'; -import { getConnection } from './mongo'; - -import '../broker'; - -getConnection().then((db) => { - api.registerService(new Authorization(db)); -}); diff --git a/ee/server/services/Authorization/Authorization.ts b/ee/server/services/Authorization/Authorization.ts new file mode 100644 index 0000000000000..6fda34cd91183 --- /dev/null +++ b/ee/server/services/Authorization/Authorization.ts @@ -0,0 +1,9 @@ +import { api } from '../../../../server/sdk/api'; +import { Authorization } from '../../../../server/services/authorization/service'; +import { getConnection } from '../mongo'; + +import '../../broker'; + +getConnection().then((db) => { + api.registerService(new Authorization(db)); +}); diff --git a/ee/server/services/ecosystem.config.js b/ee/server/services/ecosystem.config.js index 8155fd5c530d5..4e9de52a9cf36 100644 --- a/ee/server/services/ecosystem.config.js +++ b/ee/server/services/ecosystem.config.js @@ -1,7 +1,7 @@ module.exports = { apps: [{ name: 'Authorization', - script: 'ts-node Authorization.ts', + script: 'ts-node Authorization/Authorization.ts', watch: true, instances: 2, // interpreter: '', From dc4a509561dde0e3b2e93227e5d9efcce0d45e21 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Sat, 5 Sep 2020 14:15:27 -0300 Subject: [PATCH 016/198] Implement broker node list helper --- ee/server/broker.ts | 6 +++++- server/sdk/lib/BaseBroker.ts | 20 -------------------- server/sdk/lib/LocalBroker.ts | 6 +++++- server/sdk/types/IBroker.ts | 20 ++++++++++++++++++++ 4 files changed, 30 insertions(+), 22 deletions(-) delete mode 100644 server/sdk/lib/BaseBroker.ts diff --git a/ee/server/broker.ts b/ee/server/broker.ts index c6bae3160ecc3..1745fcb2089f1 100644 --- a/ee/server/broker.ts +++ b/ee/server/broker.ts @@ -2,7 +2,7 @@ import { ServiceBroker, Context } from 'moleculer'; import { asyncLocalStorage } from '../../server/sdk'; import { api } from '../../server/sdk/api'; -import { IBroker } from '../../server/sdk/types/IBroker'; +import { IBroker, IBrokerNode } from '../../server/sdk/types/IBroker'; import { ServiceClass } from '../../server/sdk/types/ServiceClass'; // import { onLicense } from '../app/license/server'; @@ -42,6 +42,10 @@ class NetworkBroker implements IBroker { this.broker.createService(service); } + + async nodeList(): Promise { + return this.broker.call('$node.list'); + } } // onLicense('scalability', async () => { diff --git a/server/sdk/lib/BaseBroker.ts b/server/sdk/lib/BaseBroker.ts deleted file mode 100644 index 543b875e4336b..0000000000000 --- a/server/sdk/lib/BaseBroker.ts +++ /dev/null @@ -1,20 +0,0 @@ -export abstract class BaseBroker { - protected log(...args: Array): void { - console.log('BROKER:', ...args); - } - - abstract register(name: string, method: Function): void; - - registerInstance(instance: object): void { - const methods = instance.constructor?.name === 'Object' ? Object.getOwnPropertyNames(instance) : Object.getOwnPropertyNames(Object.getPrototypeOf(instance)); - for (const method of methods) { - if (method === 'constructor') { - continue; - } - const i = instance as any; - this.register(method, i[method]); - } - } - - abstract async call(method: string, data: any): Promise; -} diff --git a/server/sdk/lib/LocalBroker.ts b/server/sdk/lib/LocalBroker.ts index 1049bd83bd186..a62c7df6930c9 100644 --- a/server/sdk/lib/LocalBroker.ts +++ b/server/sdk/lib/LocalBroker.ts @@ -1,4 +1,4 @@ -import { IBroker } from '../types/IBroker'; +import { IBroker, IBrokerNode } from '../types/IBroker'; import { ServiceClass } from '../types/ServiceClass'; import { asyncLocalStorage } from '..'; @@ -29,4 +29,8 @@ export class LocalBroker implements IBroker { this.methods.set(`${ namespace }.${ method }`, i[method].bind(i)); } } + + async nodeList(): Promise { + return []; + } } diff --git a/server/sdk/types/IBroker.ts b/server/sdk/types/IBroker.ts index 981257d29d64e..b894a50761550 100644 --- a/server/sdk/types/IBroker.ts +++ b/server/sdk/types/IBroker.ts @@ -1,6 +1,26 @@ import { ServiceClass } from './ServiceClass'; +export interface IBrokerNode { + id: string; + instanceID: string; + available: boolean; + local: boolean; + // lastHeartbeatTime: 16, + // config: {}, + // client: { type: 'nodejs', version: '0.14.10', langVersion: 'v12.18.3' }, + // metadata: {}, + // ipList: [ '192.168.0.100', '192.168.1.25' ], + // port: 59989, + // hostname: 'RocketChats-MacBook-Pro-Rodrigo-Nascimento.local', + // udpAddress: null, + // cpu: 25, + // cpuSeq: 1, + // seq: 3, + // offlineSince: null +} + export interface IBroker { createService(service: ServiceClass): void; call(method: string, data: any): Promise; + nodeList(): Promise; } From 9d9b63ca550dcb19952bb8c134386d65640487a2 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Sat, 5 Sep 2020 14:16:14 -0300 Subject: [PATCH 017/198] Add missing interfaces --- definition/IUser.ts | 4 +++- definition/IUserSession.ts | 14 ++++++++++++++ definition/UserStatus.ts | 6 ++++++ 3 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 definition/IUserSession.ts create mode 100644 definition/UserStatus.ts diff --git a/definition/IUser.ts b/definition/IUser.ts index fb1628d3b674a..319e909e25dc7 100644 --- a/definition/IUser.ts +++ b/definition/IUser.ts @@ -1,3 +1,5 @@ +import { USER_STATUS } from './UserStatus'; + export interface ILoginToken { hashedToken: string; twoFactorAuthorizedUntil?: Date; @@ -89,7 +91,7 @@ export interface IUser { avatarOrigin?: string; utcOffset?: number; language?: string; - statusDefault?: string; + statusDefault?: USER_STATUS; oauth?: { authorizedClients: string[]; }; diff --git a/definition/IUserSession.ts b/definition/IUserSession.ts new file mode 100644 index 0000000000000..4d98e73a8e7e4 --- /dev/null +++ b/definition/IUserSession.ts @@ -0,0 +1,14 @@ +import { USER_STATUS } from './UserStatus'; + +export interface IUserSessionConnection { + id: string; + instanceId: string; + status: USER_STATUS; + _createdAt: Date; + _updatedAt: Date; +} + +export interface IUserSession { + _id: string; + connections: IUserSessionConnection[]; +} diff --git a/definition/UserStatus.ts b/definition/UserStatus.ts new file mode 100644 index 0000000000000..115d0b3d0c0ee --- /dev/null +++ b/definition/UserStatus.ts @@ -0,0 +1,6 @@ +export enum USER_STATUS { + ONLINE = 'online', + AWAY = 'away', + OFFLINE = 'offline', + BUSY = 'busy', +} From bb1bcc80e25a76aa1ad8fa107d269c8766eed199 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Sat, 5 Sep 2020 14:17:06 -0300 Subject: [PATCH 018/198] Implement central place for collections and fix error when compiling due to pm2 file --- ee/server/services/mongo.ts | 17 ++++++++++++++++- ee/server/services/tsconfig.json | 3 ++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/ee/server/services/mongo.ts b/ee/server/services/mongo.ts index 42935a44bd815..110b692f07c2b 100644 --- a/ee/server/services/mongo.ts +++ b/ee/server/services/mongo.ts @@ -1,5 +1,5 @@ -import { MongoClient, Db } from 'mongodb'; +import { MongoClient, Db, Collection } from 'mongodb'; const { MONGO_URL = 'mongodb://localhost:27017/rocketchat', @@ -11,6 +11,16 @@ const client = new MongoClient(MONGO_URL, { useNewUrlParser: true, }); +export enum Collections { + Subscriptions = 'rocketchat_subscription', + UserSession = 'usersSessions', + User = 'users', + Trash = 'rocketchat_trash', + Messages = 'rocketchat_message', + Rooms = 'rocketchat_room', + Settings = 'rocketchat_settings', +} + let db: Db; export async function getConnection(): Promise { if (!db) { @@ -20,3 +30,8 @@ export async function getConnection(): Promise { return db; } + +export async function getCollection(name: Collections): Promise> { + await getConnection(); + return db.collection(name); +} diff --git a/ee/server/services/tsconfig.json b/ee/server/services/tsconfig.json index bde9fe44c4529..30716527c395d 100644 --- a/ee/server/services/tsconfig.json +++ b/ee/server/services/tsconfig.json @@ -41,6 +41,7 @@ // "experimentalDecorators": true, }, "exclude": [ - "./dist" + "./dist", + "./ecosystem.config.js" ] } From ac330af0b874f5eceafed17adde0a48ab33fd150 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Sat, 5 Sep 2020 14:17:42 -0300 Subject: [PATCH 019/198] Initial implementation of Presence --- ee/server/services/Presence/Presence.ts | 9 +++ ee/server/services/Presence/actions/index.ts | 45 +++++++++++++ .../Presence/actions/newConnection.ts | 46 +++++++++++++ .../Presence/actions/removeConnection.ts | 23 +++++++ .../Presence/actions/removeLostConnections.ts | 67 +++++++++++++++++++ .../services/Presence/actions/setStatus.ts | 54 +++++++++++++++ .../Presence/actions/updateUserPresence.ts | 31 +++++++++ ee/server/services/Presence/hooks/afterAll.ts | 5 ++ ee/server/services/Presence/hooks/index.ts | 1 + .../Presence/lib/processConnectionStatus.ts | 25 +++++++ ee/server/services/Presence/service.ts | 46 +++++++++++++ ee/server/services/ecosystem.config.js | 8 ++- server/sdk/index.ts | 2 + server/sdk/types/IPresence.ts | 10 +++ 14 files changed, 371 insertions(+), 1 deletion(-) create mode 100755 ee/server/services/Presence/Presence.ts create mode 100755 ee/server/services/Presence/actions/index.ts create mode 100755 ee/server/services/Presence/actions/newConnection.ts create mode 100755 ee/server/services/Presence/actions/removeConnection.ts create mode 100755 ee/server/services/Presence/actions/removeLostConnections.ts create mode 100755 ee/server/services/Presence/actions/setStatus.ts create mode 100755 ee/server/services/Presence/actions/updateUserPresence.ts create mode 100755 ee/server/services/Presence/hooks/afterAll.ts create mode 100755 ee/server/services/Presence/hooks/index.ts create mode 100644 ee/server/services/Presence/lib/processConnectionStatus.ts create mode 100644 ee/server/services/Presence/service.ts create mode 100644 server/sdk/types/IPresence.ts diff --git a/ee/server/services/Presence/Presence.ts b/ee/server/services/Presence/Presence.ts new file mode 100755 index 0000000000000..1bcf4edbd9e7c --- /dev/null +++ b/ee/server/services/Presence/Presence.ts @@ -0,0 +1,9 @@ +// TODO: check config +// import config from 'moleculer.config'; + +import { api } from '../../../../server/sdk/api'; +import { Presence } from './actions'; + +import '../../broker'; + +api.registerService(new Presence()); diff --git a/ee/server/services/Presence/actions/index.ts b/ee/server/services/Presence/actions/index.ts new file mode 100755 index 0000000000000..2754b2f2db792 --- /dev/null +++ b/ee/server/services/Presence/actions/index.ts @@ -0,0 +1,45 @@ +import { newConnection } from './newConnection'; +import { removeConnection } from './removeConnection'; +import { removeLostConnections } from './removeLostConnections'; +import { setStatus, setConnectionStatus } from './setStatus'; +import { updateUserPresence } from './updateUserPresence'; +import { ServiceClass } from '../../../../../server/sdk/types/ServiceClass'; +import { IPresence } from '../../../../../server/sdk/types/IPresence'; +import { USER_STATUS } from '../../../../../definition/UserStatus'; + +export default { + newConnection, + removeConnection, + removeLostConnections, + setStatus, + setConnectionStatus, + updateUserPresence, +}; + +export class Presence extends ServiceClass implements IPresence { + protected name = 'presence'; + + async newConnection(uid: string, session: object): Promise { + return newConnection(uid, session, this.context); + } + + async removeConnection(uid: string, session: object): Promise { + return removeConnection(uid, session); + } + + async removeLostConnections(nodeID: string): Promise { + return removeLostConnections(nodeID, this.context); + } + + async setStatus(uid: string, status: USER_STATUS, statusText?: string): Promise { + return setStatus(uid, status, statusText); + } + + async setConnectionStatus(uid: string, status: USER_STATUS, session: string): Promise { + return setConnectionStatus(uid, status, session); + } + + async updateUserPresence(uid: string): Promise { + return updateUserPresence(uid); + } +} diff --git a/ee/server/services/Presence/actions/newConnection.ts b/ee/server/services/Presence/actions/newConnection.ts new file mode 100755 index 0000000000000..e75ff10e52c55 --- /dev/null +++ b/ee/server/services/Presence/actions/newConnection.ts @@ -0,0 +1,46 @@ +import { getCollection, Collections } from '../../mongo'; +import { IServiceContext } from '../../../../../server/sdk/types/ServiceClass'; + +const status = 'online'; + +export async function newConnection(uid: string, session: object, context?: IServiceContext): Promise { + const instanceId = context?.nodeID; + + if (!instanceId) { + return; + } + + const query = { + _id: uid, + }; + + const now = new Date(); + + + const update = { + $push: { + connections: { + id: session, + instanceId, + status, + _createdAt: now, + _updatedAt: now, + }, + }, + }; + + // if (metadata) { + // update.$set = { + // metadata: metadata + // }; + // connection.metadata = metadata; + // } + + const UserSession = await getCollection(Collections.UserSession); + await UserSession.updateOne(query, update, { upsert: true }); + + return { + uid, + connectionId: session, + }; +} diff --git a/ee/server/services/Presence/actions/removeConnection.ts b/ee/server/services/Presence/actions/removeConnection.ts new file mode 100755 index 0000000000000..ff5e72113bbe0 --- /dev/null +++ b/ee/server/services/Presence/actions/removeConnection.ts @@ -0,0 +1,23 @@ +import { getCollection, Collections } from '../../mongo'; + +export async function removeConnection(uid: string, session: object): Promise { + const query = { + 'connections.id': session, + }; + + const update = { + $pull: { + connections: { + id: session, + }, + }, + }; + + const UserSession = await getCollection(Collections.UserSession); + await UserSession.updateMany(query, update); + + return { + uid, + session, + }; +} diff --git a/ee/server/services/Presence/actions/removeLostConnections.ts b/ee/server/services/Presence/actions/removeLostConnections.ts new file mode 100755 index 0000000000000..955c25d38872e --- /dev/null +++ b/ee/server/services/Presence/actions/removeLostConnections.ts @@ -0,0 +1,67 @@ +import { Collection } from 'mongodb'; + +import { getCollection, Collections } from '../../mongo'; +import { IServiceContext } from '../../../../../server/sdk/types/ServiceClass'; + +async function getAffectedUsers(model: Collection, query: object): Promise { + const list = await model.find<{_id: string}>(query, { projection: { _id: 1 } }).toArray(); + return list.map(({ _id }) => _id); +} + +export async function removeLostConnections(nodeID: string, context?: IServiceContext): Promise { + const UserSession = await getCollection(Collections.UserSession); + + if (nodeID) { + const query = { + 'connections.instanceId': nodeID, + }; + const update = { + $pull: { + connections: { + instanceId: nodeID, + }, + }, + }; + const affectedUsers = await getAffectedUsers(UserSession, query); + + const { modifiedCount } = await UserSession.updateMany(query, update); + + if (modifiedCount === 0) { + return []; + } + + return affectedUsers; + } + + if (!context) { + return []; + } + + const nodes = await context.broker.nodeList(); + + const ids = nodes.filter((node) => node.available).map(({ id }) => id); + + const affectedUsers = await getAffectedUsers(UserSession, { + 'connections.instanceId': { + $exists: true, + $nin: ids, + }, + }); + + const update = { + $pull: { + connections: { + instanceId: { + $nin: ids, + }, + }, + }, + }; + const { modifiedCount } = await UserSession.updateMany({}, update); + + if (modifiedCount === 0) { + return []; + } + + return affectedUsers; +} diff --git a/ee/server/services/Presence/actions/setStatus.ts b/ee/server/services/Presence/actions/setStatus.ts new file mode 100755 index 0000000000000..347c3841abd68 --- /dev/null +++ b/ee/server/services/Presence/actions/setStatus.ts @@ -0,0 +1,54 @@ +import { processPresenceAndStatus } from '../lib/processConnectionStatus'; +import { getCollection, Collections } from '../../mongo'; +import { USER_STATUS } from '../../../../../definition/UserStatus'; +import { IUserSession } from '../../../../../definition/IUserSession'; + +export async function setStatus(uid: string, statusDefault: USER_STATUS, statusText?: string): Promise { + const query = { _id: uid }; + + const UserSession = await getCollection(Collections.UserSession); + const userSessions = await UserSession.findOne(query) || { connections: [] }; + + const { status, statusConnection } = processPresenceAndStatus(userSessions.connections, statusDefault); + + const update = { + statusDefault, + status, + statusConnection, + ...typeof statusText !== 'undefined' ? { + // TODO logic duplicated from Rocket.Chat core + statusText: String(statusText || '').trim().substr(0, 120), + } : {}, + }; + + const User = await getCollection(Collections.User); + const result = await User.updateOne( + query, + { + $set: update, + }, + ); + + return !!result.modifiedCount; +} + +export async function setConnectionStatus(uid: string, status: USER_STATUS, session: string): Promise { + const query = { + _id: uid, + 'connections.id': session, + }; + + const now = new Date(); + + const update = { + $set: { + 'connections.$.status': status, + 'connections.$._updatedAt': now, + }, + }; + + const UserSession = await getCollection(Collections.UserSession); + const result = await UserSession.updateOne(query, update); + + return !!result.modifiedCount; +} diff --git a/ee/server/services/Presence/actions/updateUserPresence.ts b/ee/server/services/Presence/actions/updateUserPresence.ts new file mode 100755 index 0000000000000..99dd126dfe7f1 --- /dev/null +++ b/ee/server/services/Presence/actions/updateUserPresence.ts @@ -0,0 +1,31 @@ +// import { afterAll } from '../hooks'; +import { processPresenceAndStatus } from '../lib/processConnectionStatus'; +import { getCollection, Collections } from '../../mongo'; +import { IUserSession } from '../../../../../definition/IUserSession'; +import { IUser } from '../../../../../definition/IUser'; +import { USER_STATUS } from '../../../../../definition/UserStatus'; + +// export default afterAll; + +const projection = { + projection: { + statusDefault: 1, + }, +}; + +export async function updateUserPresence(uid: string): Promise { + const query = { _id: uid }; + + const UserSession = await getCollection(Collections.UserSession); + const User = await getCollection(Collections.User); + + const user = await User.findOne(query, projection); + if (!user) { return; } + + const userSessions = await UserSession.findOne(query) || { connections: [] }; + const { statusDefault = USER_STATUS.OFFLINE } = user; + const { status, statusConnection } = processPresenceAndStatus(userSessions.connections, statusDefault); + User.updateOne(query, { + $set: { status, statusConnection }, + }); +} diff --git a/ee/server/services/Presence/hooks/afterAll.ts b/ee/server/services/Presence/hooks/afterAll.ts new file mode 100755 index 0000000000000..b36b8c67e29f7 --- /dev/null +++ b/ee/server/services/Presence/hooks/afterAll.ts @@ -0,0 +1,5 @@ +import { updateUserPresence } from '../actions/updateUserPresence'; + +export async function afterAll(uid: string): Promise { + updateUserPresence(uid); +} diff --git a/ee/server/services/Presence/hooks/index.ts b/ee/server/services/Presence/hooks/index.ts new file mode 100755 index 0000000000000..66e5365b77b32 --- /dev/null +++ b/ee/server/services/Presence/hooks/index.ts @@ -0,0 +1 @@ +export * from './afterAll'; diff --git a/ee/server/services/Presence/lib/processConnectionStatus.ts b/ee/server/services/Presence/lib/processConnectionStatus.ts new file mode 100644 index 0000000000000..88e7b2c9111a3 --- /dev/null +++ b/ee/server/services/Presence/lib/processConnectionStatus.ts @@ -0,0 +1,25 @@ +import { IUserSessionConnection } from '../../../../../definition/IUserSession'; +import { USER_STATUS } from '../../../../../definition/UserStatus'; + +export const processConnectionStatus = (current: USER_STATUS, status: USER_STATUS): USER_STATUS => { + if (status === USER_STATUS.ONLINE) { + return USER_STATUS.ONLINE; + } + if (status !== USER_STATUS.OFFLINE) { + return status; + } + return current; +}; + +export const processStatus = (statusConnection: USER_STATUS, statusDefault: USER_STATUS): USER_STATUS => ( + statusConnection !== USER_STATUS.OFFLINE ? statusDefault : statusConnection +); + +export const processPresenceAndStatus = (userSessions: IUserSessionConnection[] = [], statusDefault = USER_STATUS.ONLINE): {status: USER_STATUS; statusConnection: USER_STATUS} => { + const statusConnection = userSessions.map((s) => s.status).reduce(processConnectionStatus, USER_STATUS.OFFLINE); + const status = processStatus(statusConnection, statusDefault); + return { + status, + statusConnection, + }; +}; diff --git a/ee/server/services/Presence/service.ts b/ee/server/services/Presence/service.ts new file mode 100644 index 0000000000000..56d40bbcb8d51 --- /dev/null +++ b/ee/server/services/Presence/service.ts @@ -0,0 +1,46 @@ +// import PromService from 'moleculer-prometheus'; + +// import { afterAll } from './hooks'; +// import actions from './actions'; + +// const { PROMETHEUS_PORT = 9100 } = process.env; + +// export default { +// settings: { +// port: PROMETHEUS_PORT, +// $noVersionPrefix: true, +// }, +// mixins: PROMETHEUS_PORT !== 'false' ? [PromService] : [], +// name: 'presence', +// TODO: implement hooks +// hooks: { +// after: { +// setConnectionStatus: 'afterAll', +// newConnection: 'afterAll', +// removeConnection: 'afterAll', +// }, +// }, +// TODO: implement events +// events: { +// async '$node.disconnected'({ node }): Promise { +// // this.removeNode(node._id); +// const affectedUsers = await this.broker.call('presence.removeLostConnections', { node._id }); +// return affectedUsers.forEach(({ _id: uid }) => this.broker.call('presence.updateUserPresence', { uid })); +// }, +// }, +// actions, +// methods: { +// // async removeNode(nodeID: string): Promise { +// // const affectedUsers = await this.broker.call('presence.removeLostConnections', { nodeID }); +// // return affectedUsers.forEach(({ _id: uid }) => this.broker.call('presence.updateUserPresence', { uid })); +// // }, +// afterAll, +// }, +// TODO: check +// async started(): Promise { +// setTimeout(async () => { +// const affectedUsers = await this.broker.call('presence.removeLostConnections'); +// return affectedUsers.forEach(({ _id: uid }) => this.broker.call('presence.updateUserPresence', { uid })); +// }, 100); +// }, +// }; diff --git a/ee/server/services/ecosystem.config.js b/ee/server/services/ecosystem.config.js index 4e9de52a9cf36..5f65d171e545c 100644 --- a/ee/server/services/ecosystem.config.js +++ b/ee/server/services/ecosystem.config.js @@ -3,7 +3,13 @@ module.exports = { name: 'Authorization', script: 'ts-node Authorization/Authorization.ts', watch: true, - instances: 2, + instances: 1, + // interpreter: '', + }, { + name: 'Presence', + script: 'ts-node Presence/Presence.ts', + watch: true, + instances: 1, // interpreter: '', }], }; diff --git a/server/sdk/index.ts b/server/sdk/index.ts index 349bb63e7565e..212eb7a1f39c9 100644 --- a/server/sdk/index.ts +++ b/server/sdk/index.ts @@ -3,8 +3,10 @@ import { AsyncLocalStorage } from 'async_hooks'; import { proxify } from './lib/proxify'; import { IAuthorization } from './types/IAuthorization'; import { IServiceContext } from './types/ServiceClass'; +import { IPresence } from './types/IPresence'; // TODO try not having to duplicate the namespace 'authorization' here export const Authorization = proxify('authorization'); +export const Presence = proxify('presence'); export const asyncLocalStorage = new AsyncLocalStorage(); diff --git a/server/sdk/types/IPresence.ts b/server/sdk/types/IPresence.ts new file mode 100644 index 0000000000000..a18b4cf2eed3b --- /dev/null +++ b/server/sdk/types/IPresence.ts @@ -0,0 +1,10 @@ +import { USER_STATUS } from '../../../definition/UserStatus'; + +export interface IPresence { + newConnection(uid: string, session: object): any; + removeConnection(uid: string, session: object): Promise; + removeLostConnections(nodeID: string): Promise; + setStatus(uid: string, status: USER_STATUS, statusText?: string): Promise; + setConnectionStatus(uid: string, status: USER_STATUS, session: string): Promise; + updateUserPresence(uid: string): Promise; +} From bd3c7ec363c5a2e08b2fc62f0f10bcdaa0b1f0ab Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Sat, 5 Sep 2020 14:23:17 -0300 Subject: [PATCH 020/198] Reorganization of Presence --- ee/server/services/Presence/Presence.ts | 2 +- ee/server/services/Presence/actions/index.ts | 45 ------------ ee/server/services/Presence/service.ts | 76 +++++++++----------- 3 files changed, 34 insertions(+), 89 deletions(-) delete mode 100755 ee/server/services/Presence/actions/index.ts mode change 100644 => 100755 ee/server/services/Presence/service.ts diff --git a/ee/server/services/Presence/Presence.ts b/ee/server/services/Presence/Presence.ts index 1bcf4edbd9e7c..99adc98c1905f 100755 --- a/ee/server/services/Presence/Presence.ts +++ b/ee/server/services/Presence/Presence.ts @@ -2,7 +2,7 @@ // import config from 'moleculer.config'; import { api } from '../../../../server/sdk/api'; -import { Presence } from './actions'; +import { Presence } from './service'; import '../../broker'; diff --git a/ee/server/services/Presence/actions/index.ts b/ee/server/services/Presence/actions/index.ts deleted file mode 100755 index 2754b2f2db792..0000000000000 --- a/ee/server/services/Presence/actions/index.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { newConnection } from './newConnection'; -import { removeConnection } from './removeConnection'; -import { removeLostConnections } from './removeLostConnections'; -import { setStatus, setConnectionStatus } from './setStatus'; -import { updateUserPresence } from './updateUserPresence'; -import { ServiceClass } from '../../../../../server/sdk/types/ServiceClass'; -import { IPresence } from '../../../../../server/sdk/types/IPresence'; -import { USER_STATUS } from '../../../../../definition/UserStatus'; - -export default { - newConnection, - removeConnection, - removeLostConnections, - setStatus, - setConnectionStatus, - updateUserPresence, -}; - -export class Presence extends ServiceClass implements IPresence { - protected name = 'presence'; - - async newConnection(uid: string, session: object): Promise { - return newConnection(uid, session, this.context); - } - - async removeConnection(uid: string, session: object): Promise { - return removeConnection(uid, session); - } - - async removeLostConnections(nodeID: string): Promise { - return removeLostConnections(nodeID, this.context); - } - - async setStatus(uid: string, status: USER_STATUS, statusText?: string): Promise { - return setStatus(uid, status, statusText); - } - - async setConnectionStatus(uid: string, status: USER_STATUS, session: string): Promise { - return setConnectionStatus(uid, status, session); - } - - async updateUserPresence(uid: string): Promise { - return updateUserPresence(uid); - } -} diff --git a/ee/server/services/Presence/service.ts b/ee/server/services/Presence/service.ts old mode 100644 new mode 100755 index 56d40bbcb8d51..3932f259523df --- a/ee/server/services/Presence/service.ts +++ b/ee/server/services/Presence/service.ts @@ -1,46 +1,36 @@ -// import PromService from 'moleculer-prometheus'; +import { newConnection } from './newConnection'; +import { removeConnection } from './removeConnection'; +import { removeLostConnections } from './removeLostConnections'; +import { setStatus, setConnectionStatus } from './setStatus'; +import { updateUserPresence } from './updateUserPresence'; +import { ServiceClass } from '../../../../../server/sdk/types/ServiceClass'; +import { IPresence } from '../../../../../server/sdk/types/IPresence'; +import { USER_STATUS } from '../../../../../definition/UserStatus'; -// import { afterAll } from './hooks'; -// import actions from './actions'; +export class Presence extends ServiceClass implements IPresence { + protected name = 'presence'; -// const { PROMETHEUS_PORT = 9100 } = process.env; + async newConnection(uid: string, session: object): Promise { + return newConnection(uid, session, this.context); + } -// export default { -// settings: { -// port: PROMETHEUS_PORT, -// $noVersionPrefix: true, -// }, -// mixins: PROMETHEUS_PORT !== 'false' ? [PromService] : [], -// name: 'presence', -// TODO: implement hooks -// hooks: { -// after: { -// setConnectionStatus: 'afterAll', -// newConnection: 'afterAll', -// removeConnection: 'afterAll', -// }, -// }, -// TODO: implement events -// events: { -// async '$node.disconnected'({ node }): Promise { -// // this.removeNode(node._id); -// const affectedUsers = await this.broker.call('presence.removeLostConnections', { node._id }); -// return affectedUsers.forEach(({ _id: uid }) => this.broker.call('presence.updateUserPresence', { uid })); -// }, -// }, -// actions, -// methods: { -// // async removeNode(nodeID: string): Promise { -// // const affectedUsers = await this.broker.call('presence.removeLostConnections', { nodeID }); -// // return affectedUsers.forEach(({ _id: uid }) => this.broker.call('presence.updateUserPresence', { uid })); -// // }, -// afterAll, -// }, -// TODO: check -// async started(): Promise { -// setTimeout(async () => { -// const affectedUsers = await this.broker.call('presence.removeLostConnections'); -// return affectedUsers.forEach(({ _id: uid }) => this.broker.call('presence.updateUserPresence', { uid })); -// }, 100); -// }, -// }; + async removeConnection(uid: string, session: object): Promise { + return removeConnection(uid, session); + } + + async removeLostConnections(nodeID: string): Promise { + return removeLostConnections(nodeID, this.context); + } + + async setStatus(uid: string, status: USER_STATUS, statusText?: string): Promise { + return setStatus(uid, status, statusText); + } + + async setConnectionStatus(uid: string, status: USER_STATUS, session: string): Promise { + return setConnectionStatus(uid, status, session); + } + + async updateUserPresence(uid: string): Promise { + return updateUserPresence(uid); + } +} From 3feffd9201290670fe003b04bd30091be5bd7829 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Sat, 5 Sep 2020 14:36:40 -0300 Subject: [PATCH 021/198] Fix types and resolve hooks --- .../Presence/actions/newConnection.ts | 2 +- .../Presence/actions/removeConnection.ts | 2 +- ee/server/services/Presence/service.ts | 72 +++++++++++++++---- server/sdk/types/IPresence.ts | 4 +- 4 files changed, 63 insertions(+), 17 deletions(-) diff --git a/ee/server/services/Presence/actions/newConnection.ts b/ee/server/services/Presence/actions/newConnection.ts index e75ff10e52c55..342553bbc7588 100755 --- a/ee/server/services/Presence/actions/newConnection.ts +++ b/ee/server/services/Presence/actions/newConnection.ts @@ -3,7 +3,7 @@ import { IServiceContext } from '../../../../../server/sdk/types/ServiceClass'; const status = 'online'; -export async function newConnection(uid: string, session: object, context?: IServiceContext): Promise { +export async function newConnection(uid: string, session: string, context?: IServiceContext): Promise<{uid: string; connectionId: string} | undefined> { const instanceId = context?.nodeID; if (!instanceId) { diff --git a/ee/server/services/Presence/actions/removeConnection.ts b/ee/server/services/Presence/actions/removeConnection.ts index ff5e72113bbe0..1f11aa6add7d6 100755 --- a/ee/server/services/Presence/actions/removeConnection.ts +++ b/ee/server/services/Presence/actions/removeConnection.ts @@ -1,6 +1,6 @@ import { getCollection, Collections } from '../../mongo'; -export async function removeConnection(uid: string, session: object): Promise { +export async function removeConnection(uid: string, session: string): Promise<{uid: string; session: string}> { const query = { 'connections.id': session, }; diff --git a/ee/server/services/Presence/service.ts b/ee/server/services/Presence/service.ts index 3932f259523df..0450cc2de46f7 100755 --- a/ee/server/services/Presence/service.ts +++ b/ee/server/services/Presence/service.ts @@ -1,21 +1,25 @@ -import { newConnection } from './newConnection'; -import { removeConnection } from './removeConnection'; -import { removeLostConnections } from './removeLostConnections'; -import { setStatus, setConnectionStatus } from './setStatus'; -import { updateUserPresence } from './updateUserPresence'; -import { ServiceClass } from '../../../../../server/sdk/types/ServiceClass'; -import { IPresence } from '../../../../../server/sdk/types/IPresence'; -import { USER_STATUS } from '../../../../../definition/UserStatus'; +import { newConnection } from './actions/newConnection'; +import { removeConnection } from './actions/removeConnection'; +import { removeLostConnections } from './actions/removeLostConnections'; +import { setStatus, setConnectionStatus } from './actions/setStatus'; +import { updateUserPresence } from './actions/updateUserPresence'; +import { ServiceClass } from '../../../../server/sdk/types/ServiceClass'; +import { IPresence } from '../../../../server/sdk/types/IPresence'; +import { USER_STATUS } from '../../../../definition/UserStatus'; export class Presence extends ServiceClass implements IPresence { protected name = 'presence'; - async newConnection(uid: string, session: object): Promise { - return newConnection(uid, session, this.context); + async newConnection(uid: string, session: string): Promise<{uid: string; connectionId: string} | undefined> { + const result = await newConnection(uid, session, this.context); + await updateUserPresence(uid); + return result; } - async removeConnection(uid: string, session: object): Promise { - return removeConnection(uid, session); + async removeConnection(uid: string, session: string): Promise<{uid: string; session: string}> { + const result = await removeConnection(uid, session); + await updateUserPresence(uid); + return result; } async removeLostConnections(nodeID: string): Promise { @@ -27,10 +31,52 @@ export class Presence extends ServiceClass implements IPresence { } async setConnectionStatus(uid: string, status: USER_STATUS, session: string): Promise { - return setConnectionStatus(uid, status, session); + const result = await setConnectionStatus(uid, status, session); + await updateUserPresence(uid); + return result; } async updateUserPresence(uid: string): Promise { return updateUserPresence(uid); } } + + +// import PromService from 'moleculer-prometheus'; + +// import { afterAll } from './hooks'; +// import actions from './actions'; + +// const { PROMETHEUS_PORT = 9100 } = process.env; + +// export default { +// settings: { +// port: PROMETHEUS_PORT, +// $noVersionPrefix: true, +// }, +// mixins: PROMETHEUS_PORT !== 'false' ? [PromService] : [], +// name: 'presence', +// TODO: implement events +// events: { +// async '$node.disconnected'({ node }): Promise { +// // this.removeNode(node._id); +// const affectedUsers = await this.broker.call('presence.removeLostConnections', { node._id }); +// return affectedUsers.forEach(({ _id: uid }) => this.broker.call('presence.updateUserPresence', { uid })); +// }, +// }, +// actions, +// methods: { +// // async removeNode(nodeID: string): Promise { +// // const affectedUsers = await this.broker.call('presence.removeLostConnections', { nodeID }); +// // return affectedUsers.forEach(({ _id: uid }) => this.broker.call('presence.updateUserPresence', { uid })); +// // }, +// afterAll, +// }, +// TODO: check +// async started(): Promise { +// setTimeout(async () => { +// const affectedUsers = await this.broker.call('presence.removeLostConnections'); +// return affectedUsers.forEach(({ _id: uid }) => this.broker.call('presence.updateUserPresence', { uid })); +// }, 100); +// }, +// }; diff --git a/server/sdk/types/IPresence.ts b/server/sdk/types/IPresence.ts index a18b4cf2eed3b..cf777618da369 100644 --- a/server/sdk/types/IPresence.ts +++ b/server/sdk/types/IPresence.ts @@ -1,8 +1,8 @@ import { USER_STATUS } from '../../../definition/UserStatus'; export interface IPresence { - newConnection(uid: string, session: object): any; - removeConnection(uid: string, session: object): Promise; + newConnection(uid: string, session: string): Promise<{uid: string; connectionId: string} | undefined>; + removeConnection(uid: string, session: string): Promise<{uid: string; session: string}>; removeLostConnections(nodeID: string): Promise; setStatus(uid: string, status: USER_STATUS, statusText?: string): Promise; setConnectionStatus(uid: string, status: USER_STATUS, session: string): Promise; From 073dcce99f5ab57be4d13f5cbd6c678871557a03 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Sat, 5 Sep 2020 16:12:50 -0300 Subject: [PATCH 022/198] Finish Presence implementation --- ee/server/broker.ts | 34 ++++++++++++-- .../Presence/actions/removeLostConnections.ts | 3 +- ee/server/services/Presence/hooks/afterAll.ts | 5 -- ee/server/services/Presence/hooks/index.ts | 1 - ee/server/services/Presence/service.ts | 46 +++++++------------ server/sdk/types/IPresence.ts | 3 +- server/sdk/types/ServiceClass.ts | 15 +++++- 7 files changed, 64 insertions(+), 43 deletions(-) delete mode 100755 ee/server/services/Presence/hooks/afterAll.ts delete mode 100755 ee/server/services/Presence/hooks/index.ts diff --git a/ee/server/broker.ts b/ee/server/broker.ts index 1745fcb2089f1..86ce4346bbf7a 100644 --- a/ee/server/broker.ts +++ b/ee/server/broker.ts @@ -1,4 +1,4 @@ -import { ServiceBroker, Context } from 'moleculer'; +import { ServiceBroker, Context, ServiceSchema } from 'moleculer'; import { asyncLocalStorage } from '../../server/sdk'; import { api } from '../../server/sdk/api'; @@ -6,6 +6,18 @@ import { IBroker, IBrokerNode } from '../../server/sdk/types/IBroker'; import { ServiceClass } from '../../server/sdk/types/ServiceClass'; // import { onLicense } from '../app/license/server'; +const events: {[k: string]: string} = { + onNodeConnected: '$node.connected', + onNodeUpdated: '$node.updated', + onNodeDisconnected: '$node.disconnected', +}; + +const lifecycle: {[k: string]: string} = { + created: 'created', + started: 'started', + stopped: 'stopped', +}; + class NetworkBroker implements IBroker { private broker: ServiceBroker; @@ -20,18 +32,34 @@ class NetworkBroker implements IBroker { createService(instance: ServiceClass): void { const name = instance.getName(); - const service = { + const service: ServiceSchema = { name, - actions: {} as any, + actions: {}, + events: {}, }; + if (!service.events || !service.actions) { + return; + } + const methods = instance.constructor?.name === 'Object' ? Object.getOwnPropertyNames(instance) : Object.getOwnPropertyNames(Object.getPrototypeOf(instance)); for (const method of methods) { if (method === 'constructor') { continue; } + const i = instance as any; + if (method.match(/^on[A-Z]/)) { + service.events[events[method]] = i[method].bind(i); + continue; + } + + if (lifecycle[method]) { + service[method] = i[method].bind(i); + continue; + } + service.actions[method] = async (ctx: Context<[]>): Promise => asyncLocalStorage.run({ id: ctx.id, nodeID: ctx.nodeID, diff --git a/ee/server/services/Presence/actions/removeLostConnections.ts b/ee/server/services/Presence/actions/removeLostConnections.ts index 955c25d38872e..ae6a9fbacc60b 100755 --- a/ee/server/services/Presence/actions/removeLostConnections.ts +++ b/ee/server/services/Presence/actions/removeLostConnections.ts @@ -8,7 +8,8 @@ async function getAffectedUsers(model: Collection, query: object): Promise _id); } -export async function removeLostConnections(nodeID: string, context?: IServiceContext): Promise { +// TODO: Change this to use find and modify +export async function removeLostConnections(nodeID?: string, context?: IServiceContext): Promise { const UserSession = await getCollection(Collections.UserSession); if (nodeID) { diff --git a/ee/server/services/Presence/hooks/afterAll.ts b/ee/server/services/Presence/hooks/afterAll.ts deleted file mode 100755 index b36b8c67e29f7..0000000000000 --- a/ee/server/services/Presence/hooks/afterAll.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { updateUserPresence } from '../actions/updateUserPresence'; - -export async function afterAll(uid: string): Promise { - updateUserPresence(uid); -} diff --git a/ee/server/services/Presence/hooks/index.ts b/ee/server/services/Presence/hooks/index.ts deleted file mode 100755 index 66e5365b77b32..0000000000000 --- a/ee/server/services/Presence/hooks/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './afterAll'; diff --git a/ee/server/services/Presence/service.ts b/ee/server/services/Presence/service.ts index 0450cc2de46f7..47f4eeddcf22c 100755 --- a/ee/server/services/Presence/service.ts +++ b/ee/server/services/Presence/service.ts @@ -6,10 +6,25 @@ import { updateUserPresence } from './actions/updateUserPresence'; import { ServiceClass } from '../../../../server/sdk/types/ServiceClass'; import { IPresence } from '../../../../server/sdk/types/IPresence'; import { USER_STATUS } from '../../../../definition/UserStatus'; +import { IBrokerNode } from '../../../../server/sdk/types/IBroker'; + +// import PromService from 'moleculer-prometheus'; export class Presence extends ServiceClass implements IPresence { protected name = 'presence'; + async onNodeDisconnected({ node }: {node: IBrokerNode}): Promise { + const affectedUsers = await this.removeLostConnections(node.id); + return affectedUsers.forEach((uid) => this.updateUserPresence(uid)); + } + + async started(): Promise { + setTimeout(async () => { + const affectedUsers = await this.removeLostConnections(); + return affectedUsers.forEach((uid) => this.updateUserPresence(uid)); + }, 100); + } + async newConnection(uid: string, session: string): Promise<{uid: string; connectionId: string} | undefined> { const result = await newConnection(uid, session, this.context); await updateUserPresence(uid); @@ -22,7 +37,7 @@ export class Presence extends ServiceClass implements IPresence { return result; } - async removeLostConnections(nodeID: string): Promise { + async removeLostConnections(nodeID?: string): Promise { return removeLostConnections(nodeID, this.context); } @@ -41,12 +56,6 @@ export class Presence extends ServiceClass implements IPresence { } } - -// import PromService from 'moleculer-prometheus'; - -// import { afterAll } from './hooks'; -// import actions from './actions'; - // const { PROMETHEUS_PORT = 9100 } = process.env; // export default { @@ -55,28 +64,5 @@ export class Presence extends ServiceClass implements IPresence { // $noVersionPrefix: true, // }, // mixins: PROMETHEUS_PORT !== 'false' ? [PromService] : [], -// name: 'presence', -// TODO: implement events -// events: { -// async '$node.disconnected'({ node }): Promise { -// // this.removeNode(node._id); -// const affectedUsers = await this.broker.call('presence.removeLostConnections', { node._id }); -// return affectedUsers.forEach(({ _id: uid }) => this.broker.call('presence.updateUserPresence', { uid })); -// }, -// }, -// actions, -// methods: { -// // async removeNode(nodeID: string): Promise { -// // const affectedUsers = await this.broker.call('presence.removeLostConnections', { nodeID }); -// // return affectedUsers.forEach(({ _id: uid }) => this.broker.call('presence.updateUserPresence', { uid })); -// // }, -// afterAll, -// }, -// TODO: check -// async started(): Promise { -// setTimeout(async () => { -// const affectedUsers = await this.broker.call('presence.removeLostConnections'); -// return affectedUsers.forEach(({ _id: uid }) => this.broker.call('presence.updateUserPresence', { uid })); -// }, 100); // }, // }; diff --git a/server/sdk/types/IPresence.ts b/server/sdk/types/IPresence.ts index cf777618da369..54b9e9c5a21b1 100644 --- a/server/sdk/types/IPresence.ts +++ b/server/sdk/types/IPresence.ts @@ -1,6 +1,7 @@ import { USER_STATUS } from '../../../definition/UserStatus'; +import { IServiceClass } from './ServiceClass'; -export interface IPresence { +export interface IPresence extends IServiceClass { newConnection(uid: string, session: string): Promise<{uid: string; connectionId: string} | undefined>; removeConnection(uid: string, session: string): Promise<{uid: string; session: string}>; removeLostConnections(nodeID: string): Promise; diff --git a/server/sdk/types/ServiceClass.ts b/server/sdk/types/ServiceClass.ts index f9fd648073754..5ce2c575ccf58 100644 --- a/server/sdk/types/ServiceClass.ts +++ b/server/sdk/types/ServiceClass.ts @@ -1,5 +1,5 @@ import { asyncLocalStorage } from '..'; -import { IBroker } from './IBroker'; +import { IBroker, IBrokerNode } from './IBroker'; export interface IServiceContext { id: string; // Context ID @@ -20,7 +20,18 @@ export interface IServiceContext { // span: Span; // Current active span. } -export abstract class ServiceClass { +export interface IServiceClass { + getName(): string; + onNodeConnected?({ node, reconnected }: {node: IBrokerNode; reconnected: boolean}): void; + onNodeUpdated?({ node }: {node: IBrokerNode }): void; + onNodeDisconnected?({ node, unexpected }: {node: IBrokerNode; unexpected: boolean}): Promise; + + created?(): Promise; + started?(): Promise; + stopped?(): Promise; +} + +export abstract class ServiceClass implements IServiceClass { protected name: string; getName(): string { From 1cea003293512bf523e067e242d3f9d4cf1fb326 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Sat, 5 Sep 2020 17:11:58 -0300 Subject: [PATCH 023/198] Implement service Accounts --- definition/IUser.ts | 2 +- ee/server/services/Account/Accounts.ts | 6 + ee/server/services/Account/lib/utils.ts | 62 ++++ ee/server/services/Account/service.ts | 89 ++++++ ee/server/services/ecosystem.config.js | 6 + ee/server/services/package-lock.json | 376 ++++++++++++++++++++++-- ee/server/services/package.json | 4 +- server/sdk/index.ts | 2 + server/sdk/types/IAccount.ts | 12 + 9 files changed, 525 insertions(+), 34 deletions(-) create mode 100644 ee/server/services/Account/Accounts.ts create mode 100644 ee/server/services/Account/lib/utils.ts create mode 100644 ee/server/services/Account/service.ts create mode 100644 server/sdk/types/IAccount.ts diff --git a/definition/IUser.ts b/definition/IUser.ts index 319e909e25dc7..5626557676895 100644 --- a/definition/IUser.ts +++ b/definition/IUser.ts @@ -29,7 +29,7 @@ export interface IUserEmailCode { expire: Date; } -type LoginToken = ILoginToken & IPersonalAccessToken; +type LoginToken = IMeteorLoginToken & IPersonalAccessToken; export interface IUserServices { password?: { diff --git a/ee/server/services/Account/Accounts.ts b/ee/server/services/Account/Accounts.ts new file mode 100644 index 0000000000000..c38928108035f --- /dev/null +++ b/ee/server/services/Account/Accounts.ts @@ -0,0 +1,6 @@ +import { api } from '../../../../server/sdk/api'; +import { Account } from './service'; + +import '../../broker'; + +api.registerService(new Account()); diff --git a/ee/server/services/Account/lib/utils.ts b/ee/server/services/Account/lib/utils.ts new file mode 100644 index 0000000000000..56f202ddcf109 --- /dev/null +++ b/ee/server/services/Account/lib/utils.ts @@ -0,0 +1,62 @@ +import crypto from 'crypto'; + +import bcrypt from 'bcrypt'; +import { v4 as uuidv4 } from 'uuid'; + +export interface IStampedToken { + token: string; + when: Date; + [key: string]: any; +} + +export interface IHashedStampedToken { + when: Date; + hashedToken: string; +} + +type Password = string | { + digest: string; +} + +export const getPassword = (password: Password): string => { + if (typeof password === 'string') { + return crypto.createHash('sha256').update(password).digest('hex'); + } + if (typeof password.digest === 'undefined') { + throw new Error('invalid password'); + } + return password.digest; +}; + +// https://github.com/meteor/meteor/blob/c5b51b0fc2a8cef498b9390ebcb4925e02de83e8/packages/accounts-base/accounts_server.js#L934 +export const _generateStampedLoginToken = (): IStampedToken => ({ + token: uuidv4(), + when: new Date(), +}); + +// https://github.com/meteor/meteor/blob/c5b51b0fc2a8cef498b9390ebcb4925e02de83e8/packages/accounts-base/accounts_server.js#L780 +export const _hashLoginToken = (loginToken: string): string => { + const hash = crypto.createHash('sha256'); + hash.update(loginToken); + return hash.digest('base64'); +}; + +// https://github.com/meteor/meteor/blob/c5b51b0fc2a8cef498b9390ebcb4925e02de83e8/packages/accounts-base/accounts_server.js#L787 +export const _hashStampedToken = (stampedToken: IStampedToken): IHashedStampedToken => { + const hashedStampedToken = Object.keys(stampedToken).reduce( + (prev, key) => (key === 'token' + ? prev + : { ...prev, [key]: stampedToken[key] }), + {}, + ); + + return { + ...hashedStampedToken, + hashedToken: _hashLoginToken(stampedToken.token), + } as IHashedStampedToken; +}; + +export const validatePassword = (password: string, bcryptPassword: string): Promise => bcrypt.compare(getPassword(password), bcryptPassword); + +const expiryDaysInMS = 15 * 60 * 60 * 24 * 1000; +export const _tokenExpiration = (when: string | Date): Date => new Date(new Date(when).getTime() + expiryDaysInMS); diff --git a/ee/server/services/Account/service.ts b/ee/server/services/Account/service.ts new file mode 100644 index 0000000000000..6971f9fd0ee55 --- /dev/null +++ b/ee/server/services/Account/service.ts @@ -0,0 +1,89 @@ +import { + IStampedToken, + _generateStampedLoginToken, + _hashStampedToken, + _hashLoginToken, + _tokenExpiration, + validatePassword, +} from './lib/utils'; +import { getCollection, Collections } from '../mongo'; +import { IUser } from '../../../../definition/IUser'; +import { ServiceClass } from '../../../../server/sdk/types/ServiceClass'; +import { IAccount, ILoginResult } from '../../../../server/sdk/types/IAccount'; + + +const saveSession = async (uid: string, newToken: IStampedToken): Promise => { + const Users = await getCollection(Collections.User); + await Users.updateOne({ _id: uid }, { + $push: { + 'services.resume.loginTokens': _hashStampedToken(newToken), + }, + }); +}; + +const loginViaResume = async (resume: string): Promise => { + const Users = await getCollection(Collections.User); + const hashedToken = _hashLoginToken(resume); + + const user = await Users.findOne({ + 'services.resume.loginTokens.hashedToken': hashedToken, + }, { + projection: { + 'services.resume.loginTokens': 1, + }, + }); + if (!user) { + return false; + } + + const { when } = user.services?.resume?.loginTokens?.find((token) => + token.hashedToken === hashedToken, + ) || {}; + + return { + uid: user._id, + token: resume, + tokenExpires: when ? _tokenExpiration(when) : undefined, + type: 'resume', + }; +}; + +const loginViaUsername = async ({ username }: {username: string}, password: string): Promise => { + const Users = await getCollection(Collections.User); + const user = await Users.findOne({ username }, { projection: { 'services.password.bcrypt': 1 } }); + if (!user) { + return false; + } + + const valid = user.services?.password?.bcrypt && validatePassword(password, user.services.password.bcrypt); + if (!valid) { + return false; + } + + const newToken = _generateStampedLoginToken(); + + await saveSession(user._id, newToken); + + return { + uid: user._id, + token: newToken.token, + tokenExpires: _tokenExpiration(newToken.when), + type: 'password', + }; +}; + +export class Account extends ServiceClass implements IAccount { + protected name = 'accounts'; + + async login({ resume, user, password }: {resume: string; user: {username: string}; password: string}): Promise { + if (resume) { + return loginViaResume(resume); + } + + if (user && password) { + return loginViaUsername(user, password); + } + + return false; + } +} diff --git a/ee/server/services/ecosystem.config.js b/ee/server/services/ecosystem.config.js index 5f65d171e545c..02c066662f99d 100644 --- a/ee/server/services/ecosystem.config.js +++ b/ee/server/services/ecosystem.config.js @@ -11,5 +11,11 @@ module.exports = { watch: true, instances: 1, // interpreter: '', + }, { + name: 'Account', + script: 'ts-node Account/Account.ts', + watch: true, + instances: 1, + // interpreter: '', }], }; diff --git a/ee/server/services/package-lock.json b/ee/server/services/package-lock.json index 00c3b26e8f205..64a64a5fa75ec 100644 --- a/ee/server/services/package-lock.json +++ b/ee/server/services/package-lock.json @@ -15,6 +15,14 @@ "semver": "^5.5.0", "shimmer": "^1.2.0", "uuid": "^3.2.1" + }, + "dependencies": { + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "dev": true + } } }, "@opencensus/propagation-b3": { @@ -39,6 +47,12 @@ "shimmer": "^1.2.0", "uuid": "^3.2.1" } + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "dev": true } } }, @@ -210,6 +224,11 @@ "integrity": "sha512-Wk7nG1JSaMfMpoMJDKUsWYugliB2Vy55pdjLpmLixeyMi7HizW2I/9QoxsPCkXl3dO+ZOVqPumKaDUv5zJu2uQ==", "dev": true }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, "agent-base": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", @@ -240,6 +259,11 @@ "integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==", "dev": true }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, "ansi-styles": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", @@ -260,6 +284,20 @@ "picomatch": "^2.0.4" } }, + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" + }, + "are-we-there-yet": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, "arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", @@ -326,8 +364,16 @@ "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "bcrypt": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-4.0.1.tgz", + "integrity": "sha512-hSIZHkUxIDS5zA2o00Kf2O5RfVbQ888n54xQoF/eIaquU4uaLxK8vhhBdktd0B3n2MjkcAWzv4mnhogykBKOUQ==", + "requires": { + "node-addon-api": "^2.0.0", + "node-pre-gyp": "0.14.0" + } }, "binary-extensions": { "version": "2.1.0", @@ -354,7 +400,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -418,6 +463,11 @@ "readdirp": "~3.4.0" } }, + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + }, "cli-tableau": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/cli-tableau/-/cli-tableau-2.0.1.tgz", @@ -433,6 +483,11 @@ "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", "dev": true }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + }, "color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -457,8 +512,12 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" }, "continuation-local-storage": { "version": "3.2.1", @@ -505,6 +564,11 @@ "ms": "^2.1.1" } }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" + }, "deep-is": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", @@ -522,6 +586,11 @@ "esprima": "3.x.x" } }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" + }, "denque": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/denque/-/denque-1.4.1.tgz", @@ -533,6 +602,11 @@ "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", "dev": true }, + "detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" + }, "diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -682,11 +756,18 @@ } } }, + "fs-minipass": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", + "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", + "requires": { + "minipass": "^2.6.0" + } + }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "fsevents": { "version": "2.1.3", @@ -731,6 +812,21 @@ } } }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, "get-uri": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-2.0.4.tgz", @@ -766,7 +862,6 @@ "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -791,6 +886,11 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" + }, "http-errors": { "version": "1.7.3", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", @@ -856,16 +956,22 @@ "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, "requires": { "safer-buffer": ">= 2.1.2 < 3" } }, + "ignore-walk": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz", + "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==", + "requires": { + "minimatch": "^3.0.4" + } + }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -876,6 +982,11 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" + }, "ip": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", @@ -897,6 +1008,14 @@ "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", "dev": true }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "^1.0.0" + } + }, "is-glob": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", @@ -970,11 +1089,32 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, "requires": { "brace-expansion": "^1.1.7" } }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + }, + "minipass": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", + "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", + "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", + "requires": { + "minipass": "^2.9.0" + } + }, "mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", @@ -1018,8 +1158,7 @@ "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "mute-stream": { "version": "0.0.8", @@ -1031,7 +1170,6 @@ "version": "2.4.0", "resolved": "https://registry.npmjs.org/needle/-/needle-2.4.0.tgz", "integrity": "sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg==", - "dev": true, "requires": { "debug": "^3.2.6", "iconv-lite": "^0.4.4", @@ -1042,7 +1180,6 @@ "version": "3.2.6", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, "requires": { "ms": "^2.1.1" } @@ -1055,12 +1192,87 @@ "integrity": "sha1-ICl+idhvb2QA8lDZ9Pa0wZRfzTU=", "dev": true }, + "node-addon-api": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz", + "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==" + }, + "node-pre-gyp": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.14.0.tgz", + "integrity": "sha512-+CvDC7ZttU/sSt9rFjix/P05iS43qHCOOGzcr3Ry99bXG7VX953+vFyEuph/tfqoYu8dttBkE86JSKBO2OzcxA==", + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.1", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.2.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4.4.2" + }, + "dependencies": { + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "requires": { + "minimist": "^1.2.5" + } + } + } + }, + "nopt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", + "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true }, + "npm-bundled": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz", + "integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==", + "requires": { + "npm-normalize-package-bin": "^1.0.1" + } + }, + "npm-normalize-package-bin": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", + "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==" + }, + "npm-packlist": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz", + "integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==", + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1", + "npm-normalize-package-bin": "^1.0.1" + } + }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, "nssocket": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/nssocket/-/nssocket-0.6.0.tgz", @@ -1079,11 +1291,20 @@ } } }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, "requires": { "wrappy": "1" } @@ -1102,6 +1323,25 @@ "word-wrap": "~1.2.3" } }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" + }, + "osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, "pac-proxy-agent": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-3.0.1.tgz", @@ -1134,8 +1374,7 @@ "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, "path-parse": { "version": "1.0.6", @@ -1327,6 +1566,17 @@ "unpipe": "1.0.0" } }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + } + }, "read": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", @@ -1400,6 +1650,14 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz", "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=" }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "requires": { + "glob": "^7.1.3" + } + }, "run-series": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/run-series/-/run-series-1.1.8.tgz", @@ -1414,8 +1672,7 @@ "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "saslprep": { "version": "1.0.3", @@ -1429,14 +1686,18 @@ "sax": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", - "dev": true + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" }, "semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, "setprototypeof": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", @@ -1452,8 +1713,7 @@ "signal-exit": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", - "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", - "dev": true + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" }, "smart-buffer": { "version": "4.1.0", @@ -1529,6 +1789,16 @@ "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", "dev": true }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -1544,6 +1814,19 @@ } } }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" + }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -1560,6 +1843,30 @@ "dev": true, "optional": true }, + "tar": { + "version": "4.4.13", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", + "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.8.6", + "minizlib": "^1.2.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.3" + }, + "dependencies": { + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "requires": { + "minimist": "^1.2.5" + } + } + } + }, "thunkify": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/thunkify/-/thunkify-2.1.2.tgz", @@ -1645,10 +1952,9 @@ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "dev": true + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz", + "integrity": "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==" }, "vizion": { "version": "0.2.13", @@ -1667,6 +1973,14 @@ } } }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "requires": { + "string-width": "^1.0.2 || 2" + } + }, "word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", @@ -1676,8 +1990,7 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "ws": { "version": "7.2.5", @@ -1694,8 +2007,7 @@ "yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" }, "yamljs": { "version": "0.3.0", diff --git a/ee/server/services/package.json b/ee/server/services/package.json index efe027b192842..9697f4b089315 100644 --- a/ee/server/services/package.json +++ b/ee/server/services/package.json @@ -16,7 +16,9 @@ "author": "Rocket.Chat", "license": "MIT", "dependencies": { - "mongodb": "^3.6.1" + "mongodb": "^3.6.1", + "bcrypt": "^4.0.1", + "uuid": "^7.0.3" }, "devDependencies": { "@types/node": "^14.6.4", diff --git a/server/sdk/index.ts b/server/sdk/index.ts index 212eb7a1f39c9..3bac64e786ae6 100644 --- a/server/sdk/index.ts +++ b/server/sdk/index.ts @@ -4,9 +4,11 @@ import { proxify } from './lib/proxify'; import { IAuthorization } from './types/IAuthorization'; import { IServiceContext } from './types/ServiceClass'; import { IPresence } from './types/IPresence'; +import { IAccount } from './types/IAccount'; // TODO try not having to duplicate the namespace 'authorization' here export const Authorization = proxify('authorization'); export const Presence = proxify('presence'); +export const Account = proxify('accounts'); export const asyncLocalStorage = new AsyncLocalStorage(); diff --git a/server/sdk/types/IAccount.ts b/server/sdk/types/IAccount.ts new file mode 100644 index 0000000000000..c75ad9fc6ecc8 --- /dev/null +++ b/server/sdk/types/IAccount.ts @@ -0,0 +1,12 @@ +import { IServiceClass } from './ServiceClass'; + +export interface ILoginResult { + uid: string; + token: string; + tokenExpires?: Date; + type: 'resume' | 'password'; +} + +export interface IAccount extends IServiceClass { + login({ resume, user, password }: {resume: string; user: {username: string}; password: string}): Promise; +} From 8730ae37999dd8c7a58cbee174569c230d23a3ea Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Sat, 5 Sep 2020 17:14:47 -0300 Subject: [PATCH 024/198] Fix build error --- app/meteor-accounts-saml/server/lib/Utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/meteor-accounts-saml/server/lib/Utils.ts b/app/meteor-accounts-saml/server/lib/Utils.ts index 3a76a4e6c6581..d31421d45c2bf 100644 --- a/app/meteor-accounts-saml/server/lib/Utils.ts +++ b/app/meteor-accounts-saml/server/lib/Utils.ts @@ -393,7 +393,7 @@ export class SAMLUtils { return mainValue; } - public static convertArrayBufferToString(buffer: ArrayBuffer, encoding = 'utf8'): string { + public static convertArrayBufferToString(buffer: ArrayBuffer, encoding: BufferEncoding = 'utf8'): string { return Buffer.from(buffer).toString(encoding); } From cd003cec068c60dccee921876e1216c1fb38d044 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Sat, 5 Sep 2020 17:20:10 -0300 Subject: [PATCH 025/198] Fix typo --- ee/server/services/Account/{Accounts.ts => Account.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename ee/server/services/Account/{Accounts.ts => Account.ts} (100%) diff --git a/ee/server/services/Account/Accounts.ts b/ee/server/services/Account/Account.ts similarity index 100% rename from ee/server/services/Account/Accounts.ts rename to ee/server/services/Account/Account.ts From 1e2b1d667416045b58a924b5d5ac72d936bdbc82 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Sat, 5 Sep 2020 18:46:14 -0300 Subject: [PATCH 026/198] Implement StreamHub --- definition/IInquiry.ts | 4 + definition/IMessage.ts | 4 + definition/IRole.ts | 4 + definition/IRoom.ts | 4 + definition/ISubscription.ts | 4 + definition/IUser.ts | 1 + ee/server/broker.ts | 4 + ee/server/services/StreamHub/StreamHub.ts | 9 ++ ee/server/services/StreamHub/service.ts | 62 +++++++++++++ ee/server/services/StreamHub/utils.ts | 5 ++ .../services/StreamHub/watchInquiries.ts | 15 ++++ ee/server/services/StreamHub/watchMessages.ts | 20 +++++ ee/server/services/StreamHub/watchRoles.ts | 24 +++++ ee/server/services/StreamHub/watchRooms.ts | 26 ++++++ ee/server/services/StreamHub/watchSettings.ts | 28 ++++++ .../services/StreamHub/watchSubscriptions.ts | 26 ++++++ ee/server/services/StreamHub/watchUsers.ts | 87 +++++++++++++++++++ ee/server/services/package-lock.json | 30 ++++++- ee/server/services/package.json | 4 +- server/sdk/lib/Api.ts | 4 + server/sdk/lib/LocalBroker.ts | 6 ++ server/sdk/types/IBroker.ts | 1 + 22 files changed, 369 insertions(+), 3 deletions(-) create mode 100644 definition/IInquiry.ts create mode 100644 definition/IMessage.ts create mode 100644 definition/IRole.ts create mode 100644 definition/IRoom.ts create mode 100644 definition/ISubscription.ts create mode 100755 ee/server/services/StreamHub/StreamHub.ts create mode 100755 ee/server/services/StreamHub/service.ts create mode 100644 ee/server/services/StreamHub/utils.ts create mode 100644 ee/server/services/StreamHub/watchInquiries.ts create mode 100644 ee/server/services/StreamHub/watchMessages.ts create mode 100644 ee/server/services/StreamHub/watchRoles.ts create mode 100644 ee/server/services/StreamHub/watchRooms.ts create mode 100644 ee/server/services/StreamHub/watchSettings.ts create mode 100644 ee/server/services/StreamHub/watchSubscriptions.ts create mode 100644 ee/server/services/StreamHub/watchUsers.ts diff --git a/definition/IInquiry.ts b/definition/IInquiry.ts new file mode 100644 index 0000000000000..e2b17d93abfa8 --- /dev/null +++ b/definition/IInquiry.ts @@ -0,0 +1,4 @@ +export interface IInquiry { + _id: string; + _updatedAt?: Date; +} diff --git a/definition/IMessage.ts b/definition/IMessage.ts new file mode 100644 index 0000000000000..7bf96c26e07ca --- /dev/null +++ b/definition/IMessage.ts @@ -0,0 +1,4 @@ +export interface IMessage { + _id: string; + _updatedAt?: Date; +} diff --git a/definition/IRole.ts b/definition/IRole.ts new file mode 100644 index 0000000000000..e4c5106ae295f --- /dev/null +++ b/definition/IRole.ts @@ -0,0 +1,4 @@ +export interface IRole { + _id: string; + _updatedAt?: Date; +} diff --git a/definition/IRoom.ts b/definition/IRoom.ts new file mode 100644 index 0000000000000..6e6cbe368c1e7 --- /dev/null +++ b/definition/IRoom.ts @@ -0,0 +1,4 @@ +export interface IRoom { + _id: string; + _updatedAt?: Date; +} diff --git a/definition/ISubscription.ts b/definition/ISubscription.ts new file mode 100644 index 0000000000000..c45ecbe5c4764 --- /dev/null +++ b/definition/ISubscription.ts @@ -0,0 +1,4 @@ +export interface ISubscription { + _id: string; + _updatedAt?: Date; +} diff --git a/definition/IUser.ts b/definition/IUser.ts index 5626557676895..0260047c7774e 100644 --- a/definition/IUser.ts +++ b/definition/IUser.ts @@ -92,6 +92,7 @@ export interface IUser { utcOffset?: number; language?: string; statusDefault?: USER_STATUS; + statusText?: string; oauth?: { authorizedClients: string[]; }; diff --git a/ee/server/broker.ts b/ee/server/broker.ts index 86ce4346bbf7a..3d5c4107024da 100644 --- a/ee/server/broker.ts +++ b/ee/server/broker.ts @@ -71,6 +71,10 @@ class NetworkBroker implements IBroker { this.broker.createService(service); } + async broadcast(eventName: string, data: D): Promise { + return this.broker.broadcast(eventName, data); + } + async nodeList(): Promise { return this.broker.call('$node.list'); } diff --git a/ee/server/services/StreamHub/StreamHub.ts b/ee/server/services/StreamHub/StreamHub.ts new file mode 100755 index 0000000000000..495e0aca40b74 --- /dev/null +++ b/ee/server/services/StreamHub/StreamHub.ts @@ -0,0 +1,9 @@ +// TODO: check config +// import config from 'moleculer.config'; + +import { api } from '../../../../server/sdk/api'; +import { StreamHub } from './service'; + +import '../../broker'; + +api.registerService(new StreamHub()); diff --git a/ee/server/services/StreamHub/service.ts b/ee/server/services/StreamHub/service.ts new file mode 100755 index 0000000000000..94b025d5b2b83 --- /dev/null +++ b/ee/server/services/StreamHub/service.ts @@ -0,0 +1,62 @@ +import { watchUsers } from './watchUsers'; +import { watchMessages } from './watchMessages'; +import { watchSettings } from './watchSettings'; +import { watchRooms } from './watchRooms'; +import { watchSubscriptions } from './watchSubscriptions'; +import { watchRoles } from './watchRoles'; +import { watchInquiries } from './watchInquiries'; +import { getConnection } from '../mongo'; +import { ServiceClass, IServiceClass } from '../../../../server/sdk/types/ServiceClass'; + +export class StreamHub extends ServiceClass implements IServiceClass { + protected name = 'hub'; + + async created(): Promise { + const db = await getConnection(); + + const Trash = db.collection('rocketchat_trash'); + const Users = db.collection('users'); + const Roles = db.collection('rocketchat_roles'); + const Messages = db.collection('rocketchat_message'); + const Subscriptions = db.collection('rocketchat_subscription'); + const Rooms = db.collection('rocketchat_room'); + const Settings = db.collection('rocketchat_settings'); + const Inquiry = db.collection('rocketchat_livechat_inquiry'); + + Users.watch([], { fullDocument: 'updateLookup' }).on('change', watchUsers); + + Messages.watch([{ + $addFields: { + tmpfields: { + $objectToArray: '$updateDescription.updatedFields', + }, + } }, { + $match: { + 'tmpfields.k': { + $nin: ['u.username'], // avoid flood the streamer with messages changes (by username change) + }, + } }], { fullDocument: 'updateLookup' }).on('change', watchMessages); + + Subscriptions.watch([], { fullDocument: 'updateLookup' }).on('change', watchSubscriptions(Trash)); + + Inquiry.watch([], { fullDocument: 'updateLookup' }).on('change', watchInquiries); + + Rooms.watch([], { fullDocument: 'updateLookup' }).on('change', watchRooms); + + Roles.watch([], { fullDocument: 'updateLookup' }).on('change', watchRoles); + + Settings.watch([{ + $addFields: { + tmpfields: { + $objectToArray: '$updateDescription.updatedFields', + }, + }, + }, { + $match: { + 'tmpfields.k': { + $in: ['value'], // avoid flood the streamer with messages changes (by username change) + }, + }, + }], { fullDocument: 'updateLookup' }).on('change', watchSettings); + } +} diff --git a/ee/server/services/StreamHub/utils.ts b/ee/server/services/StreamHub/utils.ts new file mode 100644 index 0000000000000..92cfa674656d1 --- /dev/null +++ b/ee/server/services/StreamHub/utils.ts @@ -0,0 +1,5 @@ +export const normalize: {[key: string]: string} = { + update: 'updated', + insert: 'inserted', + remove: 'removed', +}; diff --git a/ee/server/services/StreamHub/watchInquiries.ts b/ee/server/services/StreamHub/watchInquiries.ts new file mode 100644 index 0000000000000..34a880520e00f --- /dev/null +++ b/ee/server/services/StreamHub/watchInquiries.ts @@ -0,0 +1,15 @@ +import { ChangeEvent } from 'mongodb'; + +import { normalize } from './utils'; +import { IInquiry } from '../../../../definition/IInquiry'; +import { api } from '../../../../server/sdk/api'; + +export async function watchInquiries(event: ChangeEvent): Promise { + switch (event.operationType) { + case 'insert': + case 'update': + const record = event.fullDocument; + return api.broadcast('livechat-inquiry-queue-observer', { action: normalize[event.operationType], inquiry: record }); + default: + } +} diff --git a/ee/server/services/StreamHub/watchMessages.ts b/ee/server/services/StreamHub/watchMessages.ts new file mode 100644 index 0000000000000..74104f69cfe15 --- /dev/null +++ b/ee/server/services/StreamHub/watchMessages.ts @@ -0,0 +1,20 @@ +import { ChangeEvent } from 'mongodb'; + +import { normalize } from './utils'; +import { IMessage } from '../../../../definition/IMessage'; +import { api } from '../../../../server/sdk/api'; + +export async function watchMessages(event: ChangeEvent): Promise { + switch (event.operationType) { + case 'insert': + case 'update': + // const message = await Messages.findOne(documentKey); + const message = event.fullDocument; + // Streamer.emitWithoutBroadcast('__my_messages__', message, {}); + api.broadcast('message', { action: normalize[event.operationType], message }); + // TODO: + // RocketChat.Logger.info('Message record', fullDocument); + // return Streamer[method]({ stream: STREA M_NAMES['room-messages'], eventName: message.rid, args: message }); + // publishMessage(operationType, message); + } +} diff --git a/ee/server/services/StreamHub/watchRoles.ts b/ee/server/services/StreamHub/watchRoles.ts new file mode 100644 index 0000000000000..db4884d7d6542 --- /dev/null +++ b/ee/server/services/StreamHub/watchRoles.ts @@ -0,0 +1,24 @@ +import { ChangeEvent } from 'mongodb'; + +import { api } from '../../../../server/sdk/api'; +import { IRole } from '../../../../definition/IRole'; + +export async function watchRoles(event: ChangeEvent): Promise { + // TODO: + // RocketChat.Logger.info('Role record', documentKey); + switch (event.operationType) { + case 'insert': + case 'update': + api.broadcast('role', { + type: 'changed', + ...event.fullDocument, + }); + break; + case 'delete': + api.broadcast('role', { + type: 'removed', + name: event.documentKey._id, + }); + break; + } +} diff --git a/ee/server/services/StreamHub/watchRooms.ts b/ee/server/services/StreamHub/watchRooms.ts new file mode 100644 index 0000000000000..d6905af4e2954 --- /dev/null +++ b/ee/server/services/StreamHub/watchRooms.ts @@ -0,0 +1,26 @@ +import { ChangeEvent } from 'mongodb'; + +import { normalize } from './utils'; +import { api } from '../../../../server/sdk/api'; +import { IRoom } from '../../../../definition/IRoom'; + +export async function watchRooms(event: ChangeEvent): Promise { + let room; + switch (event.operationType) { + case 'insert': + case 'update': + // room = await Rooms.findOne(documentKey/* , { fields }*/); + room = event.fullDocument; + break; + case 'delete': + room = event.documentKey; + break; + default: + return; + } + // console.log(room, documentKey); + api.broadcast('room', { action: normalize[event.operationType], room }); + // RocketChat.Notifications.streamUser.__emit(data._id, operationType, data); + // TODO: + // RocketChat.Logger.info('Rooms record', room); +} diff --git a/ee/server/services/StreamHub/watchSettings.ts b/ee/server/services/StreamHub/watchSettings.ts new file mode 100644 index 0000000000000..17e8227999b5f --- /dev/null +++ b/ee/server/services/StreamHub/watchSettings.ts @@ -0,0 +1,28 @@ +import { ChangeEvent } from 'mongodb'; + +import { normalize } from './utils'; +import { api } from '../../../../server/sdk/api'; +import { ISetting } from '../../../../imports/client/@rocket.chat/apps-engine/definition/settings/ISetting'; + +export async function watchSettings(event: ChangeEvent): Promise { + if ('updateDescription' in event && event.updateDescription.updatedFields._updatedAt) { + return; + } + let setting; + switch (event.operationType) { + case 'insert': + case 'update': + // setting = Settings.findOne(documentKey/* , { fields }*/); + setting = event.fullDocument; + break; + case 'delete': + setting = event.documentKey; + break; + default: + return; + } + api.broadcast('setting', { action: normalize[event.operationType], setting }); + // RocketChat.Notifications.streamUser.__emit(data._id, operationType, data); + // TODO: + // RocketChat.Logger.info('Settings record', setting); +} diff --git a/ee/server/services/StreamHub/watchSubscriptions.ts b/ee/server/services/StreamHub/watchSubscriptions.ts new file mode 100644 index 0000000000000..c484c73c55faf --- /dev/null +++ b/ee/server/services/StreamHub/watchSubscriptions.ts @@ -0,0 +1,26 @@ +import { ChangeEvent, Collection } from 'mongodb'; + +import { normalize } from './utils'; +import { api } from '../../../../server/sdk/api'; +import { ISubscription } from '../../../../definition/ISubscription'; + +export function watchSubscriptions(Trash: Collection) { + return async (event: ChangeEvent): Promise => { + let subscription; + switch (event.operationType) { + case 'insert': + case 'update': + // subscription = await Subscriptions.findOne(documentKey/* , { fields }*/); + subscription = event.fullDocument; + break; + case 'delete': + subscription = await Trash.findOne(event.documentKey, { fields: { u: 1, rid: 1 } }); + break; + default: + return; + } + api.broadcast('subscription', { action: normalize[event.operationType], subscription }); + // TODO: + // RocketChat.Logger.info('Subscription record', fullDocument); + }; +} diff --git a/ee/server/services/StreamHub/watchUsers.ts b/ee/server/services/StreamHub/watchUsers.ts new file mode 100644 index 0000000000000..dc6877b1ffaa7 --- /dev/null +++ b/ee/server/services/StreamHub/watchUsers.ts @@ -0,0 +1,87 @@ +import { ChangeEvent } from 'mongodb'; +import msgpack5 from 'msgpack5'; + +import { normalize } from './utils'; +import { IUser } from '../../../../definition/IUser'; +import { api } from '../../../../server/sdk/api'; + +const msgpack = msgpack5(); + +function nestStringProperties(obj: object): object { + if (!obj) { + return {}; + } + + const isPlainObject = (obj: object): boolean => !!obj && obj.constructor === {}.constructor; + + const getNestedObject = (obj: object): object => + Object.entries(obj).reduce<{[k: string]: any}>((result, [prop, val]) => { + prop.split('.').reduce((nestedResult, prop, propIndex, propArray) => { + const lastProp = propIndex === propArray.length - 1; + if (lastProp) { + nestedResult[prop] = isPlainObject(val) ? getNestedObject(val) : val; + } else { + nestedResult[prop] = nestedResult[prop] || {}; + } + return nestedResult[prop]; + }, result); + return result; + }, {}); + + return getNestedObject(obj); +} + +export async function watchUsers(event: ChangeEvent): Promise { + switch (event.operationType) { + case 'insert': + case 'update': + const { updatedFields } = 'updateDescription' in event ? event.updateDescription : { updatedFields: undefined }; + // const message = await Messages.findOne(documentKey); + const user = event.fullDocument; + + if (!user) { + break; + } + + // Streamer.emitWithoutBroadcast('__my_messages__', message, {}); + if (updatedFields) { + if (updatedFields.status || updatedFields.statusText) { + const { status, _id, username, statusText } = user; // remove username + api.broadcast('userpresence', msgpack.encode({ action: normalize[event.operationType], user: { status, _id, username, statusText } })); // remove username + // TODO: + // RocketChat.Logger.info('User: userpresence', { status, _id, username, statusText }); + } + + if (updatedFields.username || updatedFields.name) { + const { name, username } = updatedFields; + const { _id } = event.documentKey; + const nameChange = { + _id, + name: name || user.name, + username: username || user.username, + }; + + api.broadcast('user.name', msgpack.encode({ + action: normalize[event.operationType], + user: nameChange, + })); + // TODO: + // RocketChat.Logger.info('User: user.name', nameChange); + } + } + api.broadcast( + 'user', + msgpack.encode({ + action: normalize[event.operationType], + user: { + ...event.documentKey, + ...updatedFields ? nestStringProperties(updatedFields) : {}, + }, + }), + ); + // TODO: + // RocketChat.Logger.info('User record', user); + // return Streamer[method]({ stream: STREAM_NAMES['room-messages'], eventName: message.rid, args: message }); + // publishMessage(operationType, message); + } +} diff --git a/ee/server/services/package-lock.json b/ee/server/services/package-lock.json index 64a64a5fa75ec..4f3aada898aa0 100644 --- a/ee/server/services/package-lock.json +++ b/ee/server/services/package-lock.json @@ -212,17 +212,32 @@ "debug": "^4.1.1" } }, + "@types/bl": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/bl/-/bl-2.1.0.tgz", + "integrity": "sha512-1TdA9IXOy4sdqn8vgieQ6GZAiHiPNrOiO1s2GJjuYPw4QVY7gYoVjkW049avj33Ez7IcIvu43hQsMsoUFbCn2g==", + "requires": { + "@types/node": "*" + } + }, "@types/color-name": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", "dev": true }, + "@types/msgpack5": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@types/msgpack5/-/msgpack5-3.4.1.tgz", + "integrity": "sha512-E3wILUjTXONukpiI6tmqpLwf7eV3MVTdxpjz56FqNn7koMF/6sSPUh5TxMlwgoOhyeejxwVoNZUiDcdqChKkAw==", + "requires": { + "@types/bl": "*" + } + }, "@types/node": { "version": "14.6.4", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.6.4.tgz", - "integrity": "sha512-Wk7nG1JSaMfMpoMJDKUsWYugliB2Vy55pdjLpmLixeyMi7HizW2I/9QoxsPCkXl3dO+ZOVqPumKaDUv5zJu2uQ==", - "dev": true + "integrity": "sha512-Wk7nG1JSaMfMpoMJDKUsWYugliB2Vy55pdjLpmLixeyMi7HizW2I/9QoxsPCkXl3dO+ZOVqPumKaDUv5zJu2uQ==" }, "abbrev": { "version": "1.1.1", @@ -1160,6 +1175,17 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, + "msgpack5": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/msgpack5/-/msgpack5-4.2.1.tgz", + "integrity": "sha512-Xo7nE9ZfBVonQi1rSopNAqPdts/QHyuSEUwIEzAkB+V2FtmkkLUbP6MyVqVVQxsZYI65FpvW3Bb8Z9ZWEjbgHQ==", + "requires": { + "bl": "^2.0.1", + "inherits": "^2.0.3", + "readable-stream": "^2.3.6", + "safe-buffer": "^5.1.2" + } + }, "mute-stream": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", diff --git a/ee/server/services/package.json b/ee/server/services/package.json index 9697f4b089315..238e0d8cb8080 100644 --- a/ee/server/services/package.json +++ b/ee/server/services/package.json @@ -16,8 +16,10 @@ "author": "Rocket.Chat", "license": "MIT", "dependencies": { - "mongodb": "^3.6.1", + "@types/msgpack5": "^3.4.1", "bcrypt": "^4.0.1", + "mongodb": "^3.6.1", + "msgpack5": "^4.2.1", "uuid": "^7.0.3" }, "devDependencies": { diff --git a/server/sdk/lib/Api.ts b/server/sdk/lib/Api.ts index 843f19468bde9..be72cbc78078c 100644 --- a/server/sdk/lib/Api.ts +++ b/server/sdk/lib/Api.ts @@ -25,4 +25,8 @@ export class Api { async call(method: string, data: any): Promise { return this.broker.call(method, data); } + + async broadcast(eventName: string, data: D): Promise { + return this.broker.broadcast(eventName, data); + } } diff --git a/server/sdk/lib/LocalBroker.ts b/server/sdk/lib/LocalBroker.ts index a62c7df6930c9..f900afcb57c85 100644 --- a/server/sdk/lib/LocalBroker.ts +++ b/server/sdk/lib/LocalBroker.ts @@ -30,6 +30,12 @@ export class LocalBroker implements IBroker { } } + async broadcast(eventName: string, data: D): Promise { + // TODO: + console.log('broadcast implementation missing', eventName, data); + // return this.broker.broadcast(eventName, data); + } + async nodeList(): Promise { return []; } diff --git a/server/sdk/types/IBroker.ts b/server/sdk/types/IBroker.ts index b894a50761550..690fb806c7706 100644 --- a/server/sdk/types/IBroker.ts +++ b/server/sdk/types/IBroker.ts @@ -22,5 +22,6 @@ export interface IBrokerNode { export interface IBroker { createService(service: ServiceClass): void; call(method: string, data: any): Promise; + broadcast(eventName: string, data: D): Promise; nodeList(): Promise; } From 301328f1930c8d3ecbdfb33a8df2f4582039c353 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Sat, 5 Sep 2020 18:49:49 -0300 Subject: [PATCH 027/198] Organize names --- ee/server/services/Account/Account.ts | 91 ++++++++++++++++++- ee/server/services/Account/service.ts | 91 +------------------ .../{Authorization.ts => service.ts} | 0 ee/server/services/Presence/Presence.ts | 71 +++++++++++++-- ee/server/services/Presence/service.ts | 71 ++------------- ee/server/services/StreamHub/StreamHub.ts | 65 +++++++++++-- ee/server/services/StreamHub/service.ts | 65 ++----------- ee/server/services/ecosystem.config.js | 12 ++- 8 files changed, 236 insertions(+), 230 deletions(-) rename ee/server/services/Authorization/{Authorization.ts => service.ts} (100%) diff --git a/ee/server/services/Account/Account.ts b/ee/server/services/Account/Account.ts index c38928108035f..6971f9fd0ee55 100644 --- a/ee/server/services/Account/Account.ts +++ b/ee/server/services/Account/Account.ts @@ -1,6 +1,89 @@ -import { api } from '../../../../server/sdk/api'; -import { Account } from './service'; +import { + IStampedToken, + _generateStampedLoginToken, + _hashStampedToken, + _hashLoginToken, + _tokenExpiration, + validatePassword, +} from './lib/utils'; +import { getCollection, Collections } from '../mongo'; +import { IUser } from '../../../../definition/IUser'; +import { ServiceClass } from '../../../../server/sdk/types/ServiceClass'; +import { IAccount, ILoginResult } from '../../../../server/sdk/types/IAccount'; -import '../../broker'; -api.registerService(new Account()); +const saveSession = async (uid: string, newToken: IStampedToken): Promise => { + const Users = await getCollection(Collections.User); + await Users.updateOne({ _id: uid }, { + $push: { + 'services.resume.loginTokens': _hashStampedToken(newToken), + }, + }); +}; + +const loginViaResume = async (resume: string): Promise => { + const Users = await getCollection(Collections.User); + const hashedToken = _hashLoginToken(resume); + + const user = await Users.findOne({ + 'services.resume.loginTokens.hashedToken': hashedToken, + }, { + projection: { + 'services.resume.loginTokens': 1, + }, + }); + if (!user) { + return false; + } + + const { when } = user.services?.resume?.loginTokens?.find((token) => + token.hashedToken === hashedToken, + ) || {}; + + return { + uid: user._id, + token: resume, + tokenExpires: when ? _tokenExpiration(when) : undefined, + type: 'resume', + }; +}; + +const loginViaUsername = async ({ username }: {username: string}, password: string): Promise => { + const Users = await getCollection(Collections.User); + const user = await Users.findOne({ username }, { projection: { 'services.password.bcrypt': 1 } }); + if (!user) { + return false; + } + + const valid = user.services?.password?.bcrypt && validatePassword(password, user.services.password.bcrypt); + if (!valid) { + return false; + } + + const newToken = _generateStampedLoginToken(); + + await saveSession(user._id, newToken); + + return { + uid: user._id, + token: newToken.token, + tokenExpires: _tokenExpiration(newToken.when), + type: 'password', + }; +}; + +export class Account extends ServiceClass implements IAccount { + protected name = 'accounts'; + + async login({ resume, user, password }: {resume: string; user: {username: string}; password: string}): Promise { + if (resume) { + return loginViaResume(resume); + } + + if (user && password) { + return loginViaUsername(user, password); + } + + return false; + } +} diff --git a/ee/server/services/Account/service.ts b/ee/server/services/Account/service.ts index 6971f9fd0ee55..c38928108035f 100644 --- a/ee/server/services/Account/service.ts +++ b/ee/server/services/Account/service.ts @@ -1,89 +1,6 @@ -import { - IStampedToken, - _generateStampedLoginToken, - _hashStampedToken, - _hashLoginToken, - _tokenExpiration, - validatePassword, -} from './lib/utils'; -import { getCollection, Collections } from '../mongo'; -import { IUser } from '../../../../definition/IUser'; -import { ServiceClass } from '../../../../server/sdk/types/ServiceClass'; -import { IAccount, ILoginResult } from '../../../../server/sdk/types/IAccount'; +import { api } from '../../../../server/sdk/api'; +import { Account } from './service'; +import '../../broker'; -const saveSession = async (uid: string, newToken: IStampedToken): Promise => { - const Users = await getCollection(Collections.User); - await Users.updateOne({ _id: uid }, { - $push: { - 'services.resume.loginTokens': _hashStampedToken(newToken), - }, - }); -}; - -const loginViaResume = async (resume: string): Promise => { - const Users = await getCollection(Collections.User); - const hashedToken = _hashLoginToken(resume); - - const user = await Users.findOne({ - 'services.resume.loginTokens.hashedToken': hashedToken, - }, { - projection: { - 'services.resume.loginTokens': 1, - }, - }); - if (!user) { - return false; - } - - const { when } = user.services?.resume?.loginTokens?.find((token) => - token.hashedToken === hashedToken, - ) || {}; - - return { - uid: user._id, - token: resume, - tokenExpires: when ? _tokenExpiration(when) : undefined, - type: 'resume', - }; -}; - -const loginViaUsername = async ({ username }: {username: string}, password: string): Promise => { - const Users = await getCollection(Collections.User); - const user = await Users.findOne({ username }, { projection: { 'services.password.bcrypt': 1 } }); - if (!user) { - return false; - } - - const valid = user.services?.password?.bcrypt && validatePassword(password, user.services.password.bcrypt); - if (!valid) { - return false; - } - - const newToken = _generateStampedLoginToken(); - - await saveSession(user._id, newToken); - - return { - uid: user._id, - token: newToken.token, - tokenExpires: _tokenExpiration(newToken.when), - type: 'password', - }; -}; - -export class Account extends ServiceClass implements IAccount { - protected name = 'accounts'; - - async login({ resume, user, password }: {resume: string; user: {username: string}; password: string}): Promise { - if (resume) { - return loginViaResume(resume); - } - - if (user && password) { - return loginViaUsername(user, password); - } - - return false; - } -} +api.registerService(new Account()); diff --git a/ee/server/services/Authorization/Authorization.ts b/ee/server/services/Authorization/service.ts similarity index 100% rename from ee/server/services/Authorization/Authorization.ts rename to ee/server/services/Authorization/service.ts diff --git a/ee/server/services/Presence/Presence.ts b/ee/server/services/Presence/Presence.ts index 99adc98c1905f..47f4eeddcf22c 100755 --- a/ee/server/services/Presence/Presence.ts +++ b/ee/server/services/Presence/Presence.ts @@ -1,9 +1,68 @@ -// TODO: check config -// import config from 'moleculer.config'; +import { newConnection } from './actions/newConnection'; +import { removeConnection } from './actions/removeConnection'; +import { removeLostConnections } from './actions/removeLostConnections'; +import { setStatus, setConnectionStatus } from './actions/setStatus'; +import { updateUserPresence } from './actions/updateUserPresence'; +import { ServiceClass } from '../../../../server/sdk/types/ServiceClass'; +import { IPresence } from '../../../../server/sdk/types/IPresence'; +import { USER_STATUS } from '../../../../definition/UserStatus'; +import { IBrokerNode } from '../../../../server/sdk/types/IBroker'; -import { api } from '../../../../server/sdk/api'; -import { Presence } from './service'; +// import PromService from 'moleculer-prometheus'; -import '../../broker'; +export class Presence extends ServiceClass implements IPresence { + protected name = 'presence'; -api.registerService(new Presence()); + async onNodeDisconnected({ node }: {node: IBrokerNode}): Promise { + const affectedUsers = await this.removeLostConnections(node.id); + return affectedUsers.forEach((uid) => this.updateUserPresence(uid)); + } + + async started(): Promise { + setTimeout(async () => { + const affectedUsers = await this.removeLostConnections(); + return affectedUsers.forEach((uid) => this.updateUserPresence(uid)); + }, 100); + } + + async newConnection(uid: string, session: string): Promise<{uid: string; connectionId: string} | undefined> { + const result = await newConnection(uid, session, this.context); + await updateUserPresence(uid); + return result; + } + + async removeConnection(uid: string, session: string): Promise<{uid: string; session: string}> { + const result = await removeConnection(uid, session); + await updateUserPresence(uid); + return result; + } + + async removeLostConnections(nodeID?: string): Promise { + return removeLostConnections(nodeID, this.context); + } + + async setStatus(uid: string, status: USER_STATUS, statusText?: string): Promise { + return setStatus(uid, status, statusText); + } + + async setConnectionStatus(uid: string, status: USER_STATUS, session: string): Promise { + const result = await setConnectionStatus(uid, status, session); + await updateUserPresence(uid); + return result; + } + + async updateUserPresence(uid: string): Promise { + return updateUserPresence(uid); + } +} + +// const { PROMETHEUS_PORT = 9100 } = process.env; + +// export default { +// settings: { +// port: PROMETHEUS_PORT, +// $noVersionPrefix: true, +// }, +// mixins: PROMETHEUS_PORT !== 'false' ? [PromService] : [], +// }, +// }; diff --git a/ee/server/services/Presence/service.ts b/ee/server/services/Presence/service.ts index 47f4eeddcf22c..35c7c5789e13a 100755 --- a/ee/server/services/Presence/service.ts +++ b/ee/server/services/Presence/service.ts @@ -1,68 +1,9 @@ -import { newConnection } from './actions/newConnection'; -import { removeConnection } from './actions/removeConnection'; -import { removeLostConnections } from './actions/removeLostConnections'; -import { setStatus, setConnectionStatus } from './actions/setStatus'; -import { updateUserPresence } from './actions/updateUserPresence'; -import { ServiceClass } from '../../../../server/sdk/types/ServiceClass'; -import { IPresence } from '../../../../server/sdk/types/IPresence'; -import { USER_STATUS } from '../../../../definition/UserStatus'; -import { IBrokerNode } from '../../../../server/sdk/types/IBroker'; +// TODO: check config +// import config from 'moleculer.config'; -// import PromService from 'moleculer-prometheus'; +import { api } from '../../../../server/sdk/api'; +import { Presence } from './Presence'; -export class Presence extends ServiceClass implements IPresence { - protected name = 'presence'; +import '../../broker'; - async onNodeDisconnected({ node }: {node: IBrokerNode}): Promise { - const affectedUsers = await this.removeLostConnections(node.id); - return affectedUsers.forEach((uid) => this.updateUserPresence(uid)); - } - - async started(): Promise { - setTimeout(async () => { - const affectedUsers = await this.removeLostConnections(); - return affectedUsers.forEach((uid) => this.updateUserPresence(uid)); - }, 100); - } - - async newConnection(uid: string, session: string): Promise<{uid: string; connectionId: string} | undefined> { - const result = await newConnection(uid, session, this.context); - await updateUserPresence(uid); - return result; - } - - async removeConnection(uid: string, session: string): Promise<{uid: string; session: string}> { - const result = await removeConnection(uid, session); - await updateUserPresence(uid); - return result; - } - - async removeLostConnections(nodeID?: string): Promise { - return removeLostConnections(nodeID, this.context); - } - - async setStatus(uid: string, status: USER_STATUS, statusText?: string): Promise { - return setStatus(uid, status, statusText); - } - - async setConnectionStatus(uid: string, status: USER_STATUS, session: string): Promise { - const result = await setConnectionStatus(uid, status, session); - await updateUserPresence(uid); - return result; - } - - async updateUserPresence(uid: string): Promise { - return updateUserPresence(uid); - } -} - -// const { PROMETHEUS_PORT = 9100 } = process.env; - -// export default { -// settings: { -// port: PROMETHEUS_PORT, -// $noVersionPrefix: true, -// }, -// mixins: PROMETHEUS_PORT !== 'false' ? [PromService] : [], -// }, -// }; +api.registerService(new Presence()); diff --git a/ee/server/services/StreamHub/StreamHub.ts b/ee/server/services/StreamHub/StreamHub.ts index 495e0aca40b74..94b025d5b2b83 100755 --- a/ee/server/services/StreamHub/StreamHub.ts +++ b/ee/server/services/StreamHub/StreamHub.ts @@ -1,9 +1,62 @@ -// TODO: check config -// import config from 'moleculer.config'; +import { watchUsers } from './watchUsers'; +import { watchMessages } from './watchMessages'; +import { watchSettings } from './watchSettings'; +import { watchRooms } from './watchRooms'; +import { watchSubscriptions } from './watchSubscriptions'; +import { watchRoles } from './watchRoles'; +import { watchInquiries } from './watchInquiries'; +import { getConnection } from '../mongo'; +import { ServiceClass, IServiceClass } from '../../../../server/sdk/types/ServiceClass'; -import { api } from '../../../../server/sdk/api'; -import { StreamHub } from './service'; +export class StreamHub extends ServiceClass implements IServiceClass { + protected name = 'hub'; -import '../../broker'; + async created(): Promise { + const db = await getConnection(); -api.registerService(new StreamHub()); + const Trash = db.collection('rocketchat_trash'); + const Users = db.collection('users'); + const Roles = db.collection('rocketchat_roles'); + const Messages = db.collection('rocketchat_message'); + const Subscriptions = db.collection('rocketchat_subscription'); + const Rooms = db.collection('rocketchat_room'); + const Settings = db.collection('rocketchat_settings'); + const Inquiry = db.collection('rocketchat_livechat_inquiry'); + + Users.watch([], { fullDocument: 'updateLookup' }).on('change', watchUsers); + + Messages.watch([{ + $addFields: { + tmpfields: { + $objectToArray: '$updateDescription.updatedFields', + }, + } }, { + $match: { + 'tmpfields.k': { + $nin: ['u.username'], // avoid flood the streamer with messages changes (by username change) + }, + } }], { fullDocument: 'updateLookup' }).on('change', watchMessages); + + Subscriptions.watch([], { fullDocument: 'updateLookup' }).on('change', watchSubscriptions(Trash)); + + Inquiry.watch([], { fullDocument: 'updateLookup' }).on('change', watchInquiries); + + Rooms.watch([], { fullDocument: 'updateLookup' }).on('change', watchRooms); + + Roles.watch([], { fullDocument: 'updateLookup' }).on('change', watchRoles); + + Settings.watch([{ + $addFields: { + tmpfields: { + $objectToArray: '$updateDescription.updatedFields', + }, + }, + }, { + $match: { + 'tmpfields.k': { + $in: ['value'], // avoid flood the streamer with messages changes (by username change) + }, + }, + }], { fullDocument: 'updateLookup' }).on('change', watchSettings); + } +} diff --git a/ee/server/services/StreamHub/service.ts b/ee/server/services/StreamHub/service.ts index 94b025d5b2b83..495e0aca40b74 100755 --- a/ee/server/services/StreamHub/service.ts +++ b/ee/server/services/StreamHub/service.ts @@ -1,62 +1,9 @@ -import { watchUsers } from './watchUsers'; -import { watchMessages } from './watchMessages'; -import { watchSettings } from './watchSettings'; -import { watchRooms } from './watchRooms'; -import { watchSubscriptions } from './watchSubscriptions'; -import { watchRoles } from './watchRoles'; -import { watchInquiries } from './watchInquiries'; -import { getConnection } from '../mongo'; -import { ServiceClass, IServiceClass } from '../../../../server/sdk/types/ServiceClass'; +// TODO: check config +// import config from 'moleculer.config'; -export class StreamHub extends ServiceClass implements IServiceClass { - protected name = 'hub'; +import { api } from '../../../../server/sdk/api'; +import { StreamHub } from './service'; - async created(): Promise { - const db = await getConnection(); +import '../../broker'; - const Trash = db.collection('rocketchat_trash'); - const Users = db.collection('users'); - const Roles = db.collection('rocketchat_roles'); - const Messages = db.collection('rocketchat_message'); - const Subscriptions = db.collection('rocketchat_subscription'); - const Rooms = db.collection('rocketchat_room'); - const Settings = db.collection('rocketchat_settings'); - const Inquiry = db.collection('rocketchat_livechat_inquiry'); - - Users.watch([], { fullDocument: 'updateLookup' }).on('change', watchUsers); - - Messages.watch([{ - $addFields: { - tmpfields: { - $objectToArray: '$updateDescription.updatedFields', - }, - } }, { - $match: { - 'tmpfields.k': { - $nin: ['u.username'], // avoid flood the streamer with messages changes (by username change) - }, - } }], { fullDocument: 'updateLookup' }).on('change', watchMessages); - - Subscriptions.watch([], { fullDocument: 'updateLookup' }).on('change', watchSubscriptions(Trash)); - - Inquiry.watch([], { fullDocument: 'updateLookup' }).on('change', watchInquiries); - - Rooms.watch([], { fullDocument: 'updateLookup' }).on('change', watchRooms); - - Roles.watch([], { fullDocument: 'updateLookup' }).on('change', watchRoles); - - Settings.watch([{ - $addFields: { - tmpfields: { - $objectToArray: '$updateDescription.updatedFields', - }, - }, - }, { - $match: { - 'tmpfields.k': { - $in: ['value'], // avoid flood the streamer with messages changes (by username change) - }, - }, - }], { fullDocument: 'updateLookup' }).on('change', watchSettings); - } -} +api.registerService(new StreamHub()); diff --git a/ee/server/services/ecosystem.config.js b/ee/server/services/ecosystem.config.js index 02c066662f99d..1d50736ab998c 100644 --- a/ee/server/services/ecosystem.config.js +++ b/ee/server/services/ecosystem.config.js @@ -1,19 +1,25 @@ module.exports = { apps: [{ name: 'Authorization', - script: 'ts-node Authorization/Authorization.ts', + script: 'ts-node Authorization/service.ts', watch: true, instances: 1, // interpreter: '', }, { name: 'Presence', - script: 'ts-node Presence/Presence.ts', + script: 'ts-node Presence/service.ts', watch: true, instances: 1, // interpreter: '', }, { name: 'Account', - script: 'ts-node Account/Account.ts', + script: 'ts-node Account/service.ts', + watch: true, + instances: 1, + // interpreter: '', + }, { + name: 'StreamHub', + script: 'ts-node StreamHub/service.ts', watch: true, instances: 1, // interpreter: '', From 0bb15ed3df8c7beeca5fa388871ea9e0b16e824c Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Sun, 6 Sep 2020 15:19:50 -0300 Subject: [PATCH 028/198] Initial version of DDPSrteamer --- definition/ISubscription.ts | 1 + ee/server/services/DDPStreamer/Client.ts | 165 +++++++++++ .../services/DDPStreamer/ClientMobile.ts | 119 ++++++++ ee/server/services/DDPStreamer/Publication.ts | 96 +++++++ ee/server/services/DDPStreamer/Server.ts | 194 +++++++++++++ ee/server/services/DDPStreamer/Streamer.ts | 260 ++++++++++++++++++ ee/server/services/DDPStreamer/constants.ts | 71 +++++ ee/server/services/DDPStreamer/index.ts | 248 +++++++++++++++++ .../services/DDPStreamer/lib/sendBroadcast.ts | 32 +++ ee/server/services/DDPStreamer/lib/utils.ts | 9 + ee/server/services/DDPStreamer/streams/all.ts | 5 + .../services/DDPStreamer/streams/index.ts | 10 + .../DDPStreamer/streams/livechatInquiry.ts | 8 + .../services/DDPStreamer/streams/missing.ts | 17 ++ .../DDPStreamer/streams/notifyLogged.ts | 7 + .../DDPStreamer/streams/notifyUser.ts | 74 +++++ .../services/DDPStreamer/streams/presence.ts | 6 + .../services/DDPStreamer/streams/room.ts | 6 + .../DDPStreamer/streams/roomMessages.ts | 13 + .../services/DDPStreamer/streams/roomUsers.ts | 6 + .../services/DDPStreamer/types/IPacket.ts | 9 + ee/server/services/DDPStreamer/types/ws.d.ts | 18 ++ ee/server/services/package-lock.json | 23 +- ee/server/services/package.json | 8 +- 24 files changed, 1401 insertions(+), 4 deletions(-) create mode 100644 ee/server/services/DDPStreamer/Client.ts create mode 100644 ee/server/services/DDPStreamer/ClientMobile.ts create mode 100644 ee/server/services/DDPStreamer/Publication.ts create mode 100644 ee/server/services/DDPStreamer/Server.ts create mode 100644 ee/server/services/DDPStreamer/Streamer.ts create mode 100644 ee/server/services/DDPStreamer/constants.ts create mode 100644 ee/server/services/DDPStreamer/index.ts create mode 100644 ee/server/services/DDPStreamer/lib/sendBroadcast.ts create mode 100644 ee/server/services/DDPStreamer/lib/utils.ts create mode 100644 ee/server/services/DDPStreamer/streams/all.ts create mode 100644 ee/server/services/DDPStreamer/streams/index.ts create mode 100644 ee/server/services/DDPStreamer/streams/livechatInquiry.ts create mode 100644 ee/server/services/DDPStreamer/streams/missing.ts create mode 100644 ee/server/services/DDPStreamer/streams/notifyLogged.ts create mode 100644 ee/server/services/DDPStreamer/streams/notifyUser.ts create mode 100644 ee/server/services/DDPStreamer/streams/presence.ts create mode 100644 ee/server/services/DDPStreamer/streams/room.ts create mode 100644 ee/server/services/DDPStreamer/streams/roomMessages.ts create mode 100644 ee/server/services/DDPStreamer/streams/roomUsers.ts create mode 100644 ee/server/services/DDPStreamer/types/IPacket.ts create mode 100644 ee/server/services/DDPStreamer/types/ws.d.ts diff --git a/definition/ISubscription.ts b/definition/ISubscription.ts index c45ecbe5c4764..9ba72263c66a5 100644 --- a/definition/ISubscription.ts +++ b/definition/ISubscription.ts @@ -1,4 +1,5 @@ export interface ISubscription { _id: string; _updatedAt?: Date; + rid: string; } diff --git a/ee/server/services/DDPStreamer/Client.ts b/ee/server/services/DDPStreamer/Client.ts new file mode 100644 index 0000000000000..bde5bc0d034a3 --- /dev/null +++ b/ee/server/services/DDPStreamer/Client.ts @@ -0,0 +1,165 @@ +import { EventEmitter } from 'events'; + +import { v1 as uuidv1 } from 'uuid'; +import WebSocket from 'ws'; +import { ServiceBroker } from 'moleculer'; + +import { DDP_EVENTS, WS_ERRORS, WS_ERRORS_MESSAGES, TIMEOUT } from './constants'; +import { server, SERVER_ID } from './Server'; +import { IPacket } from './types/IPacket'; + +export class Client extends EventEmitter { + protected kind = 'default'; + + private timeout: NodeJS.Timeout; + + private chain = Promise.resolve(); + + public session = uuidv1(); + + public subscriptions = new Map(); + + public wait = false; + + public uid: string; + + constructor( + public ws: WebSocket, + public broker: ServiceBroker, + ) { + super(); + + this.renewTimeout(TIMEOUT / 1000); + this.ws.on('message', this.handler); + this.ws.on('close', (...args) => { + server.emit(DDP_EVENTS.DISCONNECTED, this); + this.emit('close', ...args); + this.subscriptions.clear(); + clearTimeout(this.timeout); + }); + + this.setMaxListeners(50); + + this.greeting(); + + server.emit(DDP_EVENTS.CONNECTED, this); + + this.ws.on('message', () => this.renewTimeout(TIMEOUT)); + + this.once('message', ({ msg }) => { + if (msg !== DDP_EVENTS.CONNECT) { + return this.ws.close(WS_ERRORS.CLOSE_PROTOCOL_ERROR, WS_ERRORS_MESSAGES.CLOSE_PROTOCOL_ERROR); + } + return this.send( + server.serialize({ [DDP_EVENTS.MSG]: DDP_EVENTS.CONNECTED, session: this.session }), + ); + }); + + this.send(SERVER_ID); + } + + greeting(): void { + // no greeting by default + } + + async callMethod(packet: IPacket): Promise { + this.chain = this.chain.then(() => server.callMethod(this, packet)).catch(); + } + + async callSubscribe(packet: IPacket): Promise { + this.chain = this.chain.then(() => server.callSubscribe(this, packet)).catch(); + } + + process(action: string, packet: IPacket): void { + switch (action) { + case DDP_EVENTS.PING: + this.pong(packet.id); + break; + case DDP_EVENTS.METHOD: + if (!packet.method) { + return this.ws.close(WS_ERRORS.CLOSE_PROTOCOL_ERROR); + } + if (!packet.id) { + return this.ws.close(WS_ERRORS.CLOSE_PROTOCOL_ERROR); + } + this.callMethod(packet); + break; + case DDP_EVENTS.SUSBCRIBE: + if (!packet.name) { + return this.ws.close(WS_ERRORS.CLOSE_PROTOCOL_ERROR); + } + if (!packet.id) { + return this.ws.close(WS_ERRORS.CLOSE_PROTOCOL_ERROR); + } + this.callSubscribe(packet); + break; + case DDP_EVENTS.UNSUBSCRIBE: + if (!packet.id) { + return this.ws.close(WS_ERRORS.CLOSE_PROTOCOL_ERROR); + } + const subscription = this.subscriptions.get(packet.id); + if (!subscription) { + return; + } + subscription.stop(); + break; + } + } + + closeTimeout = (): void => { + this.ws.close(WS_ERRORS.TIMEOUT, WS_ERRORS_MESSAGES.TIMEOUT); + }; + + ping(id?: string): void { + this.send(server.serialize({ [DDP_EVENTS.MSG]: DDP_EVENTS.PING, ...id && { [DDP_EVENTS.ID]: id } })); + } + + pong(id?: string): void { + this.send(server.serialize({ [DDP_EVENTS.MSG]: DDP_EVENTS.PONG, ...id && { [DDP_EVENTS.ID]: id } })); + } + + handleIdle = (): void => { + this.ping(); + this.timeout = setTimeout(this.closeTimeout, TIMEOUT); + }; + + renewTimeout(timeout = TIMEOUT): void { + clearTimeout(this.timeout); + this.timeout = setTimeout(this.handleIdle, timeout); + } + + handler = async (payload: string): Promise => { + try { + const packet = server.parse(payload); + this.emit('message', packet); + if (this.wait) { + return new Promise((resolve) => this.once(DDP_EVENTS.LOGGED, () => resolve(this.process(packet.msg, packet)))); + } + this.process(packet.msg, packet); + } catch (err) { + return this.ws.close( + WS_ERRORS.UNSUPPORTED_DATA, + WS_ERRORS_MESSAGES.UNSUPPORTED_DATA, + ); + } + }; + + send(payload: string): void { + return this.ws.send(payload); + } +} + +export class MeteorClient extends Client { + kind = 'meteor'; + + // TODO implement meteor errors + // a["{\"msg\":\"result\",\"id\":\"12\",\"error\":{\"isClientSafe\":true,\"error\":403,\"reason\":\"User has no password set\",\"message\":\"User has no password set [403]\",\"errorType\":\"Meteor.Error\"}}"] + + greeting(): void { + return this.ws.send('o'); + } + + send(payload: string): void { + return this.ws.send(`a${ JSON.stringify([payload]) }`); + } +} diff --git a/ee/server/services/DDPStreamer/ClientMobile.ts b/ee/server/services/DDPStreamer/ClientMobile.ts new file mode 100644 index 0000000000000..93c578e5cc51e --- /dev/null +++ b/ee/server/services/DDPStreamer/ClientMobile.ts @@ -0,0 +1,119 @@ +import { v1 as uuidv1 } from 'uuid'; + +import { Client } from './Client'; +import { DDP_EVENTS, WS_ERRORS, WS_ERRORS_MESSAGES, TIMEOUT } from './constants'; +import { server, SERVER_ID } from './Server'; + +export class ClientMobile extends Client { + session = uuidv1(); + + subscriptions = new Map(); + + constructor(socket, broker) { + super(); + this.broker = broker; + this.ws = socket; + this.renewTimeout(TIMEOUT / 1000); + this.ws.on('message', this.handler); + this.ws.on('close', (...args) => { + server.emit(DDP_EVENTS.DISCONNECTED, this); + this.emit('close', ...args); + this.subscriptions.clear(); + clearTimeout(this.timeout); + }); + + // Meteor thing + this.ws.send('o'); + + server.emit(DDP_EVENTS.CONNECTED, this); + + this.ws.on('message', () => this.renewTimeout(TIMEOUT)); + + this.once('message', ({ msg }) => { + if (msg !== DDP_EVENTS.CONNECT) { + return this.ws.close(WS_ERRORS.CLOSE_PROTOCOL_ERROR, WS_ERRORS_MESSAGES.PROTOCOL_ERROR); + } + return this.send( + server.serialize({ [DDP_EVENTS.MSG]: DDP_EVENTS.CONNECTED, session: this.session }) + ); + }); + + this.send(SERVER_ID); + } + + process(action, packet) { + switch (action) { + case DDP_EVENTS.PING: + this.pong(packet.id); + break; + case DDP_EVENTS.METHOD: + if (!packet.method) { + return this.ws.close(WS_ERRORS.CLOSE_PROTOCOL_ERROR); + } + if (!packet.id) { + return this.ws.close(WS_ERRORS.CLOSE_PROTOCOL_ERROR); + } + server.callMethod(this, packet); + break; + case DDP_EVENTS.SUSBCRIBE: + if (!packet.name) { + return this.ws.close(WS_ERRORS.CLOSE_PROTOCOL_ERROR); + } + if (!packet.id) { + return this.ws.close(WS_ERRORS.CLOSE_PROTOCOL_ERROR); + } + server.callSubscribe(this, packet); + break; + case DDP_EVENTS.UNSUBSCRIBE: + if (!packet.id) { + return this.ws.close(WS_ERRORS.CLOSE_PROTOCOL_ERROR); + } + const subscription = this.subscriptions.get(packet.id); + if (!subscription) { + return; + } + subscription.stop(); + break; + } + } + + closeTimeout = () => { + this.ws.close(WS_ERRORS.TIMEOUT, WS_ERRORS_MESSAGES.TIMEOUT); + }; + + ping(id) { + this.send(server.serialize({ [DDP_EVENTS.MSG]: DDP_EVENTS.PING, ...id && { [DDP_EVENTS.ID]: id } })); + } + + pong(id) { + this.send(server.serialize({ [DDP_EVENTS.MSG]: DDP_EVENTS.PONG, ...id && { [DDP_EVENTS.ID]: id } })); + } + + handleIdle = () => { + this.ping(); + this.timeout = setTimeout(this.closeTimeout, TIMEOUT); + }; + + renewTimeout(timeout = TIMEOUT) { + clearTimeout(this.timeout); + this.timeout = setTimeout(this.handleIdle, timeout); + } + + handler = (payload) => { + try { + const packet = server.parse(payload); + this.emit('message', packet); + this.process(packet.msg, packet); + } catch (err) { + return this.ws.close( + WS_ERRORS.UNSUPPORTED_DATA, + WS_ERRORS_MESSAGES.UNSUPPORTED_DATA + ); + } + }; + + send(payload) { + // Meteor format + return this.ws.send(`a${ JSON.stringify([payload]) }`); + } +} diff --git a/ee/server/services/DDPStreamer/Publication.ts b/ee/server/services/DDPStreamer/Publication.ts new file mode 100644 index 0000000000000..8011386ccbe20 --- /dev/null +++ b/ee/server/services/DDPStreamer/Publication.ts @@ -0,0 +1,96 @@ +import { EventEmitter } from 'events'; + +import { DDP_EVENTS } from './constants'; +import { server, Server } from './Server'; +import { sendBroadcast } from './lib/sendBroadcast'; +import { Client } from './Client'; +import { IPacket } from './types/IPacket'; +import { ISubscription } from '../../../../definition/ISubscription'; + +export class Publication extends EventEmitter { + constructor( + public client: Client, + private packet: IPacket, + private server: Server, + ) { + super(); + this.packet = packet; + client.subscriptions.set(packet.id, this); + client.once('close', () => this.emit('stop', this.client, this.packet)); + this.once('stop', () => client.subscriptions.delete(packet.id)); + } + + ready(): void { + return this.server.ready(this.client, this.packet); + } + + stop(): void { + this.server.nosub(this.client, this.packet); + this.emit('stop', this.client, this.packet); + } + + get uid(): string { + return this.client.uid; + } +} + +export const changedPayload = (collection: string, id: string, fields: object, cleared: string[]): string => + server.serialize({ + [DDP_EVENTS.MSG]: DDP_EVENTS.CHANGED, + [DDP_EVENTS.COLLECTION]: collection, + [DDP_EVENTS.ID]: id, + [DDP_EVENTS.FIELDS]: fields, + [DDP_EVENTS.CLEARED]: cleared, + }); + + +export class Publish { + subscriptionsByEventName = new Map(); + + constructor(private name: string) { + // + } + + addSubscription(subscription: ISubscription, eventName: string): void { + // this.subscriptions.add(subscription); + if (!this.subscriptionsByEventName.has(eventName)) { + this.subscriptionsByEventName.set( + eventName, + new Set([subscription]), + ); + return; + } + + this.subscriptionsByEventName + .get(eventName) + .add(subscription); + } + + removeSubscription(subscription: ISubscription, eventName: string): void { + // this.subscriptions.delete(subscription); + const subscriptions = this.subscriptionsByEventName.get( + eventName, + ); + if (subscriptions) { + subscriptions.delete(subscription); + if (!subscriptions.size) { + this.subscriptionsByEventName.delete(eventName); + } + } + } + + emit(eventName: string, id: string, fields: object, cleared: string[]): void { + const subscriptions = this.subscriptionsByEventName.get(eventName); + if (!subscriptions || !subscriptions.size) { + return; + } + + const msg = changedPayload(this.name, id, fields, cleared); + + if (!msg) { + return; + } + + sendBroadcast(subscriptions, msg); + } +} diff --git a/ee/server/services/DDPStreamer/Server.ts b/ee/server/services/DDPStreamer/Server.ts new file mode 100644 index 0000000000000..5f99e3a70de50 --- /dev/null +++ b/ee/server/services/DDPStreamer/Server.ts @@ -0,0 +1,194 @@ +import { EventEmitter } from 'events'; + +import ejson from 'ejson'; + +import { DDP_EVENTS } from './constants'; +import { Publication } from './Publication'; +import { Client } from './Client'; +import { IPacket } from './types/IPacket'; +import { Account, Presence } from '../../../../server/sdk'; +import { USER_STATUS } from '../../../../definition/UserStatus'; + +type SubscriptionFn = (publication: Publication, client: Client, eventName: string, options: object) => void; +type MethodFn = (this: Client, ...args: any[]) => any; +type Methods = { + [k: string]: MethodFn; +} + +// eslint-disable-next-line @typescript-eslint/camelcase +export const SERVER_ID = ejson.stringify({ server_id: '0' }); + +const methods = Symbol('methods'); +const subscriptions = Symbol('subscriptions'); + +// TODO: remove, not used by current rocket.chat versions +// export const User = new Publish('user'); + +export class Server extends EventEmitter { + [subscriptions] = new Map(); + + [methods] = new Map(); + + serialize = ejson.stringify; + + parse = (packet: string): IPacket => { + const [payload] = JSON.parse(packet); + return ejson.parse(payload); + } + + async callMethod(client: Client, packet: IPacket): Promise { + try { + if (!this[methods].has(packet.method)) { + throw new Error(`Method '${ packet.method }' doesn't exist`); + } + const fn = this[methods].get(packet.method); + + if (!fn) { + throw Error('method not found'); + } + + const result = await fn.apply(client, packet.params); + return this.result(client, packet, result); + } catch (error) { + return this.result(client, packet, null, error.toString()); + } + } + + methods(obj: Methods): void { + Object.entries(obj).forEach(([name, fn]) => { + if (this[methods].has(name)) { + return; + } + this[methods].set(name, fn); + }); + } + + async callSubscribe(client: Client, packet: IPacket): Promise { + try { + if (!this[subscriptions].has(packet.name)) { + throw new Error(`Subscription '${ packet.name }' doesn't exist`); + } + const fn = this[subscriptions].get(packet.name); + if (!fn) { + throw new Error('subscription not found'); + } + + const publication = new Publication(client, packet, this); + const [eventName, ...options] = packet.params; + await fn(publication, client, eventName, options); + } catch (error) { + this.nosub(client, packet, error.toString()); + } + } + + subscribe(name: string, fn: SubscriptionFn): void { + if (this[subscriptions].has(name)) { + return; + } + this[subscriptions].set(name, fn); + } + + stream(stream: string, fn: SubscriptionFn): void { + return this.subscribe(`stream-${ stream }`, fn); + } + + result(client: Client, { id }: IPacket, result?: any, error?: string): void { + client.send( + this.serialize({ + [DDP_EVENTS.MSG]: DDP_EVENTS.RESULT, + id, + ...result && { result }, + ...error && { error }, + }), + ); + return client.send( + this.serialize({ + [DDP_EVENTS.MSG]: DDP_EVENTS.UPDATED, + [DDP_EVENTS.METHODS]: [id], + }), + ); + } + + nosub(client: Client, { id }: IPacket, error?: string): void { + return client.send( + this.serialize({ + [DDP_EVENTS.MSG]: DDP_EVENTS.NO_SUBSCRIBE, + id, + ...error && { error }, + }), + ); + } + + ready(client: Client, packet: IPacket): void { + return client.send( + this.serialize({ [DDP_EVENTS.MSG]: DDP_EVENTS.READY, [DDP_EVENTS.SUBSCRIPTIONS]: [packet.id] }), + ); + } +} + +export const server = new Server(); + +// TODO: remove, not used by current rocket.chat versions +// server.subscribe('userData', async function(publication) { +// if (!publication.uid) { +// throw new Error('user should be connected'); +// } + +// const key = `${ STREAMER_EVENTS.USER_CHANGED }/${ publication.uid }`; +// await User.addSubscription(publication, key); +// publication.once('stop', () => User.removeSubscription(publication, key)); +// publication.ready(); +// }); + +// TODO: remove, not used by current rocket.chat versions +// server.subscribe('activeUsers', function(publication) { +// publication.ready(); +// }); + +server.subscribe('meteor.loginServiceConfiguration', function(pub) { + // TODO implement? + pub.ready(); +}); + +server.subscribe('meteor_autoupdate_clientVersions', function(pub) { + // TODO implement? + pub.ready(); +}); + +server.methods({ + async login({ resume, user, password }: {resume: string; user: {username: string}; password: string}) { + const result = await Account.login({ resume, user, password }); + if (!result) { + throw new Error('login error'); + } + + this.uid = result.uid; + + this.emit(DDP_EVENTS.LOGGED); + + server.emit(DDP_EVENTS.LOGGED, this); + + return { + id: result.uid, + token: result.token, + tokenExpires: result.tokenExpires, + type: result.type, + }; + }, + 'UserPresence:setDefaultStatus'(status) { + const { uid } = this; + return Presence.setStatus(uid, status); + }, + 'UserPresence:online'() { + const { uid, session } = this; + return Presence.setConnectionStatus(uid, USER_STATUS.ONLINE, session); + }, + 'UserPresence:away'() { + const { uid, session } = this; + return Presence.setConnectionStatus(uid, USER_STATUS.AWAY, session); + }, + 'setUserStatus'(status, statusText) { + const { uid } = this; + return Presence.setStatus(uid, status, statusText); + }, +}); diff --git a/ee/server/services/DDPStreamer/Streamer.ts b/ee/server/services/DDPStreamer/Streamer.ts new file mode 100644 index 0000000000000..f188758a93ab7 --- /dev/null +++ b/ee/server/services/DDPStreamer/Streamer.ts @@ -0,0 +1,260 @@ +import { EventEmitter } from 'events'; + +import { server } from './Server'; +import { STREAMER_EVENTS, DDP_EVENTS, STREAM_NAMES } from './constants'; +import { sendBroadcast } from './lib/sendBroadcast'; +import { isEmpty } from './lib/utils'; +import { Publication } from './Publication'; +import { Client } from './Client'; + +type Rule = (this: Publication, eventName: string, ...args: any) => Promise; + +interface IRules { + [k: string]: Rule; +} + +export type ISubscription = { + client: Client; +} + +export const send = function(self: Publication, msg: string): void { + if (!self.client) { + return; + } + self.client.send(msg); +}; + +export const changedPayload = (collection: string, fields: object): string | false => !isEmpty(fields) && server.serialize({ + [DDP_EVENTS.MSG]: DDP_EVENTS.CHANGED, + [DDP_EVENTS.COLLECTION]: collection, + [DDP_EVENTS.ID]: 'id', + [DDP_EVENTS.FIELDS]: fields, +}); + +// PRIVATE METHODS + +const allow = Symbol('allow'); +const isAllowed = Symbol('isAllowed'); +export const publish = Symbol('publish'); + +export const Streams = new Map(); + +export class Stream extends EventEmitter { + subscriptionName: string; + + // retransmit = true; + + // retransmitToSelf = false; + + private subscriptionsByEventName = new Map>(); + + private _allowRead = {}; + + private _allowWrite = {}; + + constructor( + private name: string, + // { retransmit = true, retransmitToSelf = false }: {retransmit?: boolean; retransmitToSelf?: boolean } = {}, + ) { + super(); + + this.subscriptionName = `${ STREAM_NAMES.STREAMER_PREFIX }${ name }`; + // this.retransmit = retransmit; + // this.retransmitToSelf = retransmitToSelf; + + // this.subscriptionsByEventName = new Map(); + + this.iniPublication(); + + this.allowRead('none'); + this.allowWrite('none'); + + Streams.set(name, this); + } + + [allow](rules: IRules, name: string) { + return (eventName: string | boolean | Rule, fn?: Rule): boolean | undefined => { + const _eventName: string = typeof eventName === 'string' ? eventName : '__all__'; + + if (typeof eventName === 'function') { + fn = eventName; + } + + if (fn) { + rules[_eventName] = fn; + return; + } + + if (!['all', 'none', 'logged'].includes(_eventName)) { + console.error(`${ name } shortcut '${ fn }' is invalid`); + } + + if (eventName === 'all' || eventName === true) { + rules[_eventName] = async function(): Promise { + return true; + }; + return; + } + + if (eventName === 'none' || eventName === false) { + rules[_eventName] = async function(): Promise { + return false; + }; + return; + } + + if (eventName === 'logged') { + rules[_eventName] = async function(): Promise { + return Boolean(this.uid); + }; + } + }; + } + + allowRead(eventName: string | boolean | Rule, fn?: Rule): boolean | undefined { + return this[allow](this._allowRead, 'allowRead')(eventName, fn); + } + + allowWrite(eventName: string | boolean | Rule, fn?: Rule): boolean | undefined { + return this[allow](this._allowWrite, 'allowWrite')(eventName, fn); + } + + [isAllowed](rules: IRules) { + return async (scope: Publication, eventName: string, args: any): Promise => { + if (rules[eventName]) { + return rules[eventName].call(scope, eventName, ...args); + } + + return rules.__all__.call(scope, eventName, ...args); + }; + } + + async isReadAllowed(scope: Publication, eventName: string, args: any): Promise { + return this[isAllowed](this._allowRead)(scope, eventName, args); + } + + async isWriteAllowed(scope: Publication, eventName: string, args: any): Promise { + return this[isAllowed](this._allowWrite)(scope, eventName, args); + } + + addSubscription(subscription: ISubscription, eventName: string): void { + // this.subscriptions.add(subscription); + if (!this.subscriptionsByEventName.has(eventName)) { + this.subscriptionsByEventName.set( + eventName, + new Set([subscription]), + ); + return; + } + + this.subscriptionsByEventName.get(eventName)?.add(subscription); + } + + removeSubscription(subscription: ISubscription, eventName: string): void { + // this.subscriptions.delete(subscription); + const subscriptions = this.subscriptionsByEventName.get(eventName); + if (subscriptions) { + subscriptions.delete(subscription); + if (!subscriptions.size) { + this.subscriptionsByEventName.delete(eventName); + } + } + } + + async [publish](publication: Publication, eventName = '', options: boolean | {useCollection?: boolean; args?: any} = false): Promise { + let args = []; + + if (typeof options === 'boolean') { + // useCollection = options; + } else { + if (options.useCollection) { + // useCollection = options.useCollection; + } + + if (options.args) { + args = options.args; + } + } + if (!eventName || eventName.length === 0) { + throw new Error('invalid-event-name'); + } + + const isAllowed = await this.isReadAllowed(publication, eventName, args); + if (isAllowed !== true) { + throw new Error('not-allowed'); + } + + this.addSubscription(publication, eventName); + publication.once('stop', () => + this.removeSubscription(publication, eventName), + ); + publication.ready(); + } + + async iniPublication(): Promise { + const p = this[publish].bind(this); + const { initMethod } = this; + server.subscribe(this.subscriptionName, function(publication, _client, eventName, options) { + initMethod(publication); + return p(publication, eventName, options); + }); + } + + async initMethod(publication: Publication): Promise { + const { isWriteAllowed, name, subscriptionName } = this; + + const method = { + async [this.subscriptionName](this: Client, eventName: string, ...args: any[]): Promise { + const isAllowed = await isWriteAllowed(publication, eventName, args); + if (isAllowed !== true) { + return; + } + this.broker.broadcast(STREAMER_EVENTS.STREAM, [ + name, + eventName, + changedPayload(subscriptionName, { eventName, args }), + ]); + }, + }; + + server.methods(method); + } + + async emitPayload(eventName: string, payload: string): Promise { + if (!payload) { + return; + } + const subscriptions = this.subscriptionsByEventName.get(eventName); + if (!subscriptions || !subscriptions.size) { + return; + } + return sendBroadcast(subscriptions, payload); + } + + emit(eventName: string, ...args: any[]): boolean { + const subscriptions = this.subscriptionsByEventName.get(eventName); + if (!subscriptions || !subscriptions.size) { + return false; + } + + const msg = changedPayload(this.subscriptionName, { + eventName, + args, + }); + + if (!msg) { + return false; + } + + sendBroadcast(subscriptions, msg); + return true; + } + + __emit(event: string, ...args: any[]): boolean { + return super.emit(event, ...args); + } + + // emitWithoutBroadcast(event: string, ...args: any[]): boolean { + // return this._emit(event, args, undefined, false); + // } +} diff --git a/ee/server/services/DDPStreamer/constants.ts b/ee/server/services/DDPStreamer/constants.ts new file mode 100644 index 0000000000000..e64679e57406c --- /dev/null +++ b/ee/server/services/DDPStreamer/constants.ts @@ -0,0 +1,71 @@ +export const STREAMER_EVENTS = { + STREAM: 'stream', + USER_CHANGED: 'user-changed', +}; + +export const DDP_EVENTS = { + ID: 'id', + FIELDS: 'fields', + COLLECTION: 'collection', + CLEARED: 'cleared', + METHODS: 'methods', + + MSG: 'msg', + READY: 'ready', + ADDED: 'added', + CHANGED: 'changed', + RESUME: 'resume', + RESULT: 'result', + METHOD: 'method', + UPDATED: 'updated', + PING: 'ping', + PONG: 'pong', + SUSBCRIBE: 'sub', + CONNECT: 'connect', + CONNECTED: 'connected', + SUBSCRIPTIONS: 'subs', + NO_SUBSCRIBE: 'nosub', + UNSUBSCRIBE: 'unsub', + DISCONNECTED: 'disconnected', + LOGGED: 'logged', +}; + +export const WS_ERRORS = { + CLOSE_PROTOCOL_ERROR: 1002, + UNSUPPORTED_DATA: 1007, + + TIMEOUT: 4000, +}; + +export const WS_ERRORS_MESSAGES = { + CLOSE_PROTOCOL_ERROR: 'CLOSE_PROTOCOL_ERROR', + UNSUPPORTED_DATA: 'UNSUPPORTED_DATA', + TIMEOUT: 'TIMEOUT', +}; + +export const TIMEOUT = 1000 * 30; // 30 seconds + +export const STREAM_NAMES = { + STREAMER_PREFIX: 'stream-', + + ROOMS_CHANGED: 'rooms-changed', + ROOM_DATA: 'room-data', // TODO both data are the same plx merge them + + + ROOM_MESSAGES: 'room-messages', + NOTIFY_ALL: 'notify-all', + NOTIFY_LOGGED: 'notify-logged', + NOTIFY_ROOM: 'notify-room', + NOTIFY_USER: 'notify-user', + PRESENCE: 'userpresence', + + IMPORTERS: 'importers', + ROLES: 'roles', + APPS: 'apps', + CANNED_RESPONSES: 'canned-responses', + LIVECHAT_INQUIRY: 'livechat-inquiry-queue-observer', + LIVECHAT_ROOM: 'livechat-room', + + NOTIFY_ROOM_USERS: 'notify-room-users', + 'my-message': '__my_messages__', +}; diff --git a/ee/server/services/DDPStreamer/index.ts b/ee/server/services/DDPStreamer/index.ts new file mode 100644 index 0000000000000..a8184a5b18535 --- /dev/null +++ b/ee/server/services/DDPStreamer/index.ts @@ -0,0 +1,248 @@ +import http from 'http'; + +import { + ServiceBroker, +} from 'moleculer'; +// import msgpack from 'msgpack-lite'; +import WebSocket from 'ws'; +// import PromService from 'moleculer-prometheus'; +// import config from 'moleculer.config'; +import msgpack5 from 'msgpack5'; + +import * as Streamer from './streams'; +import { Client, MeteorClient } from './Client'; +import { server } from './Server'; +import { STREAMER_EVENTS, DDP_EVENTS, STREAM_NAMES } from './constants'; +import { isEmpty } from './lib/utils'; +import { Presence } from '../../../../server/sdk'; + +const msgpack = msgpack5(); + +const broker = new ServiceBroker(config); +const { + PORT: port = 4000, + PROMETHEUS_PORT = 9100, +} = process.env; + +const httpServer = http.createServer((req, res) => { + res.setHeader('Access-Control-Allow-Origin', '*'); + + if (!/^\/sockjs\/info\?cb=/.test(req.url)) { + return; + } + res.writeHead(200, { 'Content-Type': 'text/plain' }); + + res.end('{"websocket":true,"origins":["*:*"],"cookie_needed":false,"entropy":666}'); +}); + +httpServer.listen(port); + +const wss = new WebSocket.Server({ server: httpServer }); + +wss.on('connection', (ws, req) => { + const isMobile = /^RC Mobile/.test(req.headers['user-agent'] || ''); + + return isMobile ? new Client(ws, broker) : new MeteorClient(ws, broker); +}); + +// export default { +// name: 'streamer', +// events: { +// ...events, +// stream({ stream, eventName, args }) { +// return ( +// Streamer[STREAM_NAMES[stream]] +// && Streamer[stream].emit(eventName, ...args) +// ); +// }, +// stream_internal: { +// handler({ stream, eventName, args }) { +// return ( +// Streamer[STREAM_NAMES[stream]] +// && Streamer[stream].internal.emit(eventName, ...args) +// ); +// }, +// }, +// }, +// }; +broker.createService({ + name: 'streamer', + // settings: { + // port: PROMETHEUS_PORT, + // metrics: { + // streamer_users_connected: { + // type: 'Gauge', + // labelNames: ['nodeID'], + // help: 'Users connecteds by streamer', + // }, + // streamer_users_logged: { + // type: 'Gauge', + // labelNames: ['nodeID'], + // help: 'Users logged by streamer', + // }, + // }, + // }, + // mixins: PROMETHEUS_PORT !== 'false' ? [PromService] : [], + events: { + [STREAM_NAMES.LIVECHAT_INQUIRY]({ action, inquiry }) { + if (!inquiry.department) { + return Streamer.streamLivechatInquiry.emit('public', action, inquiry); + } + Streamer.streamLivechatInquiry.emit(`department/${ inquiry.department }`, action, inquiry); + Streamer.streamLivechatInquiry.emit(inquiry._id, action, inquiry); + }, + [STREAMER_EVENTS.STREAM]([streamer, eventName, payload]) { + const stream = Streamer.Streams.get(streamer); + return stream && stream.emitPayload(eventName, payload); + }, + message({ message }) { + // roomMessages.emitWithoutBroadcast('__my_messages__', record, {}); + Streamer.roomMessages.emit(message.rid, message); + }, + userpresence(payload) { + const STATUS_MAP = { + offline: 0, + online: 1, + away: 2, + busy: 3, + }; + + const { + user: { _id, username, status, statusText }, + } = msgpack.decode(payload); + // Streamer.userpresence.emit(_id, status); + Streamer.notifyLogged.emit('user-status', [_id, username, STATUS_MAP[status], statusText]); + // User.emit(`${ STREAMER_EVENTS.USER_CHANGED }/${ _id }`, _id, user); // use this method + }, + user(payload) { + const { + action, + user: { _id, _updatedAt, ...user }, + } = msgpack.decode(payload); + // User.emit(`${ STREAMER_EVENTS.USER_CHANGED }/${ _id }`, _id, user); + + if (isEmpty(user)) { + return; + } + + const data = { + type: action, + }; + + switch (action) { + case 'updated': + data.diff = user; + break; + case 'inserted': + data.data = user; + break; + case 'removed': + data.id = _id; + break; + } + + Streamer.notifyUser.emit( + `${ _id }/userData`, + data, + ); + + // Notifications.notifyUserInThisInstance(id, 'userData', { diff, type: clientAction }); + }, + 'user.name'(payload) { + const { + user: { _id, name, username }, + } = msgpack.decode(payload); + // User.emit(`${ STREAMER_EVENTS.USER_CHANGED }/${ _id }`, _id, user); + Streamer.notifyLogged.emit('Users:NameChanged', { _id, name, username }); + }, + // 'setting'() { }, + subscription({ action, subscription }) { + Streamer.notifyUser.emit( + `${ subscription.u._id }/subscriptions-changed`, + action, + subscription, + ); + + Streamer.notifyUser.__emit(subscription.u._id, action, subscription); + + // RocketChat.Notifications.notifyUserInThisInstance( + // subscription.u._id, + // 'subscriptions-changed', + // action, + // subscription + // ); + // notifyUser.emit('subscriptions-changed', action, subscription); TODO REMOVE ID + + // notifyUser.emit(subscription.u._id, 'subscriptions-changed', action, subscription); + // RocketChat.Notifications.streamUser.__emit(subscription.u._id, action, subscription); + // RocketChat.Notifications.notifyUserInThisInstance(subscription.u._id, 'subscriptions-changed', action, subscription); + }, + room({ room, action }) { + // RocketChat.Notifications.streamUser.__emit(id, clientAction, data); + Streamer.notifyUser.__emit(room._id, action, room); + Streamer.streamRoomData.emit(room._id, action, room); // TODO REMOVE + }, + // stream: { + // group: 'streamer', + // handler(payload) { + // const [stream, ev, data] = msgpack.decode(payload); + // Streamer.central.emit(stream, ev, data); + // }, + // }, + role(payload) { + Streamer.streamRoles.emit('roles', payload); + }, + }, +}); + +broker.start(); + +server.on(DDP_EVENTS.LOGGED, ({ uid, session }) => { + Presence.newConnection(uid, session); +}); + +server.on(DDP_EVENTS.DISCONNECTED, ({ uid, session }) => { + if (!uid) { + return; + } + Presence.removeConnection(uid, session); +}); + +server.on(DDP_EVENTS.CONNECTED, () => { + broker.emit('metrics.update', { + name: 'streamer_users_connected', + method: 'inc', + labels: { + nodeID: broker.nodeID, + }, + }); +}); + +server.on(DDP_EVENTS.LOGGED, (/* client*/) => { + broker.emit('metrics.update', { + name: 'streamer_users_logged', + method: 'inc', + labels: { + nodeID: broker.nodeID, + }, + }); +}); + +server.on(DDP_EVENTS.DISCONNECTED, ({ uid }) => { + broker.emit('metrics.update', { + name: 'streamer_users_connected', + method: 'dec', + labels: { + nodeID: broker.nodeID, + }, + }); + if (uid) { + broker.emit('metrics.update', { + name: 'streamer_users_logged', + method: 'dec', + labels: { + nodeID: broker.nodeID, + }, + }); + } +}); diff --git a/ee/server/services/DDPStreamer/lib/sendBroadcast.ts b/ee/server/services/DDPStreamer/lib/sendBroadcast.ts new file mode 100644 index 0000000000000..3a523dbd94241 --- /dev/null +++ b/ee/server/services/DDPStreamer/lib/sendBroadcast.ts @@ -0,0 +1,32 @@ +import { Sender } from 'ws'; + +import { ISubscription } from '../Streamer'; + +const broadcastData = (message: string): Buffer[] => { + const data = Sender.frame(Buffer.from(message), { + fin: true, // sending a single fragment message + rsv1: false, // don"t set rsv1 bit (no compression) + opcode: 1, // opcode for a text frame + mask: false, // set false for client-side + readOnly: false, // the data can be modified as needed + }); + + return [Buffer.concat(data)]; +}; + +export const sendBroadcast = async (subscriptions: Set, message: string): Promise => { + const frames = { + default: broadcastData(message), + meteor: broadcastData(`a${ JSON.stringify([message]) }`), + }; + + for (const subscription of subscriptions) { + // eslint-disable-next-line + await new Promise((resolve) => { + subscription.client.ws._sender.sendFrame( + frames[subscription.client.kind], + resolve, + ); + }); + } +}; diff --git a/ee/server/services/DDPStreamer/lib/utils.ts b/ee/server/services/DDPStreamer/lib/utils.ts new file mode 100644 index 0000000000000..e6efcfb4bcb15 --- /dev/null +++ b/ee/server/services/DDPStreamer/lib/utils.ts @@ -0,0 +1,9 @@ +export const isEmpty = function(obj: object | any[] | string): boolean { + if (obj == null) { + return true; + } + if (Array.isArray(obj) || typeof obj === 'string') { + return obj.length === 0; + } + return !Object.values(obj).some((value) => value !== null); +}; diff --git a/ee/server/services/DDPStreamer/streams/all.ts b/ee/server/services/DDPStreamer/streams/all.ts new file mode 100644 index 0000000000000..8492b28ddc68e --- /dev/null +++ b/ee/server/services/DDPStreamer/streams/all.ts @@ -0,0 +1,5 @@ +import { Stream } from '../Streamer'; +import { STREAM_NAMES } from '../constants'; + +export const streamAll = new Stream(STREAM_NAMES.NOTIFY_ALL); +streamAll.allowRead('all'); diff --git a/ee/server/services/DDPStreamer/streams/index.ts b/ee/server/services/DDPStreamer/streams/index.ts new file mode 100644 index 0000000000000..8e945eb57b18d --- /dev/null +++ b/ee/server/services/DDPStreamer/streams/index.ts @@ -0,0 +1,10 @@ +export * from '../Streamer'; +export * from './all'; +export * from './livechatInquiry'; +export * from './missing'; +export * from './notifyLogged'; +export * from './notifyUser'; +export * from './room'; +export * from './roomMessages'; +export * from './roomUsers'; +export * from './presence'; diff --git a/ee/server/services/DDPStreamer/streams/livechatInquiry.ts b/ee/server/services/DDPStreamer/streams/livechatInquiry.ts new file mode 100644 index 0000000000000..f9d81a497ad05 --- /dev/null +++ b/ee/server/services/DDPStreamer/streams/livechatInquiry.ts @@ -0,0 +1,8 @@ +import { Stream } from '../Streamer'; +import { STREAM_NAMES } from '../constants'; +import { Authorization } from '../../../../../server/sdk'; + +export const streamLivechatInquiry = new Stream(STREAM_NAMES.LIVECHAT_INQUIRY); +streamLivechatInquiry.allowRead(function() { + return Authorization.hasPermission(this.uid, 'view-l-room'); +}); diff --git a/ee/server/services/DDPStreamer/streams/missing.ts b/ee/server/services/DDPStreamer/streams/missing.ts new file mode 100644 index 0000000000000..40add6bd38788 --- /dev/null +++ b/ee/server/services/DDPStreamer/streams/missing.ts @@ -0,0 +1,17 @@ +import { Stream } from '../Streamer'; +import { STREAM_NAMES } from '../constants'; + +export const streamApps = new Stream(STREAM_NAMES.APPS); +streamApps.allowRead('all'); + +export const streamImporters = new Stream(STREAM_NAMES.IMPORTERS); +streamImporters.allowRead('all'); + +export const streamRoles = new Stream(STREAM_NAMES.ROLES); +streamRoles.allowRead('all'); + +export const streamCannedresponses = new Stream(STREAM_NAMES.CANNED_RESPONSES); +streamCannedresponses.allowRead('all'); + +export const streamLivechatRoom = new Stream(STREAM_NAMES.LIVECHAT_ROOM); +streamLivechatRoom.allowRead('all'); diff --git a/ee/server/services/DDPStreamer/streams/notifyLogged.ts b/ee/server/services/DDPStreamer/streams/notifyLogged.ts new file mode 100644 index 0000000000000..79bfcb36d3b2c --- /dev/null +++ b/ee/server/services/DDPStreamer/streams/notifyLogged.ts @@ -0,0 +1,7 @@ +import { Stream } from '../Streamer'; +import { STREAM_NAMES } from '../constants'; + +export const notifyLogged = new Stream(STREAM_NAMES.NOTIFY_LOGGED); + +notifyLogged.allowWrite('none'); +notifyLogged.allowRead('logged'); diff --git a/ee/server/services/DDPStreamer/streams/notifyUser.ts b/ee/server/services/DDPStreamer/streams/notifyUser.ts new file mode 100644 index 0000000000000..6d5efa8623d19 --- /dev/null +++ b/ee/server/services/DDPStreamer/streams/notifyUser.ts @@ -0,0 +1,74 @@ +import { Stream, send, changedPayload, publish } from '../Streamer'; +import { STREAM_NAMES } from '../constants'; +import { ISubscription } from '../../../../../definition/ISubscription'; +import { getCollection, Collections } from '../../mongo'; +import { Publication } from '../Publication'; + +class RoomStreamer extends Stream { + async [publish](publication: Publication, eventName = '', options: boolean | {useCollection?: boolean; args?: any} = false): Promise { + super[publish](publication, eventName, options); + // const uid = Meteor.userId(); + const { uid } = publication.client; + if (/rooms-changed/.test(eventName)) { + // TODO: change this to serialize only once + const roomEvent = (...args: any[]): void => { + const payload = changedPayload(this.subscriptionName, { + eventName: `${ uid }/rooms-changed`, + args, + }); + + payload && send( + publication, + payload, + ); + }; + + const Subscription = await getCollection(Collections.Subscriptions); + + const subscriptions = await Subscription.find>( + { 'u._id': uid }, + { projection: { rid: 1 } }, + ).toArray(); + + subscriptions.forEach(({ rid }) => { + this.on(rid, roomEvent); + }); + + const userEvent = (clientAction: string, { rid }: Partial = {}): void => { + if (!rid) { + return; + } + + switch (clientAction) { + case 'inserted': + subscriptions.push({ rid }); + this.on(rid, roomEvent); + break; + + case 'removed': + this.removeListener(rid, roomEvent); + break; + } + }; + this.on(uid, userEvent); + + publication.once('stop', () => { + this.removeListener(uid, userEvent); + subscriptions.forEach(({ rid }) => this.removeListener(rid, roomEvent)); + }); + } + } +} + +export const notifyUser = new RoomStreamer(STREAM_NAMES.NOTIFY_USER); +notifyUser.allowWrite('none'); +notifyUser.allowRead('logged'); + +export const streamRoomData = new Stream(STREAM_NAMES.ROOM_DATA); +streamRoomData.allowRead(function(rid) { + // TODO: missing method + return this.client.broker.call('authorization.canAccessRoom', { + room: { _id: rid }, + user: { _id: this.uid }, + }); +}); diff --git a/ee/server/services/DDPStreamer/streams/presence.ts b/ee/server/services/DDPStreamer/streams/presence.ts new file mode 100644 index 0000000000000..a18c037b6d9f1 --- /dev/null +++ b/ee/server/services/DDPStreamer/streams/presence.ts @@ -0,0 +1,6 @@ +import { Stream } from '../Streamer'; +import { STREAM_NAMES } from '../constants'; + +export const userpresence = new Stream(STREAM_NAMES.PRESENCE); + +userpresence.allowRead('all'); diff --git a/ee/server/services/DDPStreamer/streams/room.ts b/ee/server/services/DDPStreamer/streams/room.ts new file mode 100644 index 0000000000000..d1df351ef1dc1 --- /dev/null +++ b/ee/server/services/DDPStreamer/streams/room.ts @@ -0,0 +1,6 @@ +import { Stream } from '../Streamer'; +import { STREAM_NAMES } from '../constants'; + +export const streamRoom = new Stream(STREAM_NAMES.NOTIFY_ROOM); +streamRoom.allowWrite('all'); +streamRoom.allowRead('all'); diff --git a/ee/server/services/DDPStreamer/streams/roomMessages.ts b/ee/server/services/DDPStreamer/streams/roomMessages.ts new file mode 100644 index 0000000000000..a4208a11a202c --- /dev/null +++ b/ee/server/services/DDPStreamer/streams/roomMessages.ts @@ -0,0 +1,13 @@ +import { Stream } from '../Streamer'; +import { STREAM_NAMES } from '../constants'; +// import { Authorization } from '../../../../../server/sdk'; + +export const roomMessages = new Stream(STREAM_NAMES.ROOM_MESSAGES); +roomMessages.allowWrite('none'); +roomMessages.allowRead(function(rid) { + // TODO: missing method + return this.client.broker.call('authorization.canAccessRoom', { + room: { _id: rid }, + user: { _id: this.uid }, + }); +}); diff --git a/ee/server/services/DDPStreamer/streams/roomUsers.ts b/ee/server/services/DDPStreamer/streams/roomUsers.ts new file mode 100644 index 0000000000000..0d5d0bbba515d --- /dev/null +++ b/ee/server/services/DDPStreamer/streams/roomUsers.ts @@ -0,0 +1,6 @@ +// mangola stream detected??? +import { Stream } from '../Streamer'; +import { STREAM_NAMES } from '../constants'; + +export const streamRoomUsers = new Stream(STREAM_NAMES.NOTIFY_ROOM_USERS); +streamRoomUsers.allowRead('none'); diff --git a/ee/server/services/DDPStreamer/types/IPacket.ts b/ee/server/services/DDPStreamer/types/IPacket.ts new file mode 100644 index 0000000000000..5a2ebd086b376 --- /dev/null +++ b/ee/server/services/DDPStreamer/types/IPacket.ts @@ -0,0 +1,9 @@ +export interface IPacket { + name: string; + id: string; + method: string; + msg: string; + version: string; + support: string[]; + params: any[]; +} diff --git a/ee/server/services/DDPStreamer/types/ws.d.ts b/ee/server/services/DDPStreamer/types/ws.d.ts new file mode 100644 index 0000000000000..b71218b72140b --- /dev/null +++ b/ee/server/services/DDPStreamer/types/ws.d.ts @@ -0,0 +1,18 @@ +/* eslint-disable no-redeclare */ +/* eslint-disable no-unused-vars */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import 'ws'; + +type FrameOptions = { + opcode: number; // The opcode + readOnly: boolean; // Specifies whether `data` can be modified + fin: boolean; // Specifies whether or not to set the FIN bit + mask: boolean; // Specifies whether or not to mask `data` + rsv1: boolean; // Specifies whether or not to set the RSV1 bit +} + +declare module 'ws' { + class Sender { + static frame(data: Buffer, options: FrameOptions): Buffer[]; + } +} diff --git a/ee/server/services/package-lock.json b/ee/server/services/package-lock.json index 4f3aada898aa0..f430c0ef22506 100644 --- a/ee/server/services/package-lock.json +++ b/ee/server/services/package-lock.json @@ -226,6 +226,12 @@ "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", "dev": true }, + "@types/ejson": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@types/ejson/-/ejson-2.1.2.tgz", + "integrity": "sha1-oMuiYNUAYxDch3kFRj2D1fPN8RI=", + "dev": true + }, "@types/msgpack5": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/@types/msgpack5/-/msgpack5-3.4.1.tgz", @@ -239,6 +245,15 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-14.6.4.tgz", "integrity": "sha512-Wk7nG1JSaMfMpoMJDKUsWYugliB2Vy55pdjLpmLixeyMi7HizW2I/9QoxsPCkXl3dO+ZOVqPumKaDUv5zJu2uQ==" }, + "@types/ws": { + "version": "7.2.6", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.2.6.tgz", + "integrity": "sha512-Q07IrQUSNpr+cXU4E4LtkSIBPie5GLZyyMC1QtQYRLWz701+XcoVygGUZgvLqElq1nU4ICldMYPnexlBsg3dqQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -628,6 +643,11 @@ "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", "dev": true }, + "ejson": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ejson/-/ejson-2.2.0.tgz", + "integrity": "sha512-kWa0AKAxDhmr4t6c4pgQqk6yL52/M67xOMh60HRnAeydzo5QIxOitN5bE1+e0rbdnxfly7FTB9e2Ny0ypLMbag==" + }, "emitter-listener": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/emitter-listener/-/emitter-listener-1.1.2.tgz", @@ -2021,8 +2041,7 @@ "ws": { "version": "7.2.5", "resolved": "https://registry.npmjs.org/ws/-/ws-7.2.5.tgz", - "integrity": "sha512-C34cIU4+DB2vMyAbmEKossWq2ZQDr6QEyuuCzWrM9zfw1sGc0mYiJ0UnG9zzNykt49C2Fi34hvr2vssFQRS6EA==", - "dev": true + "integrity": "sha512-C34cIU4+DB2vMyAbmEKossWq2ZQDr6QEyuuCzWrM9zfw1sGc0mYiJ0UnG9zzNykt49C2Fi34hvr2vssFQRS6EA==" }, "xregexp": { "version": "2.0.0", diff --git a/ee/server/services/package.json b/ee/server/services/package.json index 238e0d8cb8080..c222e1b533d3c 100644 --- a/ee/server/services/package.json +++ b/ee/server/services/package.json @@ -17,13 +17,17 @@ "license": "MIT", "dependencies": { "@types/msgpack5": "^3.4.1", + "ejson": "^2.2.0", + "uuid": "^7.0.3", + "ws": "^7.2.3", "bcrypt": "^4.0.1", "mongodb": "^3.6.1", - "msgpack5": "^4.2.1", - "uuid": "^7.0.3" + "msgpack5": "^4.2.1" }, "devDependencies": { + "@types/ejson": "^2.1.2", "@types/node": "^14.6.4", + "@types/ws": "^7.2.6", "pm2": "^4.4.1", "ts-node": "^9.0.0", "typescript": "^3.9.7" From f2c58ed73aaa29c02eccfeed4a7f3142d17c6dd2 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Sun, 6 Sep 2020 16:18:51 -0300 Subject: [PATCH 029/198] Fix more TS errors --- ee/server/services/DDPStreamer/Client.ts | 8 ++++---- ee/server/services/DDPStreamer/lib/sendBroadcast.ts | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ee/server/services/DDPStreamer/Client.ts b/ee/server/services/DDPStreamer/Client.ts index bde5bc0d034a3..76ad10571158b 100644 --- a/ee/server/services/DDPStreamer/Client.ts +++ b/ee/server/services/DDPStreamer/Client.ts @@ -9,13 +9,13 @@ import { server, SERVER_ID } from './Server'; import { IPacket } from './types/IPacket'; export class Client extends EventEmitter { - protected kind = 'default'; - private timeout: NodeJS.Timeout; private chain = Promise.resolve(); - public session = uuidv1(); + public kind = 'default'; + + public readonly session = uuidv1(); public subscriptions = new Map(); @@ -150,7 +150,7 @@ export class Client extends EventEmitter { } export class MeteorClient extends Client { - kind = 'meteor'; + public kind = 'meteor'; // TODO implement meteor errors // a["{\"msg\":\"result\",\"id\":\"12\",\"error\":{\"isClientSafe\":true,\"error\":403,\"reason\":\"User has no password set\",\"message\":\"User has no password set [403]\",\"errorType\":\"Meteor.Error\"}}"] diff --git a/ee/server/services/DDPStreamer/lib/sendBroadcast.ts b/ee/server/services/DDPStreamer/lib/sendBroadcast.ts index 3a523dbd94241..729d55ad93782 100644 --- a/ee/server/services/DDPStreamer/lib/sendBroadcast.ts +++ b/ee/server/services/DDPStreamer/lib/sendBroadcast.ts @@ -15,7 +15,7 @@ const broadcastData = (message: string): Buffer[] => { }; export const sendBroadcast = async (subscriptions: Set, message: string): Promise => { - const frames = { + const frames: {[k: string]: Buffer[]} = { default: broadcastData(message), meteor: broadcastData(`a${ JSON.stringify([message]) }`), }; From 540515496f7e11eeb0815f861efad66871af7276 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Mon, 7 Sep 2020 11:02:54 -0300 Subject: [PATCH 030/198] Import canAccessRooms from other areas --- .../server/functions/canAccessRoom.js | 39 +----------- app/authorization/server/index.js | 2 - app/discussion/server/authorization.js | 10 --- app/discussion/server/index.js | 1 - .../roomAccessValidator.compatibility.js | 51 ++++++++++++++++ .../roomAccessValidator.internalService.ts | 21 +++++++ app/livechat/server/startup.js | 61 +------------------ .../roomAccessValidator.compatibility.js | 18 ++++++ .../roomAccessValidator.internalService.ts | 21 +++++++ app/tokenpass/server/startup.js | 23 +------ definition/IRoom.ts | 9 +++ server/sdk/types/IAuthorization.ts | 7 ++- server/sdk/types/IAuthorizationLivechat.ts | 5 ++ server/sdk/types/IAuthorizationTokenpass.ts | 5 ++ .../services/authorization/canAccessRoom.ts | 53 ++++++++++++++++ .../authorization/canAccessRoomLivechat.ts | 14 +++++ .../authorization/canAccessRoomTokenpass.ts | 14 +++++ server/services/authorization/service.ts | 6 ++ 18 files changed, 228 insertions(+), 132 deletions(-) delete mode 100644 app/discussion/server/authorization.js create mode 100644 app/livechat/server/roomAccessValidator.compatibility.js create mode 100644 app/livechat/server/roomAccessValidator.internalService.ts create mode 100644 app/tokenpass/server/roomAccessValidator.compatibility.js create mode 100644 app/tokenpass/server/roomAccessValidator.internalService.ts create mode 100644 server/sdk/types/IAuthorizationLivechat.ts create mode 100644 server/sdk/types/IAuthorizationTokenpass.ts create mode 100644 server/services/authorization/canAccessRoom.ts create mode 100644 server/services/authorization/canAccessRoomLivechat.ts create mode 100644 server/services/authorization/canAccessRoomTokenpass.ts diff --git a/app/authorization/server/functions/canAccessRoom.js b/app/authorization/server/functions/canAccessRoom.js index abfa3e70c631a..b33c6bbed9598 100644 --- a/app/authorization/server/functions/canAccessRoom.js +++ b/app/authorization/server/functions/canAccessRoom.js @@ -1,40 +1,5 @@ -import { hasPermissionAsync } from './hasPermission'; -import { Subscriptions } from '../../../models/server/raw'; -import { getValue } from '../../../settings/server/raw'; +import { Authorization } from '../../../../server/sdk'; -export const roomAccessValidators = [ - async function(room, user = {}) { - if (room && room.t === 'c') { - const anonymous = await getValue('Accounts_AllowAnonymousRead'); - if (!user._id && anonymous === true) { - return true; - } - - return hasPermissionAsync(user._id, 'view-c-room'); - } - }, - async function(room, user) { - if (!room || !user) { - return; - } - - const exists = await Subscriptions.countByRoomIdAndUserId(room._id, user._id); - if (exists) { - return true; - } - }, -]; - -export const canAccessRoomAsync = async (room, user, extraData) => { - for (let i = 0, total = roomAccessValidators.length; i < total; i++) { - // eslint-disable-next-line no-await-in-loop - const permitted = await roomAccessValidators[i](room, user, extraData); - if (permitted) { - return true; - } - } -}; +export const canAccessRoomAsync = async (room, user, extraData) => Authorization.hasAllPermission(room, user, extraData); export const canAccessRoom = (room, user, extraData) => Promise.await(canAccessRoomAsync(room, user, extraData)); - -export const addRoomAccessValidator = (validator) => roomAccessValidators.push(validator.bind(this)); diff --git a/app/authorization/server/index.js b/app/authorization/server/index.js index c4ceb42c9a3d5..c248f1c6e0e90 100644 --- a/app/authorization/server/index.js +++ b/app/authorization/server/index.js @@ -1,6 +1,5 @@ import { addUserRoles } from './functions/addUserRoles'; import { - addRoomAccessValidator, canAccessRoom, roomAccessValidators, } from './functions/canAccessRoom'; @@ -32,7 +31,6 @@ export { removeUserFromRoles, canSendMessage, validateRoomMessagePermissions, - addRoomAccessValidator, roomAccessValidators, addUserRoles, canAccessRoom, diff --git a/app/discussion/server/authorization.js b/app/discussion/server/authorization.js deleted file mode 100644 index 6835305ed82be..0000000000000 --- a/app/discussion/server/authorization.js +++ /dev/null @@ -1,10 +0,0 @@ -import { Meteor } from 'meteor/meteor'; - -import { addRoomAccessValidator, canAccessRoom } from '../../authorization'; -import { Rooms } from '../../models'; - -Meteor.startup(() => { - addRoomAccessValidator(function(room, user) { - return room && room.prid && canAccessRoom(Rooms.findOne(room.prid), user); - }); -}); diff --git a/app/discussion/server/index.js b/app/discussion/server/index.js index 32b341422bf65..92e47178450a5 100644 --- a/app/discussion/server/index.js +++ b/app/discussion/server/index.js @@ -1,5 +1,4 @@ import './config'; -import './authorization'; import './permissions'; import './hooks/propagateDiscussionMetadata'; diff --git a/app/livechat/server/roomAccessValidator.compatibility.js b/app/livechat/server/roomAccessValidator.compatibility.js new file mode 100644 index 0000000000000..a2092302d01b2 --- /dev/null +++ b/app/livechat/server/roomAccessValidator.compatibility.js @@ -0,0 +1,51 @@ +import { LivechatRooms } from '../../models'; +import { hasPermission, hasRole } from '../../authorization'; +import { LivechatDepartment, LivechatDepartmentAgents, LivechatInquiry } from '../../models/server'; +import { RoutingManager } from './lib/RoutingManager'; + +export const validators = [ + function(room, user) { + return hasPermission(user._id, 'view-livechat-rooms'); + }, + function(room, user) { + const { _id: userId } = user; + const { servedBy: { _id: agentId } = {} } = room; + return userId === agentId || (!room.open && hasPermission(user._id, 'view-livechat-room-closed-by-another-agent')); + }, + function(room, user, extraData) { + if (extraData && extraData.rid) { + room = LivechatRooms.findOneById(extraData.rid); + } + return extraData && extraData.visitorToken && room.v && room.v.token === extraData.visitorToken; + }, + function(room, user) { + const { previewRoom } = RoutingManager.getConfig(); + if (!previewRoom) { + return; + } + + let departmentIds; + if (!hasRole(user._id, 'livechat-manager')) { + const departmentAgents = LivechatDepartmentAgents.findByAgentId(user._id).fetch().map((d) => d.departmentId); + departmentIds = LivechatDepartment.find({ _id: { $in: departmentAgents }, enabled: true }).fetch().map((d) => d._id); + } + + const filter = { + rid: room._id, + ...departmentIds && departmentIds.length > 0 && { department: { $in: departmentIds } }, + }; + + const inquiry = LivechatInquiry.findOne(filter, { fields: { status: 1 } }); + return inquiry && inquiry.status === 'queued'; + }, + function(room, user) { + if (!room.departmentId || room.open) { + return; + } + const agentOfDepartment = LivechatDepartmentAgents.findOneByAgentIdAndDepartmentId(user._id, room.departmentId); + if (!agentOfDepartment) { + return; + } + return hasPermission(user._id, 'view-livechat-room-closed-same-department'); + }, +]; diff --git a/app/livechat/server/roomAccessValidator.internalService.ts b/app/livechat/server/roomAccessValidator.internalService.ts new file mode 100644 index 0000000000000..9e8df40c4a63a --- /dev/null +++ b/app/livechat/server/roomAccessValidator.internalService.ts @@ -0,0 +1,21 @@ +import { ServiceClass } from '../../../server/sdk/types/ServiceClass'; +import { IAuthorizationLivechat } from '../../../server/sdk/types/IAuthorizationLivechat'; +import { validators } from './roomAccessValidator.compatibility'; +import { RoomAccessValidator } from '../../../server/sdk/types/IAuthorization'; +import { api } from '../../../server/sdk/api'; + +class AuthorizationLivechat extends ServiceClass implements IAuthorizationLivechat { + protected name = 'authorization.livechat'; + + canAccessRoom: RoomAccessValidator = async (room, user, extraData): Promise => { + for (const validator of validators) { + if (validator(room, user, extraData)) { + return true; + } + } + + return false; + } +} + +api.registerService(new AuthorizationLivechat()); diff --git a/app/livechat/server/startup.js b/app/livechat/server/startup.js index 8ae1e047bab80..c1f52886d1205 100644 --- a/app/livechat/server/startup.js +++ b/app/livechat/server/startup.js @@ -3,77 +3,18 @@ import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; import { roomTypes } from '../../utils'; import { LivechatRooms } from '../../models'; -import { hasPermission, hasRole, addRoomAccessValidator } from '../../authorization'; import { callbacks } from '../../callbacks'; import { settings } from '../../settings'; -import { LivechatDepartment, LivechatDepartmentAgents, LivechatInquiry } from '../../models/server'; -import { RoutingManager } from './lib/RoutingManager'; import { createLivechatQueueView } from './lib/Helper'; import { LivechatAgentActivityMonitor } from './statistics/LivechatAgentActivityMonitor'; import { businessHourManager } from './business-hour'; import { createDefaultBusinessHourIfNotExists } from './business-hour/Helper'; -function allowAccessClosedRoomOfSameDepartment(room, user) { - if (!room || !user || room.t !== 'l' || !room.departmentId || room.open) { - return; - } - const agentOfDepartment = LivechatDepartmentAgents.findOneByAgentIdAndDepartmentId(user._id, room.departmentId); - if (!agentOfDepartment) { - return; - } - return hasPermission(user._id, 'view-livechat-room-closed-same-department'); -} +import './roomAccessValidator.internalService'; Meteor.startup(async () => { roomTypes.setRoomFind('l', (_id) => LivechatRooms.findOneById(_id)); - addRoomAccessValidator(function(room, user) { - return room && room.t === 'l' && user && hasPermission(user._id, 'view-livechat-rooms'); - }); - - addRoomAccessValidator(function(room, user) { - if (!room || !user || room.t !== 'l') { - return; - } - const { _id: userId } = user; - const { servedBy: { _id: agentId } = {} } = room; - return userId === agentId || (!room.open && hasPermission(user._id, 'view-livechat-room-closed-by-another-agent')); - }); - - addRoomAccessValidator(function(room, user, extraData) { - if (!room && extraData && extraData.rid) { - room = LivechatRooms.findOneById(extraData.rid); - } - return room && room.t === 'l' && extraData && extraData.visitorToken && room.v && room.v.token === extraData.visitorToken; - }); - - addRoomAccessValidator(function(room, user) { - const { previewRoom } = RoutingManager.getConfig(); - if (!previewRoom) { - return; - } - - if (!user || !room || room.t !== 'l') { - return; - } - - let departmentIds; - if (!hasRole(user._id, 'livechat-manager')) { - const departmentAgents = LivechatDepartmentAgents.findByAgentId(user._id).fetch().map((d) => d.departmentId); - departmentIds = LivechatDepartment.find({ _id: { $in: departmentAgents }, enabled: true }).fetch().map((d) => d._id); - } - - const filter = { - rid: room._id, - ...departmentIds && departmentIds.length > 0 && { department: { $in: departmentIds } }, - }; - - const inquiry = LivechatInquiry.findOne(filter, { fields: { status: 1 } }); - return inquiry && inquiry.status === 'queued'; - }); - - addRoomAccessValidator(allowAccessClosedRoomOfSameDepartment); - callbacks.add('beforeLeaveRoom', function(user, room) { if (room.t !== 'l') { return user; diff --git a/app/tokenpass/server/roomAccessValidator.compatibility.js b/app/tokenpass/server/roomAccessValidator.compatibility.js new file mode 100644 index 0000000000000..051227f1eaa55 --- /dev/null +++ b/app/tokenpass/server/roomAccessValidator.compatibility.js @@ -0,0 +1,18 @@ +import { Tokenpass } from './Tokenpass'; +import { Users } from '../../models'; + +export function validateTokenAccess(userData, roomData) { + if (!userData || !userData.services || !userData.services.tokenpass || !userData.services.tokenpass.tcaBalances) { + return false; + } + + return Tokenpass.validateAccess(roomData.tokenpass, userData.services.tokenpass.tcaBalances); +} + +export const validators = [ + function(room, user) { + const userData = Users.getTokenBalancesByUserId(user._id); + + return validateTokenAccess(userData, room); + }, +]; diff --git a/app/tokenpass/server/roomAccessValidator.internalService.ts b/app/tokenpass/server/roomAccessValidator.internalService.ts new file mode 100644 index 0000000000000..b0a9a59eea329 --- /dev/null +++ b/app/tokenpass/server/roomAccessValidator.internalService.ts @@ -0,0 +1,21 @@ +import { ServiceClass } from '../../../server/sdk/types/ServiceClass'; +import { validators } from './roomAccessValidator.compatibility'; +import { RoomAccessValidator } from '../../../server/sdk/types/IAuthorization'; +import { api } from '../../../server/sdk/api'; +import { IAuthorizationTokenpass } from '../../../server/sdk/types/IAuthorizationTokenpass'; + +class AuthorizationTokenpass extends ServiceClass implements IAuthorizationTokenpass { + protected name = 'authorization.livechat'; + + canAccessRoom: RoomAccessValidator = async (room, user): Promise => { + for (const validator of validators) { + if (validator(room, user)) { + return true; + } + } + + return false; + } +} + +api.registerService(new AuthorizationTokenpass()); diff --git a/app/tokenpass/server/startup.js b/app/tokenpass/server/startup.js index 459cac9111fa0..3ea4d3b2f0192 100644 --- a/app/tokenpass/server/startup.js +++ b/app/tokenpass/server/startup.js @@ -2,11 +2,10 @@ import { Meteor } from 'meteor/meteor'; import { Accounts } from 'meteor/accounts-base'; import { updateUserTokenpassBalances } from './functions/updateUserTokenpassBalances'; -import { Tokenpass } from './Tokenpass'; import { settings } from '../../settings'; -import { addRoomAccessValidator } from '../../authorization'; -import { Users } from '../../models'; import { callbacks } from '../../callbacks'; +import { validateTokenAccess } from './roomAccessValidator.compatibility'; +import './roomAccessValidator.internalService'; settings.addGroup('OAuth', function() { this.section('Tokenpass', function() { @@ -23,25 +22,7 @@ settings.addGroup('OAuth', function() { }); }); -function validateTokenAccess(userData, roomData) { - if (!userData || !userData.services || !userData.services.tokenpass || !userData.services.tokenpass.tcaBalances) { - return false; - } - - return Tokenpass.validateAccess(roomData.tokenpass, userData.services.tokenpass.tcaBalances); -} - Meteor.startup(function() { - addRoomAccessValidator(function(room, user) { - if (!room || !room.tokenpass || !user) { - return false; - } - - const userData = Users.getTokenBalancesByUserId(user._id); - - return validateTokenAccess(userData, room); - }); - callbacks.add('beforeJoinRoom', function(user, room) { if (room.tokenpass && !validateTokenAccess(user, room)) { throw new Meteor.Error('error-not-allowed', 'Token required', { method: 'joinRoom' }); diff --git a/definition/IRoom.ts b/definition/IRoom.ts index 6e6cbe368c1e7..30e567a337cd5 100644 --- a/definition/IRoom.ts +++ b/definition/IRoom.ts @@ -1,4 +1,13 @@ export interface IRoom { _id: string; + prid: string; + t: 'c' | 'p' | 'd' | 'l'; _updatedAt?: Date; + tokenpass?: { + require: string; + tokens: { + token: string; + balance: number; + }[]; + }; } diff --git a/server/sdk/types/IAuthorization.ts b/server/sdk/types/IAuthorization.ts index 74c7fe3c50aae..bedfcc64085ed 100644 --- a/server/sdk/types/IAuthorization.ts +++ b/server/sdk/types/IAuthorization.ts @@ -1,6 +1,11 @@ +import { IRoom } from '../../../definition/IRoom'; +import { IUser } from '../../../definition/IUser'; + +export type RoomAccessValidator = (room: Partial, user: Pick, extraData?: object) => Promise; + export interface IAuthorization { hasAllPermission(userId: string, permissions: string[], scope?: string): Promise; hasPermission(userId: string, permissionId: string, scope?: string): Promise; hasAtLeastOnePermission(userId: string, permissions: string[], scope?: string): Promise; - prop?: string; + canAccessRoom: RoomAccessValidator; } diff --git a/server/sdk/types/IAuthorizationLivechat.ts b/server/sdk/types/IAuthorizationLivechat.ts new file mode 100644 index 0000000000000..be7718c408e4f --- /dev/null +++ b/server/sdk/types/IAuthorizationLivechat.ts @@ -0,0 +1,5 @@ +import { RoomAccessValidator } from './IAuthorization'; + +export interface IAuthorizationLivechat { + canAccessRoom: RoomAccessValidator; +} diff --git a/server/sdk/types/IAuthorizationTokenpass.ts b/server/sdk/types/IAuthorizationTokenpass.ts new file mode 100644 index 0000000000000..14860dff49eb9 --- /dev/null +++ b/server/sdk/types/IAuthorizationTokenpass.ts @@ -0,0 +1,5 @@ +import { RoomAccessValidator } from './IAuthorization'; + +export interface IAuthorizationTokenpass { + canAccessRoom: RoomAccessValidator; +} diff --git a/server/services/authorization/canAccessRoom.ts b/server/services/authorization/canAccessRoom.ts new file mode 100644 index 0000000000000..58aef0767ee46 --- /dev/null +++ b/server/services/authorization/canAccessRoom.ts @@ -0,0 +1,53 @@ + +import { Authorization } from '../../sdk'; +// TODO: change to MS instance +import { getValue } from '../../../app/settings/server/raw'; +import { Subscriptions, Rooms } from '../../../app/models/server/raw'; +import { RoomAccessValidator } from '../../sdk/types/IAuthorization'; +import { canAccessRoomLivechat } from './canAccessRoomLivechat'; +import { canAccessRoomTokenpass } from './canAccessRoomTokenpass'; + +export const roomAccessValidators: RoomAccessValidator[] = [ + async function(room, user): Promise { + if (room.t === 'c') { + const anonymous = await getValue('Accounts_AllowAnonymousRead'); + if (!user._id && anonymous === true) { + return true; + } + + return Authorization.hasPermission(user._id, 'view-c-room'); + } + + return false; + }, + + async function(room, user): Promise { + if (await Subscriptions.countByRoomIdAndUserId(room._id, user._id)) { + return true; + } + return false; + }, + + async function(room, user): Promise { + if (!room.prid) { + return false; + } + return Authorization.canAccessRoom(Rooms.findOne(room.prid), user); + }, + canAccessRoomLivechat, + canAccessRoomTokenpass, +]; + +export const canAccessRoom: RoomAccessValidator = async (room, user, extraData) => { + if (!room || !user) { + return false; + } + + for await (const roomAccessValidator of roomAccessValidators) { + if (await roomAccessValidator(room, user, extraData)) { + return true; + } + } + + return false; +}; diff --git a/server/services/authorization/canAccessRoomLivechat.ts b/server/services/authorization/canAccessRoomLivechat.ts new file mode 100644 index 0000000000000..a184dc100db5b --- /dev/null +++ b/server/services/authorization/canAccessRoomLivechat.ts @@ -0,0 +1,14 @@ +import { IAuthorizationLivechat } from '../../sdk/types/IAuthorizationLivechat'; +import { proxify } from '../../sdk/lib/proxify'; +import { RoomAccessValidator } from '../../sdk/types/IAuthorization'; + +export const AuthorizationLivechat = proxify('authorization.livechat'); + +export const canAccessRoomLivechat: RoomAccessValidator = async (room, user): Promise => { + if (room.t !== 'l') { + return false; + } + + // Call back core temporarily + return AuthorizationLivechat.canAccessRoom(room, user); +}; diff --git a/server/services/authorization/canAccessRoomTokenpass.ts b/server/services/authorization/canAccessRoomTokenpass.ts new file mode 100644 index 0000000000000..2250e3c469fc7 --- /dev/null +++ b/server/services/authorization/canAccessRoomTokenpass.ts @@ -0,0 +1,14 @@ +import { IAuthorizationTokenpass } from '../../sdk/types/IAuthorizationTokenpass'; +import { proxify } from '../../sdk/lib/proxify'; +import { RoomAccessValidator } from '../../sdk/types/IAuthorization'; + +export const AuthorizationTokenpass = proxify('authorization.tokenpass'); + +export const canAccessRoomTokenpass: RoomAccessValidator = async (room, user): Promise => { + if (!room.tokenpass) { + return false; + } + + // Call back core temporarily + return AuthorizationTokenpass.canAccessRoom(room, user); +}; diff --git a/server/services/authorization/service.ts b/server/services/authorization/service.ts index dedae21743d7e..b3d8637d19538 100644 --- a/server/services/authorization/service.ts +++ b/server/services/authorization/service.ts @@ -5,6 +5,10 @@ import { IAuthorization } from '../../sdk/types/IAuthorization'; import { ServiceClass } from '../../sdk/types/ServiceClass'; import { AuthorizationUtils } from '../../../app/authorization/lib/AuthorizationUtils'; import { IUser } from '../../../definition/IUser'; +import { canAccessRoom } from './canAccessRoom'; + +import './canAccessRoomLivechat'; +import './canAccessRoomTokenpass'; // Register as class export class Authorization extends ServiceClass implements IAuthorization { @@ -45,6 +49,8 @@ export class Authorization extends ServiceClass implements IAuthorization { return this.atLeastOne(userId, permissions, scope); } + canAccessRoom = canAccessRoom; + private async rolesHasPermission(permission: string, roles: string[]): Promise { // TODO this AuthorizationUtils should be brought to this service. currently its state is kept on the application only, but it needs to kept here if (AuthorizationUtils.isPermissionRestrictedForRoleList(permission, roles)) { From 7d5bbb9dd0bbb5b9ba1ffa74246a1af7d396ae35 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Mon, 7 Sep 2020 11:26:37 -0300 Subject: [PATCH 031/198] DDPStreamer: Use canAccessRoom --- ee/server/services/DDPStreamer/lib/sendBroadcast.ts | 3 ++- ee/server/services/DDPStreamer/streams/roomMessages.ts | 8 ++------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/ee/server/services/DDPStreamer/lib/sendBroadcast.ts b/ee/server/services/DDPStreamer/lib/sendBroadcast.ts index 729d55ad93782..fb90c458525fd 100644 --- a/ee/server/services/DDPStreamer/lib/sendBroadcast.ts +++ b/ee/server/services/DDPStreamer/lib/sendBroadcast.ts @@ -23,7 +23,8 @@ export const sendBroadcast = async (subscriptions: Set, message: for (const subscription of subscriptions) { // eslint-disable-next-line await new Promise((resolve) => { - subscription.client.ws._sender.sendFrame( + // TODO: missing typing + (subscription.client.ws as any)._sender.sendFrame( frames[subscription.client.kind], resolve, ); diff --git a/ee/server/services/DDPStreamer/streams/roomMessages.ts b/ee/server/services/DDPStreamer/streams/roomMessages.ts index a4208a11a202c..a5f3caaee6862 100644 --- a/ee/server/services/DDPStreamer/streams/roomMessages.ts +++ b/ee/server/services/DDPStreamer/streams/roomMessages.ts @@ -1,13 +1,9 @@ import { Stream } from '../Streamer'; import { STREAM_NAMES } from '../constants'; -// import { Authorization } from '../../../../../server/sdk'; +import { Authorization } from '../../../../../server/sdk'; export const roomMessages = new Stream(STREAM_NAMES.ROOM_MESSAGES); roomMessages.allowWrite('none'); roomMessages.allowRead(function(rid) { - // TODO: missing method - return this.client.broker.call('authorization.canAccessRoom', { - room: { _id: rid }, - user: { _id: this.uid }, - }); + return Authorization.canAccessRoom({ _id: rid }, { _id: this.uid }); }); From ca19598a2551515db57692d70bf94c00ba714f14 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Mon, 7 Sep 2020 14:58:51 -0300 Subject: [PATCH 032/198] DDPStreamer: Fix ClientMobile --- ee/server/services/DDPStreamer/Client.ts | 9 +- .../services/DDPStreamer/ClientMobile.ts | 39 +++--- ee/server/services/DDPStreamer/Publication.ts | 3 +- .../services/DDPStreamer/configureServer.ts | 122 ++++++++++++++++++ 4 files changed, 147 insertions(+), 26 deletions(-) create mode 100644 ee/server/services/DDPStreamer/configureServer.ts diff --git a/ee/server/services/DDPStreamer/Client.ts b/ee/server/services/DDPStreamer/Client.ts index 76ad10571158b..dbd13246b7447 100644 --- a/ee/server/services/DDPStreamer/Client.ts +++ b/ee/server/services/DDPStreamer/Client.ts @@ -2,17 +2,17 @@ import { EventEmitter } from 'events'; import { v1 as uuidv1 } from 'uuid'; import WebSocket from 'ws'; -import { ServiceBroker } from 'moleculer'; import { DDP_EVENTS, WS_ERRORS, WS_ERRORS_MESSAGES, TIMEOUT } from './constants'; -import { server, SERVER_ID } from './Server'; +import { SERVER_ID } from './Server'; +import { server } from './configureServer'; import { IPacket } from './types/IPacket'; export class Client extends EventEmitter { - private timeout: NodeJS.Timeout; - private chain = Promise.resolve(); + protected timeout: NodeJS.Timeout; + public kind = 'default'; public readonly session = uuidv1(); @@ -25,7 +25,6 @@ export class Client extends EventEmitter { constructor( public ws: WebSocket, - public broker: ServiceBroker, ) { super(); diff --git a/ee/server/services/DDPStreamer/ClientMobile.ts b/ee/server/services/DDPStreamer/ClientMobile.ts index 93c578e5cc51e..c8fdd5cf7a9fb 100644 --- a/ee/server/services/DDPStreamer/ClientMobile.ts +++ b/ee/server/services/DDPStreamer/ClientMobile.ts @@ -1,18 +1,17 @@ -import { v1 as uuidv1 } from 'uuid'; +import WebSocket from 'ws'; import { Client } from './Client'; import { DDP_EVENTS, WS_ERRORS, WS_ERRORS_MESSAGES, TIMEOUT } from './constants'; -import { server, SERVER_ID } from './Server'; +import { SERVER_ID } from './Server'; +import { server } from './configureServer'; +import { IPacket } from './types/IPacket'; export class ClientMobile extends Client { - session = uuidv1(); + constructor( + public ws: WebSocket, + ) { + super(ws); - subscriptions = new Map(); - - constructor(socket, broker) { - super(); - this.broker = broker; - this.ws = socket; this.renewTimeout(TIMEOUT / 1000); this.ws.on('message', this.handler); this.ws.on('close', (...args) => { @@ -31,17 +30,17 @@ export class ClientMobile extends Client { this.once('message', ({ msg }) => { if (msg !== DDP_EVENTS.CONNECT) { - return this.ws.close(WS_ERRORS.CLOSE_PROTOCOL_ERROR, WS_ERRORS_MESSAGES.PROTOCOL_ERROR); + return this.ws.close(WS_ERRORS.CLOSE_PROTOCOL_ERROR, WS_ERRORS_MESSAGES.CLOSE_PROTOCOL_ERROR); } return this.send( - server.serialize({ [DDP_EVENTS.MSG]: DDP_EVENTS.CONNECTED, session: this.session }) + server.serialize({ [DDP_EVENTS.MSG]: DDP_EVENTS.CONNECTED, session: this.session }), ); }); this.send(SERVER_ID); } - process(action, packet) { + process(action: string, packet: IPacket): void { switch (action) { case DDP_EVENTS.PING: this.pong(packet.id); @@ -77,29 +76,29 @@ export class ClientMobile extends Client { } } - closeTimeout = () => { + closeTimeout = (): void => { this.ws.close(WS_ERRORS.TIMEOUT, WS_ERRORS_MESSAGES.TIMEOUT); }; - ping(id) { + ping(id?: string): void { this.send(server.serialize({ [DDP_EVENTS.MSG]: DDP_EVENTS.PING, ...id && { [DDP_EVENTS.ID]: id } })); } - pong(id) { + pong(id?: string): void { this.send(server.serialize({ [DDP_EVENTS.MSG]: DDP_EVENTS.PONG, ...id && { [DDP_EVENTS.ID]: id } })); } - handleIdle = () => { + handleIdle = (): void => { this.ping(); this.timeout = setTimeout(this.closeTimeout, TIMEOUT); }; - renewTimeout(timeout = TIMEOUT) { + renewTimeout(timeout = TIMEOUT): void { clearTimeout(this.timeout); this.timeout = setTimeout(this.handleIdle, timeout); } - handler = (payload) => { + handler = async (payload: string): Promise => { try { const packet = server.parse(payload); this.emit('message', packet); @@ -107,12 +106,12 @@ export class ClientMobile extends Client { } catch (err) { return this.ws.close( WS_ERRORS.UNSUPPORTED_DATA, - WS_ERRORS_MESSAGES.UNSUPPORTED_DATA + WS_ERRORS_MESSAGES.UNSUPPORTED_DATA, ); } }; - send(payload) { + send(payload: string): void { // Meteor format return this.ws.send(`a${ JSON.stringify([payload]) }`); } diff --git a/ee/server/services/DDPStreamer/Publication.ts b/ee/server/services/DDPStreamer/Publication.ts index 8011386ccbe20..e69146c2142d3 100644 --- a/ee/server/services/DDPStreamer/Publication.ts +++ b/ee/server/services/DDPStreamer/Publication.ts @@ -1,7 +1,8 @@ import { EventEmitter } from 'events'; import { DDP_EVENTS } from './constants'; -import { server, Server } from './Server'; +import { Server } from './Server'; +import { server } from './configureServer'; import { sendBroadcast } from './lib/sendBroadcast'; import { Client } from './Client'; import { IPacket } from './types/IPacket'; diff --git a/ee/server/services/DDPStreamer/configureServer.ts b/ee/server/services/DDPStreamer/configureServer.ts new file mode 100644 index 0000000000000..5e02fd1d971c3 --- /dev/null +++ b/ee/server/services/DDPStreamer/configureServer.ts @@ -0,0 +1,122 @@ +import { DDP_EVENTS } from './constants'; +import { Account, Presence } from '../../../../server/sdk'; +import { USER_STATUS } from '../../../../definition/UserStatus'; +import { Server } from './Server'; + +export const server = new Server(); + +// TODO: remove, not used by current rocket.chat versions +// server.subscribe('userData', async function(publication) { +// if (!publication.uid) { +// throw new Error('user should be connected'); +// } + +// const key = `${ STREAMER_EVENTS.USER_CHANGED }/${ publication.uid }`; +// await User.addSubscription(publication, key); +// publication.once('stop', () => User.removeSubscription(publication, key)); +// publication.ready(); +// }); + +// TODO: remove, not used by current rocket.chat versions +// server.subscribe('activeUsers', function(publication) { +// publication.ready(); +// }); + +server.subscribe('meteor.loginServiceConfiguration', function(pub) { + // TODO implement? + pub.ready(); +}); + +server.subscribe('meteor_autoupdate_clientVersions', function(pub) { + // TODO implement? + pub.ready(); +}); + +server.methods({ + async login({ resume, user, password }: {resume: string; user: {username: string}; password: string}) { + const result = await Account.login({ resume, user, password }); + if (!result) { + throw new Error('login error'); + } + + this.uid = result.uid; + + this.emit(DDP_EVENTS.LOGGED); + + server.emit(DDP_EVENTS.LOGGED, this); + + return { + id: result.uid, + token: result.token, + tokenExpires: result.tokenExpires, + type: result.type, + }; + }, + 'UserPresence:setDefaultStatus'(status) { + const { uid } = this; + return Presence.setStatus(uid, status); + }, + 'UserPresence:online'() { + const { uid, session } = this; + return Presence.setConnectionStatus(uid, USER_STATUS.ONLINE, session); + }, + 'UserPresence:away'() { + const { uid, session } = this; + return Presence.setConnectionStatus(uid, USER_STATUS.AWAY, session); + }, + 'setUserStatus'(status, statusText) { + const { uid } = this; + return Presence.setStatus(uid, status, statusText); + }, +}); + +server.on(DDP_EVENTS.LOGGED, ({ uid, session }) => { + Presence.newConnection(uid, session); +}); + +server.on(DDP_EVENTS.DISCONNECTED, ({ uid, session }) => { + if (!uid) { + return; + } + Presence.removeConnection(uid, session); +}); + +// TODO: resolve metrics +// server.on(DDP_EVENTS.CONNECTED, () => { +// broker.emit('metrics.update', { +// name: 'streamer_users_connected', +// method: 'inc', +// labels: { +// nodeID: broker.nodeID, +// }, +// }); +// }); + +// server.on(DDP_EVENTS.LOGGED, (/* client*/) => { +// broker.emit('metrics.update', { +// name: 'streamer_users_logged', +// method: 'inc', +// labels: { +// nodeID: broker.nodeID, +// }, +// }); +// }); + +// server.on(DDP_EVENTS.DISCONNECTED, ({ uid }) => { +// broker.emit('metrics.update', { +// name: 'streamer_users_connected', +// method: 'dec', +// labels: { +// nodeID: broker.nodeID, +// }, +// }); +// if (uid) { +// broker.emit('metrics.update', { +// name: 'streamer_users_logged', +// method: 'dec', +// labels: { +// nodeID: broker.nodeID, +// }, +// }); +// } +// }); From 6f9034ce6ff3e04d2bdebe8fbb2477bbce21f594 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Mon, 7 Sep 2020 15:44:26 -0300 Subject: [PATCH 033/198] DDPStreamer: Finish import --- definition/IInquiry.ts | 1 + definition/IMessage.ts | 1 + definition/IRole.ts | 1 + definition/ISubscription.ts | 3 + ee/server/broker.ts | 5 +- .../DDPStreamer/{index.ts => DDPStreamer.ts} | 187 ++++++++---------- ee/server/services/DDPStreamer/Server.ts | 69 ------- ee/server/services/DDPStreamer/Streamer.ts | 16 +- ee/server/services/DDPStreamer/service.ts | 6 + .../DDPStreamer/streams/notifyUser.ts | 7 +- ee/server/services/StreamHub/service.ts | 2 +- .../services/StreamHub/watchInquiries.ts | 3 + ee/server/services/StreamHub/watchMessages.ts | 3 + ee/server/services/StreamHub/watchRoles.ts | 4 + ee/server/services/StreamHub/watchRooms.ts | 3 + ee/server/services/StreamHub/watchSettings.ts | 7 +- .../services/StreamHub/watchSubscriptions.ts | 5 +- ee/server/services/ecosystem.config.js | 6 + package-lock.json | 40 ++++ package.json | 2 + server/sdk/lib/Api.ts | 5 +- server/sdk/lib/Events.ts | 23 +++ server/sdk/types/IBroker.ts | 3 +- server/sdk/types/ServiceClass.ts | 11 ++ 24 files changed, 224 insertions(+), 189 deletions(-) rename ee/server/services/DDPStreamer/{index.ts => DDPStreamer.ts} (64%) create mode 100755 ee/server/services/DDPStreamer/service.ts create mode 100644 server/sdk/lib/Events.ts diff --git a/definition/IInquiry.ts b/definition/IInquiry.ts index e2b17d93abfa8..8cc531ef1f49d 100644 --- a/definition/IInquiry.ts +++ b/definition/IInquiry.ts @@ -1,4 +1,5 @@ export interface IInquiry { _id: string; _updatedAt?: Date; + department?: string; } diff --git a/definition/IMessage.ts b/definition/IMessage.ts index 7bf96c26e07ca..c01bce32c5ee9 100644 --- a/definition/IMessage.ts +++ b/definition/IMessage.ts @@ -1,4 +1,5 @@ export interface IMessage { _id: string; + rid: string; _updatedAt?: Date; } diff --git a/definition/IRole.ts b/definition/IRole.ts index e4c5106ae295f..04af0654b28dd 100644 --- a/definition/IRole.ts +++ b/definition/IRole.ts @@ -1,4 +1,5 @@ export interface IRole { _id: string; + name: string; _updatedAt?: Date; } diff --git a/definition/ISubscription.ts b/definition/ISubscription.ts index 9ba72263c66a5..fab1f113d7c19 100644 --- a/definition/ISubscription.ts +++ b/definition/ISubscription.ts @@ -2,4 +2,7 @@ export interface ISubscription { _id: string; _updatedAt?: Date; rid: string; + u: { + _id: string; + }; } diff --git a/ee/server/broker.ts b/ee/server/broker.ts index 3d5c4107024da..14ef09de81c41 100644 --- a/ee/server/broker.ts +++ b/ee/server/broker.ts @@ -5,6 +5,7 @@ import { api } from '../../server/sdk/api'; import { IBroker, IBrokerNode } from '../../server/sdk/types/IBroker'; import { ServiceClass } from '../../server/sdk/types/ServiceClass'; // import { onLicense } from '../app/license/server'; +import { EventSignatures } from '../../server/sdk/lib/Events'; const events: {[k: string]: string} = { onNodeConnected: '$node.connected', @@ -71,8 +72,8 @@ class NetworkBroker implements IBroker { this.broker.createService(service); } - async broadcast(eventName: string, data: D): Promise { - return this.broker.broadcast(eventName, data); + async broadcast(event: T, ...args: Parameters): Promise { + return this.broker.broadcast(event, args); } async nodeList(): Promise { diff --git a/ee/server/services/DDPStreamer/index.ts b/ee/server/services/DDPStreamer/DDPStreamer.ts similarity index 64% rename from ee/server/services/DDPStreamer/index.ts rename to ee/server/services/DDPStreamer/DDPStreamer.ts index a8184a5b18535..0dc3ac1511e75 100644 --- a/ee/server/services/DDPStreamer/index.ts +++ b/ee/server/services/DDPStreamer/DDPStreamer.ts @@ -1,8 +1,5 @@ import http from 'http'; -import { - ServiceBroker, -} from 'moleculer'; // import msgpack from 'msgpack-lite'; import WebSocket from 'ws'; // import PromService from 'moleculer-prometheus'; @@ -11,23 +8,22 @@ import msgpack5 from 'msgpack5'; import * as Streamer from './streams'; import { Client, MeteorClient } from './Client'; -import { server } from './Server'; -import { STREAMER_EVENTS, DDP_EVENTS, STREAM_NAMES } from './constants'; +// import { STREAMER_EVENTS, STREAM_NAMES } from './constants'; import { isEmpty } from './lib/utils'; -import { Presence } from '../../../../server/sdk'; +import { ServiceClass } from '../../../../server/sdk/types/ServiceClass'; const msgpack = msgpack5(); -const broker = new ServiceBroker(config); +// const broker = new ServiceBroker(config); const { PORT: port = 4000, - PROMETHEUS_PORT = 9100, +// PROMETHEUS_PORT = 9100, } = process.env; const httpServer = http.createServer((req, res) => { res.setHeader('Access-Control-Allow-Origin', '*'); - if (!/^\/sockjs\/info\?cb=/.test(req.url)) { + if (!/^\/sockjs\/info\?cb=/.test(req.url || '')) { return; } res.writeHead(200, { 'Content-Type': 'text/plain' }); @@ -42,7 +38,7 @@ const wss = new WebSocket.Server({ server: httpServer }); wss.on('connection', (ws, req) => { const isMobile = /^RC Mobile/.test(req.headers['user-agent'] || ''); - return isMobile ? new Client(ws, broker) : new MeteorClient(ws, broker); + return isMobile ? new Client(ws) : new MeteorClient(ws); }); // export default { @@ -65,42 +61,56 @@ wss.on('connection', (ws, req) => { // }, // }, // }; -broker.createService({ - name: 'streamer', - // settings: { - // port: PROMETHEUS_PORT, - // metrics: { - // streamer_users_connected: { - // type: 'Gauge', - // labelNames: ['nodeID'], - // help: 'Users connecteds by streamer', - // }, - // streamer_users_logged: { - // type: 'Gauge', - // labelNames: ['nodeID'], - // help: 'Users logged by streamer', - // }, - // }, - // }, - // mixins: PROMETHEUS_PORT !== 'false' ? [PromService] : [], - events: { - [STREAM_NAMES.LIVECHAT_INQUIRY]({ action, inquiry }) { + +// broker.createService({ +// settings: { +// port: PROMETHEUS_PORT, +// metrics: { +// streamer_users_connected: { +// type: 'Gauge', +// labelNames: ['nodeID'], +// help: 'Users connecteds by streamer', +// }, +// streamer_users_logged: { +// type: 'Gauge', +// labelNames: ['nodeID'], +// help: 'Users logged by streamer', +// }, +// }, +// }, +// mixins: PROMETHEUS_PORT !== 'false' ? [PromService] : [], + +export class DDPStreamer extends ServiceClass { + protected name = 'streamer'; + + constructor() { + super(); + + // [STREAM_NAMES.LIVECHAT_INQUIRY]({ action, inquiry }) { + this.onEvent('livechat-inquiry-queue-observer', ({ action, inquiry }): void => { if (!inquiry.department) { - return Streamer.streamLivechatInquiry.emit('public', action, inquiry); + Streamer.streamLivechatInquiry.emit('public', action, inquiry); + return; } Streamer.streamLivechatInquiry.emit(`department/${ inquiry.department }`, action, inquiry); Streamer.streamLivechatInquiry.emit(inquiry._id, action, inquiry); - }, - [STREAMER_EVENTS.STREAM]([streamer, eventName, payload]) { + }); + + // [STREAMER_EVENTS.STREAM]([streamer, eventName, payload]) { + this.onEvent('stream', ([streamer, eventName, payload]): void => { const stream = Streamer.Streams.get(streamer); return stream && stream.emitPayload(eventName, payload); - }, - message({ message }) { + }); + + // message({ message }) { + this.onEvent('message', ({ message }): void => { // roomMessages.emitWithoutBroadcast('__my_messages__', record, {}); Streamer.roomMessages.emit(message.rid, message); - }, - userpresence(payload) { - const STATUS_MAP = { + }); + + // userpresence(payload) { + this.onEvent('userpresence', (payload): void => { + const STATUS_MAP: {[k: string]: number} = { offline: 0, online: 1, away: 2, @@ -113,11 +123,13 @@ broker.createService({ // Streamer.userpresence.emit(_id, status); Streamer.notifyLogged.emit('user-status', [_id, username, STATUS_MAP[status], statusText]); // User.emit(`${ STREAMER_EVENTS.USER_CHANGED }/${ _id }`, _id, user); // use this method - }, - user(payload) { + }); + + // user(payload) { + this.onEvent('user', (payload): void => { const { action, - user: { _id, _updatedAt, ...user }, + user: { _id, ...user }, } = msgpack.decode(payload); // User.emit(`${ STREAMER_EVENTS.USER_CHANGED }/${ _id }`, _id, user); @@ -125,7 +137,12 @@ broker.createService({ return; } - const data = { + const data: { + type: string; + diff?: object; + data?: object; + id?: string; + } = { type: action, }; @@ -147,16 +164,24 @@ broker.createService({ ); // Notifications.notifyUserInThisInstance(id, 'userData', { diff, type: clientAction }); - }, - 'user.name'(payload) { + }); + + // 'user.name'(payload) { + this.onEvent('user.name', (payload): void => { const { user: { _id, name, username }, } = msgpack.decode(payload); // User.emit(`${ STREAMER_EVENTS.USER_CHANGED }/${ _id }`, _id, user); Streamer.notifyLogged.emit('Users:NameChanged', { _id, name, username }); - }, + }); + // 'setting'() { }, - subscription({ action, subscription }) { + // subscription({ action, subscription }) { + this.onEvent('subscription', ({ action, subscription }): void => { + if (!subscription.u?._id) { + return; + } + Streamer.notifyUser.emit( `${ subscription.u._id }/subscriptions-changed`, action, @@ -176,12 +201,17 @@ broker.createService({ // notifyUser.emit(subscription.u._id, 'subscriptions-changed', action, subscription); // RocketChat.Notifications.streamUser.__emit(subscription.u._id, action, subscription); // RocketChat.Notifications.notifyUserInThisInstance(subscription.u._id, 'subscriptions-changed', action, subscription); - }, - room({ room, action }) { + }); + + // room({ room, action }) { + this.onEvent('room', ({ room, action }): void => { // RocketChat.Notifications.streamUser.__emit(id, clientAction, data); + if (!room._id) { + return; + } Streamer.notifyUser.__emit(room._id, action, room); Streamer.streamRoomData.emit(room._id, action, room); // TODO REMOVE - }, + }); // stream: { // group: 'streamer', // handler(payload) { @@ -189,60 +219,11 @@ broker.createService({ // Streamer.central.emit(stream, ev, data); // }, // }, - role(payload) { + // role(payload) { + this.onEvent('role', (payload): void => { Streamer.streamRoles.emit('roles', payload); - }, - }, -}); - -broker.start(); - -server.on(DDP_EVENTS.LOGGED, ({ uid, session }) => { - Presence.newConnection(uid, session); -}); - -server.on(DDP_EVENTS.DISCONNECTED, ({ uid, session }) => { - if (!uid) { - return; - } - Presence.removeConnection(uid, session); -}); - -server.on(DDP_EVENTS.CONNECTED, () => { - broker.emit('metrics.update', { - name: 'streamer_users_connected', - method: 'inc', - labels: { - nodeID: broker.nodeID, - }, - }); -}); - -server.on(DDP_EVENTS.LOGGED, (/* client*/) => { - broker.emit('metrics.update', { - name: 'streamer_users_logged', - method: 'inc', - labels: { - nodeID: broker.nodeID, - }, - }); -}); - -server.on(DDP_EVENTS.DISCONNECTED, ({ uid }) => { - broker.emit('metrics.update', { - name: 'streamer_users_connected', - method: 'dec', - labels: { - nodeID: broker.nodeID, - }, - }); - if (uid) { - broker.emit('metrics.update', { - name: 'streamer_users_logged', - method: 'dec', - labels: { - nodeID: broker.nodeID, - }, }); } -}); +} + +// broker.start(); diff --git a/ee/server/services/DDPStreamer/Server.ts b/ee/server/services/DDPStreamer/Server.ts index 5f99e3a70de50..df68ac76aed39 100644 --- a/ee/server/services/DDPStreamer/Server.ts +++ b/ee/server/services/DDPStreamer/Server.ts @@ -6,8 +6,6 @@ import { DDP_EVENTS } from './constants'; import { Publication } from './Publication'; import { Client } from './Client'; import { IPacket } from './types/IPacket'; -import { Account, Presence } from '../../../../server/sdk'; -import { USER_STATUS } from '../../../../definition/UserStatus'; type SubscriptionFn = (publication: Publication, client: Client, eventName: string, options: object) => void; type MethodFn = (this: Client, ...args: any[]) => any; @@ -125,70 +123,3 @@ export class Server extends EventEmitter { ); } } - -export const server = new Server(); - -// TODO: remove, not used by current rocket.chat versions -// server.subscribe('userData', async function(publication) { -// if (!publication.uid) { -// throw new Error('user should be connected'); -// } - -// const key = `${ STREAMER_EVENTS.USER_CHANGED }/${ publication.uid }`; -// await User.addSubscription(publication, key); -// publication.once('stop', () => User.removeSubscription(publication, key)); -// publication.ready(); -// }); - -// TODO: remove, not used by current rocket.chat versions -// server.subscribe('activeUsers', function(publication) { -// publication.ready(); -// }); - -server.subscribe('meteor.loginServiceConfiguration', function(pub) { - // TODO implement? - pub.ready(); -}); - -server.subscribe('meteor_autoupdate_clientVersions', function(pub) { - // TODO implement? - pub.ready(); -}); - -server.methods({ - async login({ resume, user, password }: {resume: string; user: {username: string}; password: string}) { - const result = await Account.login({ resume, user, password }); - if (!result) { - throw new Error('login error'); - } - - this.uid = result.uid; - - this.emit(DDP_EVENTS.LOGGED); - - server.emit(DDP_EVENTS.LOGGED, this); - - return { - id: result.uid, - token: result.token, - tokenExpires: result.tokenExpires, - type: result.type, - }; - }, - 'UserPresence:setDefaultStatus'(status) { - const { uid } = this; - return Presence.setStatus(uid, status); - }, - 'UserPresence:online'() { - const { uid, session } = this; - return Presence.setConnectionStatus(uid, USER_STATUS.ONLINE, session); - }, - 'UserPresence:away'() { - const { uid, session } = this; - return Presence.setConnectionStatus(uid, USER_STATUS.AWAY, session); - }, - 'setUserStatus'(status, statusText) { - const { uid } = this; - return Presence.setStatus(uid, status, statusText); - }, -}); diff --git a/ee/server/services/DDPStreamer/Streamer.ts b/ee/server/services/DDPStreamer/Streamer.ts index f188758a93ab7..364dfadc4791f 100644 --- a/ee/server/services/DDPStreamer/Streamer.ts +++ b/ee/server/services/DDPStreamer/Streamer.ts @@ -1,11 +1,12 @@ import { EventEmitter } from 'events'; -import { server } from './Server'; -import { STREAMER_EVENTS, DDP_EVENTS, STREAM_NAMES } from './constants'; +import { server } from './configureServer'; +import { DDP_EVENTS, STREAM_NAMES } from './constants'; import { sendBroadcast } from './lib/sendBroadcast'; import { isEmpty } from './lib/utils'; import { Publication } from './Publication'; import { Client } from './Client'; +import { api } from '../../../../server/sdk/api'; type Rule = (this: Publication, eventName: string, ...args: any) => Promise; @@ -209,10 +210,17 @@ export class Stream extends EventEmitter { if (isAllowed !== true) { return; } - this.broker.broadcast(STREAMER_EVENTS.STREAM, [ + + const payload = changedPayload(subscriptionName, { eventName, args }); + + if (!payload) { + return; + } + + api.broadcast('stream', [ name, eventName, - changedPayload(subscriptionName, { eventName, args }), + payload, ]); }, }; diff --git a/ee/server/services/DDPStreamer/service.ts b/ee/server/services/DDPStreamer/service.ts new file mode 100755 index 0000000000000..d2cfc846d3ceb --- /dev/null +++ b/ee/server/services/DDPStreamer/service.ts @@ -0,0 +1,6 @@ +import { api } from '../../../../server/sdk/api'; +import { DDPStreamer } from './DDPStreamer'; + +import '../../broker'; + +api.registerService(new DDPStreamer()); diff --git a/ee/server/services/DDPStreamer/streams/notifyUser.ts b/ee/server/services/DDPStreamer/streams/notifyUser.ts index 6d5efa8623d19..47b38872800ab 100644 --- a/ee/server/services/DDPStreamer/streams/notifyUser.ts +++ b/ee/server/services/DDPStreamer/streams/notifyUser.ts @@ -3,6 +3,7 @@ import { STREAM_NAMES } from '../constants'; import { ISubscription } from '../../../../../definition/ISubscription'; import { getCollection, Collections } from '../../mongo'; import { Publication } from '../Publication'; +import { Authorization } from '../../../../../server/sdk'; class RoomStreamer extends Stream { async [publish](publication: Publication, eventName = '', options: boolean | {useCollection?: boolean; args?: any} = false): Promise { @@ -66,9 +67,5 @@ notifyUser.allowRead('logged'); export const streamRoomData = new Stream(STREAM_NAMES.ROOM_DATA); streamRoomData.allowRead(function(rid) { - // TODO: missing method - return this.client.broker.call('authorization.canAccessRoom', { - room: { _id: rid }, - user: { _id: this.uid }, - }); + return Authorization.canAccessRoom({ _id: rid }, { _id: this.uid }); }); diff --git a/ee/server/services/StreamHub/service.ts b/ee/server/services/StreamHub/service.ts index 495e0aca40b74..764915adc1fda 100755 --- a/ee/server/services/StreamHub/service.ts +++ b/ee/server/services/StreamHub/service.ts @@ -2,7 +2,7 @@ // import config from 'moleculer.config'; import { api } from '../../../../server/sdk/api'; -import { StreamHub } from './service'; +import { StreamHub } from './StreamHub'; import '../../broker'; diff --git a/ee/server/services/StreamHub/watchInquiries.ts b/ee/server/services/StreamHub/watchInquiries.ts index 34a880520e00f..4d51717c3d0b5 100644 --- a/ee/server/services/StreamHub/watchInquiries.ts +++ b/ee/server/services/StreamHub/watchInquiries.ts @@ -9,6 +9,9 @@ export async function watchInquiries(event: ChangeEvent): Promise): Promise // const message = await Messages.findOne(documentKey); const message = event.fullDocument; // Streamer.emitWithoutBroadcast('__my_messages__', message, {}); + if (!message) { + return; + } api.broadcast('message', { action: normalize[event.operationType], message }); // TODO: // RocketChat.Logger.info('Message record', fullDocument); diff --git a/ee/server/services/StreamHub/watchRoles.ts b/ee/server/services/StreamHub/watchRoles.ts index db4884d7d6542..85790fc1fa8d7 100644 --- a/ee/server/services/StreamHub/watchRoles.ts +++ b/ee/server/services/StreamHub/watchRoles.ts @@ -9,6 +9,10 @@ export async function watchRoles(event: ChangeEvent): Promise { switch (event.operationType) { case 'insert': case 'update': + if (!event.fullDocument) { + return; + } + api.broadcast('role', { type: 'changed', ...event.fullDocument, diff --git a/ee/server/services/StreamHub/watchRooms.ts b/ee/server/services/StreamHub/watchRooms.ts index d6905af4e2954..e036a639a6649 100644 --- a/ee/server/services/StreamHub/watchRooms.ts +++ b/ee/server/services/StreamHub/watchRooms.ts @@ -19,6 +19,9 @@ export async function watchRooms(event: ChangeEvent): Promise { return; } // console.log(room, documentKey); + if (!room) { + return; + } api.broadcast('room', { action: normalize[event.operationType], room }); // RocketChat.Notifications.streamUser.__emit(data._id, operationType, data); // TODO: diff --git a/ee/server/services/StreamHub/watchSettings.ts b/ee/server/services/StreamHub/watchSettings.ts index 17e8227999b5f..915ab93ce3e7b 100644 --- a/ee/server/services/StreamHub/watchSettings.ts +++ b/ee/server/services/StreamHub/watchSettings.ts @@ -2,7 +2,7 @@ import { ChangeEvent } from 'mongodb'; import { normalize } from './utils'; import { api } from '../../../../server/sdk/api'; -import { ISetting } from '../../../../imports/client/@rocket.chat/apps-engine/definition/settings/ISetting'; +import { ISetting } from '../../../../definition/ISetting'; export async function watchSettings(event: ChangeEvent): Promise { if ('updateDescription' in event && event.updateDescription.updatedFields._updatedAt) { @@ -21,6 +21,11 @@ export async function watchSettings(event: ChangeEvent): Promise default: return; } + + if (!setting) { + return; + } + api.broadcast('setting', { action: normalize[event.operationType], setting }); // RocketChat.Notifications.streamUser.__emit(data._id, operationType, data); // TODO: diff --git a/ee/server/services/StreamHub/watchSubscriptions.ts b/ee/server/services/StreamHub/watchSubscriptions.ts index c484c73c55faf..332315055cf86 100644 --- a/ee/server/services/StreamHub/watchSubscriptions.ts +++ b/ee/server/services/StreamHub/watchSubscriptions.ts @@ -14,11 +14,14 @@ export function watchSubscriptions(Trash: Collection) { subscription = event.fullDocument; break; case 'delete': - subscription = await Trash.findOne(event.documentKey, { fields: { u: 1, rid: 1 } }); + subscription = await Trash.findOne>(event.documentKey, { fields: { u: 1, rid: 1 } }); break; default: return; } + if (!subscription) { + return; + } api.broadcast('subscription', { action: normalize[event.operationType], subscription }); // TODO: // RocketChat.Logger.info('Subscription record', fullDocument); diff --git a/ee/server/services/ecosystem.config.js b/ee/server/services/ecosystem.config.js index 1d50736ab998c..2d448a3182425 100644 --- a/ee/server/services/ecosystem.config.js +++ b/ee/server/services/ecosystem.config.js @@ -23,5 +23,11 @@ module.exports = { watch: true, instances: 1, // interpreter: '', + }, { + name: 'DDPStreamer', + script: 'ts-node DDPStreamer/service.ts', + watch: true, + instances: 1, + // interpreter: '', }], }; diff --git a/package-lock.json b/package-lock.json index 5e93451c52789..e3242468303cd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8140,6 +8140,15 @@ "integrity": "sha512-nohgNyv+1ViVcubKBh0+XiNJ3dO8nYu///9aJ4cgSqv70gBL+94SNy/iC2NLzKPT2Zt/QavrOkBVbZRLZmw6NQ==", "dev": true }, + "@types/bl": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/bl/-/bl-2.1.0.tgz", + "integrity": "sha512-1TdA9IXOy4sdqn8vgieQ6GZAiHiPNrOiO1s2GJjuYPw4QVY7gYoVjkW049avj33Ez7IcIvu43hQsMsoUFbCn2g==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/bn.js": { "version": "4.11.6", "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-4.11.6.tgz", @@ -8425,6 +8434,15 @@ "@types/node": "*" } }, + "@types/msgpack5": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@types/msgpack5/-/msgpack5-3.4.1.tgz", + "integrity": "sha512-E3wILUjTXONukpiI6tmqpLwf7eV3MVTdxpjz56FqNn7koMF/6sSPUh5TxMlwgoOhyeejxwVoNZUiDcdqChKkAw==", + "dev": true, + "requires": { + "@types/bl": "*" + } + }, "@types/node": { "version": "14.6.4", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.6.4.tgz", @@ -25148,6 +25166,28 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, + "msgpack5": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/msgpack5/-/msgpack5-4.2.1.tgz", + "integrity": "sha512-Xo7nE9ZfBVonQi1rSopNAqPdts/QHyuSEUwIEzAkB+V2FtmkkLUbP6MyVqVVQxsZYI65FpvW3Bb8Z9ZWEjbgHQ==", + "requires": { + "bl": "^2.0.1", + "inherits": "^2.0.3", + "readable-stream": "^2.3.6", + "safe-buffer": "^5.1.2" + }, + "dependencies": { + "bl": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz", + "integrity": "sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==", + "requires": { + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.1" + } + } + } + }, "mute-stream": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", diff --git a/package.json b/package.json index f3d6a6e4fc56a..7b62f9468a346 100644 --- a/package.json +++ b/package.json @@ -65,6 +65,7 @@ "@types/chai": "^4.2.12", "@types/chai-spies": "^1.0.1", "@types/meteor": "^1.4.49", + "@types/msgpack5": "^3.4.1", "@types/mocha": "^8.0.3", "@types/mock-require": "^2.0.0", "@types/moment-timezone": "^0.5.30", @@ -205,6 +206,7 @@ "mailparser": "^2.8.1", "marked": "^0.6.3", "mem": "^6.1.0", + "msgpack5": "^4.2.1", "meteor-node-stubs": "^1.0.1", "mime-db": "^1.44.0", "mime-type": "^3.1.0", diff --git a/server/sdk/lib/Api.ts b/server/sdk/lib/Api.ts index be72cbc78078c..c73568be32c3b 100644 --- a/server/sdk/lib/Api.ts +++ b/server/sdk/lib/Api.ts @@ -1,6 +1,7 @@ // import { BaseBroker } from './BaseBroker'; import { IBroker } from '../types/IBroker'; import { ServiceClass } from '../types/ServiceClass'; +import { EventSignatures } from './Events'; export class Api { private services: ServiceClass[] = []; @@ -26,7 +27,7 @@ export class Api { return this.broker.call(method, data); } - async broadcast(eventName: string, data: D): Promise { - return this.broker.broadcast(eventName, data); + async broadcast(event: T, ...args: Parameters): Promise { + return this.broker.broadcast(event, ...args); } } diff --git a/server/sdk/lib/Events.ts b/server/sdk/lib/Events.ts new file mode 100644 index 0000000000000..fa182cf862e2b --- /dev/null +++ b/server/sdk/lib/Events.ts @@ -0,0 +1,23 @@ +import { MessagePack } from 'msgpack5'; + +import { IInquiry } from '../../../definition/IInquiry'; +import { IMessage } from '../../../definition/IMessage'; +import { IRole } from '../../../definition/IRole'; +import { IRoom } from '../../../definition/IRoom'; +import { ISetting } from '../../../definition/ISetting'; +import { ISubscription } from '../../../definition/ISubscription'; + +export type BufferList = ReturnType; + +export type EventSignatures = { + 'livechat-inquiry-queue-observer'(data: {action: string; inquiry: IInquiry}): void; + 'stream'([streamer, eventName, payload]: [string, string, string]): void; + 'subscription'(data: { action: string; subscription: Partial }): void; + 'room'(data: { action: string; room: Partial }): void; + 'message'(data: { action: string; message: IMessage }): void; + 'setting'(data: { action: string; setting: Partial }): void; + 'userpresence'(payload: BufferList): void; + 'user'(payload: BufferList): void; + 'user.name'(payload: BufferList): void; + 'role'(data: {type: 'changed' | 'removed' } & Partial): void; +} diff --git a/server/sdk/types/IBroker.ts b/server/sdk/types/IBroker.ts index 690fb806c7706..196f7ef99a466 100644 --- a/server/sdk/types/IBroker.ts +++ b/server/sdk/types/IBroker.ts @@ -1,4 +1,5 @@ import { ServiceClass } from './ServiceClass'; +import { EventSignatures } from '../lib/Events'; export interface IBrokerNode { id: string; @@ -22,6 +23,6 @@ export interface IBrokerNode { export interface IBroker { createService(service: ServiceClass): void; call(method: string, data: any): Promise; - broadcast(eventName: string, data: D): Promise; + broadcast(event: T, ...args: Parameters): Promise; nodeList(): Promise; } diff --git a/server/sdk/types/ServiceClass.ts b/server/sdk/types/ServiceClass.ts index 5ce2c575ccf58..77744f234b4d6 100644 --- a/server/sdk/types/ServiceClass.ts +++ b/server/sdk/types/ServiceClass.ts @@ -1,5 +1,6 @@ import { asyncLocalStorage } from '..'; import { IBroker, IBrokerNode } from './IBroker'; +import { EventSignatures } from '../lib/Events'; export interface IServiceContext { id: string; // Context ID @@ -34,6 +35,8 @@ export interface IServiceClass { export abstract class ServiceClass implements IServiceClass { protected name: string; + protected events = new Map(); + getName(): string { return this.name; } @@ -41,4 +44,12 @@ export abstract class ServiceClass implements IServiceClass { get context(): IServiceContext | undefined { return asyncLocalStorage.getStore(); } + + protected onEvent(event: T, handler: EventSignatures[T]): void { + if (this.events.has(event)) { + throw new Error('event already registered'); + } + + this.events.set(event, handler); + } } From 88858fadb5a1d5b7b3cdae086d5910278af04682 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Mon, 7 Sep 2020 15:54:07 -0300 Subject: [PATCH 034/198] Fix small errors --- ee/server/services/Account/service.ts | 2 +- ee/server/services/DDPStreamer/lib/sendBroadcast.ts | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/ee/server/services/Account/service.ts b/ee/server/services/Account/service.ts index c38928108035f..757f2262eb72a 100644 --- a/ee/server/services/Account/service.ts +++ b/ee/server/services/Account/service.ts @@ -1,5 +1,5 @@ import { api } from '../../../../server/sdk/api'; -import { Account } from './service'; +import { Account } from './Account'; import '../../broker'; diff --git a/ee/server/services/DDPStreamer/lib/sendBroadcast.ts b/ee/server/services/DDPStreamer/lib/sendBroadcast.ts index fb90c458525fd..d5ee2dcd90ef3 100644 --- a/ee/server/services/DDPStreamer/lib/sendBroadcast.ts +++ b/ee/server/services/DDPStreamer/lib/sendBroadcast.ts @@ -1,9 +1,10 @@ -import { Sender } from 'ws'; +import WebSocket from 'ws'; import { ISubscription } from '../Streamer'; const broadcastData = (message: string): Buffer[] => { - const data = Sender.frame(Buffer.from(message), { + // TODO: missing typing + const data = (WebSocket as any).Sender.frame(Buffer.from(message), { fin: true, // sending a single fragment message rsv1: false, // don"t set rsv1 bit (no compression) opcode: 1, // opcode for a text frame From e9f098fd436b689bebcf4858e16ddc0bb1b6fbb8 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Mon, 7 Sep 2020 16:15:44 -0300 Subject: [PATCH 035/198] Fix issues with Authorization importing meteor code --- server/services/authorization/canAccessRoom.ts | 10 +++++----- server/services/authorization/service.ts | 16 ++++++++++++---- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/server/services/authorization/canAccessRoom.ts b/server/services/authorization/canAccessRoom.ts index 58aef0767ee46..2e7468392ae1a 100644 --- a/server/services/authorization/canAccessRoom.ts +++ b/server/services/authorization/canAccessRoom.ts @@ -1,16 +1,16 @@ import { Authorization } from '../../sdk'; // TODO: change to MS instance -import { getValue } from '../../../app/settings/server/raw'; -import { Subscriptions, Rooms } from '../../../app/models/server/raw'; import { RoomAccessValidator } from '../../sdk/types/IAuthorization'; import { canAccessRoomLivechat } from './canAccessRoomLivechat'; import { canAccessRoomTokenpass } from './canAccessRoomTokenpass'; +import { Subscriptions, Rooms, Settings } from './service'; -export const roomAccessValidators: RoomAccessValidator[] = [ +const roomAccessValidators: RoomAccessValidator[] = [ async function(room, user): Promise { if (room.t === 'c') { - const anonymous = await getValue('Accounts_AllowAnonymousRead'); + // TODO: it was using cached version from /app/settings/server/raw.js + const anonymous = await Settings.getValueById('Accounts_AllowAnonymousRead'); if (!user._id && anonymous === true) { return true; } @@ -38,7 +38,7 @@ export const roomAccessValidators: RoomAccessValidator[] = [ canAccessRoomTokenpass, ]; -export const canAccessRoom: RoomAccessValidator = async (room, user, extraData) => { +export const canAccessRoom: RoomAccessValidator = async (room, user, extraData): Promise => { if (!room || !user) { return false; } diff --git a/server/services/authorization/service.ts b/server/services/authorization/service.ts index b3d8637d19538..671d3cc4bbe7c 100644 --- a/server/services/authorization/service.ts +++ b/server/services/authorization/service.ts @@ -6,10 +6,17 @@ import { ServiceClass } from '../../sdk/types/ServiceClass'; import { AuthorizationUtils } from '../../../app/authorization/lib/AuthorizationUtils'; import { IUser } from '../../../definition/IUser'; import { canAccessRoom } from './canAccessRoom'; +import { SubscriptionsRaw } from '../../../app/models/server/raw/Subscriptions'; +import { SettingsRaw } from '../../../app/models/server/raw/Settings'; +import { RoomsRaw } from '../../../app/models/server/raw/Rooms'; import './canAccessRoomLivechat'; import './canAccessRoomTokenpass'; +export let Subscriptions: SubscriptionsRaw; +export let Settings: SettingsRaw; +export let Rooms: RoomsRaw; + // Register as class export class Authorization extends ServiceClass implements IAuthorization { protected name = 'authorization'; @@ -18,14 +25,15 @@ export class Authorization extends ServiceClass implements IAuthorization { private Users: Collection; - private Subscriptions: Collection; - constructor(db: Db) { super(); this.Permissions = db.collection('rocketchat_permissions'); - this.Subscriptions = db.collection('rocketchat_subscriptions'); this.Users = db.collection('users'); + + Subscriptions = new SubscriptionsRaw(db.collection('rocketchat_subscription')); + Settings = new SettingsRaw(db.collection('rocketchat_settings')); + Rooms = new RoomsRaw(db.collection('rocketchat_room')); } async hasAllPermission(userId: string, permissions: string[], scope?: string): Promise { @@ -67,7 +75,7 @@ export class Authorization extends ServiceClass implements IAuthorization { private async getRoles(uid: string, scope?: string): Promise { const { roles: userRoles = [] } = await this.Users.findOne({ _id: uid }, { projection: { roles: 1 } }) || {}; - const { roles: subscriptionsRoles = [] } = (scope && await this.Subscriptions.findOne<{ roles: string[] }>({ rid: scope, 'u._id': uid }, { projection: { roles: 1 } })) || {}; + const { roles: subscriptionsRoles = [] } = (scope && await Subscriptions.findOne({ rid: scope, 'u._id': uid }, { projection: { roles: 1 } })) || {}; return [...userRoles, ...subscriptionsRoles].sort((a, b) => a.localeCompare(b)); } // , { maxAge: 1000, cacheKey: JSON.stringify }); From b184070d3f8500463fbdea799bd513e745e81cf3 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Mon, 7 Sep 2020 16:20:54 -0300 Subject: [PATCH 036/198] Prevent DDPStreamer to lock http calls --- ee/server/services/DDPStreamer/DDPStreamer.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ee/server/services/DDPStreamer/DDPStreamer.ts b/ee/server/services/DDPStreamer/DDPStreamer.ts index 0dc3ac1511e75..d16b03443b165 100644 --- a/ee/server/services/DDPStreamer/DDPStreamer.ts +++ b/ee/server/services/DDPStreamer/DDPStreamer.ts @@ -24,8 +24,10 @@ const httpServer = http.createServer((req, res) => { res.setHeader('Access-Control-Allow-Origin', '*'); if (!/^\/sockjs\/info\?cb=/.test(req.url || '')) { - return; + res.writeHead(404); + return res.end(); } + res.writeHead(200, { 'Content-Type': 'text/plain' }); res.end('{"websocket":true,"origins":["*:*"],"cookie_needed":false,"entropy":666}'); From c5760ea69ed88a8f0452db9419df494e005731d9 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Mon, 7 Sep 2020 17:10:53 -0300 Subject: [PATCH 037/198] Fix canAccessRoom not been registered as micro services --- server/services/authorization/service.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/server/services/authorization/service.ts b/server/services/authorization/service.ts index 671d3cc4bbe7c..9b8bee3c67785 100644 --- a/server/services/authorization/service.ts +++ b/server/services/authorization/service.ts @@ -1,7 +1,7 @@ // import mem from 'mem'; import { Db, Collection } from 'mongodb'; -import { IAuthorization } from '../../sdk/types/IAuthorization'; +import { IAuthorization, RoomAccessValidator } from '../../sdk/types/IAuthorization'; import { ServiceClass } from '../../sdk/types/ServiceClass'; import { AuthorizationUtils } from '../../../app/authorization/lib/AuthorizationUtils'; import { IUser } from '../../../definition/IUser'; @@ -57,7 +57,9 @@ export class Authorization extends ServiceClass implements IAuthorization { return this.atLeastOne(userId, permissions, scope); } - canAccessRoom = canAccessRoom; + async canAccessRoom(...args: Parameters): Promise { + return canAccessRoom(...args); + } private async rolesHasPermission(permission: string, roles: string[]): Promise { // TODO this AuthorizationUtils should be brought to this service. currently its state is kept on the application only, but it needs to kept here From baae8fce0ff8269c8e9be32ea69f0be8023b71ce Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Mon, 7 Sep 2020 17:29:32 -0300 Subject: [PATCH 038/198] Fix some exceptions --- .../server/functions/canAccessRoom.js | 5 ----- .../server/functions/canAccessRoom.ts | 8 ++++++++ ee/server/services/DDPStreamer/Streamer.ts | 14 ++++++++++---- 3 files changed, 18 insertions(+), 9 deletions(-) delete mode 100644 app/authorization/server/functions/canAccessRoom.js create mode 100644 app/authorization/server/functions/canAccessRoom.ts diff --git a/app/authorization/server/functions/canAccessRoom.js b/app/authorization/server/functions/canAccessRoom.js deleted file mode 100644 index b33c6bbed9598..0000000000000 --- a/app/authorization/server/functions/canAccessRoom.js +++ /dev/null @@ -1,5 +0,0 @@ -import { Authorization } from '../../../../server/sdk'; - -export const canAccessRoomAsync = async (room, user, extraData) => Authorization.hasAllPermission(room, user, extraData); - -export const canAccessRoom = (room, user, extraData) => Promise.await(canAccessRoomAsync(room, user, extraData)); diff --git a/app/authorization/server/functions/canAccessRoom.ts b/app/authorization/server/functions/canAccessRoom.ts new file mode 100644 index 0000000000000..5a943ec031a66 --- /dev/null +++ b/app/authorization/server/functions/canAccessRoom.ts @@ -0,0 +1,8 @@ +import { Promise } from 'meteor/promise'; + +import { Authorization } from '../../../../server/sdk'; +import { IAuthorization } from '../../../../server/sdk/types/IAuthorization'; + +export const canAccessRoomAsync = Authorization.canAccessRoom; + +export const canAccessRoom = (...args: Parameters): boolean => Promise.await(canAccessRoomAsync(...args)); diff --git a/ee/server/services/DDPStreamer/Streamer.ts b/ee/server/services/DDPStreamer/Streamer.ts index 364dfadc4791f..3e8d2b5c7861f 100644 --- a/ee/server/services/DDPStreamer/Streamer.ts +++ b/ee/server/services/DDPStreamer/Streamer.ts @@ -120,18 +120,24 @@ export class Stream extends EventEmitter { return this[allow](this._allowWrite, 'allowWrite')(eventName, fn); } - [isAllowed](rules: IRules) { + [isAllowed](rules: IRules, defaultPermission = false) { return async (scope: Publication, eventName: string, args: any): Promise => { if (rules[eventName]) { return rules[eventName].call(scope, eventName, ...args); } - return rules.__all__.call(scope, eventName, ...args); + if (rules.__all__) { + return rules.__all__.call(scope, eventName, ...args); + } + + // TODO: Check this since we have permissions not defined here yet + return defaultPermission; }; } async isReadAllowed(scope: Publication, eventName: string, args: any): Promise { - return this[isAllowed](this._allowRead)(scope, eventName, args); + // TODO: Check this since we have permissions not defined here yet + return this[isAllowed](this._allowRead, true)(scope, eventName, args); } async isWriteAllowed(scope: Publication, eventName: string, args: any): Promise { @@ -194,7 +200,7 @@ export class Stream extends EventEmitter { async iniPublication(): Promise { const p = this[publish].bind(this); - const { initMethod } = this; + const initMethod = this.initMethod.bind(this); server.subscribe(this.subscriptionName, function(publication, _client, eventName, options) { initMethod(publication); return p(publication, eventName, options); From 0a47431e9634a2215ee911e63558426a995831db Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Mon, 7 Sep 2020 17:29:53 -0300 Subject: [PATCH 039/198] DDPStreamer: Add proxy to localhost:3000 --- ee/server/services/DDPStreamer/DDPStreamer.ts | 34 ++++++++++++++++--- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/ee/server/services/DDPStreamer/DDPStreamer.ts b/ee/server/services/DDPStreamer/DDPStreamer.ts index d16b03443b165..feca77266c000 100644 --- a/ee/server/services/DDPStreamer/DDPStreamer.ts +++ b/ee/server/services/DDPStreamer/DDPStreamer.ts @@ -1,6 +1,7 @@ -import http from 'http'; - +import http, { RequestOptions, IncomingMessage, ServerResponse } from 'http'; // import msgpack from 'msgpack-lite'; +import url from 'url'; + import WebSocket from 'ws'; // import PromService from 'moleculer-prometheus'; // import config from 'moleculer.config'; @@ -12,6 +13,7 @@ import { Client, MeteorClient } from './Client'; import { isEmpty } from './lib/utils'; import { ServiceClass } from '../../../../server/sdk/types/ServiceClass'; + const msgpack = msgpack5(); // const broker = new ServiceBroker(config); @@ -20,12 +22,36 @@ const { // PROMETHEUS_PORT = 9100, } = process.env; +const proxy = function(req: IncomingMessage, res: ServerResponse): void { + // console.log(`request ${ req.url }`); + req.pause(); + const options: RequestOptions = url.parse(req.url || ''); + options.headers = req.headers; + options.method = req.method; + options.agent = false; + options.hostname = 'localhost'; + options.port = 3000; + + const connector = http.request(options, function(serverResponse) { + serverResponse.pause(); + if (serverResponse.statusCode) { + res.writeHead(serverResponse.statusCode, serverResponse.headers); + } + serverResponse.pipe(res); + serverResponse.resume(); + }); + req.pipe(connector); + req.resume(); +}; + const httpServer = http.createServer((req, res) => { res.setHeader('Access-Control-Allow-Origin', '*'); if (!/^\/sockjs\/info\?cb=/.test(req.url || '')) { - res.writeHead(404); - return res.end(); + return proxy(req, res); + + // res.writeHead(404); + // return res.end(); } res.writeHead(200, { 'Content-Type': 'text/plain' }); From ae5eec59fda9c2e451d999f1268ead9c9de90da9 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Mon, 7 Sep 2020 17:58:16 -0300 Subject: [PATCH 040/198] Register events correctly --- ee/server/broker.ts | 2 +- server/sdk/types/ServiceClass.ts | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/ee/server/broker.ts b/ee/server/broker.ts index 14ef09de81c41..3804eea4cbd28 100644 --- a/ee/server/broker.ts +++ b/ee/server/broker.ts @@ -36,7 +36,7 @@ class NetworkBroker implements IBroker { const service: ServiceSchema = { name, actions: {}, - events: {}, + events: instance.getEvents(), }; if (!service.events || !service.actions) { diff --git a/server/sdk/types/ServiceClass.ts b/server/sdk/types/ServiceClass.ts index 77744f234b4d6..086b17e82c1e5 100644 --- a/server/sdk/types/ServiceClass.ts +++ b/server/sdk/types/ServiceClass.ts @@ -37,6 +37,10 @@ export abstract class ServiceClass implements IServiceClass { protected events = new Map(); + getEvents(): Record { + return Object.fromEntries(this.events.entries()); + } + getName(): string { return this.name; } From 4f7fa345da27906b75f8b145cbe295072448f425 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Mon, 7 Sep 2020 18:34:20 -0300 Subject: [PATCH 041/198] Improve PM2 config watch --- ee/server/services/ecosystem.config.js | 27 +++++++------------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/ee/server/services/ecosystem.config.js b/ee/server/services/ecosystem.config.js index 2d448a3182425..9b8c6699cdc17 100644 --- a/ee/server/services/ecosystem.config.js +++ b/ee/server/services/ecosystem.config.js @@ -1,33 +1,20 @@ +const watch = ['.', '../broker.ts', '../../../server/sdk']; + module.exports = { apps: [{ name: 'Authorization', - script: 'ts-node Authorization/service.ts', - watch: true, - instances: 1, - // interpreter: '', + watch: [...watch, '../../../server/services/authorization'], }, { name: 'Presence', - script: 'ts-node Presence/service.ts', - watch: true, - instances: 1, - // interpreter: '', }, { name: 'Account', - script: 'ts-node Account/service.ts', - watch: true, - instances: 1, - // interpreter: '', }, { name: 'StreamHub', - script: 'ts-node StreamHub/service.ts', - watch: true, - instances: 1, - // interpreter: '', }, { name: 'DDPStreamer', - script: 'ts-node DDPStreamer/service.ts', - watch: true, + }].map((app) => Object.assign(app, { + script: app.script || `ts-node ${ app.name }/service.ts`, + watch: app.watch || ['.', '../broker.ts', '../../../server/sdk'], instances: 1, - // interpreter: '', - }], + })), }; From babfcda535025eb8150e4fb7cb1c081c72bf0efa Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Mon, 7 Sep 2020 18:39:50 -0300 Subject: [PATCH 042/198] Add typecheck script --- ee/server/services/package.json | 67 +++++++++++++++++---------------- 1 file changed, 34 insertions(+), 33 deletions(-) diff --git a/ee/server/services/package.json b/ee/server/services/package.json index c222e1b533d3c..8b82894287c4c 100644 --- a/ee/server/services/package.json +++ b/ee/server/services/package.json @@ -1,35 +1,36 @@ { - "name": "rocketchat-authorization", - "version": "1.0.0", - "description": "Rocket.Chat Authorization service", - "main": "index.js", - "scripts": { - "dev": "pm2 start ecosystem.config.js", - "pm2": "pm2", - "start": "ts-node index.ts", - "build": "tsc --outDir ./dist", - "test": "echo \"Error: no test specified\" && exit 1" - }, - "keywords": [ - "rocketchat" - ], - "author": "Rocket.Chat", - "license": "MIT", - "dependencies": { - "@types/msgpack5": "^3.4.1", - "ejson": "^2.2.0", - "uuid": "^7.0.3", - "ws": "^7.2.3", - "bcrypt": "^4.0.1", - "mongodb": "^3.6.1", - "msgpack5": "^4.2.1" - }, - "devDependencies": { - "@types/ejson": "^2.1.2", - "@types/node": "^14.6.4", - "@types/ws": "^7.2.6", - "pm2": "^4.4.1", - "ts-node": "^9.0.0", - "typescript": "^3.9.7" - } + "name": "rocketchat-authorization", + "version": "1.0.0", + "description": "Rocket.Chat Authorization service", + "main": "index.js", + "scripts": { + "dev": "pm2 start ecosystem.config.js", + "pm2": "pm2", + "start": "ts-node index.ts", + "typecheck": "tsc --noEmit --skipLibCheck", + "build": "tsc --outDir ./dist", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [ + "rocketchat" + ], + "author": "Rocket.Chat", + "license": "MIT", + "dependencies": { + "@types/msgpack5": "^3.4.1", + "ejson": "^2.2.0", + "uuid": "^7.0.3", + "ws": "^7.2.3", + "bcrypt": "^4.0.1", + "mongodb": "^3.6.1", + "msgpack5": "^4.2.1" + }, + "devDependencies": { + "@types/ejson": "^2.1.2", + "@types/node": "^14.6.4", + "@types/ws": "^7.2.6", + "pm2": "^4.4.1", + "ts-node": "^9.0.0", + "typescript": "^3.9.7" + } } From 703154793293cd195db21a8083a11b502103624c Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Mon, 7 Sep 2020 18:43:06 -0300 Subject: [PATCH 043/198] Fix missing module --- ee/server/services/package-lock.json | 5 ++++- ee/server/services/package.json | 2 +- package-lock.json | 11 +++++++++++ package.json | 2 ++ 4 files changed, 18 insertions(+), 2 deletions(-) diff --git a/ee/server/services/package-lock.json b/ee/server/services/package-lock.json index f430c0ef22506..245d0da085b57 100644 --- a/ee/server/services/package-lock.json +++ b/ee/server/services/package-lock.json @@ -216,6 +216,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/@types/bl/-/bl-2.1.0.tgz", "integrity": "sha512-1TdA9IXOy4sdqn8vgieQ6GZAiHiPNrOiO1s2GJjuYPw4QVY7gYoVjkW049avj33Ez7IcIvu43hQsMsoUFbCn2g==", + "dev": true, "requires": { "@types/node": "*" } @@ -236,6 +237,7 @@ "version": "3.4.1", "resolved": "https://registry.npmjs.org/@types/msgpack5/-/msgpack5-3.4.1.tgz", "integrity": "sha512-E3wILUjTXONukpiI6tmqpLwf7eV3MVTdxpjz56FqNn7koMF/6sSPUh5TxMlwgoOhyeejxwVoNZUiDcdqChKkAw==", + "dev": true, "requires": { "@types/bl": "*" } @@ -243,7 +245,8 @@ "@types/node": { "version": "14.6.4", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.6.4.tgz", - "integrity": "sha512-Wk7nG1JSaMfMpoMJDKUsWYugliB2Vy55pdjLpmLixeyMi7HizW2I/9QoxsPCkXl3dO+ZOVqPumKaDUv5zJu2uQ==" + "integrity": "sha512-Wk7nG1JSaMfMpoMJDKUsWYugliB2Vy55pdjLpmLixeyMi7HizW2I/9QoxsPCkXl3dO+ZOVqPumKaDUv5zJu2uQ==", + "dev": true }, "@types/ws": { "version": "7.2.6", diff --git a/ee/server/services/package.json b/ee/server/services/package.json index 8b82894287c4c..77fb30ea591dc 100644 --- a/ee/server/services/package.json +++ b/ee/server/services/package.json @@ -17,7 +17,6 @@ "author": "Rocket.Chat", "license": "MIT", "dependencies": { - "@types/msgpack5": "^3.4.1", "ejson": "^2.2.0", "uuid": "^7.0.3", "ws": "^7.2.3", @@ -28,6 +27,7 @@ "devDependencies": { "@types/ejson": "^2.1.2", "@types/node": "^14.6.4", + "@types/msgpack5": "^3.4.1", "@types/ws": "^7.2.6", "pm2": "^4.4.1", "ts-node": "^9.0.0", diff --git a/package-lock.json b/package-lock.json index e3242468303cd..db937c9e0ca04 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8230,6 +8230,12 @@ "integrity": "sha512-EGlKlgMhnLt/cM4DbUSafFdrkeJoC9Mvnj0PUCU7tFmTjMjNRT957kXCx0wYm3JuEq4o4ZsS5vG+NlkM2DMd2A==", "dev": true }, + "@types/ejson": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@types/ejson/-/ejson-2.1.2.tgz", + "integrity": "sha1-oMuiYNUAYxDch3kFRj2D1fPN8RI=", + "dev": true + }, "@types/elliptic": { "version": "6.4.12", "resolved": "https://registry.npmjs.org/@types/elliptic/-/elliptic-6.4.12.tgz", @@ -15939,6 +15945,11 @@ "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.5.9.tgz", "integrity": "sha512-GJCAeDBKfREgkBtgrYSf9hQy9kTb3helv0zGdzqhM7iAkW8FA/ZF97VQDbwFiwIT8MQLLOe5VlPZOEvZAqtUAQ==" }, + "ejson": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ejson/-/ejson-2.2.0.tgz", + "integrity": "sha512-kWa0AKAxDhmr4t6c4pgQqk6yL52/M67xOMh60HRnAeydzo5QIxOitN5bE1+e0rbdnxfly7FTB9e2Ny0ypLMbag==" + }, "electron-to-chromium": { "version": "1.3.540", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.540.tgz", diff --git a/package.json b/package.json index 7b62f9468a346..fb906b5dde1d2 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,7 @@ "@types/bcrypt": "^3.0.0", "@types/chai": "^4.2.12", "@types/chai-spies": "^1.0.1", + "@types/ejson": "^2.1.2", "@types/meteor": "^1.4.49", "@types/msgpack5": "^3.4.1", "@types/mocha": "^8.0.3", @@ -172,6 +173,7 @@ "core-js": "^2.6.11", "cors": "^2.8.5", "csv-parse": "^4.12.0", + "ejson": "^2.2.0", "emailreplyparser": "^0.0.5", "emojione": "^4.5.0", "eslint-plugin-import": "^2.22.0", From 7977226797f954b438448c4b8fbf3d2f9ba591b9 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Tue, 8 Sep 2020 00:24:08 -0300 Subject: [PATCH 044/198] First implementation of license check --- .../license/server/license.internalService.ts | 22 ++++++++ ee/app/license/server/license.ts | 7 ++- ee/app/license/server/startup.js | 1 + ee/server/broker.ts | 56 ++++++++++++++++++- server/sdk/index.ts | 2 + server/sdk/lib/Events.ts | 1 + server/sdk/lib/LocalBroker.ts | 5 +- server/sdk/types/ILicense.ts | 9 +++ server/sdk/types/ServiceClass.ts | 2 +- 9 files changed, 98 insertions(+), 7 deletions(-) create mode 100644 ee/app/license/server/license.internalService.ts create mode 100644 server/sdk/types/ILicense.ts diff --git a/ee/app/license/server/license.internalService.ts b/ee/app/license/server/license.internalService.ts new file mode 100644 index 0000000000000..dba506f8a2027 --- /dev/null +++ b/ee/app/license/server/license.internalService.ts @@ -0,0 +1,22 @@ +import { ServiceClass } from '../../../../server/sdk/types/ServiceClass'; +import { api } from '../../../../server/sdk/api'; +import { ILicense } from '../../../../server/sdk/types/ILicense'; +import { hasLicense, isEnterprise, getModules } from './license'; + +class License extends ServiceClass implements ILicense { + protected name = 'license'; + + hasLicense(feature: string): boolean { + return hasLicense(feature); + } + + isEnterprise(): boolean { + return isEnterprise(); + } + + getModules(): string[] { + return getModules(); + } +} + +api.registerService(new License()); diff --git a/ee/app/license/server/license.ts b/ee/app/license/server/license.ts index 09027ae2fe98f..bfe5103423f9c 100644 --- a/ee/app/license/server/license.ts +++ b/ee/app/license/server/license.ts @@ -6,6 +6,7 @@ import { resetEnterprisePermissions } from '../../authorization/server/resetEnte import { getBundleModules, isBundle, getBundleFromModule } from './bundles'; import decrypt from './decrypt'; import { getTagColor } from './getTagColor'; +import { api } from '../../../../server/sdk/api'; const EnterpriseLicenses = new EventEmitter(); @@ -62,6 +63,7 @@ class LicenseClass { modules.forEach((module) => { this.modules.add(module); + api.broadcast('license.module', { module, valid: true }); EnterpriseLicenses.emit(`valid:${ module }`); }); }); @@ -73,7 +75,10 @@ class LicenseClass { ? getBundleModules(licenseModule) : [licenseModule]; - modules.forEach((module) => EnterpriseLicenses.emit(`invalid:${ module }`)); + modules.forEach((module) => { + api.broadcast('license.module', { module, valid: false }); + EnterpriseLicenses.emit(`invalid:${ module }`); + }); }); } diff --git a/ee/app/license/server/startup.js b/ee/app/license/server/startup.js index 8c9c23cf09ea2..98a210cb0030b 100644 --- a/ee/app/license/server/startup.js +++ b/ee/app/license/server/startup.js @@ -3,6 +3,7 @@ import { callbacks } from '../../../../app/callbacks'; import { addLicense, setURL } from './license'; import './settings'; import './methods'; +import './license.internalService'; settings.get('Site_Url', (key, value) => { if (value) { diff --git a/ee/server/broker.ts b/ee/server/broker.ts index 3804eea4cbd28..85cfef5b19614 100644 --- a/ee/server/broker.ts +++ b/ee/server/broker.ts @@ -1,11 +1,12 @@ import { ServiceBroker, Context, ServiceSchema } from 'moleculer'; -import { asyncLocalStorage } from '../../server/sdk'; +import { asyncLocalStorage, License } from '../../server/sdk'; import { api } from '../../server/sdk/api'; import { IBroker, IBrokerNode } from '../../server/sdk/types/IBroker'; import { ServiceClass } from '../../server/sdk/types/ServiceClass'; // import { onLicense } from '../app/license/server'; import { EventSignatures } from '../../server/sdk/lib/Events'; +import { LocalBroker } from '../../server/sdk/lib/LocalBroker'; const events: {[k: string]: string} = { onNodeConnected: '$node.connected', @@ -22,21 +23,63 @@ const lifecycle: {[k: string]: string} = { class NetworkBroker implements IBroker { private broker: ServiceBroker; + private localBroker = new LocalBroker(); + + private allowed = false; + + private whitelist = { + events: ['license.module'], + actions: ['hasLicense'], + } + constructor(broker: ServiceBroker) { this.broker = broker; } async call(method: string, data: any): Promise { + if (!this.allowed && !this.whitelist.actions.includes(method)) { + return this.localBroker.call(method, data); + } + + await this.broker.waitForServices(method.split('.')[0]); return this.broker.call(method, data); } createService(instance: ServiceClass): void { + this.localBroker.createService(instance); + const name = instance.getName(); + // Listen for module license + instance.onEvent('license.module', ({ module, valid }) => { + if (module === 'scalability') { + this.allowed = valid; + console.log('on license.module', { allowed: this.allowed }); + } + }); + const service: ServiceSchema = { name, actions: {}, - events: instance.getEvents(), + // Prevent listen events when not allowed except by `license.module` + events: Object.fromEntries(Object.entries(instance.getEvents()).map(([event, fn]) => { + if (!this.whitelist.events.includes(event)) { + fn = (...args: any[]): any => { + if (this.allowed) { + return fn(...args); + } + }; + } + return [event, (data: any[]): void => fn(...data)]; + })), + started: async (): Promise => { + if (name === 'license') { + return; + } + + this.allowed = await License.hasLicense('scalability'); + console.log('on started', { allowed: this.allowed }); + }, }; if (!service.events || !service.actions) { @@ -66,13 +109,20 @@ class NetworkBroker implements IBroker { nodeID: ctx.nodeID, requestID: ctx.requestID, broker: this, - }, (): any => i[method](...ctx.params)); + }, (): any => { + if (this.allowed || this.whitelist.actions.includes(method)) { + return i[method](...ctx.params); + } + }); } this.broker.createService(service); } async broadcast(event: T, ...args: Parameters): Promise { + if (!this.allowed && !this.whitelist.events.includes(event)) { + return this.localBroker.broadcast(event, ...args); + } return this.broker.broadcast(event, args); } diff --git a/server/sdk/index.ts b/server/sdk/index.ts index 3bac64e786ae6..d042dc4642b78 100644 --- a/server/sdk/index.ts +++ b/server/sdk/index.ts @@ -5,10 +5,12 @@ import { IAuthorization } from './types/IAuthorization'; import { IServiceContext } from './types/ServiceClass'; import { IPresence } from './types/IPresence'; import { IAccount } from './types/IAccount'; +import { ILicense } from './types/ILicense'; // TODO try not having to duplicate the namespace 'authorization' here export const Authorization = proxify('authorization'); export const Presence = proxify('presence'); export const Account = proxify('accounts'); +export const License = proxify('license'); export const asyncLocalStorage = new AsyncLocalStorage(); diff --git a/server/sdk/lib/Events.ts b/server/sdk/lib/Events.ts index fa182cf862e2b..7e4adfa0e1bc8 100644 --- a/server/sdk/lib/Events.ts +++ b/server/sdk/lib/Events.ts @@ -20,4 +20,5 @@ export type EventSignatures = { 'user'(payload: BufferList): void; 'user.name'(payload: BufferList): void; 'role'(data: {type: 'changed' | 'removed' } & Partial): void; + 'license.module'(data: {module: string; valid: boolean}): void; } diff --git a/server/sdk/lib/LocalBroker.ts b/server/sdk/lib/LocalBroker.ts index f900afcb57c85..a681bfd394317 100644 --- a/server/sdk/lib/LocalBroker.ts +++ b/server/sdk/lib/LocalBroker.ts @@ -1,6 +1,7 @@ import { IBroker, IBrokerNode } from '../types/IBroker'; import { ServiceClass } from '../types/ServiceClass'; import { asyncLocalStorage } from '..'; +import { EventSignatures } from './Events'; export class LocalBroker implements IBroker { private methods = new Map(); @@ -30,9 +31,9 @@ export class LocalBroker implements IBroker { } } - async broadcast(eventName: string, data: D): Promise { + async broadcast(event: T, ...args: Parameters): Promise { // TODO: - console.log('broadcast implementation missing', eventName, data); + console.log('broadcast implementation missing', event, args); // return this.broker.broadcast(eventName, data); } diff --git a/server/sdk/types/ILicense.ts b/server/sdk/types/ILicense.ts new file mode 100644 index 0000000000000..b6bcc6ed1b210 --- /dev/null +++ b/server/sdk/types/ILicense.ts @@ -0,0 +1,9 @@ +import { IServiceClass } from './ServiceClass'; + +export interface ILicense extends IServiceClass { + hasLicense(feature: string): boolean; + + isEnterprise(): boolean; + + getModules(): string[]; +} diff --git a/server/sdk/types/ServiceClass.ts b/server/sdk/types/ServiceClass.ts index 086b17e82c1e5..c2248a904af58 100644 --- a/server/sdk/types/ServiceClass.ts +++ b/server/sdk/types/ServiceClass.ts @@ -49,7 +49,7 @@ export abstract class ServiceClass implements IServiceClass { return asyncLocalStorage.getStore(); } - protected onEvent(event: T, handler: EventSignatures[T]): void { + public onEvent(event: T, handler: EventSignatures[T]): void { if (this.events.has(event)) { throw new Error('event already registered'); } From f733accb9b4b3549fa5d06e8da7136be40bfe081 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Tue, 8 Sep 2020 00:39:00 -0300 Subject: [PATCH 045/198] Fix whitelist and ensure license module availability on broker --- ee/server/broker.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/ee/server/broker.ts b/ee/server/broker.ts index 85cfef5b19614..0ba56b6c9483c 100644 --- a/ee/server/broker.ts +++ b/ee/server/broker.ts @@ -29,7 +29,7 @@ class NetworkBroker implements IBroker { private whitelist = { events: ['license.module'], - actions: ['hasLicense'], + actions: ['license.hasLicense'], } constructor(broker: ServiceBroker) { @@ -51,9 +51,10 @@ class NetworkBroker implements IBroker { const name = instance.getName(); // Listen for module license - instance.onEvent('license.module', ({ module, valid }) => { + instance.onEvent('license.module', async ({ module, valid }) => { if (module === 'scalability') { - this.allowed = valid; + // Should we believe on the event only? Could it be a call from the CE version? + this.allowed = valid && await License.hasLicense('scalability'); console.log('on license.module', { allowed: this.allowed }); } }); @@ -110,7 +111,7 @@ class NetworkBroker implements IBroker { requestID: ctx.requestID, broker: this, }, (): any => { - if (this.allowed || this.whitelist.actions.includes(method)) { + if (this.allowed || this.whitelist.actions.includes(`${ name }.${ method }`)) { return i[method](...ctx.params); } }); From a07ac65f796c83bed6d7bd885a552c5c103850ae Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Tue, 8 Sep 2020 07:48:49 -0300 Subject: [PATCH 046/198] Disable CI --- .github/{workflows => workflows_disabled}/build_and_test.yml | 0 .github/{workflows => workflows_disabled}/stale.yml | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename .github/{workflows => workflows_disabled}/build_and_test.yml (100%) rename .github/{workflows => workflows_disabled}/stale.yml (100%) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows_disabled/build_and_test.yml similarity index 100% rename from .github/workflows/build_and_test.yml rename to .github/workflows_disabled/build_and_test.yml diff --git a/.github/workflows/stale.yml b/.github/workflows_disabled/stale.yml similarity index 100% rename from .github/workflows/stale.yml rename to .github/workflows_disabled/stale.yml From cb18b2e72aed877d58e004792f84805cc8381ded Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Tue, 8 Sep 2020 09:39:57 -0300 Subject: [PATCH 047/198] Fix issue when calling events --- ee/server/broker.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ee/server/broker.ts b/ee/server/broker.ts index 0ba56b6c9483c..f321d87fba768 100644 --- a/ee/server/broker.ts +++ b/ee/server/broker.ts @@ -65,9 +65,10 @@ class NetworkBroker implements IBroker { // Prevent listen events when not allowed except by `license.module` events: Object.fromEntries(Object.entries(instance.getEvents()).map(([event, fn]) => { if (!this.whitelist.events.includes(event)) { + const originalFn = fn; fn = (...args: any[]): any => { if (this.allowed) { - return fn(...args); + return originalFn(...args); } }; } From ce65cabb1618446cf201c906375d49f9c81de784 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Tue, 8 Sep 2020 10:11:47 -0300 Subject: [PATCH 048/198] Start network broaker as soon as possible --- ee/server/broker.ts | 48 +++++++++++++++++++++------------------------ server/main.js | 1 + 2 files changed, 23 insertions(+), 26 deletions(-) diff --git a/ee/server/broker.ts b/ee/server/broker.ts index f321d87fba768..c01a92373276b 100644 --- a/ee/server/broker.ts +++ b/ee/server/broker.ts @@ -133,32 +133,28 @@ class NetworkBroker implements IBroker { } } -// onLicense('scalability', async () => { -(async (): Promise => { - const network = new ServiceBroker({ - transporter: 'TCP', - // logLevel: 'debug', - logLevel: { - // "TRACING": "trace", - // "TRANS*": "warn", - BROKER: 'debug', - TRANSIT: 'debug', - '**': 'info', +const network = new ServiceBroker({ + transporter: 'TCP', + // logLevel: 'debug', + logLevel: { + // "TRACING": "trace", + // "TRANS*": "warn", + BROKER: 'debug', + TRANSIT: 'debug', + '**': 'info', + }, + logger: { + type: 'Console', + options: { + formatter: 'short', }, - logger: { - type: 'Console', - options: { - formatter: 'short', - }, - }, - registry: { - strategy: 'RoundRobin', - preferLocal: false, - }, - }); + }, + registry: { + strategy: 'RoundRobin', + preferLocal: false, + }, +}); - await network.start(); +api.setBroker(new NetworkBroker(network)); - api.setBroker(new NetworkBroker(network)); -})(); -// }); +network.start(); diff --git a/server/main.js b/server/main.js index ff5e955502f55..9048f3460948d 100644 --- a/server/main.js +++ b/server/main.js @@ -1,3 +1,4 @@ +import '../ee/server/broker'; import './importPackages'; import '../imports/startup/server'; From a3113eb6a18e228b5e51e3c2649be00bd8ed7746 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Sat, 19 Sep 2020 08:42:04 -0300 Subject: [PATCH 049/198] Fix lint --- app/livechat/server/startup.js | 1 + 1 file changed, 1 insertion(+) diff --git a/app/livechat/server/startup.js b/app/livechat/server/startup.js index 6b72b95afbd49..b0b9a13887ae9 100644 --- a/app/livechat/server/startup.js +++ b/app/livechat/server/startup.js @@ -9,6 +9,7 @@ import { createLivechatQueueView } from './lib/Helper'; import { LivechatAgentActivityMonitor } from './statistics/LivechatAgentActivityMonitor'; import { businessHourManager } from './business-hour'; import { createDefaultBusinessHourIfNotExists } from './business-hour/Helper'; +import { hasPermission } from '../../authorization/server'; import './roomAccessValidator.internalService'; From 6c5d9831c20c763db638f34cdd8f6558a354e471 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Sat, 19 Sep 2020 09:10:05 -0300 Subject: [PATCH 050/198] Fix missing type --- definition/ISetting.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/definition/ISetting.ts b/definition/ISetting.ts index a89556b5f44fb..eb29e6b8cf307 100644 --- a/definition/ISetting.ts +++ b/definition/ISetting.ts @@ -1,3 +1,5 @@ +import { FilterQuery } from 'mongodb'; + export type SettingId = string; export type GroupId = SettingId; export type SectionName = string; @@ -37,6 +39,6 @@ export interface ISetting { editor?: SettingEditor; packageEditor?: SettingEditor; blocked: boolean; - enableQuery?: string | Mongo.ObjectID | Mongo.Query | Mongo.QueryWithModifiers; + enableQuery?: string | FilterQuery; sorter?: number; } From 8616171f51754f77074f037eef5a73773f29049f Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Sat, 19 Sep 2020 11:03:06 -0300 Subject: [PATCH 051/198] Implement initial option to build containers --- ee/server/broker.ts | 2 +- ee/server/services/Dockerfile | 28 ++++++++++++ ee/server/services/docker-compose.yml | 62 +++++++++++++++++++++++++++ ee/server/services/package.json | 8 +++- ee/server/services/tsconfig.json | 2 + package-lock.json | 29 +++++++++++++ package.json | 1 + 7 files changed, 129 insertions(+), 3 deletions(-) create mode 100644 ee/server/services/Dockerfile create mode 100644 ee/server/services/docker-compose.yml diff --git a/ee/server/broker.ts b/ee/server/broker.ts index c01a92373276b..7362efa66a708 100644 --- a/ee/server/broker.ts +++ b/ee/server/broker.ts @@ -134,7 +134,7 @@ class NetworkBroker implements IBroker { } const network = new ServiceBroker({ - transporter: 'TCP', + transporter: process.env.TRANSPORTER || 'TCP', // logLevel: 'debug', logLevel: { // "TRACING": "trace", diff --git a/ee/server/services/Dockerfile b/ee/server/services/Dockerfile new file mode 100644 index 0000000000000..157f1028992c6 --- /dev/null +++ b/ee/server/services/Dockerfile @@ -0,0 +1,28 @@ +FROM node:12 as build + +WORKDIR /app + +RUN apt-get update \ + && apt-get install -y build-essential git + +# ADD ./moleculer.config.js . +ADD ./dist . +ADD ./package.json . + +RUN npm install --production + +FROM node:12-alpine + +ARG SERVICE + +WORKDIR /app + +COPY --from=build /app . + +ENV NODE_ENV=production + +WORKDIR /app/ee/server/services/${SERVICE} + +RUN ls . + +CMD ["node", "service.js"] diff --git a/ee/server/services/docker-compose.yml b/ee/server/services/docker-compose.yml new file mode 100644 index 0000000000000..be80520881556 --- /dev/null +++ b/ee/server/services/docker-compose.yml @@ -0,0 +1,62 @@ +version: '3.1' + +services: + authorization-service: + build: + context: . + args: + SERVICE: Authorization + # image: registry.rocket.chat/microservices_authorization-service:latest + environment: + - TRANSPORTER=${TRANSPORTER:-nats://nats:4222} + - MONGO_URL=${MONGO_URL:-mongodb://host.docker.internal:27017/rocketchat} + # - MOLECULER_LOG_LEVEL=info + + account-service: + build: + context: . + args: + SERVICE: Account + # image: registry.rocket.chat/microservices_accounts-service:latest + environment: + - TRANSPORTER=${TRANSPORTER:-nats://nats:4222} + - MONGO_URL=${MONGO_URL:-mongodb://host.docker.internal:27017/rocketchat} + # - MOLECULER_LOG_LEVEL=info + + presence-service: + build: + context: . + args: + SERVICE: Presence + # image: registry.rocket.chat/microservices_presence-service:latest + environment: + - TRANSPORTER=${TRANSPORTER:-nats://nats:4222} + - MONGO_URL=${MONGO_URL:-mongodb://host.docker.internal:27017/rocketchat} + # - MOLECULER_LOG_LEVEL=info + + ddp-streamer-service: + build: + context: . + args: + SERVICE: DDPStreamer + # image: registry.rocket.chat/microservices_ddp-streamer:latest + environment: + - TRANSPORTER=${TRANSPORTER:-nats://nats:4222} + # - MONGO_URL=${MONGO_URL:-mongodb://host.docker.internal:27017/rocketchat} + # - MOLECULER_LOG_LEVEL=info + + stream-hub-service: + build: + context: . + args: + SERVICE: StreamHub + # image: registry.rocket.chat/microservices_mongodb-stream-hub:latest + environment: + - TRANSPORTER=${TRANSPORTER:-nats://nats:4222} + - MONGO_URL=${MONGO_URL:-mongodb://host.docker.internal:27017/rocketchat} + # - MOLECULER_LOG_LEVEL=info + + nats: + image: nats + ports: + - "4222:4222" diff --git a/ee/server/services/package.json b/ee/server/services/package.json index 77fb30ea591dc..9bb0dbe0a5f3a 100644 --- a/ee/server/services/package.json +++ b/ee/server/services/package.json @@ -8,7 +8,8 @@ "pm2": "pm2", "start": "ts-node index.ts", "typecheck": "tsc --noEmit --skipLibCheck", - "build": "tsc --outDir ./dist", + "build": "tsc", + "build-containers": "npm run build && docker-compose build && rm -rf ./dist", "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [ @@ -22,7 +23,10 @@ "ws": "^7.2.3", "bcrypt": "^4.0.1", "mongodb": "^3.6.1", - "msgpack5": "^4.2.1" + "msgpack5": "^4.2.1", + "underscore.string": "^3.3.5", + "moleculer": "^0.14.10", + "nats": "^1.4.8" }, "devDependencies": { "@types/ejson": "^2.1.2", diff --git a/ee/server/services/tsconfig.json b/ee/server/services/tsconfig.json index 30716527c395d..c9da089396932 100644 --- a/ee/server/services/tsconfig.json +++ b/ee/server/services/tsconfig.json @@ -34,6 +34,8 @@ "esModuleInterop": true, "preserveSymlinks": true, + "outDir": "./dist", + // "sourceMap": true, // "declaration": true, // "removeComments": false, diff --git a/package-lock.json b/package-lock.json index a52e3b8fc42c7..e5476542c8d5d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25266,6 +25266,15 @@ "integrity": "sha512-6+TDFewD4yxY14ptjKaS63GVdtKiES1pTPyxn9Jb0rBqPMZ7VcCiooEhPNsr+mqHtMGxa/5c/HhcC4uPEUw/nA==", "optional": true }, + "nats": { + "version": "1.4.12", + "resolved": "https://registry.npmjs.org/nats/-/nats-1.4.12.tgz", + "integrity": "sha512-Jf4qesEF0Ay0D4AMw3OZnKMRTQm+6oZ5q8/m4gpy5bTmiDiK6wCXbZpzEslmezGpE93LV3RojNEG6dpK/mysLQ==", + "requires": { + "nuid": "^1.1.4", + "ts-nkeys": "^1.0.16" + } + }, "natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -25745,6 +25754,11 @@ "boolbase": "~1.0.0" } }, + "nuid": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/nuid/-/nuid-1.1.4.tgz", + "integrity": "sha512-PXiYyHhGfrq8H4g5HyC8enO1lz6SBe5z6x1yx/JG4tmADzDGJVQy3l1sRf3VtEvPsN8dGn9hRFRwDKWL62x0BA==" + }, "num2fraction": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", @@ -33051,6 +33065,21 @@ } } }, + "ts-nkeys": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/ts-nkeys/-/ts-nkeys-1.0.16.tgz", + "integrity": "sha512-1qrhAlavbm36wtW+7NtKOgxpzl+70NTF8xlz9mEhiA5zHMlMxjj3sEVKWm3pGZhHXE0Q3ykjrj+OSRVaYw+Dqg==", + "requires": { + "tweetnacl": "^1.0.3" + }, + "dependencies": { + "tweetnacl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" + } + } + }, "ts-node": { "version": "8.10.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.10.2.tgz", diff --git a/package.json b/package.json index 6416b5342466b..9225dd8443f0b 100644 --- a/package.json +++ b/package.json @@ -217,6 +217,7 @@ "moment": "^2.27.0", "moment-timezone": "^0.5.31", "mongodb": "^3.6.0", + "nats": "^1.4.8", "node-dogstatsd": "^0.0.7", "node-gcm": "0.14.4", "node-rsa": "^1.1.1", From 85feb78669241385c47143f5b61bd3aab883c5b7 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Sat, 19 Sep 2020 11:05:25 -0300 Subject: [PATCH 052/198] Rremove uncessary ls command --- ee/server/services/Dockerfile | 2 -- 1 file changed, 2 deletions(-) diff --git a/ee/server/services/Dockerfile b/ee/server/services/Dockerfile index 157f1028992c6..79a01326e6821 100644 --- a/ee/server/services/Dockerfile +++ b/ee/server/services/Dockerfile @@ -23,6 +23,4 @@ ENV NODE_ENV=production WORKDIR /app/ee/server/services/${SERVICE} -RUN ls . - CMD ["node", "service.js"] From 411fc6c2efb7b004bb75724a7be3f631cfa09794 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Sat, 19 Sep 2020 15:01:26 -0300 Subject: [PATCH 053/198] Implement local broker event broadcast --- ee/server/broker.ts | 5 ++--- server/sdk/lib/LocalBroker.ts | 15 ++++++++++++--- server/services/startup.ts | 3 --- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/ee/server/broker.ts b/ee/server/broker.ts index 7362efa66a708..9c6d13f648637 100644 --- a/ee/server/broker.ts +++ b/ee/server/broker.ts @@ -4,7 +4,6 @@ import { asyncLocalStorage, License } from '../../server/sdk'; import { api } from '../../server/sdk/api'; import { IBroker, IBrokerNode } from '../../server/sdk/types/IBroker'; import { ServiceClass } from '../../server/sdk/types/ServiceClass'; -// import { onLicense } from '../app/license/server'; import { EventSignatures } from '../../server/sdk/lib/Events'; import { LocalBroker } from '../../server/sdk/lib/LocalBroker'; @@ -55,7 +54,7 @@ class NetworkBroker implements IBroker { if (module === 'scalability') { // Should we believe on the event only? Could it be a call from the CE version? this.allowed = valid && await License.hasLicense('scalability'); - console.log('on license.module', { allowed: this.allowed }); + // console.log('on license.module', { allowed: this.allowed }); } }); @@ -80,7 +79,7 @@ class NetworkBroker implements IBroker { } this.allowed = await License.hasLicense('scalability'); - console.log('on started', { allowed: this.allowed }); + // console.log('on started', { allowed: this.allowed }); }, }; diff --git a/server/sdk/lib/LocalBroker.ts b/server/sdk/lib/LocalBroker.ts index a681bfd394317..ce9be56f34f94 100644 --- a/server/sdk/lib/LocalBroker.ts +++ b/server/sdk/lib/LocalBroker.ts @@ -6,6 +6,8 @@ import { EventSignatures } from './Events'; export class LocalBroker implements IBroker { private methods = new Map(); + private events = new Map>(); + async call(method: string, data: any): Promise { const result = await asyncLocalStorage.run({ id: 'ctx.id', @@ -20,6 +22,12 @@ export class LocalBroker implements IBroker { createService(instance: ServiceClass): void { const namespace = instance.getName(); + for (const [event, fn] of Object.entries(instance.getEvents())) { + const fns = this.events.get(event) || new Set(); + fns.add(fn); + this.events.set(event, fns); + } + const methods = instance.constructor?.name === 'Object' ? Object.getOwnPropertyNames(instance) : Object.getOwnPropertyNames(Object.getPrototypeOf(instance)); for (const method of methods) { if (method === 'constructor') { @@ -32,9 +40,10 @@ export class LocalBroker implements IBroker { } async broadcast(event: T, ...args: Parameters): Promise { - // TODO: - console.log('broadcast implementation missing', event, args); - // return this.broker.broadcast(eventName, data); + const fns = this.events.get(event); + if (fns) { + fns.forEach((fn) => fn(...args)); + } } async nodeList(): Promise { diff --git a/server/services/startup.ts b/server/services/startup.ts index 5c314bd79e8f1..97a046e048ab1 100644 --- a/server/services/startup.ts +++ b/server/services/startup.ts @@ -1,9 +1,6 @@ import { MongoInternals } from 'meteor/mongo'; import { api } from '../sdk/api'; -// import { LocalBroker } from '../sdk/lib/LocalBroker'; import { Authorization } from './authorization/service'; -// api.setBroker(new LocalBroker()); - api.registerService(new Authorization(MongoInternals.defaultRemoteCollectionDriver().mongo.db)); From 6079eee1db298150aadff579afb4a5bf02126728 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Sat, 19 Sep 2020 17:14:44 -0300 Subject: [PATCH 054/198] Resolve some TODOs --- ee/server/broker.ts | 87 ++++++++++++++++--- ee/server/services/DDPStreamer/DDPStreamer.ts | 2 - ee/server/services/Presence/service.ts | 3 - ee/server/services/StreamHub/service.ts | 3 - ee/server/services/StreamHub/watchMessages.ts | 7 +- ee/server/services/StreamHub/watchRoles.ts | 1 - ee/server/services/StreamHub/watchRooms.ts | 1 - ee/server/services/StreamHub/watchSettings.ts | 1 - .../services/StreamHub/watchSubscriptions.ts | 1 - ee/server/services/StreamHub/watchUsers.ts | 5 +- ee/server/services/mongo.ts | 1 + server/routes/avatar/index.js | 10 --- .../services/authorization/canAccessRoom.ts | 1 - 13 files changed, 82 insertions(+), 41 deletions(-) diff --git a/ee/server/broker.ts b/ee/server/broker.ts index 9c6d13f648637..d7b0d0107c43f 100644 --- a/ee/server/broker.ts +++ b/ee/server/broker.ts @@ -132,16 +132,49 @@ class NetworkBroker implements IBroker { } } +const { + TRANSPORTER = 'TCP', + CACHE = 'Memory', + SERIALIZER = 'MsgPack', + MOLECULER_LOG_LEVEL = 'error', + BALANCE_STRATEGY = 'RoundRobin', + BALANCE_PREFER_LOCAL = 'false', + RETRY_FACTOR = '2', + RETRY_MAX_DELAY = '1000', + RETRY_DELAY = '100', + RETRY_RETRIES = '5', + RETRY_ENABLED = 'false', + REQUEST_TIMEOUT = '10', + HEARTBEAT_INTERVAL = '10', + HEARTBEAT_TIMEOUT = '30', + BULKHEAD_ENABLED = 'false', + BULKHEAD_CONCURRENCY = '10', + BULKHEAD_MAX_QUEUE_SIZE = '10000', + MS_METRICS = 'false', + MS_METRICS_PORT = '3030', +} = process.env; + const network = new ServiceBroker({ - transporter: process.env.TRANSPORTER || 'TCP', - // logLevel: 'debug', - logLevel: { - // "TRACING": "trace", - // "TRANS*": "warn", - BROKER: 'debug', - TRANSIT: 'debug', - '**': 'info', + transporter: TRANSPORTER, + metrics: { + enabled: MS_METRICS === 'true', + reporter: [{ + type: 'Prometheus', + options: { + port: MS_METRICS_PORT, + }, + }], }, + cacher: CACHE, + serializer: SERIALIZER, + logLevel: MOLECULER_LOG_LEVEL as any, + // logLevel: { + // // "TRACING": "trace", + // // "TRANS*": "warn", + // BROKER: 'debug', + // TRANSIT: 'debug', + // '**': 'info', + // }, logger: { type: 'Console', options: { @@ -149,9 +182,43 @@ const network = new ServiceBroker({ }, }, registry: { - strategy: 'RoundRobin', - preferLocal: false, + strategy: BALANCE_STRATEGY, + preferLocal: BALANCE_PREFER_LOCAL !== 'false', + }, + + requestTimeout: parseInt(REQUEST_TIMEOUT) * 1000, + retryPolicy: { + enabled: RETRY_ENABLED === 'true', + retries: parseInt(RETRY_RETRIES), + delay: parseInt(RETRY_DELAY), + maxDelay: parseInt(RETRY_MAX_DELAY), + factor: parseInt(RETRY_FACTOR), + check: (err: any): boolean => err && !!err.retryable, }, + + maxCallLevel: 100, + heartbeatInterval: parseInt(HEARTBEAT_INTERVAL), + heartbeatTimeout: parseInt(HEARTBEAT_TIMEOUT), + + // circuitBreaker: { + // enabled: false, + // threshold: 0.5, + // windowTime: 60, + // minRequestCount: 20, + // halfOpenTime: 10 * 1000, + // check: (err: any): boolean => err && err.code >= 500, + // }, + + bulkhead: { + enabled: BULKHEAD_ENABLED === 'true', + concurrency: parseInt(BULKHEAD_CONCURRENCY), + maxQueueSize: parseInt(BULKHEAD_MAX_QUEUE_SIZE), + }, + + // tracing: { + // enabled: true, + // exporter: "EventLegacy" + // }, }); api.setBroker(new NetworkBroker(network)); diff --git a/ee/server/services/DDPStreamer/DDPStreamer.ts b/ee/server/services/DDPStreamer/DDPStreamer.ts index feca77266c000..049d2800e49f2 100644 --- a/ee/server/services/DDPStreamer/DDPStreamer.ts +++ b/ee/server/services/DDPStreamer/DDPStreamer.ts @@ -1,10 +1,8 @@ import http, { RequestOptions, IncomingMessage, ServerResponse } from 'http'; -// import msgpack from 'msgpack-lite'; import url from 'url'; import WebSocket from 'ws'; // import PromService from 'moleculer-prometheus'; -// import config from 'moleculer.config'; import msgpack5 from 'msgpack5'; import * as Streamer from './streams'; diff --git a/ee/server/services/Presence/service.ts b/ee/server/services/Presence/service.ts index 35c7c5789e13a..a76e6dd8a7da8 100755 --- a/ee/server/services/Presence/service.ts +++ b/ee/server/services/Presence/service.ts @@ -1,6 +1,3 @@ -// TODO: check config -// import config from 'moleculer.config'; - import { api } from '../../../../server/sdk/api'; import { Presence } from './Presence'; diff --git a/ee/server/services/StreamHub/service.ts b/ee/server/services/StreamHub/service.ts index 764915adc1fda..b9486a61f680e 100755 --- a/ee/server/services/StreamHub/service.ts +++ b/ee/server/services/StreamHub/service.ts @@ -1,6 +1,3 @@ -// TODO: check config -// import config from 'moleculer.config'; - import { api } from '../../../../server/sdk/api'; import { StreamHub } from './StreamHub'; diff --git a/ee/server/services/StreamHub/watchMessages.ts b/ee/server/services/StreamHub/watchMessages.ts index b226a89ec1664..216155b822995 100644 --- a/ee/server/services/StreamHub/watchMessages.ts +++ b/ee/server/services/StreamHub/watchMessages.ts @@ -15,9 +15,8 @@ export async function watchMessages(event: ChangeEvent): Promise return; } api.broadcast('message', { action: normalize[event.operationType], message }); - // TODO: - // RocketChat.Logger.info('Message record', fullDocument); - // return Streamer[method]({ stream: STREA M_NAMES['room-messages'], eventName: message.rid, args: message }); - // publishMessage(operationType, message); + // RocketChat.Logger.info('Message record', fullDocument); + // return Streamer[method]({ stream: STREA M_NAMES['room-messages'], eventName: message.rid, args: message }); + // publishMessage(operationType, message); } } diff --git a/ee/server/services/StreamHub/watchRoles.ts b/ee/server/services/StreamHub/watchRoles.ts index 85790fc1fa8d7..23d355827a319 100644 --- a/ee/server/services/StreamHub/watchRoles.ts +++ b/ee/server/services/StreamHub/watchRoles.ts @@ -4,7 +4,6 @@ import { api } from '../../../../server/sdk/api'; import { IRole } from '../../../../definition/IRole'; export async function watchRoles(event: ChangeEvent): Promise { - // TODO: // RocketChat.Logger.info('Role record', documentKey); switch (event.operationType) { case 'insert': diff --git a/ee/server/services/StreamHub/watchRooms.ts b/ee/server/services/StreamHub/watchRooms.ts index e036a639a6649..5fa7ca8a2f4fd 100644 --- a/ee/server/services/StreamHub/watchRooms.ts +++ b/ee/server/services/StreamHub/watchRooms.ts @@ -24,6 +24,5 @@ export async function watchRooms(event: ChangeEvent): Promise { } api.broadcast('room', { action: normalize[event.operationType], room }); // RocketChat.Notifications.streamUser.__emit(data._id, operationType, data); - // TODO: // RocketChat.Logger.info('Rooms record', room); } diff --git a/ee/server/services/StreamHub/watchSettings.ts b/ee/server/services/StreamHub/watchSettings.ts index 915ab93ce3e7b..1643209f6d491 100644 --- a/ee/server/services/StreamHub/watchSettings.ts +++ b/ee/server/services/StreamHub/watchSettings.ts @@ -28,6 +28,5 @@ export async function watchSettings(event: ChangeEvent): Promise api.broadcast('setting', { action: normalize[event.operationType], setting }); // RocketChat.Notifications.streamUser.__emit(data._id, operationType, data); - // TODO: // RocketChat.Logger.info('Settings record', setting); } diff --git a/ee/server/services/StreamHub/watchSubscriptions.ts b/ee/server/services/StreamHub/watchSubscriptions.ts index 332315055cf86..83c6f95140fe8 100644 --- a/ee/server/services/StreamHub/watchSubscriptions.ts +++ b/ee/server/services/StreamHub/watchSubscriptions.ts @@ -23,7 +23,6 @@ export function watchSubscriptions(Trash: Collection) { return; } api.broadcast('subscription', { action: normalize[event.operationType], subscription }); - // TODO: // RocketChat.Logger.info('Subscription record', fullDocument); }; } diff --git a/ee/server/services/StreamHub/watchUsers.ts b/ee/server/services/StreamHub/watchUsers.ts index dc6877b1ffaa7..5a8bcd34e040a 100644 --- a/ee/server/services/StreamHub/watchUsers.ts +++ b/ee/server/services/StreamHub/watchUsers.ts @@ -48,7 +48,6 @@ export async function watchUsers(event: ChangeEvent): Promise { if (updatedFields.status || updatedFields.statusText) { const { status, _id, username, statusText } = user; // remove username api.broadcast('userpresence', msgpack.encode({ action: normalize[event.operationType], user: { status, _id, username, statusText } })); // remove username - // TODO: // RocketChat.Logger.info('User: userpresence', { status, _id, username, statusText }); } @@ -65,7 +64,6 @@ export async function watchUsers(event: ChangeEvent): Promise { action: normalize[event.operationType], user: nameChange, })); - // TODO: // RocketChat.Logger.info('User: user.name', nameChange); } } @@ -79,8 +77,7 @@ export async function watchUsers(event: ChangeEvent): Promise { }, }), ); - // TODO: - // RocketChat.Logger.info('User record', user); + // RocketChat.Logger.info('User record', user); // return Streamer[method]({ stream: STREAM_NAMES['room-messages'], eventName: message.rid, args: message }); // publishMessage(operationType, message); } diff --git a/ee/server/services/mongo.ts b/ee/server/services/mongo.ts index 110b692f07c2b..f7c3ef58093c6 100644 --- a/ee/server/services/mongo.ts +++ b/ee/server/services/mongo.ts @@ -8,6 +8,7 @@ const { const name = /^mongodb:\/\/.*?(?::[0-9]+)?\/([^?]*)/.exec(MONGO_URL)?.[1]; const client = new MongoClient(MONGO_URL, { + useUnifiedTopology: true, useNewUrlParser: true, }); diff --git a/server/routes/avatar/index.js b/server/routes/avatar/index.js index 8383aedf9af2f..994fad6821e17 100644 --- a/server/routes/avatar/index.js +++ b/server/routes/avatar/index.js @@ -2,18 +2,8 @@ import { WebApp } from 'meteor/webapp'; import { roomAvatar } from './room'; import { userAvatar } from './user'; -import { Authorization } from '../../sdk'; import './middlewares'; WebApp.connectHandlers.use('/avatar/room/', roomAvatar); WebApp.connectHandlers.use('/avatar/', userAvatar); - -// TODO: remove - testing purposes only -WebApp.connectHandlers.use('/sdk/', async (req, res) => { - const result = await Authorization.hasPermission(req.query.name, 'lero'); - - console.log('result ->', result); - - return res.end('done'); -}); diff --git a/server/services/authorization/canAccessRoom.ts b/server/services/authorization/canAccessRoom.ts index 2e7468392ae1a..bcf5ba078fe62 100644 --- a/server/services/authorization/canAccessRoom.ts +++ b/server/services/authorization/canAccessRoom.ts @@ -1,6 +1,5 @@ import { Authorization } from '../../sdk'; -// TODO: change to MS instance import { RoomAccessValidator } from '../../sdk/types/IAuthorization'; import { canAccessRoomLivechat } from './canAccessRoomLivechat'; import { canAccessRoomTokenpass } from './canAccessRoomTokenpass'; From 58b534df458e7c8800ac1214e0e8c01f5342d59b Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Sat, 19 Sep 2020 17:18:44 -0300 Subject: [PATCH 055/198] Remove msgpack from internal events --- ee/server/services/DDPStreamer/DDPStreamer.ts | 15 +++++------ ee/server/services/Presence/Presence.ts | 13 ---------- ee/server/services/StreamHub/watchUsers.ts | 26 +++++++------------ server/sdk/lib/Events.ts | 7 ++--- 4 files changed, 20 insertions(+), 41 deletions(-) diff --git a/ee/server/services/DDPStreamer/DDPStreamer.ts b/ee/server/services/DDPStreamer/DDPStreamer.ts index 049d2800e49f2..22905b0efa367 100644 --- a/ee/server/services/DDPStreamer/DDPStreamer.ts +++ b/ee/server/services/DDPStreamer/DDPStreamer.ts @@ -3,7 +3,6 @@ import url from 'url'; import WebSocket from 'ws'; // import PromService from 'moleculer-prometheus'; -import msgpack5 from 'msgpack5'; import * as Streamer from './streams'; import { Client, MeteorClient } from './Client'; @@ -11,10 +10,6 @@ import { Client, MeteorClient } from './Client'; import { isEmpty } from './lib/utils'; import { ServiceClass } from '../../../../server/sdk/types/ServiceClass'; - -const msgpack = msgpack5(); - -// const broker = new ServiceBroker(config); const { PORT: port = 4000, // PROMETHEUS_PORT = 9100, @@ -145,9 +140,11 @@ export class DDPStreamer extends ServiceClass { const { user: { _id, username, status, statusText }, - } = msgpack.decode(payload); + } = payload; // Streamer.userpresence.emit(_id, status); - Streamer.notifyLogged.emit('user-status', [_id, username, STATUS_MAP[status], statusText]); + if (status) { + Streamer.notifyLogged.emit('user-status', [_id, username, STATUS_MAP[status], statusText]); + } // User.emit(`${ STREAMER_EVENTS.USER_CHANGED }/${ _id }`, _id, user); // use this method }); @@ -156,7 +153,7 @@ export class DDPStreamer extends ServiceClass { const { action, user: { _id, ...user }, - } = msgpack.decode(payload); + } = payload; // User.emit(`${ STREAMER_EVENTS.USER_CHANGED }/${ _id }`, _id, user); if (isEmpty(user)) { @@ -196,7 +193,7 @@ export class DDPStreamer extends ServiceClass { this.onEvent('user.name', (payload): void => { const { user: { _id, name, username }, - } = msgpack.decode(payload); + } = payload; // User.emit(`${ STREAMER_EVENTS.USER_CHANGED }/${ _id }`, _id, user); Streamer.notifyLogged.emit('Users:NameChanged', { _id, name, username }); }); diff --git a/ee/server/services/Presence/Presence.ts b/ee/server/services/Presence/Presence.ts index 47f4eeddcf22c..80c942aae48f1 100755 --- a/ee/server/services/Presence/Presence.ts +++ b/ee/server/services/Presence/Presence.ts @@ -8,8 +8,6 @@ import { IPresence } from '../../../../server/sdk/types/IPresence'; import { USER_STATUS } from '../../../../definition/UserStatus'; import { IBrokerNode } from '../../../../server/sdk/types/IBroker'; -// import PromService from 'moleculer-prometheus'; - export class Presence extends ServiceClass implements IPresence { protected name = 'presence'; @@ -55,14 +53,3 @@ export class Presence extends ServiceClass implements IPresence { return updateUserPresence(uid); } } - -// const { PROMETHEUS_PORT = 9100 } = process.env; - -// export default { -// settings: { -// port: PROMETHEUS_PORT, -// $noVersionPrefix: true, -// }, -// mixins: PROMETHEUS_PORT !== 'false' ? [PromService] : [], -// }, -// }; diff --git a/ee/server/services/StreamHub/watchUsers.ts b/ee/server/services/StreamHub/watchUsers.ts index 5a8bcd34e040a..794841be7054b 100644 --- a/ee/server/services/StreamHub/watchUsers.ts +++ b/ee/server/services/StreamHub/watchUsers.ts @@ -1,12 +1,9 @@ import { ChangeEvent } from 'mongodb'; -import msgpack5 from 'msgpack5'; import { normalize } from './utils'; import { IUser } from '../../../../definition/IUser'; import { api } from '../../../../server/sdk/api'; -const msgpack = msgpack5(); - function nestStringProperties(obj: object): object { if (!obj) { return {}; @@ -47,7 +44,7 @@ export async function watchUsers(event: ChangeEvent): Promise { if (updatedFields) { if (updatedFields.status || updatedFields.statusText) { const { status, _id, username, statusText } = user; // remove username - api.broadcast('userpresence', msgpack.encode({ action: normalize[event.operationType], user: { status, _id, username, statusText } })); // remove username + api.broadcast('userpresence', { action: normalize[event.operationType], user: { status, _id, username, statusText } }); // remove username // RocketChat.Logger.info('User: userpresence', { status, _id, username, statusText }); } @@ -60,23 +57,20 @@ export async function watchUsers(event: ChangeEvent): Promise { username: username || user.username, }; - api.broadcast('user.name', msgpack.encode({ + api.broadcast('user.name', { action: normalize[event.operationType], user: nameChange, - })); + }); // RocketChat.Logger.info('User: user.name', nameChange); } } - api.broadcast( - 'user', - msgpack.encode({ - action: normalize[event.operationType], - user: { - ...event.documentKey, - ...updatedFields ? nestStringProperties(updatedFields) : {}, - }, - }), - ); + api.broadcast('user', { + action: normalize[event.operationType], + user: { + ...event.documentKey, + ...updatedFields ? nestStringProperties(updatedFields) : {}, + }, + }); // RocketChat.Logger.info('User record', user); // return Streamer[method]({ stream: STREAM_NAMES['room-messages'], eventName: message.rid, args: message }); // publishMessage(operationType, message); diff --git a/server/sdk/lib/Events.ts b/server/sdk/lib/Events.ts index 7e4adfa0e1bc8..68f6221eb725e 100644 --- a/server/sdk/lib/Events.ts +++ b/server/sdk/lib/Events.ts @@ -6,6 +6,7 @@ import { IRole } from '../../../definition/IRole'; import { IRoom } from '../../../definition/IRoom'; import { ISetting } from '../../../definition/ISetting'; import { ISubscription } from '../../../definition/ISubscription'; +import { IUser } from '../../../definition/IUser'; export type BufferList = ReturnType; @@ -16,9 +17,9 @@ export type EventSignatures = { 'room'(data: { action: string; room: Partial }): void; 'message'(data: { action: string; message: IMessage }): void; 'setting'(data: { action: string; setting: Partial }): void; - 'userpresence'(payload: BufferList): void; - 'user'(payload: BufferList): void; - 'user.name'(payload: BufferList): void; + 'userpresence'(data: { action: string; user: Partial }): void; + 'user'(data: { action: string; user: Partial }): void; + 'user.name'(data: { action: string; user: Partial }): void; 'role'(data: {type: 'changed' | 'removed' } & Partial): void; 'license.module'(data: {module: string; valid: boolean}): void; } From b4d45da7c733d402fcfd69c35d1ad87a0e13ae1b Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Sat, 19 Sep 2020 19:15:27 -0300 Subject: [PATCH 056/198] Add Grafana/Prometheus, use EJSON as Serializer and fix some things --- ee/server/broker.ts | 22 +- .../json-exports/moleculer-metrics.json | 1157 ++++ .../json-exports/rocketchat-metrics.json | 4725 +++++++++++++++++ .../dashboards/provider/prometheus.yml | 11 + .../provisioning/datasources/prometheus.yml | 9 + .../.config/prometheus/prometheus.yml | 27 + ee/server/services/.gitignore | 1 + ee/server/services/DDPStreamer/Streamer.ts | 3 +- ee/server/services/Dockerfile | 8 +- ee/server/services/docker-compose.yml | 44 +- 10 files changed, 5995 insertions(+), 12 deletions(-) create mode 100644 ee/server/services/.config/grafana/provisioning/dashboards/json-exports/moleculer-metrics.json create mode 100644 ee/server/services/.config/grafana/provisioning/dashboards/json-exports/rocketchat-metrics.json create mode 100644 ee/server/services/.config/grafana/provisioning/dashboards/provider/prometheus.yml create mode 100644 ee/server/services/.config/grafana/provisioning/datasources/prometheus.yml create mode 100644 ee/server/services/.config/prometheus/prometheus.yml diff --git a/ee/server/broker.ts b/ee/server/broker.ts index d7b0d0107c43f..788cf83db6e54 100644 --- a/ee/server/broker.ts +++ b/ee/server/broker.ts @@ -1,4 +1,5 @@ -import { ServiceBroker, Context, ServiceSchema } from 'moleculer'; +import { ServiceBroker, Context, ServiceSchema, Serializers } from 'moleculer'; +import EJSON from 'ejson'; import { asyncLocalStorage, License } from '../../server/sdk'; import { api } from '../../server/sdk/api'; @@ -132,10 +133,23 @@ class NetworkBroker implements IBroker { } } +const Base = Serializers.Base as unknown as new () => {}; + +class EJSONSerializer extends Base { + serialize(obj: {}): Buffer { + return Buffer.from(EJSON.stringify(obj)); + } + + deserialize(buf: Buffer): any { + return EJSON.parse(buf.toString()); + } +} + const { TRANSPORTER = 'TCP', CACHE = 'Memory', - SERIALIZER = 'MsgPack', + // SERIALIZER = 'MsgPack', + SERIALIZER = 'EJSON', MOLECULER_LOG_LEVEL = 'error', BALANCE_STRATEGY = 'RoundRobin', BALANCE_PREFER_LOCAL = 'false', @@ -151,7 +165,7 @@ const { BULKHEAD_CONCURRENCY = '10', BULKHEAD_MAX_QUEUE_SIZE = '10000', MS_METRICS = 'false', - MS_METRICS_PORT = '3030', + MS_METRICS_PORT = '9458', } = process.env; const network = new ServiceBroker({ @@ -166,7 +180,7 @@ const network = new ServiceBroker({ }], }, cacher: CACHE, - serializer: SERIALIZER, + serializer: SERIALIZER === 'EJSON' ? new EJSONSerializer() : SERIALIZER, logLevel: MOLECULER_LOG_LEVEL as any, // logLevel: { // // "TRACING": "trace", diff --git a/ee/server/services/.config/grafana/provisioning/dashboards/json-exports/moleculer-metrics.json b/ee/server/services/.config/grafana/provisioning/dashboards/json-exports/moleculer-metrics.json new file mode 100644 index 0000000000000..536bf895a6eb3 --- /dev/null +++ b/ee/server/services/.config/grafana/provisioning/dashboards/json-exports/moleculer-metrics.json @@ -0,0 +1,1157 @@ +{ + "__requires": [ + { + "type": "panel", + "id": "alertlist", + "name": "Alert List", + "version": "5.0.0" + }, + { + "type": "grafana", + "id": "grafana", + "name": "Grafana", + "version": "5.0.3" + }, + { + "type": "panel", + "id": "graph", + "name": "Graph", + "version": "5.0.0" + }, + { + "type": "datasource", + "id": "prometheus", + "name": "Prometheus", + "version": "5.0.0" + }, + { + "type": "panel", + "id": "singlestat", + "name": "Singlestat", + "version": "5.0.0" + } + ], + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "gnetId": null, + "graphTooltip": 1, + "id": null, + "links": [], + "panels": [ + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": false, + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "datasource": "Prometheus", + "format": "none", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 0 + }, + "hideTimeOverride": true, + "id": 9, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "moleculer_nodes_total", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "refId": "A", + "step": 2 + } + ], + "thresholds": "", + "timeFrom": "1s", + "title": "Nodes", + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "avg" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": false, + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "datasource": "Prometheus", + "format": "none", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 6, + "y": 0 + }, + "hideTimeOverride": true, + "id": 10, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "moleculer_services_total", + "format": "time_series", + "intervalFactor": 2, + "refId": "A", + "step": 2 + } + ], + "thresholds": "", + "timeFrom": "1s", + "title": "Services", + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "avg" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": false, + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "datasource": "Prometheus", + "format": "none", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 12, + "y": 0 + }, + "hideTimeOverride": true, + "id": 11, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "moleculer_actions_total", + "format": "time_series", + "intervalFactor": 2, + "refId": "A", + "step": 2 + } + ], + "thresholds": "", + "timeFrom": "1s", + "title": "Actions", + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "avg" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": false, + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "datasource": "Prometheus", + "format": "none", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 18, + "y": 0 + }, + "hideTimeOverride": true, + "id": 12, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "moleculer_events_total", + "format": "time_series", + "intervalFactor": 2, + "refId": "A", + "step": 2 + } + ], + "thresholds": "", + "timeFrom": "1s", + "title": "Events", + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "avg" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": false, + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "datasource": "Prometheus", + "decimals": null, + "format": "short", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 3 + }, + "hideTimeOverride": true, + "id": 2, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "sum(moleculer_req_total)", + "format": "time_series", + "hide": false, + "intervalFactor": 2, + "refId": "A", + "step": 2 + } + ], + "thresholds": "", + "timeFrom": "1s", + "title": "Total calls", + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "total" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": false, + "colors": [ + "rgba(247, 55, 55, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "datasource": "Prometheus", + "format": "short", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 6, + "y": 3 + }, + "hideTimeOverride": true, + "id": 3, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "sum(moleculer_req_errors_total)", + "format": "time_series", + "intervalFactor": 2, + "refId": "A", + "step": 2 + } + ], + "thresholds": "", + "timeFrom": "1s", + "title": "Total errors", + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "avg" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": true, + "colors": [ + "rgba(50, 172, 45, 0.97)", + "rgba(237, 129, 40, 0.89)", + "rgba(245, 54, 54, 0.9)" + ], + "datasource": "Prometheus", + "format": "ms", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 12, + "y": 3 + }, + "hideTimeOverride": true, + "id": 4, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "avg(irate(moleculer_req_duration_ms_sum[1m]))", + "format": "time_series", + "intervalFactor": 2, + "refId": "A", + "step": 2 + } + ], + "thresholds": "50, 100", + "timeFrom": "10s", + "title": "Response time", + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "avg" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": false, + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "datasource": "Prometheus", + "format": "none", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 18, + "y": 3 + }, + "hideTimeOverride": true, + "id": 5, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "sum(irate(moleculer_req_total[1m]))", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "refId": "A", + "step": 2 + } + ], + "thresholds": "", + "timeFrom": "10s", + "title": "Rps", + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "avg" + }, + { + "alert": { + "conditions": [ + { + "evaluator": { + "params": [ + 200 + ], + "type": "gt" + }, + "operator": { + "type": "and" + }, + "query": { + "params": [ + "A", + "1m", + "now" + ] + }, + "reducer": { + "params": [], + "type": "avg" + }, + "type": "query" + } + ], + "executionErrorState": "alerting", + "frequency": "10s", + "handler": 1, + "name": "Response time ( >200ms )", + "noDataState": "no_data", + "notifications": [] + }, + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 1, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 6 + }, + "id": 1, + "legend": { + "alignAsTable": false, + "avg": false, + "current": false, + "hideEmpty": false, + "hideZero": false, + "max": false, + "min": false, + "rightSide": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "avg(irate(moleculer_req_duration_ms_sum[1m]))", + "format": "time_series", + "hide": false, + "intervalFactor": 2, + "legendFormat": "Total", + "refId": "A", + "step": 2 + } + ], + "thresholds": [ + { + "colorMode": "critical", + "fill": true, + "line": true, + "op": "gt", + "value": 200 + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Response time", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "ms", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ] + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 1, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 6 + }, + "id": 6, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(irate(moleculer_req_total[1m]))", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "Calls", + "refId": "A", + "step": 2 + }, + { + "expr": "sum(irate(moleculer_req_errors_total[1m]))", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "Errors", + "refId": "B", + "step": 2 + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "Action calls", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ] + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 1, + "gridPos": { + "h": 7, + "w": 24, + "x": 0, + "y": 14 + }, + "id": 7, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum by (errorName) (irate(moleculer_req_errors_total[1m]))", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "{{errorName}}", + "refId": "A", + "step": 2 + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "Error types", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ] + }, + { + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 21 + }, + "id": 8, + "limit": 10, + "links": [], + "onlyAlertsOnDashboard": true, + "show": "current", + "sortOrder": 1, + "stateFilter": [], + "title": "Alerts", + "type": "alertlist" + }, + { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 1, + "gridPos": { + "h": 7, + "w": 12, + "x": 12, + "y": 21 + }, + "id": 13, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": false, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "moleculer_nodes_total", + "format": "time_series", + "interval": "10s", + "intervalFactor": 2, + "refId": "A", + "step": 20 + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "Available nodes", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ] + } + ], + "refresh": "5s", + "schemaVersion": 16, + "style": "dark", + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-15m", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, + "timezone": "", + "title": "Moleculer Prometheus demo", + "uid": "xtcSMfkmz", + "version": 4 +} diff --git a/ee/server/services/.config/grafana/provisioning/dashboards/json-exports/rocketchat-metrics.json b/ee/server/services/.config/grafana/provisioning/dashboards/json-exports/rocketchat-metrics.json new file mode 100644 index 0000000000000..4f1dfba53fbdd --- /dev/null +++ b/ee/server/services/.config/grafana/provisioning/dashboards/json-exports/rocketchat-metrics.json @@ -0,0 +1,4725 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "limit": 100, + "matchAny": true, + "name": "Annotations & Alerts", + "showIn": 0, + "tags": [ + "metrics", + "microservices" + ], + "type": "tags" + } + ] + }, + "editable": true, + "gnetId": null, + "graphTooltip": 0, + "id": 10, + "iteration": 1587383367925, + "links": [], + "panels": [ + { + "collapsed": false, + "datasource": null, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 43, + "panels": [], + "title": "Totals", + "type": "row" + }, + { + "cacheTimeout": null, + "colorBackground": true, + "colorValue": false, + "colors": [ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "datasource": "Prometheus", + "format": "locale", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 3, + "w": 8, + "x": 0, + "y": 1 + }, + "id": 30, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "options": {}, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": true + }, + "tableColumn": "", + "targets": [ + { + "expr": "max(rocketchat_users_online{DeploymentName=\"$deployment\"})", + "format": "time_series", + "intervalFactor": 1, + "refId": "A" + } + ], + "thresholds": "10000000", + "title": "Users Online", + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "current" + }, + { + "cacheTimeout": null, + "colorBackground": true, + "colorValue": false, + "colors": [ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "datasource": "Prometheus", + "format": "locale", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 3, + "w": 8, + "x": 8, + "y": 1 + }, + "id": 31, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "options": {}, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": true + }, + "tableColumn": "", + "targets": [ + { + "expr": "max(rocketchat_users_away{DeploymentName=\"$deployment\"})", + "format": "time_series", + "intervalFactor": 1, + "refId": "A" + } + ], + "thresholds": "", + "title": "Users Away", + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "current" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": false, + "colors": [ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "datasource": "Prometheus", + "format": "locale", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 3, + "w": 8, + "x": 16, + "y": 1 + }, + "id": 32, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "options": {}, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": true + }, + "tableColumn": "", + "targets": [ + { + "expr": "sum(rocketchat_ddp_connected_users{DeploymentName=\"$deployment\"})", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "", + "refId": "A" + } + ], + "thresholds": "", + "title": "DDP Users", + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "current" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": false, + "colors": [ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "datasource": "Prometheus", + "format": "locale", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 3, + "w": 8, + "x": 0, + "y": 4 + }, + "id": 26, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "options": {}, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": true + }, + "tableColumn": "", + "targets": [ + { + "expr": "max(rocketchat_users_total{DeploymentName=\"$deployment\"})", + "format": "time_series", + "intervalFactor": 1, + "refId": "A" + } + ], + "thresholds": "", + "title": "Users", + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "current" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": false, + "colors": [ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "datasource": "Prometheus", + "format": "locale", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 3, + "w": 8, + "x": 8, + "y": 4 + }, + "id": 27, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "options": {}, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": true + }, + "tableColumn": "", + "targets": [ + { + "expr": "max(rocketchat_rooms_total{DeploymentName=\"$deployment\"})", + "format": "time_series", + "intervalFactor": 1, + "refId": "A" + } + ], + "thresholds": "", + "title": "Rooms", + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "current" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": false, + "colors": [ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "datasource": "Prometheus", + "format": "locale", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 3, + "w": 8, + "x": 16, + "y": 4 + }, + "id": 29, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "options": {}, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": true + }, + "tableColumn": "", + "targets": [ + { + "expr": "max(rocketchat_messages_total{DeploymentName=\"$deployment\"})", + "format": "time_series", + "intervalFactor": 1, + "refId": "A" + } + ], + "thresholds": "", + "title": "Messages", + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "current" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": false, + "colors": [ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "datasource": "Prometheus", + "format": "locale", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 3, + "w": 8, + "x": 0, + "y": 7 + }, + "id": 36, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "options": {}, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "max(rocketchat_users_total{DeploymentName=\"$deployment\"})", + "format": "time_series", + "intervalFactor": 1, + "refId": "A" + } + ], + "thresholds": "", + "title": "Users Diff", + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "diff" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": false, + "colors": [ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "datasource": "Prometheus", + "format": "locale", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 3, + "w": 8, + "x": 8, + "y": 7 + }, + "id": 37, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "options": {}, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "max(rocketchat_rooms_total{DeploymentName=\"$deployment\"})", + "format": "time_series", + "intervalFactor": 1, + "refId": "A" + } + ], + "thresholds": "", + "title": "Rooms Diff", + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "diff" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": false, + "colors": [ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "datasource": "Prometheus", + "format": "locale", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 3, + "w": 8, + "x": 16, + "y": 7 + }, + "id": 38, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "options": {}, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "max(rocketchat_messages_total{DeploymentName=\"$deployment\"})", + "format": "time_series", + "intervalFactor": 1, + "refId": "A" + } + ], + "thresholds": "", + "title": "Messages Diff", + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "diff" + }, + { + "collapsed": false, + "datasource": null, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 10 + }, + "id": 69, + "panels": [], + "title": "Metrics", + "type": "row" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 11 + }, + "hiddenSeries": false, + "id": 71, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "targets": [ + { + "expr": "increase(rocketchat_metrics_requests{DeploymentName=\"$deployment\"}[5m])", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "{{instance}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Metrics Requests", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 12, + "x": 12, + "y": 11 + }, + "hiddenSeries": false, + "id": 72, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rocketchat_metrics_size{DeploymentName=\"$deployment\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "{{instance}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Metrics Size", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "decbytes", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "collapsed": true, + "datasource": null, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 18 + }, + "id": 23, + "panels": [ + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 19 + }, + "hiddenSeries": false, + "id": 1, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "nodejs_active_handles_total{DeploymentName=\"$deployment\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "{{instance}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Active Handles", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 12, + "x": 12, + "y": 19 + }, + "hiddenSeries": false, + "id": 3, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "nodejs_active_requests_total{DeploymentName=\"$deployment\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "{{instance}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Active Requests", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 26 + }, + "hiddenSeries": false, + "id": 4, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "nodejs_eventloop_lag_seconds{DeploymentName=\"$deployment\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "{{instance}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Event loop lag", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 12, + "x": 12, + "y": 26 + }, + "hiddenSeries": false, + "id": 5, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum by (space) (nodejs_heap_size_used_bytes{DeploymentName=\"$deployment\"})", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "{{space}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Heap used", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "decbytes", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 24, + "x": 0, + "y": 33 + }, + "hiddenSeries": false, + "id": 6, + "legend": { + "alignAsTable": true, + "avg": false, + "current": true, + "max": false, + "min": false, + "rightSide": true, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "nodejs_heap_size_used_bytes{DeploymentName=\"$deployment\"}", + "format": "time_series", + "hide": false, + "intervalFactor": 2, + "legendFormat": "{{instance}} [{{space}}]", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Per pod heap", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "decbytes", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 40 + }, + "hiddenSeries": false, + "id": 66, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(increase(nodejs_gc_pause_seconds_total{DeploymentName=\"$deployment\"}[5m])) by (gctype)", + "format": "time_series", + "hide": false, + "intervalFactor": 2, + "legendFormat": "{{gctype}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "GC - Seconds Running", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "s", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 12, + "x": 12, + "y": 40 + }, + "hiddenSeries": false, + "id": 67, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": false, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "targets": [ + { + "expr": "sum(increase(nodejs_gc_reclaimed_bytes_total{DeploymentName=\"$deployment\"}[5m])) by (instance)", + "format": "time_series", + "hide": false, + "intervalFactor": 2, + "legendFormat": "{{instance}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "GC - Bytes Freed", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "decbytes", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "title": "NodeJS", + "type": "row" + }, + { + "collapsed": true, + "datasource": null, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 19 + }, + "id": 63, + "panels": [ + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 6, + "w": 12, + "x": 0, + "y": 48 + }, + "hiddenSeries": false, + "id": 58, + "legend": { + "alignAsTable": true, + "avg": true, + "current": false, + "max": true, + "min": false, + "rightSide": true, + "show": true, + "sort": "avg", + "sortDesc": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(increase(rocketchat_ddp_rate_limit_exceeded{DeploymentName=\"$deployment\", user_id!=\"null\"}[5m])) by (limit_name)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "{{limit_name}} (not null)", + "refId": "A" + }, + { + "expr": "sum(increase(rocketchat_ddp_rate_limit_exceeded{DeploymentName=\"$deployment\", user_id=\"null\"}[5m])) by (limit_name)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "{{limit_name}} (null)", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Limit by type", + "tooltip": { + "shared": true, + "sort": 2, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 6, + "w": 12, + "x": 12, + "y": 48 + }, + "hiddenSeries": false, + "id": 59, + "legend": { + "alignAsTable": true, + "avg": true, + "current": false, + "max": true, + "min": false, + "rightSide": true, + "show": true, + "sort": "avg", + "sortDesc": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(increase(rocketchat_ddp_rate_limit_exceeded{DeploymentName=\"$deployment\", user_id!=\"null\"}[5m])) by (type,name)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "{{type}}: {{name}} (not null)", + "refId": "A" + }, + { + "expr": "sum(increase(rocketchat_ddp_rate_limit_exceeded{DeploymentName=\"$deployment\", user_id=\"null\"}[5m])) by (type,name)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "{{type}}: {{name}} (null)", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Limit by method", + "tooltip": { + "shared": true, + "sort": 2, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 54 + }, + "hiddenSeries": false, + "id": 61, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": false, + "rightSide": true, + "show": true, + "sort": "avg", + "sortDesc": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(increase(rocketchat_ddp_rate_limit_exceeded{DeploymentName=\"$deployment\"}[5m])) by (user_id)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "{{user_id}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Limit by userId", + "tooltip": { + "shared": true, + "sort": 2, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 54 + }, + "hiddenSeries": false, + "id": 60, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": false, + "rightSide": true, + "show": true, + "sort": "avg", + "sortDesc": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(increase(rocketchat_ddp_rate_limit_exceeded{DeploymentName=\"$deployment\", user_id!=\"null\"}[5m])) by (connection_id)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "{{connection_id}} (not null)", + "refId": "A" + }, + { + "expr": "sum(increase(rocketchat_ddp_rate_limit_exceeded{DeploymentName=\"$deployment\", user_id=\"null\"}[5m])) by (connection_id)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "{{connection_id}} (null)", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Limit by connectionId", + "tooltip": { + "shared": true, + "sort": 2, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "title": "DDP Rate Limiter", + "type": "row" + }, + { + "collapsed": true, + "datasource": null, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 20 + }, + "id": 21, + "panels": [ + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 24, + "x": 0, + "y": 21 + }, + "hiddenSeries": false, + "id": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "targets": [ + { + "expr": "increase(rocketchat_message_sent{DeploymentName=\"$deployment\"}[5m])", + "format": "time_series", + "intervalFactor": 10, + "legendFormat": "{{instance}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Messages Sent (5m)", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 24, + "x": 0, + "y": 28 + }, + "hiddenSeries": false, + "id": 35, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "targets": [ + { + "expr": "max(rocketchat_messages_total{DeploymentName=\"$deployment\"})", + "format": "time_series", + "intervalFactor": 10, + "legendFormat": "{{instance}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Messages Sent Total", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "decimals": 0, + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 9, + "w": 24, + "x": 0, + "y": 35 + }, + "hiddenSeries": false, + "id": 10, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": false, + "rightSide": true, + "show": true, + "sort": "avg", + "sortDesc": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "alias": "Sessions", + "yaxis": 1 + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "avg(rocketchat_users_online{DeploymentName=\"$deployment\"})", + "format": "time_series", + "hide": false, + "intervalFactor": 1, + "legendFormat": "Online", + "refId": "A" + }, + { + "expr": "avg(rocketchat_users_away{DeploymentName=\"$deployment\"})", + "format": "time_series", + "hide": false, + "intervalFactor": 1, + "legendFormat": "Away", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Users Presence", + "tooltip": { + "shared": true, + "sort": 2, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 9, + "w": 24, + "x": 0, + "y": 44 + }, + "hiddenSeries": false, + "id": 64, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": false, + "rightSide": true, + "show": true, + "sort": "avg", + "sortDesc": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "alias": "Sessions", + "yaxis": 1 + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(rocketchat_ddp_connected_users{DeploymentName=\"$deployment\"})", + "format": "time_series", + "hide": false, + "intervalFactor": 1, + "legendFormat": "Connected Users", + "refId": "A" + }, + { + "expr": "sum(rocketchat_ddp_sessions_count{DeploymentName=\"$deployment\"})", + "format": "time_series", + "hide": false, + "intervalFactor": 1, + "legendFormat": "All Sessions", + "refId": "B" + }, + { + "expr": "sum(rocketchat_ddp_sessions_auth{DeploymentName=\"$deployment\"})", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "Authenticated Sessions ", + "refId": "C" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Users & Sessions", + "tooltip": { + "shared": true, + "sort": 2, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 9, + "w": 24, + "x": 0, + "y": 53 + }, + "hiddenSeries": false, + "id": 56, + "legend": { + "alignAsTable": true, + "avg": false, + "current": true, + "hideEmpty": true, + "hideZero": true, + "max": true, + "min": false, + "rightSide": true, + "show": true, + "sort": "current", + "sortDesc": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "targets": [ + { + "expr": "sum(increase(rocketchat_meteor_stream{DeploymentName=\"$deployment\"}[5m])) by (stream,eventName)", + "format": "time_series", + "hide": true, + "intervalFactor": 1, + "legendFormat": "{{stream}}/{{eventName}}", + "refId": "A" + }, + { + "expr": "sum(increase(rocketchat_meteor_streamer_broadcast{DeploymentName=\"$deployment\"}[5m])) by (stream,eventName)", + "format": "time_series", + "hide": true, + "intervalFactor": 1, + "legendFormat": "broadcast ({{stream}}/{{eventName}})", + "refId": "B" + }, + { + "expr": "sum(increase(rocketchat_oplog{DeploymentName=\"$deployment\"}[5m])) by (collection,op)", + "format": "time_series", + "hide": false, + "intervalFactor": 1, + "legendFormat": "{{collection}}/{{op}}", + "refId": "C" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Oplog", + "tooltip": { + "shared": true, + "sort": 2, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 9, + "w": 24, + "x": 0, + "y": 62 + }, + "hiddenSeries": false, + "id": 65, + "legend": { + "alignAsTable": true, + "avg": false, + "current": true, + "hideEmpty": true, + "hideZero": true, + "max": true, + "min": false, + "rightSide": true, + "show": true, + "sort": "current", + "sortDesc": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 1, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rocketchat_oplog_queue{DeploymentName=\"$deployment\"}", + "format": "time_series", + "hide": false, + "intervalFactor": 1, + "legendFormat": "{{instance}}", + "refId": "C" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Oplog Queue", + "tooltip": { + "shared": true, + "sort": 2, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 9, + "w": 24, + "x": 0, + "y": 71 + }, + "hiddenSeries": false, + "id": 73, + "legend": { + "alignAsTable": true, + "avg": false, + "current": true, + "hideEmpty": true, + "hideZero": true, + "max": true, + "min": false, + "rightSide": true, + "show": true, + "sort": "current", + "sortDesc": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 1, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rocketchat_push_queue{DeploymentName=\"$deployment\"}", + "format": "time_series", + "hide": false, + "intervalFactor": 1, + "legendFormat": "{{instance}}", + "refId": "C" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Push Queue", + "tooltip": { + "shared": true, + "sort": 2, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 9, + "w": 24, + "x": 0, + "y": 80 + }, + "hiddenSeries": false, + "id": 74, + "legend": { + "alignAsTable": true, + "avg": false, + "current": true, + "hideEmpty": true, + "hideZero": true, + "max": true, + "min": false, + "rightSide": true, + "show": true, + "sort": "current", + "sortDesc": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 1, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(rocketchat_meteor_facts{DeploymentName=\"$deployment\"}) by (pkg, fact)", + "format": "time_series", + "hide": false, + "intervalFactor": 1, + "legendFormat": "{{pkg}} {{fact}}", + "refId": "C" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Meteor Facts", + "tooltip": { + "shared": true, + "sort": 2, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 89 + }, + "hiddenSeries": false, + "id": 11, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": false, + "rightSide": true, + "show": true, + "sort": "avg", + "sortDesc": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "alias": "Sessions", + "yaxis": 1 + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "max(rocketchat_users_total{DeploymentName=\"$deployment\"})", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "Users", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Total of Users", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "decimals": 0, + "format": "locale", + "label": "", + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 89 + }, + "hiddenSeries": false, + "id": 12, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": false, + "rightSide": true, + "show": true, + "sort": "avg", + "sortDesc": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "alias": "Sessions", + "yaxis": 1 + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "max(rocketchat_rooms_total{DeploymentName=\"$deployment\"})", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "Rooms", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Total of Rooms", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "decimals": 0, + "format": "locale", + "label": "", + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 9, + "w": 24, + "x": 0, + "y": 97 + }, + "hiddenSeries": false, + "id": 39, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": false, + "rightSide": true, + "show": true, + "sort": "avg", + "sortDesc": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "alias": "Sessions", + "yaxis": 1 + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(increase(rocketchat_notification_sent{DeploymentName=\"$deployment\"}[5m])) by (notification_type)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "{{notification_type}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Notifications / Minute", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "decimals": 0, + "format": "locale", + "label": "", + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "title": "RocketChat Data", + "type": "row" + }, + { + "collapsed": true, + "datasource": null, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 21 + }, + "id": 45, + "panels": [ + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "decimals": null, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 24, + "x": 0, + "y": 50 + }, + "hiddenSeries": false, + "id": 40, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "hideEmpty": true, + "hideZero": true, + "max": true, + "min": false, + "rightSide": true, + "show": true, + "sideWidth": null, + "sort": "avg", + "sortDesc": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(rocketchat_meteor_methods{DeploymentName=\"$deployment\"}) by (method) * sum(increase(rocketchat_meteor_methods_count{DeploymentName=\"$deployment\"}[5m])) by (method)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "{{method}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Meteor Methods Total Time", + "tooltip": { + "shared": true, + "sort": 2, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "decimals": null, + "format": "ms", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 57 + }, + "hiddenSeries": false, + "id": 8, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": false, + "rightSide": true, + "show": true, + "sideWidth": null, + "sort": "avg", + "sortDesc": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(rocketchat_meteor_methods{DeploymentName=\"$deployment\"}) by (method)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "{{method}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Meteor Methods Time", + "tooltip": { + "shared": true, + "sort": 2, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "ms", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 57 + }, + "hiddenSeries": false, + "id": 14, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": false, + "rightSide": true, + "show": true, + "sort": "avg", + "sortDesc": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(increase(rocketchat_meteor_methods_count{DeploymentName=\"$deployment\"}[5m])) by (method)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "{{method}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Meteor Methods Calls / Minute", + "tooltip": { + "shared": true, + "sort": 2, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "title": "Methods", + "type": "row" + }, + { + "collapsed": true, + "datasource": null, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 22 + }, + "id": 47, + "panels": [ + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 51 + }, + "hiddenSeries": false, + "id": 41, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "hideEmpty": true, + "hideZero": true, + "max": true, + "min": false, + "rightSide": true, + "show": true, + "sort": "avg", + "sortDesc": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(rocketchat_meteor_subscriptions{DeploymentName=\"$deployment\"}) by (subscription) * sum(increase(rocketchat_meteor_subscriptions_count{DeploymentName=\"$deployment\"}[5m])) by (subscription)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "{{subscription}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Meteor Subscription Total Time", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "ms", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 59 + }, + "hiddenSeries": false, + "id": 9, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": false, + "rightSide": true, + "show": true, + "sort": "avg", + "sortDesc": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(rocketchat_meteor_subscriptions{DeploymentName=\"$deployment\"}) by (subscription)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "{{subscription}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Meteor Subscription Times", + "tooltip": { + "shared": true, + "sort": 2, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "ms", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 59 + }, + "hiddenSeries": false, + "id": 15, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": false, + "rightSide": true, + "show": true, + "sort": "avg", + "sortDesc": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(increase(rocketchat_meteor_subscriptions_count{DeploymentName=\"$deployment\"}[5m])) by (subscription)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "{{subscription}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Meteor Subscription Calls / Minute", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "title": "Subscriptions", + "type": "row" + }, + { + "collapsed": true, + "datasource": null, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 23 + }, + "id": 49, + "panels": [ + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 52 + }, + "hiddenSeries": false, + "id": 53, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "hideEmpty": true, + "hideZero": true, + "max": true, + "min": false, + "rightSide": true, + "show": true, + "sort": "avg", + "sortDesc": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(rocketchat_callbacks{DeploymentName=\"$deployment\"}) by (callback) * sum(increase(rocketchat_callbacks_count{DeploymentName=\"$deployment\"}[5m])) by (callback)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "{{callback}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Callbacks Total Times", + "tooltip": { + "shared": true, + "sort": 2, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "ms", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 60 + }, + "hiddenSeries": false, + "id": 16, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": false, + "rightSide": true, + "show": true, + "sort": "avg", + "sortDesc": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(irate(rocketchat_callbacks{DeploymentName=\"$deployment\"}[5m])) by (callback)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "{{callback}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Callbacks Times", + "tooltip": { + "shared": true, + "sort": 2, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "ms", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 60 + }, + "hiddenSeries": false, + "id": 17, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": false, + "rightSide": true, + "show": true, + "sort": "avg", + "sortDesc": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(increase(rocketchat_callbacks_count{DeploymentName=\"$deployment\"}[5m])) by (callback)", + "format": "time_series", + "hide": false, + "intervalFactor": 1, + "legendFormat": "{{callback}}", + "refId": "A" + }, + { + "expr": "sum(rocketchat_callbacks_count{DeploymentName=\"$deployment\"}) by (hook)", + "format": "time_series", + "hide": true, + "intervalFactor": 1, + "legendFormat": "{{hook}}", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Callbacks Calls / Minute", + "tooltip": { + "shared": true, + "sort": 2, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "none", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 68 + }, + "hiddenSeries": false, + "id": 54, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "hideEmpty": false, + "hideZero": false, + "max": true, + "min": false, + "rightSide": true, + "show": true, + "sort": "avg", + "sortDesc": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(rocketchat_hooks{DeploymentName=\"$deployment\"}) by (hook) * sum(increase(rocketchat_hooks_count{DeploymentName=\"$deployment\"}[5m])) by (hook)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "{{hook}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Hooks Total Times", + "tooltip": { + "shared": true, + "sort": 2, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "ms", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 76 + }, + "hiddenSeries": false, + "id": 34, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": false, + "rightSide": true, + "show": true, + "sort": "avg", + "sortDesc": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(irate(rocketchat_hooks{DeploymentName=\"$deployment\"}[5m])) by (hook)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "{{hook}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Hooks Times", + "tooltip": { + "shared": true, + "sort": 2, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "ms", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 76 + }, + "hiddenSeries": false, + "id": 33, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": false, + "rightSide": true, + "show": true, + "sort": "avg", + "sortDesc": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(increase(rocketchat_hooks_count{DeploymentName=\"$deployment\"}[5m])) by (hook)", + "format": "time_series", + "hide": false, + "intervalFactor": 1, + "legendFormat": "{{hook}}", + "refId": "A" + }, + { + "expr": "sum(rocketchat_callbacks_count{DeploymentName=\"$deployment\"}) by (hook)", + "format": "time_series", + "hide": true, + "intervalFactor": 1, + "legendFormat": "{{hook}}", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Hooks Calls / Minute", + "tooltip": { + "shared": true, + "sort": 2, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "none", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "title": "Callbacks & Hooks", + "type": "row" + }, + { + "collapsed": true, + "datasource": null, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 24 + }, + "id": 51, + "panels": [ + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 24, + "x": 0, + "y": 25 + }, + "hiddenSeries": false, + "id": 52, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "hideEmpty": true, + "hideZero": true, + "max": true, + "min": false, + "rightSide": true, + "show": true, + "sort": "avg", + "sortDesc": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(rocketchat_rest_api{DeploymentName=\"$deployment\"}) by (entrypoint) * sum(increase(rocketchat_rest_api_count{DeploymentName=\"$deployment\"}[5m])) by (entrypoint)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "{{entrypoint}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "REST API Total Times", + "tooltip": { + "shared": true, + "sort": 2, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "ms", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 32 + }, + "hiddenSeries": false, + "id": 18, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": false, + "rightSide": false, + "show": true, + "sideWidth": null, + "sort": "max", + "sortDesc": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(irate(rocketchat_rest_api{DeploymentName=\"$deployment\"}[5m])) by (entrypoint)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "{{entrypoint}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "REST API Times", + "tooltip": { + "shared": true, + "sort": 2, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "s", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 32 + }, + "hiddenSeries": false, + "id": 19, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": false, + "rightSide": false, + "show": true, + "sideWidth": null, + "sort": "avg", + "sortDesc": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(increase(rocketchat_rest_api_count{DeploymentName=\"$deployment\"}[5m])) by (entrypoint)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "{{entrypoint}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "REST API Calls / Minute", + "tooltip": { + "shared": true, + "sort": 2, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "title": "REST API", + "type": "row" + } + ], + "refresh": "1m", + "schemaVersion": 22, + "style": "dark", + "tags": [], + "templating": { + "list": [ + { + "allValue": null, + "current": { + "isNone": true, + "selected": false, + "text": "None", + "value": "" + }, + "datasource": "Prometheus", + "definition": "", + "hide": 2, + "includeAll": false, + "label": null, + "multi": false, + "name": "deployment", + "options": [], + "query": "label_values(nodejs_active_handles_total,DeploymentName)", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "tagValuesQuery": "", + "tags": [], + "tagsQuery": "", + "type": "query", + "useTags": false + } + ] + }, + "time": { + "from": "now-3h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, + "timezone": "", + "title": "Rocket.Chat Metrics", + "uid": "Ee8-FY7ml", + "version": 3 +} diff --git a/ee/server/services/.config/grafana/provisioning/dashboards/provider/prometheus.yml b/ee/server/services/.config/grafana/provisioning/dashboards/provider/prometheus.yml new file mode 100644 index 0000000000000..04139c4342deb --- /dev/null +++ b/ee/server/services/.config/grafana/provisioning/dashboards/provider/prometheus.yml @@ -0,0 +1,11 @@ +apiVersion: 1 + +providers: +- name: 'Prometheus' + orgId: 1 + folder: '' + type: file + disableDeletion: false + editable: true + options: + path: /var/lib/grafana/dashboards/ diff --git a/ee/server/services/.config/grafana/provisioning/datasources/prometheus.yml b/ee/server/services/.config/grafana/provisioning/datasources/prometheus.yml new file mode 100644 index 0000000000000..e4e3850d84e8e --- /dev/null +++ b/ee/server/services/.config/grafana/provisioning/datasources/prometheus.yml @@ -0,0 +1,9 @@ +apiVersion: 1 + +datasources: +- name: Prometheus + type: prometheus + access: proxy + url: http://prometheus:9090 + version: 1 + editable: false diff --git a/ee/server/services/.config/prometheus/prometheus.yml b/ee/server/services/.config/prometheus/prometheus.yml new file mode 100644 index 0000000000000..7e72b2c5c41d0 --- /dev/null +++ b/ee/server/services/.config/prometheus/prometheus.yml @@ -0,0 +1,27 @@ +scrape_configs: +# if you just have single or multiple, but static instances - you +# can use the static configuration below. +# - job_name: rocketchat_static +# static_configs: +# - targets: +# - authorization-service:3030 +# - account-service:3030 +# - presence-service:3030 +# - ddp-streamer-service:3030 + # - stream-hub-service:3030 + +# If you use a Docker-based setup make sure to use the DNS +# discovery to always include all available application instances +# of Rocket.Chat +- job_name: rocketchat_docker + scrape_interval: 5s + dns_sd_configs: + - names: [ + "authorization-service", + "account-service", + "presence-service", + "ddp-streamer-service", + "stream-hub-service:", + ] + type: A + port: 9458 diff --git a/ee/server/services/.gitignore b/ee/server/services/.gitignore index 1521c8b7652b1..cd5e298d48f12 100644 --- a/ee/server/services/.gitignore +++ b/ee/server/services/.gitignore @@ -1 +1,2 @@ dist +.config/data diff --git a/ee/server/services/DDPStreamer/Streamer.ts b/ee/server/services/DDPStreamer/Streamer.ts index 3e8d2b5c7861f..c108164f3c127 100644 --- a/ee/server/services/DDPStreamer/Streamer.ts +++ b/ee/server/services/DDPStreamer/Streamer.ts @@ -208,7 +208,8 @@ export class Stream extends EventEmitter { } async initMethod(publication: Publication): Promise { - const { isWriteAllowed, name, subscriptionName } = this; + const { name, subscriptionName } = this; + const isWriteAllowed = this.isWriteAllowed.bind(this); const method = { async [this.subscriptionName](this: Client, eventName: string, ...args: any[]): Promise { diff --git a/ee/server/services/Dockerfile b/ee/server/services/Dockerfile index 79a01326e6821..3ee00e074f7bb 100644 --- a/ee/server/services/Dockerfile +++ b/ee/server/services/Dockerfile @@ -5,8 +5,6 @@ WORKDIR /app RUN apt-get update \ && apt-get install -y build-essential git -# ADD ./moleculer.config.js . -ADD ./dist . ADD ./package.json . RUN npm install --production @@ -19,8 +17,14 @@ WORKDIR /app COPY --from=build /app . +ADD ./dist . + ENV NODE_ENV=production +ENV PORT=80 WORKDIR /app/ee/server/services/${SERVICE} +EXPOSE 9458 +EXPOSE 80 + CMD ["node", "service.js"] diff --git a/ee/server/services/docker-compose.yml b/ee/server/services/docker-compose.yml index be80520881556..f4bafd3cbc41e 100644 --- a/ee/server/services/docker-compose.yml +++ b/ee/server/services/docker-compose.yml @@ -7,10 +7,13 @@ services: args: SERVICE: Authorization # image: registry.rocket.chat/microservices_authorization-service:latest + ports: + - 9458:9458 environment: + - MS_METRICS=true - TRANSPORTER=${TRANSPORTER:-nats://nats:4222} - MONGO_URL=${MONGO_URL:-mongodb://host.docker.internal:27017/rocketchat} - # - MOLECULER_LOG_LEVEL=info + - MOLECULER_LOG_LEVEL=debug account-service: build: @@ -19,9 +22,10 @@ services: SERVICE: Account # image: registry.rocket.chat/microservices_accounts-service:latest environment: + - MS_METRICS=true - TRANSPORTER=${TRANSPORTER:-nats://nats:4222} - MONGO_URL=${MONGO_URL:-mongodb://host.docker.internal:27017/rocketchat} - # - MOLECULER_LOG_LEVEL=info + - MOLECULER_LOG_LEVEL=debug presence-service: build: @@ -30,20 +34,24 @@ services: SERVICE: Presence # image: registry.rocket.chat/microservices_presence-service:latest environment: + - MS_METRICS=true - TRANSPORTER=${TRANSPORTER:-nats://nats:4222} - MONGO_URL=${MONGO_URL:-mongodb://host.docker.internal:27017/rocketchat} - # - MOLECULER_LOG_LEVEL=info + - MOLECULER_LOG_LEVEL=debug ddp-streamer-service: build: context: . args: SERVICE: DDPStreamer + ports: + - 4000:80 # image: registry.rocket.chat/microservices_ddp-streamer:latest environment: + - MS_METRICS=true - TRANSPORTER=${TRANSPORTER:-nats://nats:4222} # - MONGO_URL=${MONGO_URL:-mongodb://host.docker.internal:27017/rocketchat} - # - MOLECULER_LOG_LEVEL=info + - MOLECULER_LOG_LEVEL=debug stream-hub-service: build: @@ -52,11 +60,37 @@ services: SERVICE: StreamHub # image: registry.rocket.chat/microservices_mongodb-stream-hub:latest environment: + - MS_METRICS=true - TRANSPORTER=${TRANSPORTER:-nats://nats:4222} - MONGO_URL=${MONGO_URL:-mongodb://host.docker.internal:27017/rocketchat} - # - MOLECULER_LOG_LEVEL=info + - MOLECULER_LOG_LEVEL=debug nats: image: nats ports: - "4222:4222" + + grafana: + image: grafana/grafana + restart: unless-stopped + ports: + - 5000:3000 + volumes: + - ./.config/grafana/provisioning/datasources:/etc/grafana/provisioning/datasources:ro + - ./.config/grafana/provisioning/dashboards/provider:/etc/grafana/provisioning/dashboards:ro + - ./.config/grafana/provisioning/dashboards/json-exports:/var/lib/grafana/dashboards:ro + depends_on: + - prometheus + + prometheus: + image: quay.io/prometheus/prometheus + restart: unless-stopped + ports: + - 9090:9090 + command: + - --config.file=/etc/prometheus/prometheus.yml + - '--storage.tsdb.retention.time=12w' + - '--storage.tsdb.path=/prometheus' + volumes: + - ./.config/data/prometheus:/prometheus + - ./.config/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro From a25fb84259e5ec3e46dc5c17046e61dcd9523278 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Sun, 20 Sep 2020 11:24:31 -0300 Subject: [PATCH 057/198] Implement Traefik --- .../services/.config/services/service.env | 4 + .../services/.config/traefik/traefik.toml | 31 +++++++ ee/server/services/README.md | 17 ++++ ee/server/services/docker-compose.yml | 88 ++++++++++++------- 4 files changed, 107 insertions(+), 33 deletions(-) create mode 100644 ee/server/services/.config/services/service.env create mode 100644 ee/server/services/.config/traefik/traefik.toml create mode 100644 ee/server/services/README.md diff --git a/ee/server/services/.config/services/service.env b/ee/server/services/.config/services/service.env new file mode 100644 index 0000000000000..a8b269afb9814 --- /dev/null +++ b/ee/server/services/.config/services/service.env @@ -0,0 +1,4 @@ +MS_METRICS=true +TRANSPORTER=nats://nats:4222 +MONGO_URL=mongodb://host.docker.internal:27017/rocketchat +MOLECULER_LOG_LEVEL=info diff --git a/ee/server/services/.config/traefik/traefik.toml b/ee/server/services/.config/traefik/traefik.toml new file mode 100644 index 0000000000000..b01f9ebe28c74 --- /dev/null +++ b/ee/server/services/.config/traefik/traefik.toml @@ -0,0 +1,31 @@ +debug = false + +logLevel = "ERROR" +defaultEntryPoints = ["http"] + +[entryPoints] + [entryPoints.http] + address = ":80" +# [entryPoints.http.redirect] +# entryPoint = "https" +# [entryPoints.https] +# address = ":443" +# [entryPoints.https.tls] +# [[entryPoints.https.tls.certificates]] +# certFile = "/etc/certs/certs-cert.pem" +# keyFile = "/etc/certs/certs-key.pem" + +[retry] + +[docker] +endpoint = "unix:///var/run/docker.sock" +watch = true +exposedByDefault = false + +# [acme] +# email = "YOUR_EMAIL@gmail.com" +# storage = "acme.json" +# entryPoint = "https" +# onHostRule = true +# [acme.httpChallenge] +# entryPoint = "http" diff --git a/ee/server/services/README.md b/ee/server/services/README.md new file mode 100644 index 0000000000000..c97ac6ed0d09c --- /dev/null +++ b/ee/server/services/README.md @@ -0,0 +1,17 @@ +# Rocket.Chat Micro-Services + +## Docker Compose + +The `docker-compose.yml` file contais a setup of the micro-services plus some extra tools: + +### Traefik +It's used to route the the domain on port 80 to the **DDP Streamer Service** and expose these addresses +* `traefik.localhost` as Traefik admin +* `prometheus.localhost` as Prometheus admin +* `grafana.localhost` as Grafana dashboards + +### Prometheus +Used to collect metrics from the micro-services + +### Grafana +Used to expose metrics from prometheus as dashboards \ No newline at end of file diff --git a/ee/server/services/docker-compose.yml b/ee/server/services/docker-compose.yml index f4bafd3cbc41e..453a76ddbaeaa 100644 --- a/ee/server/services/docker-compose.yml +++ b/ee/server/services/docker-compose.yml @@ -7,13 +7,9 @@ services: args: SERVICE: Authorization # image: registry.rocket.chat/microservices_authorization-service:latest - ports: - - 9458:9458 - environment: - - MS_METRICS=true - - TRANSPORTER=${TRANSPORTER:-nats://nats:4222} - - MONGO_URL=${MONGO_URL:-mongodb://host.docker.internal:27017/rocketchat} - - MOLECULER_LOG_LEVEL=debug + env_file: .config/services/service.env + depends_on: + - nats account-service: build: @@ -21,11 +17,9 @@ services: args: SERVICE: Account # image: registry.rocket.chat/microservices_accounts-service:latest - environment: - - MS_METRICS=true - - TRANSPORTER=${TRANSPORTER:-nats://nats:4222} - - MONGO_URL=${MONGO_URL:-mongodb://host.docker.internal:27017/rocketchat} - - MOLECULER_LOG_LEVEL=debug + env_file: .config/services/service.env + depends_on: + - nats presence-service: build: @@ -33,25 +27,25 @@ services: args: SERVICE: Presence # image: registry.rocket.chat/microservices_presence-service:latest - environment: - - MS_METRICS=true - - TRANSPORTER=${TRANSPORTER:-nats://nats:4222} - - MONGO_URL=${MONGO_URL:-mongodb://host.docker.internal:27017/rocketchat} - - MOLECULER_LOG_LEVEL=debug + env_file: .config/services/service.env + depends_on: + - nats ddp-streamer-service: build: context: . args: SERVICE: DDPStreamer - ports: - - 4000:80 # image: registry.rocket.chat/microservices_ddp-streamer:latest - environment: - - MS_METRICS=true - - TRANSPORTER=${TRANSPORTER:-nats://nats:4222} - # - MONGO_URL=${MONGO_URL:-mongodb://host.docker.internal:27017/rocketchat} - - MOLECULER_LOG_LEVEL=debug + env_file: .config/services/service.env + depends_on: + - nats + - traefik + labels: + - "traefik.enable=true" + - "traefik.backend=ddp-streamer-service" + - "traefik.port=80" + - "traefik.frontend.rule=Host:${HOST_NAME}" stream-hub-service: build: @@ -59,11 +53,9 @@ services: args: SERVICE: StreamHub # image: registry.rocket.chat/microservices_mongodb-stream-hub:latest - environment: - - MS_METRICS=true - - TRANSPORTER=${TRANSPORTER:-nats://nats:4222} - - MONGO_URL=${MONGO_URL:-mongodb://host.docker.internal:27017/rocketchat} - - MOLECULER_LOG_LEVEL=debug + env_file: .config/services/service.env + depends_on: + - nats nats: image: nats @@ -73,20 +65,22 @@ services: grafana: image: grafana/grafana restart: unless-stopped - ports: - - 5000:3000 volumes: - ./.config/grafana/provisioning/datasources:/etc/grafana/provisioning/datasources:ro - ./.config/grafana/provisioning/dashboards/provider:/etc/grafana/provisioning/dashboards:ro - ./.config/grafana/provisioning/dashboards/json-exports:/var/lib/grafana/dashboards:ro depends_on: - prometheus + - traefik + labels: + - "traefik.enable=true" + - "traefik.port=3000" + - "traefik.frontend.rule=Host:grafana.${HOST_NAME}" + # - 'traefik.frontend.redirect.entryPoint=https' prometheus: image: quay.io/prometheus/prometheus restart: unless-stopped - ports: - - 9090:9090 command: - --config.file=/etc/prometheus/prometheus.yml - '--storage.tsdb.retention.time=12w' @@ -94,3 +88,31 @@ services: volumes: - ./.config/data/prometheus:/prometheus - ./.config/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro + depends_on: + - traefik + ports: + - 9090:9090 + labels: + - "traefik.enable=true" + - "traefik.frontend.rule=Host:prometheus.${HOST_NAME}" + - "traefik.port=9090" + # - 'traefik.frontend.redirect.entryPoint=https' + + traefik: + image: traefik:alpine + container_name: traefik + command: + - --configFile=/traefik.toml + - --web + - --logLevel=INFO + ports: + - "80:80" + # - "443:443" + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - ./.config/traefik/traefik.toml:/traefik.toml + # - ./.config/traefik/acme.json:/acme.json + labels: + - "traefik.enable=true" + - "traefik.frontend.rule=Host:traefik.${HOST_NAME}" + - "traefik.port=8080" From 3b6b75a8e5f02b26a718161f2abc69bf8b643384 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Sun, 20 Sep 2020 11:41:40 -0300 Subject: [PATCH 058/198] Enable tracing via jagger --- ee/server/broker.ts | 26 ++++- .../services/.config/services/service.env | 1 + ee/server/services/docker-compose.yml | 29 +++++ ee/server/services/package-lock.json | 100 ++++++++++++++++++ ee/server/services/package.json | 11 +- 5 files changed, 158 insertions(+), 9 deletions(-) diff --git a/ee/server/broker.ts b/ee/server/broker.ts index 788cf83db6e54..33c4719131270 100644 --- a/ee/server/broker.ts +++ b/ee/server/broker.ts @@ -166,6 +166,7 @@ const { BULKHEAD_MAX_QUEUE_SIZE = '10000', MS_METRICS = 'false', MS_METRICS_PORT = '9458', + TRACING_ENABLED = 'false', } = process.env; const network = new ServiceBroker({ @@ -229,10 +230,27 @@ const network = new ServiceBroker({ maxQueueSize: parseInt(BULKHEAD_MAX_QUEUE_SIZE), }, - // tracing: { - // enabled: true, - // exporter: "EventLegacy" - // }, + tracing: { + enabled: TRACING_ENABLED === 'true', + exporter: { + type: 'Jaeger', + options: { + endpoint: null, + host: 'jaeger', + port: 6832, + sampler: { + // Sampler type. More info: https://www.jaegertracing.io/docs/1.14/sampling/#client-sampling-configuration + type: 'Const', + // Sampler specific options. + options: {}, + }, + // Additional options for `Jaeger.Tracer` + tracerOptions: {}, + // Default tags. They will be added into all span tags. + defaultTags: null, + }, + }, + }, }); api.setBroker(new NetworkBroker(network)); diff --git a/ee/server/services/.config/services/service.env b/ee/server/services/.config/services/service.env index a8b269afb9814..a8e4d902b28b6 100644 --- a/ee/server/services/.config/services/service.env +++ b/ee/server/services/.config/services/service.env @@ -1,4 +1,5 @@ MS_METRICS=true +TRACING_ENABLED=true TRANSPORTER=nats://nats:4222 MONGO_URL=mongodb://host.docker.internal:27017/rocketchat MOLECULER_LOG_LEVEL=info diff --git a/ee/server/services/docker-compose.yml b/ee/server/services/docker-compose.yml index 453a76ddbaeaa..09ea726e635c6 100644 --- a/ee/server/services/docker-compose.yml +++ b/ee/server/services/docker-compose.yml @@ -2,6 +2,7 @@ version: '3.1' services: authorization-service: + container_name: authorization-service build: context: . args: @@ -12,6 +13,7 @@ services: - nats account-service: + container_name: account-service build: context: . args: @@ -22,6 +24,7 @@ services: - nats presence-service: + container_name: presence-service build: context: . args: @@ -32,6 +35,7 @@ services: - nats ddp-streamer-service: + container_name: ddp-streamer-service build: context: . args: @@ -48,6 +52,7 @@ services: - "traefik.frontend.rule=Host:${HOST_NAME}" stream-hub-service: + container_name: stream-hub-service build: context: . args: @@ -62,8 +67,31 @@ services: ports: - "4222:4222" + # tracing container for all tracing reporting + jaeger: + image: jaegertracing/all-in-one:latest + container_name: jaeger + ports: + - "5775:5775/udp" + - "6831:6831/udp" + - "6832:6832/udp" + - "5778:5778" + - "16686:16686" + - "14268:14268" + - "9411:9411" + depends_on: + - traefik + # environment: + # - QUERY_BASE_PATH=/jaeger + labels: + - "traefik.enable=true" + - "traefik.jaeger.frontend=jaeger" + - "traefik.jaeger.port=16686" + - 'traefik.jaeger.frontend.rule=Host:jaeger.${HOST_NAME}' + grafana: image: grafana/grafana + container_name: grafana restart: unless-stopped volumes: - ./.config/grafana/provisioning/datasources:/etc/grafana/provisioning/datasources:ro @@ -80,6 +108,7 @@ services: prometheus: image: quay.io/prometheus/prometheus + container_name: prometheus restart: unless-stopped command: - --config.file=/etc/prometheus/prometheus.yml diff --git a/ee/server/services/package-lock.json b/ee/server/services/package-lock.json index 245d0da085b57..d838d0b891914 100644 --- a/ee/server/services/package-lock.json +++ b/ee/server/services/package-lock.json @@ -286,6 +286,11 @@ "amp": "0.3.1" } }, + "ansi-color": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/ansi-color/-/ansi-color-0.2.1.tgz", + "integrity": "sha1-PnXAN0dSF1RO12Oo21cJ+prlv5o=" + }, "ansi-colors": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", @@ -458,6 +463,17 @@ "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", "dev": true }, + "bufrw": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/bufrw/-/bufrw-1.3.0.tgz", + "integrity": "sha512-jzQnSbdJqhIltU9O5KUiTtljP9ccw2u5ix59McQy4pV2xGhVLhRZIndY8GIrgh5HjXa6+QJ9AQhOd2QWQizJFQ==", + "requires": { + "ansi-color": "^0.2.1", + "error": "^7.0.0", + "hexer": "^1.5.0", + "xtend": "^4.0.0" + } + }, "bytes": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", @@ -669,6 +685,15 @@ "ansi-colors": "^3.2.1" } }, + "error": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/error/-/error-7.0.2.tgz", + "integrity": "sha1-pfdf/02ZJhJt2sDqXcOOaJFTywI=", + "requires": { + "string-template": "~0.2.1", + "xtend": "~4.0.0" + } + }, "es6-promise": { "version": "4.2.8", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", @@ -929,6 +954,17 @@ "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" }, + "hexer": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/hexer/-/hexer-1.5.0.tgz", + "integrity": "sha1-uGzoCFmOip0YksVx887dhvyfBlM=", + "requires": { + "ansi-color": "^0.2.1", + "minimist": "^1.1.0", + "process": "^0.10.0", + "xtend": "^4.0.0" + } + }, "http-errors": { "version": "1.7.3", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", @@ -1074,6 +1110,25 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, + "jaeger-client": { + "version": "3.18.1", + "resolved": "https://registry.npmjs.org/jaeger-client/-/jaeger-client-3.18.1.tgz", + "integrity": "sha512-eZLM2U6rJvYo0XbzQYFeMYfp29gQix7SKlmDReorp9hJkUwXZtTyxW81AcKdmFCjLHO5tFysTX+394BnjEnUZg==", + "requires": { + "node-int64": "^0.4.0", + "opentracing": "^0.14.4", + "thriftrw": "^3.5.0", + "uuid": "^3.2.1", + "xorshift": "^0.2.0" + }, + "dependencies": { + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + } + } + }, "lazy": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/lazy/-/lazy-1.0.11.tgz", @@ -1102,6 +1157,11 @@ "integrity": "sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==", "dev": true }, + "long": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/long/-/long-2.4.0.tgz", + "integrity": "sha1-n6GAux2VAM3CnEFWdmoZleH0Uk8=" + }, "lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -1246,6 +1306,11 @@ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz", "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==" }, + "node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=" + }, "node-pre-gyp": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.14.0.tgz", @@ -1358,6 +1423,11 @@ "wrappy": "1" } }, + "opentracing": { + "version": "0.14.4", + "resolved": "https://registry.npmjs.org/opentracing/-/opentracing-0.14.4.tgz", + "integrity": "sha512-nNnZDkUNExBwEpb7LZaeMeQgvrlO8l4bgY/LvGNZCR0xG/dGWqHqjKrAmR5GUoYo0FIz38kxasvA1aevxWs2CA==" + }, "optionator": { "version": "0.8.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", @@ -1561,6 +1631,11 @@ "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", "dev": true }, + "process": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/process/-/process-0.10.1.tgz", + "integrity": "sha1-hCRXzFHP7XLcd1r+6vuMYDQ3JyU=" + }, "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -1838,6 +1913,11 @@ "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", "dev": true }, + "string-template": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/string-template/-/string-template-0.2.1.tgz", + "integrity": "sha1-QpMuWYo1LQH8IuwzZ9nYTuxsmt0=" + }, "string-width": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", @@ -1916,6 +1996,16 @@ } } }, + "thriftrw": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/thriftrw/-/thriftrw-3.12.0.tgz", + "integrity": "sha512-4YZvR4DPEI41n4Opwr4jmrLGG4hndxr7387kzRFIIzxHQjarPusH4lGXrugvgb7TtPrfZVTpZCVe44/xUxowEw==", + "requires": { + "bufrw": "^1.3.0", + "error": "7.0.2", + "long": "^2.4.0" + } + }, "thunkify": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/thunkify/-/thunkify-2.1.2.tgz", @@ -2046,12 +2136,22 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-7.2.5.tgz", "integrity": "sha512-C34cIU4+DB2vMyAbmEKossWq2ZQDr6QEyuuCzWrM9zfw1sGc0mYiJ0UnG9zzNykt49C2Fi34hvr2vssFQRS6EA==" }, + "xorshift": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/xorshift/-/xorshift-0.2.1.tgz", + "integrity": "sha1-/NgiZ+k1HBPw+5xzMH8lMx0pxjo=" + }, "xregexp": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-2.0.0.tgz", "integrity": "sha1-UqY+VsoLhKfzpfPWGHLxJq16WUM=", "dev": true }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + }, "yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", diff --git a/ee/server/services/package.json b/ee/server/services/package.json index 9bb0dbe0a5f3a..5395d85f035b2 100644 --- a/ee/server/services/package.json +++ b/ee/server/services/package.json @@ -18,15 +18,16 @@ "author": "Rocket.Chat", "license": "MIT", "dependencies": { - "ejson": "^2.2.0", - "uuid": "^7.0.3", - "ws": "^7.2.3", "bcrypt": "^4.0.1", + "ejson": "^2.2.0", + "jaeger-client": "^3.18.1", + "moleculer": "^0.14.10", "mongodb": "^3.6.1", "msgpack5": "^4.2.1", + "nats": "^1.4.8", "underscore.string": "^3.3.5", - "moleculer": "^0.14.10", - "nats": "^1.4.8" + "uuid": "^7.0.3", + "ws": "^7.2.3" }, "devDependencies": { "@types/ejson": "^2.1.2", From 44e24d164b827f7d80c9ed2ae2631dcd51d2c8b4 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Sun, 20 Sep 2020 12:28:29 -0300 Subject: [PATCH 059/198] Add more metrics --- .../dashboards/json-exports/docker_rev2.json | 1007 +++++++ .../json-exports/moleculer-metrics.json | 2504 +++++++++-------- .../json-exports/reverse-proxy_rev1.json | 1303 +++++++++ .../.config/prometheus/prometheus.yml | 15 +- .../services/.config/traefik/traefik.toml | 8 + ee/server/services/docker-compose.yml | 14 + 6 files changed, 3686 insertions(+), 1165 deletions(-) create mode 100644 ee/server/services/.config/grafana/provisioning/dashboards/json-exports/docker_rev2.json create mode 100644 ee/server/services/.config/grafana/provisioning/dashboards/json-exports/reverse-proxy_rev1.json diff --git a/ee/server/services/.config/grafana/provisioning/dashboards/json-exports/docker_rev2.json b/ee/server/services/.config/grafana/provisioning/dashboards/json-exports/docker_rev2.json new file mode 100644 index 0000000000000..40d89fc57262c --- /dev/null +++ b/ee/server/services/.config/grafana/provisioning/dashboards/json-exports/docker_rev2.json @@ -0,0 +1,1007 @@ +{ + "__requires": [ + { + "type": "grafana", + "id": "grafana", + "name": "Grafana", + "version": "6.4.3" + }, + { + "type": "panel", + "id": "graph", + "name": "Graph", + "version": "" + }, + { + "type": "datasource", + "id": "prometheus", + "name": "Prometheus", + "version": "1.0.0" + }, + { + "type": "panel", + "id": "singlestat", + "name": "Singlestat", + "version": "" + } + ], + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "description": "cadvisor:v0.32.0", + "editable": true, + "gnetId": 11277, + "graphTooltip": 1, + "id": null, + "iteration": 1586447178129, + "links": [ + { + "icon": "external link", + "tags": [], + "targetBlank": true, + "title": "http://$instance/metrics", + "tooltip": "", + "type": "link", + "url": "http://$instance/metrics" + } + ], + "panels": [ + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": false, + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "datasource": "Prometheus", + "editable": true, + "error": false, + "format": "none", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 3, + "w": 8, + "x": 0, + "y": 0 + }, + "height": "20", + "id": 7, + "interval": null, + "isNew": true, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "options": {}, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "count(container_last_seen{project=~\"$project\",instance=~\"$instance\",image!=\"\"})", + "intervalFactor": 2, + "legendFormat": "", + "metric": "container_last_seen", + "refId": "A", + "step": 240 + } + ], + "thresholds": "", + "title": "Running containers", + "transparent": true, + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "avg" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": false, + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "datasource": "Prometheus", + "editable": true, + "error": false, + "format": "mbytes", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 3, + "w": 8, + "x": 8, + "y": 0 + }, + "height": "20", + "id": 5, + "interval": null, + "isNew": true, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "options": {}, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "sum(container_memory_usage_bytes{project=~\"$project\",instance=~\"$instance\",image!=\"\"})/1024/1024", + "intervalFactor": 2, + "legendFormat": "", + "metric": "container_memory_usage_bytes", + "refId": "A", + "step": 240 + } + ], + "thresholds": "", + "title": "Total Memory Usage", + "transparent": true, + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "current" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": false, + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "datasource": "Prometheus", + "editable": true, + "error": false, + "format": "percent", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 3, + "w": 8, + "x": 16, + "y": 0 + }, + "height": "20", + "id": 6, + "interval": null, + "isNew": true, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "options": {}, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "sum(rate(container_cpu_user_seconds_total{project=~\"$project\",instance=~\"$instance\",image!=\"\"}[5m]) * 100)", + "intervalFactor": 2, + "legendFormat": "", + "metric": "container_memory_usage_bytes", + "refId": "A", + "step": 240 + } + ], + "thresholds": "", + "title": "Total CPU Usage", + "transparent": true, + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "current" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "decimals": 2, + "editable": true, + "error": false, + "fill": 1, + "fillGradient": 0, + "grid": {}, + "gridPos": { + "h": 7, + "w": 24, + "x": 0, + "y": 3 + }, + "id": 2, + "isNew": true, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": false, + "min": false, + "rightSide": true, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 2, + "links": [], + "nullPointMode": "connected", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(container_cpu_user_seconds_total{project=~\"$project\",instance=~\"$instance\",image!=\"\"}[5m]) * 100", + "intervalFactor": 2, + "legendFormat": "{{name}}", + "metric": "cpu", + "refId": "A", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "CPU Usage", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "percent", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "decimals": 2, + "editable": true, + "error": false, + "fill": 1, + "fillGradient": 0, + "grid": {}, + "gridPos": { + "h": 7, + "w": 24, + "x": 0, + "y": 10 + }, + "id": 1, + "isNew": true, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": false, + "min": false, + "rightSide": true, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 2, + "links": [], + "nullPointMode": "connected", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "container_memory_usage_bytes{project=~\"$project\",instance=~\"$instance\",image!=\"\"}", + "hide": false, + "intervalFactor": 2, + "legendFormat": "{{name}}", + "metric": "container_memory_usage_bytes", + "refId": "A", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Memory Usage", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "bytes", + "label": "", + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": false + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "editable": true, + "error": false, + "fill": 1, + "fillGradient": 0, + "grid": {}, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 17 + }, + "id": 3, + "isNew": true, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "links": [], + "nullPointMode": "connected", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "irate(container_network_receive_bytes_total{project=~\"$project\",instance=~\"$instance\",image!=\"\"}[5m])", + "intervalFactor": 2, + "legendFormat": "{{name}}", + "metric": "container_network_receive_bytes_total", + "refId": "A", + "step": 20 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Network Rx", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "Bps", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "editable": true, + "error": false, + "fill": 1, + "fillGradient": 0, + "grid": {}, + "gridPos": { + "h": 7, + "w": 12, + "x": 12, + "y": 17 + }, + "id": 4, + "isNew": true, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "links": [], + "nullPointMode": "connected", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "irate(container_network_transmit_bytes_total{project=~\"$project\",instance=~\"$instance\",image!=\"\"}[5m])", + "intervalFactor": 2, + "legendFormat": "{{name}}", + "refId": "A", + "step": 20 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Network Tx", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "Bps", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "editable": true, + "error": false, + "fill": 1, + "fillGradient": 0, + "grid": {}, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 24 + }, + "id": 8, + "isNew": true, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "links": [], + "nullPointMode": "connected", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "irate(container_fs_reads_bytes_total{project=~\"$project\",instance=~\"$instance\",image!=\"\"}[5m])", + "intervalFactor": 2, + "legendFormat": "{{name}}", + "metric": "container_fs_reads_bytes_total", + "refId": "A", + "step": 20 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "I/O Rx", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "Bps", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "editable": true, + "error": false, + "fill": 1, + "fillGradient": 0, + "grid": {}, + "gridPos": { + "h": 7, + "w": 12, + "x": 12, + "y": 24 + }, + "id": 9, + "isNew": true, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "links": [], + "nullPointMode": "connected", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "irate(container_fs_writes_bytes_total{project=~\"$project\",instance=~\"$instance\",image!=\"\"}[5m])", + "intervalFactor": 2, + "legendFormat": "{{name}}", + "metric": "container_fs_writes_bytes_total", + "refId": "A", + "step": 20 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "I/O Tx", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "Bps", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "refresh": "10s", + "schemaVersion": 20, + "style": "dark", + "tags": [], + "templating": { + "list": [ + { + "allValue": null, + "current": {}, + "datasource": "Prometheus", + "definition": "label_values(container_last_seen, project)", + "hide": 0, + "includeAll": false, + "label": "éĄšį›Ž", + "multi": false, + "name": "project", + "options": [], + "query": "label_values(container_last_seen, project)", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 1, + "tagValuesQuery": "", + "tags": [], + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "allValue": null, + "current": {}, + "datasource": "Prometheus", + "definition": "label_values(container_last_seen{project=~\"$project\"}, server)", + "hide": 0, + "includeAll": false, + "label": "ä¸ģæœē", + "multi": false, + "name": "server", + "options": [], + "query": "label_values(container_last_seen{project=~\"$project\"}, server)", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 1, + "tagValuesQuery": "", + "tags": [], + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "allValue": null, + "current": {}, + "datasource": "Prometheus", + "definition": "label_values(container_last_seen{project=~\"$project\",server=~\"$server\"}, instance)", + "hide": 2, + "includeAll": false, + "label": "instance", + "multi": false, + "name": "instance", + "options": [], + "query": "label_values(container_last_seen{project=~\"$project\",server=~\"$server\"}, instance)", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "tagValuesQuery": "", + "tags": [], + "tagsQuery": "", + "type": "query", + "useTags": false + } + ] + }, + "time": { + "from": "now-12h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, + "timezone": "", + "title": "docker", + "uid": "docker", + "version": 1 +} diff --git a/ee/server/services/.config/grafana/provisioning/dashboards/json-exports/moleculer-metrics.json b/ee/server/services/.config/grafana/provisioning/dashboards/json-exports/moleculer-metrics.json index 536bf895a6eb3..bf1479fb39f0d 100644 --- a/ee/server/services/.config/grafana/provisioning/dashboards/json-exports/moleculer-metrics.json +++ b/ee/server/services/.config/grafana/provisioning/dashboards/json-exports/moleculer-metrics.json @@ -1,1157 +1,1349 @@ { - "__requires": [ - { - "type": "panel", - "id": "alertlist", - "name": "Alert List", - "version": "5.0.0" - }, - { - "type": "grafana", - "id": "grafana", - "name": "Grafana", - "version": "5.0.3" - }, - { - "type": "panel", - "id": "graph", - "name": "Graph", - "version": "5.0.0" - }, - { - "type": "datasource", - "id": "prometheus", - "name": "Prometheus", - "version": "5.0.0" - }, - { - "type": "panel", - "id": "singlestat", - "name": "Singlestat", - "version": "5.0.0" - } - ], - "annotations": { - "list": [ - { - "builtIn": 1, - "datasource": "-- Grafana --", - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations & Alerts", - "type": "dashboard" - } - ] - }, - "editable": true, - "gnetId": null, - "graphTooltip": 1, - "id": null, - "links": [], - "panels": [ - { - "cacheTimeout": null, - "colorBackground": false, - "colorValue": false, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "datasource": "Prometheus", - "format": "none", - "gauge": { - "maxValue": 100, - "minValue": 0, - "show": false, - "thresholdLabels": false, - "thresholdMarkers": true - }, - "gridPos": { - "h": 3, - "w": 6, - "x": 0, - "y": 0 - }, - "hideTimeOverride": true, - "id": 9, - "interval": null, - "links": [], - "mappingType": 1, - "mappingTypes": [ - { - "name": "value to text", - "value": 1 - }, - { - "name": "range to text", - "value": 2 - } - ], - "maxDataPoints": 100, - "nullPointMode": "connected", - "nullText": null, - "postfix": "", - "postfixFontSize": "50%", - "prefix": "", - "prefixFontSize": "50%", - "rangeMaps": [ - { - "from": "null", - "text": "N/A", - "to": "null" - } - ], - "sparkline": { - "fillColor": "rgba(31, 118, 189, 0.18)", - "full": false, - "lineColor": "rgb(31, 120, 193)", - "show": false - }, - "tableColumn": "", - "targets": [ - { - "expr": "moleculer_nodes_total", - "format": "time_series", - "interval": "", - "intervalFactor": 2, - "refId": "A", - "step": 2 - } - ], - "thresholds": "", - "timeFrom": "1s", - "title": "Nodes", - "type": "singlestat", - "valueFontSize": "80%", - "valueMaps": [ - { - "op": "=", - "text": "N/A", - "value": "null" - } - ], - "valueName": "avg" - }, - { - "cacheTimeout": null, - "colorBackground": false, - "colorValue": false, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "datasource": "Prometheus", - "format": "none", - "gauge": { - "maxValue": 100, - "minValue": 0, - "show": false, - "thresholdLabels": false, - "thresholdMarkers": true - }, - "gridPos": { - "h": 3, - "w": 6, - "x": 6, - "y": 0 - }, - "hideTimeOverride": true, - "id": 10, - "interval": null, - "links": [], - "mappingType": 1, - "mappingTypes": [ - { - "name": "value to text", - "value": 1 - }, - { - "name": "range to text", - "value": 2 - } - ], - "maxDataPoints": 100, - "nullPointMode": "connected", - "nullText": null, - "postfix": "", - "postfixFontSize": "50%", - "prefix": "", - "prefixFontSize": "50%", - "rangeMaps": [ - { - "from": "null", - "text": "N/A", - "to": "null" - } - ], - "sparkline": { - "fillColor": "rgba(31, 118, 189, 0.18)", - "full": false, - "lineColor": "rgb(31, 120, 193)", - "show": false - }, - "tableColumn": "", - "targets": [ - { - "expr": "moleculer_services_total", - "format": "time_series", - "intervalFactor": 2, - "refId": "A", - "step": 2 - } - ], - "thresholds": "", - "timeFrom": "1s", - "title": "Services", - "type": "singlestat", - "valueFontSize": "80%", - "valueMaps": [ - { - "op": "=", - "text": "N/A", - "value": "null" - } - ], - "valueName": "avg" - }, - { - "cacheTimeout": null, - "colorBackground": false, - "colorValue": false, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "datasource": "Prometheus", - "format": "none", - "gauge": { - "maxValue": 100, - "minValue": 0, - "show": false, - "thresholdLabels": false, - "thresholdMarkers": true - }, - "gridPos": { - "h": 3, - "w": 6, - "x": 12, - "y": 0 - }, - "hideTimeOverride": true, - "id": 11, - "interval": null, - "links": [], - "mappingType": 1, - "mappingTypes": [ - { - "name": "value to text", - "value": 1 - }, - { - "name": "range to text", - "value": 2 - } - ], - "maxDataPoints": 100, - "nullPointMode": "connected", - "nullText": null, - "postfix": "", - "postfixFontSize": "50%", - "prefix": "", - "prefixFontSize": "50%", - "rangeMaps": [ - { - "from": "null", - "text": "N/A", - "to": "null" - } - ], - "sparkline": { - "fillColor": "rgba(31, 118, 189, 0.18)", - "full": false, - "lineColor": "rgb(31, 120, 193)", - "show": false - }, - "tableColumn": "", - "targets": [ - { - "expr": "moleculer_actions_total", - "format": "time_series", - "intervalFactor": 2, - "refId": "A", - "step": 2 - } - ], - "thresholds": "", - "timeFrom": "1s", - "title": "Actions", - "type": "singlestat", - "valueFontSize": "80%", - "valueMaps": [ - { - "op": "=", - "text": "N/A", - "value": "null" - } - ], - "valueName": "avg" - }, - { - "cacheTimeout": null, - "colorBackground": false, - "colorValue": false, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "datasource": "Prometheus", - "format": "none", - "gauge": { - "maxValue": 100, - "minValue": 0, - "show": false, - "thresholdLabels": false, - "thresholdMarkers": true - }, - "gridPos": { - "h": 3, - "w": 6, - "x": 18, - "y": 0 - }, - "hideTimeOverride": true, - "id": 12, - "interval": null, - "links": [], - "mappingType": 1, - "mappingTypes": [ - { - "name": "value to text", - "value": 1 - }, - { - "name": "range to text", - "value": 2 - } - ], - "maxDataPoints": 100, - "nullPointMode": "connected", - "nullText": null, - "postfix": "", - "postfixFontSize": "50%", - "prefix": "", - "prefixFontSize": "50%", - "rangeMaps": [ - { - "from": "null", - "text": "N/A", - "to": "null" - } - ], - "sparkline": { - "fillColor": "rgba(31, 118, 189, 0.18)", - "full": false, - "lineColor": "rgb(31, 120, 193)", - "show": false - }, - "tableColumn": "", - "targets": [ - { - "expr": "moleculer_events_total", - "format": "time_series", - "intervalFactor": 2, - "refId": "A", - "step": 2 - } - ], - "thresholds": "", - "timeFrom": "1s", - "title": "Events", - "type": "singlestat", - "valueFontSize": "80%", - "valueMaps": [ - { - "op": "=", - "text": "N/A", - "value": "null" - } - ], - "valueName": "avg" - }, - { - "cacheTimeout": null, - "colorBackground": false, - "colorValue": false, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "datasource": "Prometheus", - "decimals": null, - "format": "short", - "gauge": { - "maxValue": 100, - "minValue": 0, - "show": false, - "thresholdLabels": false, - "thresholdMarkers": true - }, - "gridPos": { - "h": 3, - "w": 6, - "x": 0, - "y": 3 - }, - "hideTimeOverride": true, - "id": 2, - "interval": null, - "links": [], - "mappingType": 1, - "mappingTypes": [ - { - "name": "value to text", - "value": 1 - }, - { - "name": "range to text", - "value": 2 - } - ], - "maxDataPoints": 100, - "nullPointMode": "connected", - "nullText": null, - "postfix": "", - "postfixFontSize": "50%", - "prefix": "", - "prefixFontSize": "50%", - "rangeMaps": [ - { - "from": "null", - "text": "N/A", - "to": "null" - } - ], - "sparkline": { - "fillColor": "rgba(31, 118, 189, 0.18)", - "full": false, - "lineColor": "rgb(31, 120, 193)", - "show": false - }, - "tableColumn": "", - "targets": [ - { - "expr": "sum(moleculer_req_total)", - "format": "time_series", - "hide": false, - "intervalFactor": 2, - "refId": "A", - "step": 2 - } - ], - "thresholds": "", - "timeFrom": "1s", - "title": "Total calls", - "type": "singlestat", - "valueFontSize": "80%", - "valueMaps": [ - { - "op": "=", - "text": "N/A", - "value": "null" - } - ], - "valueName": "total" - }, - { - "cacheTimeout": null, - "colorBackground": false, - "colorValue": false, - "colors": [ - "rgba(247, 55, 55, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "datasource": "Prometheus", - "format": "short", - "gauge": { - "maxValue": 100, - "minValue": 0, - "show": false, - "thresholdLabels": false, - "thresholdMarkers": true - }, - "gridPos": { - "h": 3, - "w": 6, - "x": 6, - "y": 3 - }, - "hideTimeOverride": true, - "id": 3, - "interval": null, - "links": [], - "mappingType": 1, - "mappingTypes": [ - { - "name": "value to text", - "value": 1 - }, - { - "name": "range to text", - "value": 2 - } - ], - "maxDataPoints": 100, - "nullPointMode": "connected", - "nullText": null, - "postfix": "", - "postfixFontSize": "50%", - "prefix": "", - "prefixFontSize": "50%", - "rangeMaps": [ - { - "from": "null", - "text": "N/A", - "to": "null" - } - ], - "sparkline": { - "fillColor": "rgba(31, 118, 189, 0.18)", - "full": false, - "lineColor": "rgb(31, 120, 193)", - "show": false - }, - "tableColumn": "", - "targets": [ - { - "expr": "sum(moleculer_req_errors_total)", - "format": "time_series", - "intervalFactor": 2, - "refId": "A", - "step": 2 - } - ], - "thresholds": "", - "timeFrom": "1s", - "title": "Total errors", - "type": "singlestat", - "valueFontSize": "80%", - "valueMaps": [ - { - "op": "=", - "text": "N/A", - "value": "null" - } - ], - "valueName": "avg" - }, - { - "cacheTimeout": null, - "colorBackground": false, - "colorValue": true, - "colors": [ - "rgba(50, 172, 45, 0.97)", - "rgba(237, 129, 40, 0.89)", - "rgba(245, 54, 54, 0.9)" - ], - "datasource": "Prometheus", - "format": "ms", - "gauge": { - "maxValue": 100, - "minValue": 0, - "show": false, - "thresholdLabels": false, - "thresholdMarkers": true - }, - "gridPos": { - "h": 3, - "w": 6, - "x": 12, - "y": 3 - }, - "hideTimeOverride": true, - "id": 4, - "interval": null, - "links": [], - "mappingType": 1, - "mappingTypes": [ - { - "name": "value to text", - "value": 1 - }, - { - "name": "range to text", - "value": 2 - } - ], - "maxDataPoints": 100, - "nullPointMode": "connected", - "nullText": null, - "postfix": "", - "postfixFontSize": "50%", - "prefix": "", - "prefixFontSize": "50%", - "rangeMaps": [ - { - "from": "null", - "text": "N/A", - "to": "null" - } - ], - "sparkline": { - "fillColor": "rgba(31, 118, 189, 0.18)", - "full": false, - "lineColor": "rgb(31, 120, 193)", - "show": false - }, - "tableColumn": "", - "targets": [ - { - "expr": "avg(irate(moleculer_req_duration_ms_sum[1m]))", - "format": "time_series", - "intervalFactor": 2, - "refId": "A", - "step": 2 - } - ], - "thresholds": "50, 100", - "timeFrom": "10s", - "title": "Response time", - "type": "singlestat", - "valueFontSize": "80%", - "valueMaps": [ - { - "op": "=", - "text": "N/A", - "value": "null" - } - ], - "valueName": "avg" - }, - { - "cacheTimeout": null, - "colorBackground": false, - "colorValue": false, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "datasource": "Prometheus", - "format": "none", - "gauge": { - "maxValue": 100, - "minValue": 0, - "show": false, - "thresholdLabels": false, - "thresholdMarkers": true - }, - "gridPos": { - "h": 3, - "w": 6, - "x": 18, - "y": 3 - }, - "hideTimeOverride": true, - "id": 5, - "interval": null, - "links": [], - "mappingType": 1, - "mappingTypes": [ - { - "name": "value to text", - "value": 1 - }, - { - "name": "range to text", - "value": 2 - } - ], - "maxDataPoints": 100, - "nullPointMode": "connected", - "nullText": null, - "postfix": "", - "postfixFontSize": "50%", - "prefix": "", - "prefixFontSize": "50%", - "rangeMaps": [ - { - "from": "null", - "text": "N/A", - "to": "null" - } - ], - "sparkline": { - "fillColor": "rgba(31, 118, 189, 0.18)", - "full": false, - "lineColor": "rgb(31, 120, 193)", - "show": false - }, - "tableColumn": "", - "targets": [ - { - "expr": "sum(irate(moleculer_req_total[1m]))", - "format": "time_series", - "interval": "", - "intervalFactor": 2, - "refId": "A", - "step": 2 - } - ], - "thresholds": "", - "timeFrom": "10s", - "title": "Rps", - "type": "singlestat", - "valueFontSize": "80%", - "valueMaps": [ - { - "op": "=", - "text": "N/A", - "value": "null" - } - ], - "valueName": "avg" - }, - { - "alert": { - "conditions": [ - { - "evaluator": { - "params": [ - 200 - ], - "type": "gt" - }, - "operator": { - "type": "and" - }, - "query": { - "params": [ - "A", - "1m", - "now" - ] - }, - "reducer": { - "params": [], - "type": "avg" - }, - "type": "query" - } - ], - "executionErrorState": "alerting", - "frequency": "10s", - "handler": 1, - "name": "Response time ( >200ms )", - "noDataState": "no_data", - "notifications": [] - }, - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "Prometheus", - "fill": 1, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 6 - }, - "id": 1, - "legend": { - "alignAsTable": false, - "avg": false, - "current": false, - "hideEmpty": false, - "hideZero": false, - "max": false, - "min": false, - "rightSide": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [], - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "avg(irate(moleculer_req_duration_ms_sum[1m]))", - "format": "time_series", - "hide": false, - "intervalFactor": 2, - "legendFormat": "Total", - "refId": "A", - "step": 2 - } - ], - "thresholds": [ - { - "colorMode": "critical", - "fill": true, - "line": true, - "op": "gt", - "value": 200 - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Response time", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "ms", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ] - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "Prometheus", - "fill": 1, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 6 - }, - "id": 6, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [], - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "sum(irate(moleculer_req_total[1m]))", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "Calls", - "refId": "A", - "step": 2 - }, - { - "expr": "sum(irate(moleculer_req_errors_total[1m]))", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "Errors", - "refId": "B", - "step": 2 - } - ], - "thresholds": [], - "timeFrom": null, - "timeShift": null, - "title": "Action calls", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ] - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "Prometheus", - "fill": 1, - "gridPos": { - "h": 7, - "w": 24, - "x": 0, - "y": 14 - }, - "id": 7, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [], - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "sum by (errorName) (irate(moleculer_req_errors_total[1m]))", - "format": "time_series", - "intervalFactor": 2, - "legendFormat": "{{errorName}}", - "refId": "A", - "step": 2 - } - ], - "thresholds": [], - "timeFrom": null, - "timeShift": null, - "title": "Error types", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ] - }, - { - "gridPos": { - "h": 7, - "w": 12, - "x": 0, - "y": 21 - }, - "id": 8, - "limit": 10, - "links": [], - "onlyAlertsOnDashboard": true, - "show": "current", - "sortOrder": 1, - "stateFilter": [], - "title": "Alerts", - "type": "alertlist" - }, - { - "aliasColors": {}, - "bars": true, - "dashLength": 10, - "dashes": false, - "datasource": "Prometheus", - "fill": 1, - "gridPos": { - "h": 7, - "w": 12, - "x": 12, - "y": 21 - }, - "id": 13, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": false, - "total": false, - "values": false - }, - "lines": false, - "linewidth": 1, - "links": [], - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "moleculer_nodes_total", - "format": "time_series", - "interval": "10s", - "intervalFactor": 2, - "refId": "A", - "step": 20 - } - ], - "thresholds": [], - "timeFrom": null, - "timeShift": null, - "title": "Available nodes", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ] - } - ], - "refresh": "5s", - "schemaVersion": 16, - "style": "dark", - "tags": [], - "templating": { - "list": [] - }, - "time": { - "from": "now-15m", - "to": "now" - }, - "timepicker": { - "refresh_intervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "time_options": [ - "5m", - "15m", - "1h", - "6h", - "12h", - "24h", - "2d", - "7d", - "30d" - ] - }, - "timezone": "", - "title": "Moleculer Prometheus demo", - "uid": "xtcSMfkmz", - "version": 4 -} + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "gnetId": null, + "graphTooltip": 1, + "links": [], + "panels": [ + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": false, + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "datasource": "Prometheus", + "fieldConfig": { + "defaults": { + "custom": {} + }, + "overrides": [] + }, + "format": "none", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "hideTimeOverride": true, + "id": 9, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "avg(moleculer_registry_nodes_total)", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "", + "refId": "A", + "step": 2 + } + ], + "thresholds": "", + "timeFrom": "1s", + "title": "Nodes", + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "avg" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": false, + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "datasource": "Prometheus", + "fieldConfig": { + "defaults": { + "custom": {} + }, + "overrides": [] + }, + "format": "none", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 6, + "y": 0 + }, + "hideTimeOverride": true, + "id": 10, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "avg(moleculer_registry_services_total)", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "", + "refId": "A", + "step": 2 + } + ], + "thresholds": "", + "timeFrom": "1s", + "title": "Services", + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "avg" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": false, + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "datasource": "Prometheus", + "fieldConfig": { + "defaults": { + "custom": {} + }, + "overrides": [] + }, + "format": "none", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 12, + "y": 0 + }, + "hideTimeOverride": true, + "id": 11, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "avg(moleculer_registry_actions_total)", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "", + "refId": "A", + "step": 2 + } + ], + "thresholds": "", + "timeFrom": "1s", + "title": "Actions", + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "avg" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": false, + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "datasource": "Prometheus", + "fieldConfig": { + "defaults": { + "custom": {} + }, + "overrides": [] + }, + "format": "none", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 18, + "y": 0 + }, + "hideTimeOverride": true, + "id": 12, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "avg(moleculer_registry_events_total)", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "", + "refId": "A", + "step": 2 + } + ], + "thresholds": "", + "timeFrom": "1s", + "title": "Events", + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "avg" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": false, + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "datasource": "Prometheus", + "decimals": null, + "fieldConfig": { + "defaults": { + "custom": {} + }, + "overrides": [] + }, + "format": "short", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 3 + }, + "hideTimeOverride": true, + "id": 2, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "sum(moleculer_request_total)", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "", + "refId": "A", + "step": 2 + } + ], + "thresholds": "", + "timeFrom": "1s", + "title": "Total calls", + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "total" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": false, + "colors": [ + "rgba(247, 55, 55, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "datasource": "Prometheus", + "fieldConfig": { + "defaults": { + "custom": {} + }, + "overrides": [] + }, + "format": "short", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 6, + "y": 3 + }, + "hideTimeOverride": true, + "id": 3, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "sum(moleculer_request_error_total)", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "", + "refId": "A", + "step": 2 + } + ], + "thresholds": "", + "timeFrom": "1s", + "title": "Total errors", + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "avg" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": true, + "colors": [ + "rgba(50, 172, 45, 0.97)", + "rgba(237, 129, 40, 0.89)", + "rgba(245, 54, 54, 0.9)" + ], + "datasource": "Prometheus", + "fieldConfig": { + "defaults": { + "custom": {} + }, + "overrides": [] + }, + "format": "ms", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 12, + "y": 3 + }, + "hideTimeOverride": true, + "id": 4, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "avg(irate(moleculer_request_time_rate[1m]))", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "", + "refId": "A", + "step": 2 + } + ], + "thresholds": "50, 100", + "timeFrom": "10s", + "title": "Response time", + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "avg" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": false, + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "datasource": "Prometheus", + "fieldConfig": { + "defaults": { + "custom": {} + }, + "overrides": [] + }, + "format": "none", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 18, + "y": 3 + }, + "hideTimeOverride": true, + "id": 5, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "sum(irate(moleculer_request_total[1m]))", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "", + "refId": "A", + "step": 2 + } + ], + "thresholds": "", + "timeFrom": "10s", + "title": "Rps", + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "avg" + }, + { + "alert": { + "alertRuleTags": {}, + "conditions": [ + { + "evaluator": { + "params": [ + 200 + ], + "type": "gt" + }, + "operator": { + "type": "and" + }, + "query": { + "params": [ + "A", + "1m", + "now" + ] + }, + "reducer": { + "params": [], + "type": "avg" + }, + "type": "query" + } + ], + "executionErrorState": "alerting", + "for": "0m", + "frequency": "10s", + "handler": 1, + "name": "Response time ( >200ms )", + "noDataState": "no_data", + "notifications": [] + }, + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fieldConfig": { + "defaults": { + "custom": {} + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 6 + }, + "hiddenSeries": false, + "id": 1, + "legend": { + "alignAsTable": false, + "avg": false, + "current": false, + "hideEmpty": false, + "hideZero": false, + "max": false, + "min": false, + "rightSide": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.5", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "avg(irate(moleculer_request_time_rate[1m]))", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "Total", + "refId": "A", + "step": 2 + } + ], + "thresholds": [ + { + "colorMode": "critical", + "fill": true, + "line": true, + "op": "gt", + "value": 200 + } + ], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Response time", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "ms", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fieldConfig": { + "defaults": { + "custom": {} + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 6 + }, + "hiddenSeries": false, + "id": 6, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.5", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(irate(moleculer_request_total_rate[1m]))", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "Calls", + "refId": "A", + "step": 2 + }, + { + "expr": "sum(irate(moleculer_request_error_total_rate[1m]))", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "Errors", + "refId": "B", + "step": 2 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Action calls", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fieldConfig": { + "defaults": { + "custom": {} + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 24, + "x": 0, + "y": 14 + }, + "hiddenSeries": false, + "id": 7, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.5", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum by (errorName) (irate(moleculer_request_error_total_rate[1m]))", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "{{errorName}}", + "refId": "A", + "step": 2 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Error types", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fieldConfig": { + "defaults": { + "custom": {} + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 24, + "x": 0, + "y": 21 + }, + "hiddenSeries": false, + "id": 14, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.5", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(moleculer_request_total) by (action)", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "{{action}}", + "refId": "A", + "step": 2 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Actions", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "dashboardFilter": "", + "dashboardTags": [], + "datasource": null, + "fieldConfig": { + "defaults": { + "custom": {} + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 28 + }, + "id": 8, + "limit": 10, + "links": [], + "nameFilter": "", + "onlyAlertsOnDashboard": true, + "show": "current", + "sortOrder": 1, + "stateFilter": [], + "title": "Alerts", + "type": "alertlist" + }, + { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fieldConfig": { + "defaults": { + "custom": {} + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 12, + "x": 12, + "y": 28 + }, + "hiddenSeries": false, + "id": 13, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": false, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pluginVersion": "7.1.5", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "avg(moleculer_registry_nodes_total)", + "format": "time_series", + "interval": "10s", + "intervalFactor": 2, + "legendFormat": "", + "refId": "A", + "step": 20 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Available nodes", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "refresh": "5s", + "schemaVersion": 26, + "style": "dark", + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-1h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, + "timezone": "", + "title": "Moleculer Prometheus demo", + "uid": "xtcSMfkmz", + "version": 1 + } diff --git a/ee/server/services/.config/grafana/provisioning/dashboards/json-exports/reverse-proxy_rev1.json b/ee/server/services/.config/grafana/provisioning/dashboards/json-exports/reverse-proxy_rev1.json new file mode 100644 index 0000000000000..0479361a1d46d --- /dev/null +++ b/ee/server/services/.config/grafana/provisioning/dashboards/json-exports/reverse-proxy_rev1.json @@ -0,0 +1,1303 @@ +{ + "__inputs": [ + { + "name": "DS_SWB", + "label": "Prometheus", + "description": "Traefik2 prometheus metric endpoint", + "type": "datasource", + "pluginId": "prometheus", + "pluginName": "Prometheus" + } + ], + "__requires": [ + { + "type": "grafana", + "id": "grafana", + "name": "Grafana", + "version": "6.3.6" + }, + { + "type": "panel", + "id": "grafana-piechart-panel", + "name": "Pie Chart", + "version": "1.3.9" + }, + { + "type": "panel", + "id": "graph", + "name": "Graph", + "version": "" + }, + { + "type": "datasource", + "id": "prometheus", + "name": "Prometheus", + "version": "1.0.0" + }, + { + "type": "panel", + "id": "singlestat", + "name": "Singlestat", + "version": "" + } + ], + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "gnetId": 10906, + "graphTooltip": 0, + "id": null, + "iteration": 1569328089102, + "links": [ + { + "icon": "external link", + "tags": [ + "link" + ], + "type": "dashboards" + } + ], + "panels": [ + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": false, + "colors": [ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "datasource": "Prometheus", + "format": "s", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 6, + "w": 3, + "x": 0, + "y": 0 + }, + "id": 22, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "options": {}, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "time() - process_start_time_seconds{job=\"$job\"}", + "format": "time_series", + "intervalFactor": 2, + "refId": "A" + } + ], + "thresholds": "", + "title": "Uptime", + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "current" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": true, + "colors": [ + "rgba(50, 172, 45, 0.97)", + "rgba(26, 206, 22, 0.89)", + "rgba(245, 54, 54, 0.9)" + ], + "datasource": "Prometheus", + "decimals": 0, + "format": "none", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 6, + "w": 3, + "x": 3, + "y": 0 + }, + "id": 26, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "options": {}, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "200%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "sum(increase(traefik_service_requests_total{code=\"404\",method=\"GET\",protocol=~\"$protocol\"}[$interval]))", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "", + "metric": "traefik_requests_total", + "refId": "A", + "step": 60 + } + ], + "thresholds": "0,1", + "title": "404 Error Count last $interval", + "type": "singlestat", + "valueFontSize": "200%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "max" + }, + { + "aliasColors": {}, + "breakPoint": "50%", + "cacheTimeout": null, + "combine": { + "label": "Others", + "threshold": 0 + }, + "datasource": "Prometheus", + "fontSize": "80%", + "format": "short", + "gridPos": { + "h": 6, + "w": 7, + "x": 6, + "y": 0 + }, + "id": 18, + "interval": null, + "legend": { + "percentage": true, + "show": true, + "sort": null, + "sortDesc": null, + "values": true + }, + "legendType": "Right side", + "links": [], + "maxDataPoints": 3, + "nullPointMode": "connected", + "options": {}, + "pieType": "pie", + "strokeWidth": 1, + "targets": [ + { + "expr": "traefik_service_requests_total{protocol=~\"$protocol\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "{{method}} : {{code}}", + "refId": "A" + } + ], + "title": "$protocol return code", + "type": "grafana-piechart-panel", + "valueName": "current" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": false, + "colors": [ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "datasource": "Prometheus", + "format": "ms", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 6, + "w": 4, + "x": 13, + "y": 0 + }, + "id": 20, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "options": {}, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": true + }, + "tableColumn": "", + "targets": [ + { + "expr": "sum(traefik_entrypoint_request_duration_seconds_sum) / sum(traefik_entrypoint_requests_total) * 1000", + "format": "time_series", + "intervalFactor": 2, + "refId": "A" + } + ], + "thresholds": "", + "title": "Average response time", + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "avg" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 6, + "w": 7, + "x": 17, + "y": 0 + }, + "id": 14, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(traefik_service_request_duration_seconds_sum{protocol=~\"$protocol\"}) / sum(traefik_entrypoint_requests_total{protocol=~\"$protocol\"}) * 1000", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "Average response time (ms)", + "refId": "A", + "step": 240 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Average response time", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "ms", + "label": null, + "logBase": 10, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "decimals": 0, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 6, + "w": 12, + "x": 0, + "y": 6 + }, + "id": 10, + "legend": { + "alignAsTable": true, + "avg": false, + "current": false, + "max": false, + "min": false, + "rightSide": true, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(increase(traefik_service_requests_total{code=\"404\",method=\"GET\",protocol=~\"$protocol\"}[$interval])) by (service)", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "{{service}} ", + "refId": "A", + "step": 240 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Bad Status Code Count $interval", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "decimals": 0, + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": false + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 6, + "w": 12, + "x": 12, + "y": 6 + }, + "hideTimeOverride": false, + "id": 4, + "legend": { + "alignAsTable": true, + "avg": true, + "current": false, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": false, + "values": true + }, + "lines": false, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(rate(traefik_service_requests_total[$interval]))", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "Total requests", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Total requests over $interval", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "decimals": 0, + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "decimals": 0, + "format": "short", + "label": "", + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 6, + "w": 10, + "x": 0, + "y": 12 + }, + "id": 8, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null as zero", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "targets": [ + { + "expr": "process_open_fds{job=~\"$job\"}", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "{{ instance }}", + "refId": "A", + "step": 240 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Used sockets", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "decimals": 0, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 6, + "w": 14, + "x": 10, + "y": 12 + }, + "id": 24, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": false, + "min": false, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(rate(traefik_service_requests_total{protocol=~\"http|https\",code=\"200\"}[$interval])) by (service)", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "{{service}} {{method}} {{code}}", + "refId": "A", + "step": 240 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Access to backends", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "decimals": 0, + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": false + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 7, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 18 + }, + "id": 30, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": false, + "rightSide": true, + "show": true, + "sort": "avg", + "sortDesc": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "targets": [ + { + "expr": "sum(traefik_entrypoint_open_connections) by (method)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "{{ method }}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "ENTRYPOINT - Open Connections", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": false + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 7, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 12, + "x": 12, + "y": 18 + }, + "id": 28, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": false, + "rightSide": true, + "show": true, + "sort": "avg", + "sortDesc": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "targets": [ + { + "expr": "sum(traefik_service_open_connections) by (method)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "{{ method }}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "BACKEND - Open Connections", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": false + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "decimals": 0, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 24, + "x": 0, + "y": 25 + }, + "id": 12, + "legend": { + "alignAsTable": true, + "avg": false, + "current": false, + "max": false, + "min": false, + "rightSide": true, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "alias": "/^[^234].*/", + "transform": "negative-Y" + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(increase(traefik_service_requests_total{protocol=~\"$protocol\"}[$interval])) by (code)", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "{{code}}", + "refId": "A", + "step": 120 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Status Code Count per $interval", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "decimals": 0, + "format": "short", + "label": "", + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": false + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "refresh": "10s", + "schemaVersion": 19, + "style": "dark", + "tags": [ + "traefik", + "load-balancer", + "docker", + "prometheus", + "link" + ], + "templating": { + "list": [ + { + "allValue": null, + "current": {}, + "datasource": "Prometheus", + "definition": "", + "hide": 0, + "includeAll": false, + "label": "Job:", + "multi": false, + "name": "job", + "options": [], + "query": "label_values(job)", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 2, + "tagValuesQuery": "", + "tags": [], + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "allValue": "traefik", + "current": {}, + "datasource": "Prometheus", + "definition": "", + "hide": 0, + "includeAll": false, + "label": "Node:", + "multi": false, + "name": "node", + "options": [], + "query": "label_values(process_start_time_seconds, instance)", + "refresh": 1, + "regex": "/([^:]+):.*/", + "skipUrlSync": false, + "sort": 1, + "tagValuesQuery": "", + "tags": [], + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "allValue": null, + "current": {}, + "datasource": "Prometheus", + "definition": "label_values(traefik_service_requests_total, protocol)", + "hide": 0, + "includeAll": true, + "label": "Service:", + "multi": true, + "name": "protocol", + "options": [], + "query": "label_values(traefik_service_requests_total, protocol)", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "tagValuesQuery": "", + "tags": [], + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "auto": true, + "auto_count": 30, + "auto_min": "10s", + "current": { + "text": "30m", + "value": "30m" + }, + "hide": 0, + "label": "Interval", + "name": "interval", + "options": [ + { + "selected": false, + "text": "auto", + "value": "$__auto_interval_interval" + }, + { + "selected": false, + "text": "1m", + "value": "1m" + }, + { + "selected": false, + "text": "10m", + "value": "10m" + }, + { + "selected": true, + "text": "30m", + "value": "30m" + }, + { + "selected": false, + "text": "1h", + "value": "1h" + }, + { + "selected": false, + "text": "6h", + "value": "6h" + }, + { + "selected": false, + "text": "12h", + "value": "12h" + }, + { + "selected": false, + "text": "1d", + "value": "1d" + }, + { + "selected": false, + "text": "7d", + "value": "7d" + }, + { + "selected": false, + "text": "14d", + "value": "14d" + }, + { + "selected": false, + "text": "30d", + "value": "30d" + } + ], + "query": "1m,10m,30m,1h,6h,12h,1d,7d,14d,30d", + "refresh": 2, + "skipUrlSync": false, + "type": "interval" + } + ] + }, + "time": { + "from": "now-1h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, + "timezone": "", + "title": "Traefik2", + "uid": "3ipsWfViz", + "version": 13, + "description": "Dashboard for Traefik2/Prometheus, based upon the one by ichasco. Updated for Traefik2 metric names." +} diff --git a/ee/server/services/.config/prometheus/prometheus.yml b/ee/server/services/.config/prometheus/prometheus.yml index 7e72b2c5c41d0..45102dd914f10 100644 --- a/ee/server/services/.config/prometheus/prometheus.yml +++ b/ee/server/services/.config/prometheus/prometheus.yml @@ -1,14 +1,11 @@ scrape_configs: # if you just have single or multiple, but static instances - you # can use the static configuration below. -# - job_name: rocketchat_static -# static_configs: -# - targets: -# - authorization-service:3030 -# - account-service:3030 -# - presence-service:3030 -# - ddp-streamer-service:3030 - # - stream-hub-service:3030 +- job_name: rocketchat_static + static_configs: + - targets: + - cadvisor:8080 + # If you use a Docker-based setup make sure to use the DNS # discovery to always include all available application instances @@ -21,7 +18,7 @@ scrape_configs: "account-service", "presence-service", "ddp-streamer-service", - "stream-hub-service:", + "stream-hub-service", ] type: A port: 9458 diff --git a/ee/server/services/.config/traefik/traefik.toml b/ee/server/services/.config/traefik/traefik.toml index b01f9ebe28c74..17456bfe99cec 100644 --- a/ee/server/services/.config/traefik/traefik.toml +++ b/ee/server/services/.config/traefik/traefik.toml @@ -4,6 +4,8 @@ logLevel = "ERROR" defaultEntryPoints = ["http"] [entryPoints] + [entryPoints.metrics] + address = ":9458" [entryPoints.http] address = ":80" # [entryPoints.http.redirect] @@ -22,6 +24,12 @@ endpoint = "unix:///var/run/docker.sock" watch = true exposedByDefault = false +[metrics] + [metrics.prometheus] + buckets = [0.1,0.3,1.2,5.0] + addEntryPointsLabels = true + addServicesLabels = true + # [acme] # email = "YOUR_EMAIL@gmail.com" # storage = "acme.json" diff --git a/ee/server/services/docker-compose.yml b/ee/server/services/docker-compose.yml index 09ea726e635c6..da808687e616c 100644 --- a/ee/server/services/docker-compose.yml +++ b/ee/server/services/docker-compose.yml @@ -145,3 +145,17 @@ services: - "traefik.enable=true" - "traefik.frontend.rule=Host:traefik.${HOST_NAME}" - "traefik.port=8080" + + cadvisor: + image: google/cadvisor + container_name: cadvisor + volumes: + # -/:/rootfs:ro + # -/var/run:/var/run:ro + # -/sys:/sys:ro + - /var/lib/docker/:/var/lib/docker:ro + # -/dev/disk/:/dev/disk:ro + ports: + - 8080:8080 + # --privileged \ + # --device=/dev/kmsg \ From 4fe5d7f1360dd64f256cfc1708cacc67e29ebdde Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Sun, 20 Sep 2020 15:08:47 -0300 Subject: [PATCH 060/198] Use Traefik to redirect websocket calls to docker host 3000 --- .../.config/prometheus/prometheus.yml | 1 + .../.config/traefik/servers/localhost.yml | 20 ++++++++++ .../services/.config/traefik/traefik.toml | 39 ------------------- ee/server/services/README.md | 14 ++++++- ee/server/services/docker-compose.yml | 24 +++++++----- 5 files changed, 49 insertions(+), 49 deletions(-) create mode 100644 ee/server/services/.config/traefik/servers/localhost.yml delete mode 100644 ee/server/services/.config/traefik/traefik.toml diff --git a/ee/server/services/.config/prometheus/prometheus.yml b/ee/server/services/.config/prometheus/prometheus.yml index 45102dd914f10..c5d3d43d89e7b 100644 --- a/ee/server/services/.config/prometheus/prometheus.yml +++ b/ee/server/services/.config/prometheus/prometheus.yml @@ -19,6 +19,7 @@ scrape_configs: "presence-service", "ddp-streamer-service", "stream-hub-service", + "traefik", ] type: A port: 9458 diff --git a/ee/server/services/.config/traefik/servers/localhost.yml b/ee/server/services/.config/traefik/servers/localhost.yml new file mode 100644 index 0000000000000..1a51c75859229 --- /dev/null +++ b/ee/server/services/.config/traefik/servers/localhost.yml @@ -0,0 +1,20 @@ +http: + routers: + router2: + rule: Host(`localhost`) && PathPrefix(`/sockjs/`, `/websocket/`) + service: ddp-streamer-service-services@docker + priority: 2 + entryPoints: + - web + router1: + rule: Host(`localhost`) + service: service1 + priority: 1 + entryPoints: + - web + + services: + service1: + loadBalancer: + servers: + - url: http://host.docker.internal:3000 diff --git a/ee/server/services/.config/traefik/traefik.toml b/ee/server/services/.config/traefik/traefik.toml deleted file mode 100644 index 17456bfe99cec..0000000000000 --- a/ee/server/services/.config/traefik/traefik.toml +++ /dev/null @@ -1,39 +0,0 @@ -debug = false - -logLevel = "ERROR" -defaultEntryPoints = ["http"] - -[entryPoints] - [entryPoints.metrics] - address = ":9458" - [entryPoints.http] - address = ":80" -# [entryPoints.http.redirect] -# entryPoint = "https" -# [entryPoints.https] -# address = ":443" -# [entryPoints.https.tls] -# [[entryPoints.https.tls.certificates]] -# certFile = "/etc/certs/certs-cert.pem" -# keyFile = "/etc/certs/certs-key.pem" - -[retry] - -[docker] -endpoint = "unix:///var/run/docker.sock" -watch = true -exposedByDefault = false - -[metrics] - [metrics.prometheus] - buckets = [0.1,0.3,1.2,5.0] - addEntryPointsLabels = true - addServicesLabels = true - -# [acme] -# email = "YOUR_EMAIL@gmail.com" -# storage = "acme.json" -# entryPoint = "https" -# onHostRule = true -# [acme.httpChallenge] -# entryPoint = "http" diff --git a/ee/server/services/README.md b/ee/server/services/README.md index c97ac6ed0d09c..a23f6e8e85d89 100644 --- a/ee/server/services/README.md +++ b/ee/server/services/README.md @@ -14,4 +14,16 @@ It's used to route the the domain on port 80 to the **DDP Streamer Service** and Used to collect metrics from the micro-services ### Grafana -Used to expose metrics from prometheus as dashboards \ No newline at end of file +Used to expose metrics from prometheus as dashboards + +### Nats +Used for the communication of the microservices + +## Build containers +`npm run build-containers` will build the typescript files and generate the containers + +## Running with docker-compose +`docker-compose up --remove-orphans` will run all the micro-services, still need to run MongoDB and Rocket.Chat Core separated + +### Running rocket.chat core +`MONGO_URL=mongodb://localhost:27017/rocketchat MONGO_OPLOG_URL=mongodb://localhost:27017/local TRANSPORTER=nats://localhost:4222 MOLECULER_LOG_LEVEL=debug meteor` diff --git a/ee/server/services/docker-compose.yml b/ee/server/services/docker-compose.yml index da808687e616c..69c9ac6fc9ae7 100644 --- a/ee/server/services/docker-compose.yml +++ b/ee/server/services/docker-compose.yml @@ -128,23 +128,29 @@ services: # - 'traefik.frontend.redirect.entryPoint=https' traefik: - image: traefik:alpine + image: traefik container_name: traefik command: - - --configFile=/traefik.toml - - --web - - --logLevel=INFO + - --entrypoints.web.address=:80 + # - --entrypoints.websecure.address=:443 + - --api=true + - --providers.docker=true + - --providers.docker.exposedbydefault=false + - --providers.file.directory=/servers/ ports: - - "80:80" - # - "443:443" + - 80:80 + # - 443:443 volumes: - /var/run/docker.sock:/var/run/docker.sock - - ./.config/traefik/traefik.toml:/traefik.toml + - ./.config/traefik/servers/:/servers/ # - ./.config/traefik/acme.json:/acme.json labels: - "traefik.enable=true" - - "traefik.frontend.rule=Host:traefik.${HOST_NAME}" - - "traefik.port=8080" + # Dashboard + - "traefik.http.routers.traefik.rule=Host(`traefik.${HOST_NAME}`)" + - "traefik.http.routers.traefik.service=api@internal" + - "traefik.http.routers.traefik.entrypoints=web" + cadvisor: image: google/cadvisor From 7d88d221f40e65dcd5524e4c809678acdad9ae0a Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Sun, 20 Sep 2020 17:51:51 -0300 Subject: [PATCH 061/198] Remove unused file --- .../services/DDPStreamer/ClientMobile.ts | 118 ------------------ 1 file changed, 118 deletions(-) delete mode 100644 ee/server/services/DDPStreamer/ClientMobile.ts diff --git a/ee/server/services/DDPStreamer/ClientMobile.ts b/ee/server/services/DDPStreamer/ClientMobile.ts deleted file mode 100644 index c8fdd5cf7a9fb..0000000000000 --- a/ee/server/services/DDPStreamer/ClientMobile.ts +++ /dev/null @@ -1,118 +0,0 @@ -import WebSocket from 'ws'; - -import { Client } from './Client'; -import { DDP_EVENTS, WS_ERRORS, WS_ERRORS_MESSAGES, TIMEOUT } from './constants'; -import { SERVER_ID } from './Server'; -import { server } from './configureServer'; -import { IPacket } from './types/IPacket'; - -export class ClientMobile extends Client { - constructor( - public ws: WebSocket, - ) { - super(ws); - - this.renewTimeout(TIMEOUT / 1000); - this.ws.on('message', this.handler); - this.ws.on('close', (...args) => { - server.emit(DDP_EVENTS.DISCONNECTED, this); - this.emit('close', ...args); - this.subscriptions.clear(); - clearTimeout(this.timeout); - }); - - // Meteor thing - this.ws.send('o'); - - server.emit(DDP_EVENTS.CONNECTED, this); - - this.ws.on('message', () => this.renewTimeout(TIMEOUT)); - - this.once('message', ({ msg }) => { - if (msg !== DDP_EVENTS.CONNECT) { - return this.ws.close(WS_ERRORS.CLOSE_PROTOCOL_ERROR, WS_ERRORS_MESSAGES.CLOSE_PROTOCOL_ERROR); - } - return this.send( - server.serialize({ [DDP_EVENTS.MSG]: DDP_EVENTS.CONNECTED, session: this.session }), - ); - }); - - this.send(SERVER_ID); - } - - process(action: string, packet: IPacket): void { - switch (action) { - case DDP_EVENTS.PING: - this.pong(packet.id); - break; - case DDP_EVENTS.METHOD: - if (!packet.method) { - return this.ws.close(WS_ERRORS.CLOSE_PROTOCOL_ERROR); - } - if (!packet.id) { - return this.ws.close(WS_ERRORS.CLOSE_PROTOCOL_ERROR); - } - server.callMethod(this, packet); - break; - case DDP_EVENTS.SUSBCRIBE: - if (!packet.name) { - return this.ws.close(WS_ERRORS.CLOSE_PROTOCOL_ERROR); - } - if (!packet.id) { - return this.ws.close(WS_ERRORS.CLOSE_PROTOCOL_ERROR); - } - server.callSubscribe(this, packet); - break; - case DDP_EVENTS.UNSUBSCRIBE: - if (!packet.id) { - return this.ws.close(WS_ERRORS.CLOSE_PROTOCOL_ERROR); - } - const subscription = this.subscriptions.get(packet.id); - if (!subscription) { - return; - } - subscription.stop(); - break; - } - } - - closeTimeout = (): void => { - this.ws.close(WS_ERRORS.TIMEOUT, WS_ERRORS_MESSAGES.TIMEOUT); - }; - - ping(id?: string): void { - this.send(server.serialize({ [DDP_EVENTS.MSG]: DDP_EVENTS.PING, ...id && { [DDP_EVENTS.ID]: id } })); - } - - pong(id?: string): void { - this.send(server.serialize({ [DDP_EVENTS.MSG]: DDP_EVENTS.PONG, ...id && { [DDP_EVENTS.ID]: id } })); - } - - handleIdle = (): void => { - this.ping(); - this.timeout = setTimeout(this.closeTimeout, TIMEOUT); - }; - - renewTimeout(timeout = TIMEOUT): void { - clearTimeout(this.timeout); - this.timeout = setTimeout(this.handleIdle, timeout); - } - - handler = async (payload: string): Promise => { - try { - const packet = server.parse(payload); - this.emit('message', packet); - this.process(packet.msg, packet); - } catch (err) { - return this.ws.close( - WS_ERRORS.UNSUPPORTED_DATA, - WS_ERRORS_MESSAGES.UNSUPPORTED_DATA, - ); - } - }; - - send(payload: string): void { - // Meteor format - return this.ws.send(`a${ JSON.stringify([payload]) }`); - } -} From c9b0507778f549d7108ffa50d96252365cbf3553 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Tue, 22 Sep 2020 17:02:17 -0300 Subject: [PATCH 062/198] Add TODOs --- ee/server/services/DDPStreamer/streams/presence.ts | 1 + ee/server/services/DDPStreamer/streams/room.ts | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/ee/server/services/DDPStreamer/streams/presence.ts b/ee/server/services/DDPStreamer/streams/presence.ts index a18c037b6d9f1..fb3c7e87d77c7 100644 --- a/ee/server/services/DDPStreamer/streams/presence.ts +++ b/ee/server/services/DDPStreamer/streams/presence.ts @@ -3,4 +3,5 @@ import { STREAM_NAMES } from '../constants'; export const userpresence = new Stream(STREAM_NAMES.PRESENCE); +// TODO remove, not used userpresence.allowRead('all'); diff --git a/ee/server/services/DDPStreamer/streams/room.ts b/ee/server/services/DDPStreamer/streams/room.ts index d1df351ef1dc1..075bbf5c431c2 100644 --- a/ee/server/services/DDPStreamer/streams/room.ts +++ b/ee/server/services/DDPStreamer/streams/room.ts @@ -2,5 +2,8 @@ import { Stream } from '../Streamer'; import { STREAM_NAMES } from '../constants'; export const streamRoom = new Stream(STREAM_NAMES.NOTIFY_ROOM); -streamRoom.allowWrite('all'); + +streamRoom.allowWrite('none'); + +// TODO canAccessRoom? streamRoom.allowRead('all'); From 7ed522bdf86ea7bb7b2ecd9923fcbf2475132e7f Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Tue, 22 Sep 2020 17:05:59 -0300 Subject: [PATCH 063/198] Revert changes --- .../build_and_test.yml | 0 .github/workflows/codeql-analysis.yml | 52 +++++++++++++++++++ .../stale.yml | 0 3 files changed, 52 insertions(+) rename .github/{workflows_disabled => workflows}/build_and_test.yml (100%) create mode 100644 .github/workflows/codeql-analysis.yml rename .github/{workflows_disabled => workflows}/stale.yml (100%) diff --git a/.github/workflows_disabled/build_and_test.yml b/.github/workflows/build_and_test.yml similarity index 100% rename from .github/workflows_disabled/build_and_test.yml rename to .github/workflows/build_and_test.yml diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000000000..da9570060ed49 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,52 @@ +name: "Code scanning - action" + +on: + push: + pull_request: + schedule: + - cron: '0 13 * * *' + +jobs: + CodeQL-Build: + + # CodeQL runs on ubuntu-latest and windows-latest + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + with: + # We must fetch at least the immediate parents so that if this is + # a pull request then we can checkout the head. + fetch-depth: 2 + + # If this run was triggered by a pull request event, then checkout + # the head of the pull request instead of the merge commit. + - run: git checkout HEAD^2 + if: ${{ github.event_name == 'pull_request' }} + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + # Override language selection by uncommenting this and choosing your languages + with: + languages: javascript + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v1 + + # â„šī¸ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # âœī¸ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/.github/workflows_disabled/stale.yml b/.github/workflows/stale.yml similarity index 100% rename from .github/workflows_disabled/stale.yml rename to .github/workflows/stale.yml From b7ecafa70b5bc63d5eee965a7f6dc90aea9e145b Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Tue, 22 Sep 2020 18:05:29 -0300 Subject: [PATCH 064/198] Adjust some TODOs --- ee/server/services/DDPStreamer/Server.ts | 2 +- ee/server/services/DDPStreamer/configureServer.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ee/server/services/DDPStreamer/Server.ts b/ee/server/services/DDPStreamer/Server.ts index df68ac76aed39..4ce857a60cbc4 100644 --- a/ee/server/services/DDPStreamer/Server.ts +++ b/ee/server/services/DDPStreamer/Server.ts @@ -19,7 +19,7 @@ export const SERVER_ID = ejson.stringify({ server_id: '0' }); const methods = Symbol('methods'); const subscriptions = Symbol('subscriptions'); -// TODO: remove, not used by current rocket.chat versions +// TODO: remove, web-client still receives the object of the logged in user, verify if that works // export const User = new Publish('user'); export class Server extends EventEmitter { diff --git a/ee/server/services/DDPStreamer/configureServer.ts b/ee/server/services/DDPStreamer/configureServer.ts index 5e02fd1d971c3..847588cb7ccaf 100644 --- a/ee/server/services/DDPStreamer/configureServer.ts +++ b/ee/server/services/DDPStreamer/configureServer.ts @@ -5,7 +5,7 @@ import { Server } from './Server'; export const server = new Server(); -// TODO: remove, not used by current rocket.chat versions +// TODO: remove, this was replaced by stream-notify-user/[user-id]/userData // server.subscribe('userData', async function(publication) { // if (!publication.uid) { // throw new Error('user should be connected'); @@ -17,18 +17,18 @@ export const server = new Server(); // publication.ready(); // }); -// TODO: remove, not used by current rocket.chat versions +// TODO: remove, this was replaced by stream-notify-logged/user-status, check if sending this data // server.subscribe('activeUsers', function(publication) { // publication.ready(); // }); server.subscribe('meteor.loginServiceConfiguration', function(pub) { - // TODO implement? + // TODO implement to be compatible with meteor's web client pub.ready(); }); server.subscribe('meteor_autoupdate_clientVersions', function(pub) { - // TODO implement? + // TODO implement to be compatible with meteor's web client pub.ready(); }); From 98b0ddc129c3bfcd9947ea4e9a4649d7227b85ed Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Wed, 23 Sep 2020 11:50:12 -0300 Subject: [PATCH 065/198] Rename methods to use the same as used by Meteor --- ee/server/services/DDPStreamer/Client.ts | 4 ++-- ee/server/services/DDPStreamer/Server.ts | 8 ++++---- ee/server/services/DDPStreamer/Streamer.ts | 2 +- ee/server/services/DDPStreamer/configureServer.ts | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/ee/server/services/DDPStreamer/Client.ts b/ee/server/services/DDPStreamer/Client.ts index dbd13246b7447..23083bea8ae3c 100644 --- a/ee/server/services/DDPStreamer/Client.ts +++ b/ee/server/services/DDPStreamer/Client.ts @@ -62,11 +62,11 @@ export class Client extends EventEmitter { } async callMethod(packet: IPacket): Promise { - this.chain = this.chain.then(() => server.callMethod(this, packet)).catch(); + this.chain = this.chain.then(() => server.call(this, packet)).catch(); } async callSubscribe(packet: IPacket): Promise { - this.chain = this.chain.then(() => server.callSubscribe(this, packet)).catch(); + this.chain = this.chain.then(() => server.subscribe(this, packet)).catch(); } process(action: string, packet: IPacket): void { diff --git a/ee/server/services/DDPStreamer/Server.ts b/ee/server/services/DDPStreamer/Server.ts index 4ce857a60cbc4..05d18c19faab5 100644 --- a/ee/server/services/DDPStreamer/Server.ts +++ b/ee/server/services/DDPStreamer/Server.ts @@ -34,7 +34,7 @@ export class Server extends EventEmitter { return ejson.parse(payload); } - async callMethod(client: Client, packet: IPacket): Promise { + async call(client: Client, packet: IPacket): Promise { try { if (!this[methods].has(packet.method)) { throw new Error(`Method '${ packet.method }' doesn't exist`); @@ -61,7 +61,7 @@ export class Server extends EventEmitter { }); } - async callSubscribe(client: Client, packet: IPacket): Promise { + async subscribe(client: Client, packet: IPacket): Promise { try { if (!this[subscriptions].has(packet.name)) { throw new Error(`Subscription '${ packet.name }' doesn't exist`); @@ -79,7 +79,7 @@ export class Server extends EventEmitter { } } - subscribe(name: string, fn: SubscriptionFn): void { + publish(name: string, fn: SubscriptionFn): void { if (this[subscriptions].has(name)) { return; } @@ -87,7 +87,7 @@ export class Server extends EventEmitter { } stream(stream: string, fn: SubscriptionFn): void { - return this.subscribe(`stream-${ stream }`, fn); + return this.publish(`stream-${ stream }`, fn); } result(client: Client, { id }: IPacket, result?: any, error?: string): void { diff --git a/ee/server/services/DDPStreamer/Streamer.ts b/ee/server/services/DDPStreamer/Streamer.ts index c108164f3c127..afefa7f3fd62d 100644 --- a/ee/server/services/DDPStreamer/Streamer.ts +++ b/ee/server/services/DDPStreamer/Streamer.ts @@ -201,7 +201,7 @@ export class Stream extends EventEmitter { async iniPublication(): Promise { const p = this[publish].bind(this); const initMethod = this.initMethod.bind(this); - server.subscribe(this.subscriptionName, function(publication, _client, eventName, options) { + server.publish(this.subscriptionName, function(publication, _client, eventName, options) { initMethod(publication); return p(publication, eventName, options); }); diff --git a/ee/server/services/DDPStreamer/configureServer.ts b/ee/server/services/DDPStreamer/configureServer.ts index 847588cb7ccaf..e94a97fd9f4ec 100644 --- a/ee/server/services/DDPStreamer/configureServer.ts +++ b/ee/server/services/DDPStreamer/configureServer.ts @@ -22,12 +22,12 @@ export const server = new Server(); // publication.ready(); // }); -server.subscribe('meteor.loginServiceConfiguration', function(pub) { +server.publish('meteor.loginServiceConfiguration', function(pub) { // TODO implement to be compatible with meteor's web client pub.ready(); }); -server.subscribe('meteor_autoupdate_clientVersions', function(pub) { +server.publish('meteor_autoupdate_clientVersions', function(pub) { // TODO implement to be compatible with meteor's web client pub.ready(); }); From 82feba1ce4202667230a2a14b73292e1f77bff95 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Wed, 23 Sep 2020 15:12:26 -0300 Subject: [PATCH 066/198] Fix OplogHandle typings --- app/models/server/models/_oplogHandle.ts | 17 +++++++++-------- server/main.d.ts | 8 ++++++++ 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/app/models/server/models/_oplogHandle.ts b/app/models/server/models/_oplogHandle.ts index 74d50bf6c2342..ca45f8dd6135a 100644 --- a/app/models/server/models/_oplogHandle.ts +++ b/app/models/server/models/_oplogHandle.ts @@ -1,13 +1,13 @@ import { Meteor } from 'meteor/meteor'; import { Promise } from 'meteor/promise'; -import { MongoInternals } from 'meteor/mongo'; +import { MongoInternals, OplogHandle } from 'meteor/mongo'; import semver from 'semver'; import s from 'underscore.string'; import { MongoClient, Cursor, Timestamp, Db } from 'mongodb'; import { urlParser } from './_oplogUrlParser'; -class OplogHandle { +class CustomOplogHandle { dbName: string; client: MongoClient; @@ -28,7 +28,7 @@ class OplogHandle { return storageEngine?.name === 'wiredTiger' && semver.satisfies(semver.coerce(version) || '', '>=3.6.0'); } - async start(): Promise { + async start(): Promise { this.usingChangeStream = await this.isChangeStreamAvailable(); const oplogUrl = this.usingChangeStream ? process.env.MONGO_URL : process.env.MONGO_OPLOG_URL; @@ -159,7 +159,7 @@ class OplogHandle { } } -let oplogHandle: Promise; +let oplogHandle: Promise; // @ts-ignore // eslint-disable-next-line no-undef @@ -167,19 +167,20 @@ if (Package['disable-oplog']) { const { mongo } = MongoInternals.defaultRemoteCollectionDriver(); try { Promise.await(mongo.db.admin().command({ replSetGetStatus: 1 })); - oplogHandle = Promise.await(new OplogHandle().start()); + oplogHandle = Promise.await(new CustomOplogHandle().start()); } catch (e) { console.error(e.message); } } -export const getOplogHandle = async (): Promise => { +export const getOplogHandle = async (): Promise => { if (oplogHandle) { return oplogHandle; } const { mongo } = MongoInternals.defaultRemoteCollectionDriver(); - if (mongo._oplogHandle?.onOplogEntry) { - return mongo._oplogHandle; + if (!mongo._oplogHandle?.onOplogEntry) { + return; } + return mongo._oplogHandle; }; diff --git a/server/main.d.ts b/server/main.d.ts index 299dd593fe650..20d1e6835b665 100644 --- a/server/main.d.ts +++ b/server/main.d.ts @@ -87,8 +87,16 @@ declare module 'meteor/mongo' { interface RemoteCollectionDriver { mongo: MongoConnection; } + interface OplogHandle { + stop(): void; + onOplogEntry(trigger: Record, callback: Function): void; + onSkippedEntries(callback: Function): void; + waitUntilCaughtUp(): void; + _defineTooFarBehind(value: number): void; + } interface MongoConnection { db: Db; + _oplogHandle: OplogHandle; } namespace MongoInternals { From 4d706c0019a75ceeef5450a959c643ce488471f7 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Thu, 24 Sep 2020 19:21:44 -0300 Subject: [PATCH 067/198] Streamer service --- app/lib/server/functions/setStatusText.js | 28 ++++++++-------- imports/users-presence/server/activeUsers.js | 12 +++---- server/main.d.ts | 5 +++ server/sdk/index.ts | 4 ++- server/sdk/types/IStreamer.ts | 14 ++++++++ server/services/startup.ts | 3 ++ server/services/streamer/service.ts | 34 ++++++++++++++++++++ 7 files changed, 80 insertions(+), 20 deletions(-) create mode 100644 server/sdk/types/IStreamer.ts create mode 100644 server/services/streamer/service.ts diff --git a/app/lib/server/functions/setStatusText.js b/app/lib/server/functions/setStatusText.js index 1db1020bc18e9..63d036b60e29e 100644 --- a/app/lib/server/functions/setStatusText.js +++ b/app/lib/server/functions/setStatusText.js @@ -1,11 +1,11 @@ import { Meteor } from 'meteor/meteor'; import s from 'underscore.string'; -import { Users } from '../../../models'; +import { Users } from '../../../models/server'; import { Users as UsersRaw } from '../../../models/server/raw'; -import { Notifications } from '../../../notifications'; -import { hasPermission } from '../../../authorization'; +import { hasPermission } from '../../../authorization/server'; import { RateLimiter } from '../lib'; +import { Streamer } from '../../../../server/sdk'; // mirror of object in /imports/startup/client/listenActiveUsers.js - keep updated const STATUS_MAP = { @@ -28,12 +28,13 @@ export const _setStatusTextPromise = async function(userId, statusText) { await UsersRaw.updateStatusText(user._id, statusText); - Notifications.notifyLogged('user-status', [ - user._id, - user.username, - STATUS_MAP[user.status], + const { _id: uid, username } = user; + Streamer.sendUserStatus({ + uid, + username, + status: STATUS_MAP[user.status], statusText, - ]); + }); return true; }; @@ -59,12 +60,13 @@ export const _setStatusText = function(userId, statusText) { Users.updateStatusText(user._id, statusText); user.statusText = statusText; - Notifications.notifyLogged('user-status', [ - user._id, - user.username, - STATUS_MAP[user.status], + const { _id: uid, username } = user; + Streamer.sendUserStatus({ + uid, + username, + status: STATUS_MAP[user.status], statusText, - ]); + }); return true; }; diff --git a/imports/users-presence/server/activeUsers.js b/imports/users-presence/server/activeUsers.js index 53c5430090844..62b091ae5c40a 100644 --- a/imports/users-presence/server/activeUsers.js +++ b/imports/users-presence/server/activeUsers.js @@ -1,7 +1,7 @@ import { UserPresenceEvents } from 'meteor/konecty:user-presence'; -import { Notifications } from '../../../app/notifications/server'; import { settings } from '../../../app/settings/server'; +import { Streamer } from '../../../server/sdk'; // mirror of object in /imports/startup/client/listenActiveUsers.js - keep updated export const STATUS_MAP = { @@ -13,19 +13,19 @@ export const STATUS_MAP = { export const setUserStatus = (user, status/* , statusConnection*/) => { const { - _id, + _id: uid, username, statusText, } = user; // since this callback can be called by only one instance in the cluster // we need to broadcast the change to all instances - Notifications.notifyLogged('user-status', [ - _id, + Streamer.sendUserStatus({ + uid, username, - STATUS_MAP[status], + status: STATUS_MAP[status], statusText, - ]); + }); }; let TroubleshootDisablePresenceBroadcast; diff --git a/server/main.d.ts b/server/main.d.ts index 20d1e6835b665..110e86aacabf5 100644 --- a/server/main.d.ts +++ b/server/main.d.ts @@ -38,6 +38,11 @@ declare module 'meteor/meteor' { details?: string | undefined | Record; } + interface Streamer { + allowWrite(allow: string): void; + allowRead(allow: string): void; + } + const server: any; interface MethodThisType { diff --git a/server/sdk/index.ts b/server/sdk/index.ts index d042dc4642b78..64ff9b5472aad 100644 --- a/server/sdk/index.ts +++ b/server/sdk/index.ts @@ -6,11 +6,13 @@ import { IServiceContext } from './types/ServiceClass'; import { IPresence } from './types/IPresence'; import { IAccount } from './types/IAccount'; import { ILicense } from './types/ILicense'; +import { IStreamer } from './types/IStreamer'; -// TODO try not having to duplicate the namespace 'authorization' here +// TODO think in a way to not have to pass the service name to proxify here as well export const Authorization = proxify('authorization'); export const Presence = proxify('presence'); export const Account = proxify('accounts'); export const License = proxify('license'); +export const Streamer = proxify('streamer'); export const asyncLocalStorage = new AsyncLocalStorage(); diff --git a/server/sdk/types/IStreamer.ts b/server/sdk/types/IStreamer.ts new file mode 100644 index 0000000000000..67e7c8b291adb --- /dev/null +++ b/server/sdk/types/IStreamer.ts @@ -0,0 +1,14 @@ +import { IServiceClass } from './ServiceClass'; + +export enum STATUS_MAP { + OFFLINE = 0, + ONLINE = 1, + AWAY = 2, + BUDY = 3, +} + +export interface IStreamer extends IServiceClass { + notifyAll(eventName: string, ...args: any[]): void; + notifyUser(uid: string, eventName: string, ...args: any[]): void; + sendUserStatus({ uid, username, status, statusText }: { uid: string; username: string; status: STATUS_MAP; statusText?: string }): void; +} diff --git a/server/services/startup.ts b/server/services/startup.ts index 97a046e048ab1..94de95c8de898 100644 --- a/server/services/startup.ts +++ b/server/services/startup.ts @@ -1,6 +1,9 @@ import { MongoInternals } from 'meteor/mongo'; +import { Meteor } from 'meteor/meteor'; import { api } from '../sdk/api'; import { Authorization } from './authorization/service'; +import { Streamer } from './streamer/service'; api.registerService(new Authorization(MongoInternals.defaultRemoteCollectionDriver().mongo.db)); +api.registerService(new Streamer(Meteor.Streamer)); diff --git a/server/services/streamer/service.ts b/server/services/streamer/service.ts new file mode 100644 index 0000000000000..a153d888fcea3 --- /dev/null +++ b/server/services/streamer/service.ts @@ -0,0 +1,34 @@ +import { ServiceClass } from '../../sdk/types/ServiceClass'; +import { IStreamer, STATUS_MAP } from '../../sdk/types/IStreamer'; +// import { Notifications } from '../../../app/notifications/server'; + +export class Streamer extends ServiceClass implements IStreamer { + protected name = 'streamer'; + + private streamLogged: any; + + constructor(Streamer: any) { + super(); + + this.streamLogged = new Streamer('notify-logged'); + this.streamLogged.allowWrite('none'); + this.streamLogged.allowRead('logged'); + } + + sendUserStatus({ uid, username, status, statusText }: { uid: string; username: string; status: STATUS_MAP; statusText?: string }): void { + this.streamLogged.emit('user-status', [ + uid, + username, + status, + statusText, + ]); + } + + notifyAll(eventName: string, ...args: any[]): void { + console.log('notifyAll', eventName, args); + } + + notifyUser(uid: string, eventName: string, ...args: any[]): void { + console.log('notifyUser', uid, eventName, args); + } +} From 46de01cd9a578e330e6fb9239d3120e6742c7693 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Fri, 25 Sep 2020 14:54:31 -0300 Subject: [PATCH 068/198] Send notifications for permissions, settings and avatar --- .../server/streamer/permissions/emitter.js | 14 ++-------- app/ldap/server/sync.js | 3 +- app/lib/server/functions/setRoomAvatar.js | 4 +-- app/lib/server/functions/setUserAvatar.js | 4 +-- server/methods/resetAvatar.js | 7 ++--- server/publications/settings/emitter.js | 5 ++-- server/sdk/types/IStreamer.ts | 4 +++ server/services/streamer/service.ts | 28 +++++++++++++++++++ 8 files changed, 46 insertions(+), 23 deletions(-) diff --git a/app/authorization/server/streamer/permissions/emitter.js b/app/authorization/server/streamer/permissions/emitter.js index aafdaf2741899..27e36154fdf5d 100644 --- a/app/authorization/server/streamer/permissions/emitter.js +++ b/app/authorization/server/streamer/permissions/emitter.js @@ -1,8 +1,8 @@ import Settings from '../../../../models/server/models/Settings'; -import { Notifications } from '../../../../notifications/server'; import { CONSTANTS } from '../../../lib'; import Permissions from '../../../../models/server/models/Permissions'; import { clearCache } from '../../functions/hasPermission'; +import { Streamer } from '../../../../../server/sdk'; Permissions.on('change', ({ clientAction, id, data, diff }) => { if (diff && Object.keys(diff).length === 1 && diff._updatedAt) { @@ -22,11 +22,7 @@ Permissions.on('change', ({ clientAction, id, data, diff }) => { clearCache(); - Notifications.notifyLoggedInThisInstance( - 'permissions-changed', - clientAction, - data, - ); + Streamer.sendPermission({ clientAction, data }); if (data.level && data.level === CONSTANTS.SETTINGS_LEVEL) { // if the permission changes, the effect on the visible settings depends on the role affected. @@ -36,10 +32,6 @@ Permissions.on('change', ({ clientAction, id, data, diff }) => { if (!setting) { return; } - Notifications.notifyLoggedInThisInstance( - 'private-settings-changed', - 'updated', - setting, - ); + Streamer.sendPrivateSetting({ clientAction: 'updated', setting }); } }); diff --git a/app/ldap/server/sync.js b/app/ldap/server/sync.js index 0c3db0eca372c..6187d8e50d5fc 100644 --- a/app/ldap/server/sync.js +++ b/app/ldap/server/sync.js @@ -15,6 +15,7 @@ import { _setRealName, _setUsername } from '../../lib'; import { templateVarHandler } from '../../utils'; import { FileUpload } from '../../file-upload'; import { addUserToRoom, removeUserFromRoom, createRoom } from '../../lib/server/functions'; +import { Streamer } from '../../../server/sdk'; export const logger = new Logger('LDAPSync', {}); @@ -365,7 +366,7 @@ function syncUserAvatar(user, ldapUser) { fileStore.insert(file, rs, (err, result) => { Meteor.setTimeout(function() { Users.setAvatarData(user._id, 'ldap', result.etag); - Notifications.notifyLogged('updateAvatar', { username: user.username, etag: result.etag }); + Streamer.sendUserAvatarUpdate({ username: user.username, etag: result.etag }); }, 500); }); }); diff --git a/app/lib/server/functions/setRoomAvatar.js b/app/lib/server/functions/setRoomAvatar.js index cbcd6431eccbd..4f3d6ae93fdec 100644 --- a/app/lib/server/functions/setRoomAvatar.js +++ b/app/lib/server/functions/setRoomAvatar.js @@ -2,8 +2,8 @@ import { Meteor } from 'meteor/meteor'; import { RocketChatFile } from '../../../file'; import { FileUpload } from '../../../file-upload'; -import { Notifications } from '../../../notifications'; import { Rooms, Avatars, Messages } from '../../../models/server'; +import { Streamer } from '../../../../server/sdk'; export const setRoomAvatar = function(rid, dataURI, user) { const fileStore = FileUpload.getStore('Avatars'); @@ -33,7 +33,7 @@ export const setRoomAvatar = function(rid, dataURI, user) { } Rooms.setAvatarData(rid, 'upload', result.etag); Messages.createRoomSettingsChangedWithTypeRoomIdMessageAndUser('room_changed_avatar', rid, '', user); - Notifications.notifyLogged('updateAvatar', { rid, etag: result.etag }); + Streamer.sendRoomAvatarUpdate({ rid, etag: result.etag }); }, 500); }); }; diff --git a/app/lib/server/functions/setUserAvatar.js b/app/lib/server/functions/setUserAvatar.js index ae6c54fe1eff2..ad19fe44f55c9 100644 --- a/app/lib/server/functions/setUserAvatar.js +++ b/app/lib/server/functions/setUserAvatar.js @@ -4,7 +4,7 @@ import { HTTP } from 'meteor/http'; import { RocketChatFile } from '../../../file'; import { FileUpload } from '../../../file-upload'; import { Users } from '../../../models'; -import { Notifications } from '../../../notifications'; +import { Streamer } from '../../../../server/sdk'; export const setUserAvatar = function(user, dataURI, contentType, service) { let encoding; @@ -64,7 +64,7 @@ export const setUserAvatar = function(user, dataURI, contentType, service) { fileStore.insert(file, buffer, (err, result) => { Meteor.setTimeout(function() { Users.setAvatarData(user._id, service, result.etag); - Notifications.notifyLogged('updateAvatar', { username: user.username, etag: result.etag }); + Streamer.sendUserAvatarUpdate({ username: user.username, etag: result.etag }); }, 500); }); }; diff --git a/server/methods/resetAvatar.js b/server/methods/resetAvatar.js index 48a6c3b8e3c4c..569c706beaa3f 100644 --- a/server/methods/resetAvatar.js +++ b/server/methods/resetAvatar.js @@ -4,8 +4,8 @@ import { DDPRateLimiter } from 'meteor/ddp-rate-limiter'; import { FileUpload } from '../../app/file-upload'; import { Users } from '../../app/models/server'; import { settings } from '../../app/settings'; -import { Notifications } from '../../app/notifications'; import { hasPermission } from '../../app/authorization/server'; +import { Streamer } from '../sdk'; Meteor.methods({ resetAvatar(userId) { @@ -43,10 +43,7 @@ Meteor.methods({ FileUpload.getStore('Avatars').deleteByName(user.username); Users.unsetAvatarData(user._id); - Notifications.notifyLogged('updateAvatar', { - username: user.username, - etag: null, - }); + Streamer.sendUserAvatarUpdate({ username: user.username, etag: null }); }, }); diff --git a/server/publications/settings/emitter.js b/server/publications/settings/emitter.js index ecd3cd05a1189..77bfc706cd370 100644 --- a/server/publications/settings/emitter.js +++ b/server/publications/settings/emitter.js @@ -2,6 +2,7 @@ import { Settings } from '../../../app/models/server'; import { Notifications } from '../../../app/notifications/server'; import { hasAtLeastOnePermission } from '../../../app/authorization/server'; import { SettingsEvents } from '../../../app/settings/server/functions/settings'; +import { Streamer } from '../../sdk'; Settings.on('change', ({ clientAction, id, data, diff }) => { if (diff && Object.keys(diff).length === 1 && diff._updatedAt) { // avoid useless changes @@ -29,7 +30,7 @@ Settings.on('change', ({ clientAction, id, data, diff }) => { if (setting.public === true) { Notifications.notifyAllInThisInstance('public-settings-changed', clientAction, value); } - Notifications.notifyLoggedInThisInstance('private-settings-changed', clientAction, setting); + Streamer.sendPrivateSetting({ clientAction, setting }); break; } @@ -39,7 +40,7 @@ Settings.on('change', ({ clientAction, id, data, diff }) => { if (setting && setting.public === true) { Notifications.notifyAllInThisInstance('public-settings-changed', clientAction, { _id: id }); } - Notifications.notifyLoggedInThisInstance('private-settings-changed', clientAction, { _id: id }); + Streamer.sendPrivateSetting({ clientAction, setting: { _id: id } }); break; } } diff --git a/server/sdk/types/IStreamer.ts b/server/sdk/types/IStreamer.ts index 67e7c8b291adb..b999cdaf2d505 100644 --- a/server/sdk/types/IStreamer.ts +++ b/server/sdk/types/IStreamer.ts @@ -11,4 +11,8 @@ export interface IStreamer extends IServiceClass { notifyAll(eventName: string, ...args: any[]): void; notifyUser(uid: string, eventName: string, ...args: any[]): void; sendUserStatus({ uid, username, status, statusText }: { uid: string; username: string; status: STATUS_MAP; statusText?: string }): void; + sendPermission({ clientAction, data }: any): void; + sendPrivateSetting({ clientAction, setting }: any): void; + sendUserAvatarUpdate({ username, etag }: { username: string; etag?: string }): void; + sendRoomAvatarUpdate({ rid, etag }: { rid: string; etag?: string }): void; } diff --git a/server/services/streamer/service.ts b/server/services/streamer/service.ts index a153d888fcea3..5daaf4ccd8bf5 100644 --- a/server/services/streamer/service.ts +++ b/server/services/streamer/service.ts @@ -24,6 +24,34 @@ export class Streamer extends ServiceClass implements IStreamer { ]); } + sendPermission({ clientAction, data }: any): void { + this.streamLogged.emitWithoutBroadcast('permissions-changed', [ + clientAction, + data, + ]); + } + + sendPrivateSetting({ clientAction, setting }: any): void { + this.streamLogged.emitWithoutBroadcast('private-settings-changed', [ + clientAction, + setting, + ]); + } + + sendUserAvatarUpdate({ username, etag }: { username: string; etag?: string }): void { + this.streamLogged.emit('updateAvatar', [ + username, + etag, + ]); + } + + sendRoomAvatarUpdate({ rid, etag }: { rid: string; etag?: string }): void { + this.streamLogged.emit('updateAvatar', [ + rid, + etag, + ]); + } + notifyAll(eventName: string, ...args: any[]): void { console.log('notifyAll', eventName, args); } From d20289c0a5661d41c7087ee98832c452fbb3ce18 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Fri, 25 Sep 2020 15:11:28 -0300 Subject: [PATCH 069/198] Use streamer for roles updates --- app/authorization/server/methods/addUserToRole.js | 4 ++-- app/authorization/server/methods/removeUserFromRole.js | 4 ++-- app/authorization/server/methods/saveRole.js | 4 ++-- app/ldap/server/sync.js | 5 ++--- server/methods/addRoomLeader.js | 4 ++-- server/methods/addRoomModerator.js | 4 ++-- server/methods/addRoomOwner.js | 4 ++-- server/methods/removeRoomLeader.js | 4 ++-- server/methods/removeRoomModerator.js | 4 ++-- server/methods/removeRoomOwner.js | 4 ++-- server/sdk/types/IStreamer.ts | 1 + server/services/streamer/service.ts | 6 ++++++ 12 files changed, 27 insertions(+), 21 deletions(-) diff --git a/app/authorization/server/methods/addUserToRole.js b/app/authorization/server/methods/addUserToRole.js index b9d8302bc0b39..7a5f2df5bdaee 100644 --- a/app/authorization/server/methods/addUserToRole.js +++ b/app/authorization/server/methods/addUserToRole.js @@ -3,8 +3,8 @@ import _ from 'underscore'; import { Users, Roles } from '../../../models/server'; import { settings } from '../../../settings/server'; -import { Notifications } from '../../../notifications/server'; import { hasPermission } from '../functions/hasPermission'; +import { Streamer } from '../../../../server/sdk'; Meteor.methods({ 'authorization:addUserToRole'(roleName, username, scope) { @@ -50,7 +50,7 @@ Meteor.methods({ const add = Roles.addUserRoles(user._id, roleName, scope); if (settings.get('UI_DisplayRoles')) { - Notifications.notifyLogged('roles-change', { + Streamer.sendRoleUpdate({ type: 'added', _id: roleName, u: { diff --git a/app/authorization/server/methods/removeUserFromRole.js b/app/authorization/server/methods/removeUserFromRole.js index 4ccec8b2a7c65..4b7c5347b3b8a 100644 --- a/app/authorization/server/methods/removeUserFromRole.js +++ b/app/authorization/server/methods/removeUserFromRole.js @@ -3,8 +3,8 @@ import _ from 'underscore'; import { Roles } from '../../../models/server'; import { settings } from '../../../settings/server'; -import { Notifications } from '../../../notifications/server'; import { hasPermission } from '../functions/hasPermission'; +import { Streamer } from '../../../../server/sdk'; Meteor.methods({ 'authorization:removeUserFromRole'(roleName, username, scope) { @@ -55,7 +55,7 @@ Meteor.methods({ const remove = Roles.removeUserRoles(user._id, roleName, scope); if (settings.get('UI_DisplayRoles')) { - Notifications.notifyLogged('roles-change', { + Streamer.sendRoleUpdate({ type: 'removed', _id: roleName, u: { diff --git a/app/authorization/server/methods/saveRole.js b/app/authorization/server/methods/saveRole.js index 64cb3437c9dff..da5594f42efa1 100644 --- a/app/authorization/server/methods/saveRole.js +++ b/app/authorization/server/methods/saveRole.js @@ -2,9 +2,9 @@ import { Meteor } from 'meteor/meteor'; import { Roles } from '../../../models/server'; import { settings } from '../../../settings/server'; -import { Notifications } from '../../../notifications/server'; import { hasPermission } from '../functions/hasPermission'; import { rolesStreamer } from '../lib/streamer'; +import { Streamer } from '../../../../server/sdk'; Meteor.methods({ 'authorization:saveRole'(roleData) { @@ -27,7 +27,7 @@ Meteor.methods({ const update = Roles.createOrUpdate(roleData.name, roleData.scope, roleData.description, false, roleData.mandatory2fa); if (settings.get('UI_DisplayRoles')) { - Notifications.notifyLogged('roles-change', { + Streamer.sendRoleUpdate({ type: 'changed', _id: roleData.name, }); diff --git a/app/ldap/server/sync.js b/app/ldap/server/sync.js index 6187d8e50d5fc..aea4828a5c9dc 100644 --- a/app/ldap/server/sync.js +++ b/app/ldap/server/sync.js @@ -8,7 +8,6 @@ import LDAP from './ldap'; import { callbacks } from '../../callbacks/server'; import { RocketChatFile } from '../../file'; import { settings } from '../../settings'; -import { Notifications } from '../../notifications'; import { Users, Roles, Rooms, Subscriptions } from '../../models'; import { Logger } from '../../logger'; import { _setRealName, _setUsername } from '../../lib'; @@ -265,7 +264,7 @@ export function mapLdapGroupsToUserRoles(ldap, ldapUser, user) { const del = Roles.removeUserRoles(user._id, roleName); if (settings.get('UI_DisplayRoles') && del) { - Notifications.notifyLogged('roles-change', { + Streamer.sendRoleUpdate({ type: 'removed', _id: roleName, u: { @@ -413,7 +412,7 @@ export function syncUserData(user, ldapUser, ldap) { for (const roleName of userRoles) { const add = Roles.addUserRoles(user._id, roleName); if (settings.get('UI_DisplayRoles') && add) { - Notifications.notifyLogged('roles-change', { + Streamer.sendRoleUpdate({ type: 'added', _id: roleName, u: { diff --git a/server/methods/addRoomLeader.js b/server/methods/addRoomLeader.js index 548abf36a9845..aac0cb3b899b2 100644 --- a/server/methods/addRoomLeader.js +++ b/server/methods/addRoomLeader.js @@ -4,7 +4,7 @@ import { check } from 'meteor/check'; import { hasPermission } from '../../app/authorization'; import { Users, Subscriptions, Messages } from '../../app/models'; import { settings } from '../../app/settings'; -import { Notifications } from '../../app/notifications'; +import { Streamer } from '../sdk'; Meteor.methods({ addRoomLeader(rid, userId) { @@ -58,7 +58,7 @@ Meteor.methods({ }); if (settings.get('UI_DisplayRoles')) { - Notifications.notifyLogged('roles-change', { + Streamer.sendRoleUpdate({ type: 'added', _id: 'leader', u: { diff --git a/server/methods/addRoomModerator.js b/server/methods/addRoomModerator.js index 04a593768e2a5..7a2d7ed878bac 100644 --- a/server/methods/addRoomModerator.js +++ b/server/methods/addRoomModerator.js @@ -4,7 +4,7 @@ import { check } from 'meteor/check'; import { hasPermission } from '../../app/authorization'; import { Users, Subscriptions, Messages } from '../../app/models'; import { settings } from '../../app/settings'; -import { Notifications } from '../../app/notifications'; +import { Streamer } from '../sdk'; Meteor.methods({ addRoomModerator(rid, userId) { @@ -58,7 +58,7 @@ Meteor.methods({ }); if (settings.get('UI_DisplayRoles')) { - Notifications.notifyLogged('roles-change', { + Streamer.sendRoleUpdate({ type: 'added', _id: 'moderator', u: { diff --git a/server/methods/addRoomOwner.js b/server/methods/addRoomOwner.js index b6e0080f3a130..9e06f3650caf6 100644 --- a/server/methods/addRoomOwner.js +++ b/server/methods/addRoomOwner.js @@ -4,7 +4,7 @@ import { check } from 'meteor/check'; import { hasPermission } from '../../app/authorization'; import { Users, Subscriptions, Messages } from '../../app/models'; import { settings } from '../../app/settings'; -import { Notifications } from '../../app/notifications'; +import { Streamer } from '../sdk'; Meteor.methods({ addRoomOwner(rid, userId) { @@ -58,7 +58,7 @@ Meteor.methods({ }); if (settings.get('UI_DisplayRoles')) { - Notifications.notifyLogged('roles-change', { + Streamer.sendRoleUpdate({ type: 'added', _id: 'owner', u: { diff --git a/server/methods/removeRoomLeader.js b/server/methods/removeRoomLeader.js index b0576c3d207ee..21cff4e995a19 100644 --- a/server/methods/removeRoomLeader.js +++ b/server/methods/removeRoomLeader.js @@ -4,7 +4,7 @@ import { check } from 'meteor/check'; import { hasPermission } from '../../app/authorization'; import { Users, Subscriptions, Messages } from '../../app/models'; import { settings } from '../../app/settings'; -import { Notifications } from '../../app/notifications'; +import { Streamer } from '../sdk'; Meteor.methods({ removeRoomLeader(rid, userId) { @@ -58,7 +58,7 @@ Meteor.methods({ }); if (settings.get('UI_DisplayRoles')) { - Notifications.notifyLogged('roles-change', { + Streamer.sendRoleUpdate({ type: 'removed', _id: 'leader', u: { diff --git a/server/methods/removeRoomModerator.js b/server/methods/removeRoomModerator.js index ae00b1aeb2f09..dab2c5d990915 100644 --- a/server/methods/removeRoomModerator.js +++ b/server/methods/removeRoomModerator.js @@ -4,7 +4,7 @@ import { check } from 'meteor/check'; import { hasPermission } from '../../app/authorization'; import { Users, Subscriptions, Messages } from '../../app/models'; import { settings } from '../../app/settings'; -import { Notifications } from '../../app/notifications'; +import { Streamer } from '../sdk'; Meteor.methods({ removeRoomModerator(rid, userId) { @@ -58,7 +58,7 @@ Meteor.methods({ }); if (settings.get('UI_DisplayRoles')) { - Notifications.notifyLogged('roles-change', { + Streamer.sendRoleUpdate({ type: 'removed', _id: 'moderator', u: { diff --git a/server/methods/removeRoomOwner.js b/server/methods/removeRoomOwner.js index 2dbd82e6cb857..2e88c506ea2e8 100644 --- a/server/methods/removeRoomOwner.js +++ b/server/methods/removeRoomOwner.js @@ -4,7 +4,7 @@ import { check } from 'meteor/check'; import { hasPermission, getUsersInRole } from '../../app/authorization'; import { Users, Subscriptions, Messages } from '../../app/models'; import { settings } from '../../app/settings'; -import { Notifications } from '../../app/notifications'; +import { Streamer } from '../sdk'; Meteor.methods({ removeRoomOwner(rid, userId) { @@ -65,7 +65,7 @@ Meteor.methods({ }); if (settings.get('UI_DisplayRoles')) { - Notifications.notifyLogged('roles-change', { + Streamer.sendRoleUpdate({ type: 'removed', _id: 'owner', u: { diff --git a/server/sdk/types/IStreamer.ts b/server/sdk/types/IStreamer.ts index b999cdaf2d505..88924dab1367a 100644 --- a/server/sdk/types/IStreamer.ts +++ b/server/sdk/types/IStreamer.ts @@ -15,4 +15,5 @@ export interface IStreamer extends IServiceClass { sendPrivateSetting({ clientAction, setting }: any): void; sendUserAvatarUpdate({ username, etag }: { username: string; etag?: string }): void; sendRoomAvatarUpdate({ rid, etag }: { rid: string; etag?: string }): void; + sendRoleUpdate(update: Record): void; } diff --git a/server/services/streamer/service.ts b/server/services/streamer/service.ts index 5daaf4ccd8bf5..ec81f9595b62b 100644 --- a/server/services/streamer/service.ts +++ b/server/services/streamer/service.ts @@ -52,6 +52,12 @@ export class Streamer extends ServiceClass implements IStreamer { ]); } + sendRoleUpdate(update: Record): void { + this.streamLogged.emit('roles-change', [ + update, + ]); + } + notifyAll(eventName: string, ...args: any[]): void { console.log('notifyAll', eventName, args); } From a0db4b8f5a93841a678c94bc6ba024c279de2932 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Fri, 25 Sep 2020 15:31:30 -0300 Subject: [PATCH 070/198] Use streamer for custom status, custom emojis and some user actions --- .../server/methods/deleteEmojiCustom.js | 11 +++--- .../server/methods/insertOrUpdateEmoji.js | 6 ++-- .../server/methods/uploadEmojiCustom.js | 4 +-- app/lib/server/functions/deleteUser.js | 4 +-- app/lib/server/functions/setRealName.js | 4 +-- app/lib/server/functions/setUsername.js | 4 +-- .../server/methods/deleteCustomUserStatus.js | 15 ++++---- .../methods/insertOrUpdateUserStatus.js | 6 ++-- server/sdk/types/IStreamer.ts | 6 ++++ server/services/streamer/service.ts | 36 +++++++++++++++++++ 10 files changed, 66 insertions(+), 30 deletions(-) diff --git a/app/emoji-custom/server/methods/deleteEmojiCustom.js b/app/emoji-custom/server/methods/deleteEmojiCustom.js index 0e5b383f7188a..34dfee1134878 100644 --- a/app/emoji-custom/server/methods/deleteEmojiCustom.js +++ b/app/emoji-custom/server/methods/deleteEmojiCustom.js @@ -1,27 +1,24 @@ import { Meteor } from 'meteor/meteor'; +import { Streamer } from '../../../../server/sdk'; import { hasPermission } from '../../../authorization'; import { EmojiCustom } from '../../../models'; -import { Notifications } from '../../../notifications'; import { RocketChatFileEmojiCustomInstance } from '../startup/emoji-custom'; Meteor.methods({ deleteEmojiCustom(emojiID) { - let emoji = null; - - if (hasPermission(this.userId, 'manage-emoji')) { - emoji = EmojiCustom.findOneById(emojiID); - } else { + if (!hasPermission(this.userId, 'manage-emoji')) { throw new Meteor.Error('not_authorized'); } + const emoji = EmojiCustom.findOneById(emojiID); if (emoji == null) { throw new Meteor.Error('Custom_Emoji_Error_Invalid_Emoji', 'Invalid emoji', { method: 'deleteEmojiCustom' }); } RocketChatFileEmojiCustomInstance.deleteFile(encodeURIComponent(`${ emoji.name }.${ emoji.extension }`)); EmojiCustom.removeById(emojiID); - Notifications.notifyLogged('deleteEmojiCustom', { emojiData: emoji }); + Streamer.sendDeleteCustomEmoji(emoji); return true; }, diff --git a/app/emoji-custom/server/methods/insertOrUpdateEmoji.js b/app/emoji-custom/server/methods/insertOrUpdateEmoji.js index 0e633ecb83c8d..223ea23f40f4f 100644 --- a/app/emoji-custom/server/methods/insertOrUpdateEmoji.js +++ b/app/emoji-custom/server/methods/insertOrUpdateEmoji.js @@ -4,9 +4,9 @@ import s from 'underscore.string'; import limax from 'limax'; import { hasPermission } from '../../../authorization'; -import { Notifications } from '../../../notifications'; import { EmojiCustom } from '../../../models'; import { RocketChatFileEmojiCustomInstance } from '../startup/emoji-custom'; +import { Streamer } from '../../../../server/sdk'; Meteor.methods({ insertOrUpdateEmoji(emojiData) { @@ -73,7 +73,7 @@ Meteor.methods({ const _id = EmojiCustom.create(createEmoji); - Notifications.notifyLogged('updateEmojiCustom', { emojiData: createEmoji }); + Streamer.sendUpdateCustomEmoji(createEmoji); return _id; } @@ -107,7 +107,7 @@ Meteor.methods({ EmojiCustom.setAliases(emojiData._id, []); } - Notifications.notifyLogged('updateEmojiCustom', { emojiData }); + Streamer.sendUpdateCustomEmoji(emojiData); return true; }, diff --git a/app/emoji-custom/server/methods/uploadEmojiCustom.js b/app/emoji-custom/server/methods/uploadEmojiCustom.js index 3dc49bf31579d..3c18acf70e53e 100644 --- a/app/emoji-custom/server/methods/uploadEmojiCustom.js +++ b/app/emoji-custom/server/methods/uploadEmojiCustom.js @@ -1,10 +1,10 @@ import { Meteor } from 'meteor/meteor'; import limax from 'limax'; -import { Notifications } from '../../../notifications'; import { hasPermission } from '../../../authorization'; import { RocketChatFile } from '../../../file'; import { RocketChatFileEmojiCustomInstance } from '../startup/emoji-custom'; +import { Streamer } from '../../../../server/sdk'; Meteor.methods({ uploadEmojiCustom(binaryContent, contentType, emojiData) { @@ -21,7 +21,7 @@ Meteor.methods({ RocketChatFileEmojiCustomInstance.deleteFile(encodeURIComponent(`${ emojiData.name }.${ emojiData.extension }`)); const ws = RocketChatFileEmojiCustomInstance.createWriteStream(encodeURIComponent(`${ emojiData.name }.${ emojiData.extension }`), contentType); ws.on('end', Meteor.bindEnvironment(() => - Meteor.setTimeout(() => Notifications.notifyLogged('updateEmojiCustom', { emojiData }), 500), + Meteor.setTimeout(() => Streamer.sendUpdateCustomEmoji(emojiData), 500), )); rs.pipe(ws); diff --git a/app/lib/server/functions/deleteUser.js b/app/lib/server/functions/deleteUser.js index 6f4f4b16f5e0c..da8d35c72fc1a 100644 --- a/app/lib/server/functions/deleteUser.js +++ b/app/lib/server/functions/deleteUser.js @@ -4,11 +4,11 @@ import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; import { FileUpload } from '../../../file-upload/server'; import { Users, Subscriptions, Messages, Rooms, Integrations, FederationServers } from '../../../models/server'; import { settings } from '../../../settings/server'; -import { Notifications } from '../../../notifications/server'; import { updateGroupDMsName } from './updateGroupDMsName'; import { relinquishRoomOwnerships } from './relinquishRoomOwnerships'; import { getSubscribedRoomsForUserWithDetails, shouldRemoveOrChangeOwner } from './getRoomsWithSingleOwner'; import { getUserSingleOwnedRooms } from './getUserSingleOwnedRooms'; +import { Streamer } from '../../../../server/sdk'; export const deleteUser = function(userId, confirmRelinquish = false) { const user = Users.findOneById(userId, { @@ -65,7 +65,7 @@ export const deleteUser = function(userId, confirmRelinquish = false) { } Integrations.disableByUserId(userId); // Disables all the integrations which rely on the user being deleted. - Notifications.notifyLogged('Users:Deleted', { userId }); + Streamer.sendUserDeleted(userId); } // Remove user from users database diff --git a/app/lib/server/functions/setRealName.js b/app/lib/server/functions/setRealName.js index 190fc1b739bc4..22e047c734e7d 100644 --- a/app/lib/server/functions/setRealName.js +++ b/app/lib/server/functions/setRealName.js @@ -3,9 +3,9 @@ import s from 'underscore.string'; import { Users } from '../../../models/server'; import { settings } from '../../../settings'; -import { Notifications } from '../../../notifications'; import { hasPermission } from '../../../authorization'; import { RateLimiter } from '../lib'; +import { Streamer } from '../../../../server/sdk'; export const _setRealName = function(userId, name, fullUser) { name = s.trim(name); @@ -30,7 +30,7 @@ export const _setRealName = function(userId, name, fullUser) { user.name = name; if (settings.get('UI_Use_Real_Name') === true) { - Notifications.notifyLogged('Users:NameChanged', { + Streamer.sendUserNameChanged({ _id: user._id, name: user.name, username: user.username, diff --git a/app/lib/server/functions/setUsername.js b/app/lib/server/functions/setUsername.js index 6e5f08cf39909..629f3b2930338 100644 --- a/app/lib/server/functions/setUsername.js +++ b/app/lib/server/functions/setUsername.js @@ -6,8 +6,8 @@ import { settings } from '../../../settings'; import { Users, Invites } from '../../../models/server'; import { hasPermission } from '../../../authorization'; import { RateLimiter } from '../lib'; -import { Notifications } from '../../../notifications/server'; import { addUserToRoom } from './addUserToRoom'; +import { Streamer } from '../../../../server/sdk'; import { checkUsernameAvailability, setUserAvatar, getAvatarSuggestionForUser } from '.'; @@ -76,7 +76,7 @@ export const _setUsername = function(userId, u, fullUser) { } } - Notifications.notifyLogged('Users:NameChanged', { + Streamer.sendUserNameChanged({ _id: user._id, name: user.name, username: user.username, diff --git a/app/user-status/server/methods/deleteCustomUserStatus.js b/app/user-status/server/methods/deleteCustomUserStatus.js index 4d947ff5732c3..6a92a989d267b 100644 --- a/app/user-status/server/methods/deleteCustomUserStatus.js +++ b/app/user-status/server/methods/deleteCustomUserStatus.js @@ -1,25 +1,22 @@ import { Meteor } from 'meteor/meteor'; -import { hasPermission } from '../../../authorization'; -import { Notifications } from '../../../notifications'; -import { CustomUserStatus } from '../../../models'; +import { hasPermission } from '../../../authorization/server'; +import { CustomUserStatus } from '../../../models/server'; +import { Streamer } from '../../../../server/sdk'; Meteor.methods({ deleteCustomUserStatus(userStatusID) { - let userStatus = null; - - if (hasPermission(this.userId, 'manage-user-status')) { - userStatus = CustomUserStatus.findOneById(userStatusID); - } else { + if (!hasPermission(this.userId, 'manage-user-status')) { throw new Meteor.Error('not_authorized'); } + const userStatus = CustomUserStatus.findOneById(userStatusID); if (userStatus == null) { throw new Meteor.Error('Custom_User_Status_Error_Invalid_User_Status', 'Invalid user status', { method: 'deleteCustomUserStatus' }); } CustomUserStatus.removeById(userStatusID); - Notifications.notifyLogged('deleteCustomUserStatus', { userStatusData: userStatus }); + Streamer.sendDeleteCustomUserStatus(userStatus); return true; }, diff --git a/app/user-status/server/methods/insertOrUpdateUserStatus.js b/app/user-status/server/methods/insertOrUpdateUserStatus.js index 07b4631173be4..6f9e3a8e784c2 100644 --- a/app/user-status/server/methods/insertOrUpdateUserStatus.js +++ b/app/user-status/server/methods/insertOrUpdateUserStatus.js @@ -2,8 +2,8 @@ import { Meteor } from 'meteor/meteor'; import s from 'underscore.string'; import { hasPermission } from '../../../authorization'; -import { Notifications } from '../../../notifications'; import { CustomUserStatus } from '../../../models'; +import { Streamer } from '../../../../server/sdk'; Meteor.methods({ insertOrUpdateUserStatus(userStatusData) { @@ -49,7 +49,7 @@ Meteor.methods({ const _id = CustomUserStatus.create(createUserStatus); - Notifications.notifyLogged('updateCustomUserStatus', { userStatusData: createUserStatus }); + Streamer.sendUpdateCustomUserStatus(createUserStatus); return _id; } @@ -63,7 +63,7 @@ Meteor.methods({ CustomUserStatus.setStatusType(userStatusData._id, userStatusData.statusType); } - Notifications.notifyLogged('updateCustomUserStatus', { userStatusData }); + Streamer.sendUpdateCustomUserStatus(userStatusData); return true; }, diff --git a/server/sdk/types/IStreamer.ts b/server/sdk/types/IStreamer.ts index 88924dab1367a..1dde967caedf1 100644 --- a/server/sdk/types/IStreamer.ts +++ b/server/sdk/types/IStreamer.ts @@ -16,4 +16,10 @@ export interface IStreamer extends IServiceClass { sendUserAvatarUpdate({ username, etag }: { username: string; etag?: string }): void; sendRoomAvatarUpdate({ rid, etag }: { rid: string; etag?: string }): void; sendRoleUpdate(update: Record): void; + sendDeleteCustomEmoji(emojiData: Record): void; + sendUpdateCustomEmoji(emojiData: Record): void; + sendUserDeleted(uid: string): void; + sendUserNameChanged(userData: Record): void; + sendDeleteCustomUserStatus(userStatusData: Record): void; + sendUpdateCustomUserStatus(userStatusData: Record): void; } diff --git a/server/services/streamer/service.ts b/server/services/streamer/service.ts index ec81f9595b62b..059f06ddedc01 100644 --- a/server/services/streamer/service.ts +++ b/server/services/streamer/service.ts @@ -15,6 +15,42 @@ export class Streamer extends ServiceClass implements IStreamer { this.streamLogged.allowRead('logged'); } + sendDeleteCustomEmoji(emojiData: Record): void { + this.streamLogged.emit('deleteEmojiCustom', [ + { emojiData }, + ]); + } + + sendUpdateCustomEmoji(emojiData: Record): void { + this.streamLogged.emit('updateEmojiCustom', [ + { emojiData }, + ]); + } + + sendUserDeleted(uid: string): void { + this.streamLogged.emit('Users:Deleted', [ + { userId: uid }, + ]); + } + + sendUserNameChanged(userData: Record): void { + this.streamLogged.emit('Users:NameChanged', [ + { userData }, + ]); + } + + sendDeleteCustomUserStatus(userStatusData: Record): void { + this.streamLogged.emit('deleteCustomUserStatus', [ + { userStatusData }, + ]); + } + + sendUpdateCustomUserStatus(userStatusData: Record): void { + this.streamLogged.emit('updateCustomUserStatus', [ + { userStatusData }, + ]); + } + sendUserStatus({ uid, username, status, statusText }: { uid: string; username: string; status: STATUS_MAP; statusText?: string }): void { this.streamLogged.emit('user-status', [ uid, From d4742c336a0798b29b7e1bd9c0b919ba19361a39 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Fri, 25 Sep 2020 15:42:37 -0300 Subject: [PATCH 071/198] Fix streamer payload --- server/services/streamer/service.ts | 36 ++++++++++++++--------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/server/services/streamer/service.ts b/server/services/streamer/service.ts index 059f06ddedc01..79812e0e38177 100644 --- a/server/services/streamer/service.ts +++ b/server/services/streamer/service.ts @@ -16,39 +16,39 @@ export class Streamer extends ServiceClass implements IStreamer { } sendDeleteCustomEmoji(emojiData: Record): void { - this.streamLogged.emit('deleteEmojiCustom', [ - { emojiData }, - ]); + this.streamLogged.emit('deleteEmojiCustom', { + emojiData, + }); } sendUpdateCustomEmoji(emojiData: Record): void { - this.streamLogged.emit('updateEmojiCustom', [ - { emojiData }, - ]); + this.streamLogged.emit('updateEmojiCustom', { + emojiData, + }); } sendUserDeleted(uid: string): void { - this.streamLogged.emit('Users:Deleted', [ - { userId: uid }, - ]); + this.streamLogged.emit('Users:Deleted', { + userId: uid, + }); } sendUserNameChanged(userData: Record): void { - this.streamLogged.emit('Users:NameChanged', [ - { userData }, - ]); + this.streamLogged.emit('Users:NameChanged', { + userData, + }); } sendDeleteCustomUserStatus(userStatusData: Record): void { - this.streamLogged.emit('deleteCustomUserStatus', [ - { userStatusData }, - ]); + this.streamLogged.emit('deleteCustomUserStatus', { + userStatusData, + }); } sendUpdateCustomUserStatus(userStatusData: Record): void { - this.streamLogged.emit('updateCustomUserStatus', [ - { userStatusData }, - ]); + this.streamLogged.emit('updateCustomUserStatus', { + userStatusData, + }); } sendUserStatus({ uid, username, status, statusText }: { uid: string; username: string; status: STATUS_MAP; statusText?: string }): void { From af419541e92a593fdf429d57261f2cc49087b0b3 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Fri, 25 Sep 2020 16:10:59 -0300 Subject: [PATCH 072/198] Fix others streams --- server/services/streamer/service.ts | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/server/services/streamer/service.ts b/server/services/streamer/service.ts index 79812e0e38177..a35e5c86fbd07 100644 --- a/server/services/streamer/service.ts +++ b/server/services/streamer/service.ts @@ -34,9 +34,7 @@ export class Streamer extends ServiceClass implements IStreamer { } sendUserNameChanged(userData: Record): void { - this.streamLogged.emit('Users:NameChanged', { - userData, - }); + this.streamLogged.emit('Users:NameChanged', userData); } sendDeleteCustomUserStatus(userStatusData: Record): void { @@ -61,37 +59,29 @@ export class Streamer extends ServiceClass implements IStreamer { } sendPermission({ clientAction, data }: any): void { - this.streamLogged.emitWithoutBroadcast('permissions-changed', [ - clientAction, - data, - ]); + this.streamLogged.emitWithoutBroadcast('permissions-changed', clientAction, data); } sendPrivateSetting({ clientAction, setting }: any): void { - this.streamLogged.emitWithoutBroadcast('private-settings-changed', [ - clientAction, - setting, - ]); + this.streamLogged.emitWithoutBroadcast('private-settings-changed', clientAction, setting); } sendUserAvatarUpdate({ username, etag }: { username: string; etag?: string }): void { - this.streamLogged.emit('updateAvatar', [ + this.streamLogged.emit('updateAvatar', { username, etag, - ]); + }); } sendRoomAvatarUpdate({ rid, etag }: { rid: string; etag?: string }): void { - this.streamLogged.emit('updateAvatar', [ + this.streamLogged.emit('updateAvatar', { rid, etag, - ]); + }); } sendRoleUpdate(update: Record): void { - this.streamLogged.emit('roles-change', [ - update, - ]); + this.streamLogged.emit('roles-change', update); } notifyAll(eventName: string, ...args: any[]): void { From be8fa706d5c4319421d3aae45ee1113b2ba1d6a7 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Fri, 25 Sep 2020 17:14:00 -0300 Subject: [PATCH 073/198] Declare meteor module --- typings.d.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/typings.d.ts b/typings.d.ts index 1362f56164200..2b8e55c3d078c 100644 --- a/typings.d.ts +++ b/typings.d.ts @@ -2,6 +2,7 @@ declare module 'meteor/rocketchat:tap-i18n'; declare module 'meteor/littledata:synced-cron'; declare module 'meteor/promise'; declare module 'meteor/ddp-common'; +declare module 'meteor/meteor'; declare module 'meteor/routepolicy'; declare module 'xml-encryption'; declare module 'webdav'; From cd319b8e29dbc4ffee67ce82c8011da7712b655f Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Fri, 25 Sep 2020 17:49:55 -0300 Subject: [PATCH 074/198] Undeclare meteor module --- server/services/startup.ts | 4 +++- typings.d.ts | 1 - 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/server/services/startup.ts b/server/services/startup.ts index 94de95c8de898..b4a2f3e209d3d 100644 --- a/server/services/startup.ts +++ b/server/services/startup.ts @@ -6,4 +6,6 @@ import { Authorization } from './authorization/service'; import { Streamer } from './streamer/service'; api.registerService(new Authorization(MongoInternals.defaultRemoteCollectionDriver().mongo.db)); -api.registerService(new Streamer(Meteor.Streamer)); + +// TODO how to make typescript know that "Meteor.Streamer" actually exists? +api.registerService(new Streamer((Meteor as any).Streamer)); diff --git a/typings.d.ts b/typings.d.ts index 2b8e55c3d078c..1362f56164200 100644 --- a/typings.d.ts +++ b/typings.d.ts @@ -2,7 +2,6 @@ declare module 'meteor/rocketchat:tap-i18n'; declare module 'meteor/littledata:synced-cron'; declare module 'meteor/promise'; declare module 'meteor/ddp-common'; -declare module 'meteor/meteor'; declare module 'meteor/routepolicy'; declare module 'xml-encryption'; declare module 'webdav'; From 290d0f0b47b6bd074eb016a45be5aacfdaac4da3 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Sat, 26 Sep 2020 13:13:13 -0300 Subject: [PATCH 075/198] Improve broaker loading to wait correctly on license check --- ee/server/broker.ts | 34 ++++++++++----------- ee/server/services/Account/service.ts | 4 +-- ee/server/services/Authorization/service.ts | 4 +-- ee/server/services/DDPStreamer/service.ts | 4 +-- ee/server/services/Presence/service.ts | 4 +-- ee/server/services/StreamHub/service.ts | 4 +-- server/sdk/lib/Api.ts | 1 + 7 files changed, 28 insertions(+), 27 deletions(-) diff --git a/ee/server/broker.ts b/ee/server/broker.ts index 33c4719131270..91767da2255ae 100644 --- a/ee/server/broker.ts +++ b/ee/server/broker.ts @@ -25,7 +25,9 @@ class NetworkBroker implements IBroker { private localBroker = new LocalBroker(); - private allowed = false; + private allowed: Promise; + + private started: Promise; private whitelist = { events: ['license.module'], @@ -34,10 +36,18 @@ class NetworkBroker implements IBroker { constructor(broker: ServiceBroker) { this.broker = broker; + + api.setBroker(this); + + this.started = this.broker.start(); + + this.allowed = License.hasLicense('scalability'); } async call(method: string, data: any): Promise { - if (!this.allowed && !this.whitelist.actions.includes(method)) { + await this.started; + + if (!(this.whitelist.actions.includes(method) || await this.allowed)) { return this.localBroker.call(method, data); } @@ -54,7 +64,7 @@ class NetworkBroker implements IBroker { instance.onEvent('license.module', async ({ module, valid }) => { if (module === 'scalability') { // Should we believe on the event only? Could it be a call from the CE version? - this.allowed = valid && await License.hasLicense('scalability'); + this.allowed = valid ? License.hasLicense('scalability') : Promise.resolve(false); // console.log('on license.module', { allowed: this.allowed }); } }); @@ -74,14 +84,6 @@ class NetworkBroker implements IBroker { } return [event, (data: any[]): void => fn(...data)]; })), - started: async (): Promise => { - if (name === 'license') { - return; - } - - this.allowed = await License.hasLicense('scalability'); - // console.log('on started', { allowed: this.allowed }); - }, }; if (!service.events || !service.actions) { @@ -111,8 +113,8 @@ class NetworkBroker implements IBroker { nodeID: ctx.nodeID, requestID: ctx.requestID, broker: this, - }, (): any => { - if (this.allowed || this.whitelist.actions.includes(`${ name }.${ method }`)) { + }, async (): Promise => { + if (this.whitelist.actions.includes(`${ name }.${ method }`) || await this.allowed) { return i[method](...ctx.params); } }); @@ -122,7 +124,7 @@ class NetworkBroker implements IBroker { } async broadcast(event: T, ...args: Parameters): Promise { - if (!this.allowed && !this.whitelist.events.includes(event)) { + if (!(this.whitelist.events.includes(event) || await this.allowed)) { return this.localBroker.broadcast(event, ...args); } return this.broker.broadcast(event, args); @@ -253,6 +255,4 @@ const network = new ServiceBroker({ }, }); -api.setBroker(new NetworkBroker(network)); - -network.start(); +new NetworkBroker(network); diff --git a/ee/server/services/Account/service.ts b/ee/server/services/Account/service.ts index 757f2262eb72a..2b0a781149651 100644 --- a/ee/server/services/Account/service.ts +++ b/ee/server/services/Account/service.ts @@ -1,6 +1,6 @@ +import '../../broker'; + import { api } from '../../../../server/sdk/api'; import { Account } from './Account'; -import '../../broker'; - api.registerService(new Account()); diff --git a/ee/server/services/Authorization/service.ts b/ee/server/services/Authorization/service.ts index 6fda34cd91183..4a1975fabfbec 100644 --- a/ee/server/services/Authorization/service.ts +++ b/ee/server/services/Authorization/service.ts @@ -1,9 +1,9 @@ +import '../../broker'; + import { api } from '../../../../server/sdk/api'; import { Authorization } from '../../../../server/services/authorization/service'; import { getConnection } from '../mongo'; -import '../../broker'; - getConnection().then((db) => { api.registerService(new Authorization(db)); }); diff --git a/ee/server/services/DDPStreamer/service.ts b/ee/server/services/DDPStreamer/service.ts index d2cfc846d3ceb..2dcaaadea5eec 100755 --- a/ee/server/services/DDPStreamer/service.ts +++ b/ee/server/services/DDPStreamer/service.ts @@ -1,6 +1,6 @@ +import '../../broker'; + import { api } from '../../../../server/sdk/api'; import { DDPStreamer } from './DDPStreamer'; -import '../../broker'; - api.registerService(new DDPStreamer()); diff --git a/ee/server/services/Presence/service.ts b/ee/server/services/Presence/service.ts index a76e6dd8a7da8..f6ae2f810ef74 100755 --- a/ee/server/services/Presence/service.ts +++ b/ee/server/services/Presence/service.ts @@ -1,6 +1,6 @@ +import '../../broker'; + import { api } from '../../../../server/sdk/api'; import { Presence } from './Presence'; -import '../../broker'; - api.registerService(new Presence()); diff --git a/ee/server/services/StreamHub/service.ts b/ee/server/services/StreamHub/service.ts index b9486a61f680e..ee9f9cdbf053b 100755 --- a/ee/server/services/StreamHub/service.ts +++ b/ee/server/services/StreamHub/service.ts @@ -1,6 +1,6 @@ +import '../../broker'; + import { api } from '../../../../server/sdk/api'; import { StreamHub } from './StreamHub'; -import '../../broker'; - api.registerService(new StreamHub()); diff --git a/server/sdk/lib/Api.ts b/server/sdk/lib/Api.ts index c73568be32c3b..11832963664e1 100644 --- a/server/sdk/lib/Api.ts +++ b/server/sdk/lib/Api.ts @@ -24,6 +24,7 @@ export class Api { } async call(method: string, data: any): Promise { + // console.log('api call', method, this.broker.constructor.name); return this.broker.call(method, data); } From beb55c3e982224ddccd1dd044950b828a4bcba82 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Sat, 26 Sep 2020 14:57:38 -0300 Subject: [PATCH 076/198] Implement publication `meteor_autoupdate_clientVersions` --- ee/server/services/DDPStreamer/DDPStreamer.ts | 5 +++ ee/server/services/DDPStreamer/Publication.ts | 8 +++++ ee/server/services/DDPStreamer/Server.ts | 22 +++++++++++++ .../services/DDPStreamer/configureServer.ts | 30 +++++++++++++++-- ee/server/services/ecosystem.config.js | 3 ++ server/sdk/index.ts | 2 ++ server/sdk/lib/Events.ts | 2 ++ server/sdk/types/IMeteor.ts | 15 +++++++++ server/services/meteor/service.ts | 32 +++++++++++++++++++ server/services/startup.ts | 2 ++ 10 files changed, 118 insertions(+), 3 deletions(-) create mode 100644 server/sdk/types/IMeteor.ts create mode 100644 server/services/meteor/service.ts diff --git a/ee/server/services/DDPStreamer/DDPStreamer.ts b/ee/server/services/DDPStreamer/DDPStreamer.ts index 22905b0efa367..446fca76a0aef 100644 --- a/ee/server/services/DDPStreamer/DDPStreamer.ts +++ b/ee/server/services/DDPStreamer/DDPStreamer.ts @@ -9,6 +9,7 @@ import { Client, MeteorClient } from './Client'; // import { STREAMER_EVENTS, STREAM_NAMES } from './constants'; import { isEmpty } from './lib/utils'; import { ServiceClass } from '../../../../server/sdk/types/ServiceClass'; +import { events } from './configureServer'; const { PORT: port = 4000, @@ -246,6 +247,10 @@ export class DDPStreamer extends ServiceClass { this.onEvent('role', (payload): void => { Streamer.streamRoles.emit('roles', payload); }); + + this.onEvent('meteor.autoUpdateClientVersionChanged', ({ record }): void => { + events.emit('meteor.autoUpdateClientVersionChanged', record); + }); } } diff --git a/ee/server/services/DDPStreamer/Publication.ts b/ee/server/services/DDPStreamer/Publication.ts index e69146c2142d3..fbde827d359f6 100644 --- a/ee/server/services/DDPStreamer/Publication.ts +++ b/ee/server/services/DDPStreamer/Publication.ts @@ -30,6 +30,14 @@ export class Publication extends EventEmitter { this.emit('stop', this.client, this.packet); } + added(collection: string, id: string, fields: any): void { + this.server.added(this.client, collection, id, fields); + } + + changed(collection: string, id: string, fields: any): void { + this.server.changed(this.client, collection, id, fields); + } + get uid(): string { return this.client.uid; } diff --git a/ee/server/services/DDPStreamer/Server.ts b/ee/server/services/DDPStreamer/Server.ts index 05d18c19faab5..21baf6ea6f72f 100644 --- a/ee/server/services/DDPStreamer/Server.ts +++ b/ee/server/services/DDPStreamer/Server.ts @@ -122,4 +122,26 @@ export class Server extends EventEmitter { this.serialize({ [DDP_EVENTS.MSG]: DDP_EVENTS.READY, [DDP_EVENTS.SUBSCRIPTIONS]: [packet.id] }), ); } + + added(client: Client, collection: string, id: string, fields: any): void { + return client.send( + this.serialize({ + [DDP_EVENTS.MSG]: DDP_EVENTS.ADDED, + [DDP_EVENTS.COLLECTION]: collection, + [DDP_EVENTS.ID]: id, + [DDP_EVENTS.FIELDS]: fields, + }), + ); + } + + changed(client: Client, collection: string, id: string, fields: any): void { + return client.send( + this.serialize({ + [DDP_EVENTS.MSG]: DDP_EVENTS.CHANGED, + [DDP_EVENTS.COLLECTION]: collection, + [DDP_EVENTS.ID]: id, + [DDP_EVENTS.FIELDS]: fields, + }), + ); + } } diff --git a/ee/server/services/DDPStreamer/configureServer.ts b/ee/server/services/DDPStreamer/configureServer.ts index e94a97fd9f4ec..10ccc349575cd 100644 --- a/ee/server/services/DDPStreamer/configureServer.ts +++ b/ee/server/services/DDPStreamer/configureServer.ts @@ -1,10 +1,15 @@ +import { EventEmitter } from 'events'; + import { DDP_EVENTS } from './constants'; -import { Account, Presence } from '../../../../server/sdk'; +import { Account, Presence, MeteorService } from '../../../../server/sdk'; import { USER_STATUS } from '../../../../definition/UserStatus'; import { Server } from './Server'; +import { AutoUpdateRecord } from '../../../../server/sdk/types/IMeteor'; export const server = new Server(); +export const events = new EventEmitter(); + // TODO: remove, this was replaced by stream-notify-user/[user-id]/userData // server.subscribe('userData', async function(publication) { // if (!publication.uid) { @@ -27,8 +32,27 @@ server.publish('meteor.loginServiceConfiguration', function(pub) { pub.ready(); }); -server.publish('meteor_autoupdate_clientVersions', function(pub) { - // TODO implement to be compatible with meteor's web client +const autoUpdateRecords = new Map(); + +MeteorService.getLastAutoUpdateClientVersions().then((records) => { + records.forEach((record) => autoUpdateRecords.set(record._id, record)); +}); + +const autoUpdateCollection = 'meteor_autoupdate_clientVersions'; +server.publish(autoUpdateCollection, function(pub) { + autoUpdateRecords.forEach((record) => pub.added(autoUpdateCollection, record._id, record)); + + const fn = (record: any): void => { + autoUpdateRecords.set(record._id, record); + pub.changed(autoUpdateCollection, record._id, record); + }; + + events.on('meteor.autoUpdateClientVersionChanged', fn); + + pub.once('stop', () => { + events.removeListener('meteor.autoUpdateClientVersionChanged', fn); + }); + pub.ready(); }); diff --git a/ee/server/services/ecosystem.config.js b/ee/server/services/ecosystem.config.js index 9b8c6699cdc17..9a7670901b37e 100644 --- a/ee/server/services/ecosystem.config.js +++ b/ee/server/services/ecosystem.config.js @@ -16,5 +16,8 @@ module.exports = { script: app.script || `ts-node ${ app.name }/service.ts`, watch: app.watch || ['.', '../broker.ts', '../../../server/sdk'], instances: 1, + env: { + MOLECULER_LOG_LEVEL: 'info', + }, })), }; diff --git a/server/sdk/index.ts b/server/sdk/index.ts index 64ff9b5472aad..c09ebc4bd53e3 100644 --- a/server/sdk/index.ts +++ b/server/sdk/index.ts @@ -7,6 +7,7 @@ import { IPresence } from './types/IPresence'; import { IAccount } from './types/IAccount'; import { ILicense } from './types/ILicense'; import { IStreamer } from './types/IStreamer'; +import { IMeteor } from './types/IMeteor'; // TODO think in a way to not have to pass the service name to proxify here as well export const Authorization = proxify('authorization'); @@ -14,5 +15,6 @@ export const Presence = proxify('presence'); export const Account = proxify('accounts'); export const License = proxify('license'); export const Streamer = proxify('streamer'); +export const MeteorService = proxify('meteor'); export const asyncLocalStorage = new AsyncLocalStorage(); diff --git a/server/sdk/lib/Events.ts b/server/sdk/lib/Events.ts index 68f6221eb725e..2cb5e9d66f61f 100644 --- a/server/sdk/lib/Events.ts +++ b/server/sdk/lib/Events.ts @@ -7,6 +7,7 @@ import { IRoom } from '../../../definition/IRoom'; import { ISetting } from '../../../definition/ISetting'; import { ISubscription } from '../../../definition/ISubscription'; import { IUser } from '../../../definition/IUser'; +import { AutoUpdateRecord } from '../types/IMeteor'; export type BufferList = ReturnType; @@ -22,4 +23,5 @@ export type EventSignatures = { 'user.name'(data: { action: string; user: Partial }): void; 'role'(data: {type: 'changed' | 'removed' } & Partial): void; 'license.module'(data: {module: string; valid: boolean}): void; + 'meteor.autoUpdateClientVersionChanged'(data: {record: AutoUpdateRecord}): void; } diff --git a/server/sdk/types/IMeteor.ts b/server/sdk/types/IMeteor.ts new file mode 100644 index 0000000000000..02197c76f198f --- /dev/null +++ b/server/sdk/types/IMeteor.ts @@ -0,0 +1,15 @@ +import { IServiceClass } from './ServiceClass'; + +export type AutoUpdateRecord = { + _id: string; + version: string; + versionRefreshable?: string; + versionNonRefreshable?: string; + assets?: [{ + url: string; + }]; +} + +export interface IMeteor extends IServiceClass { + getLastAutoUpdateClientVersions(): Promise; +} diff --git a/server/services/meteor/service.ts b/server/services/meteor/service.ts new file mode 100644 index 0000000000000..d8d580c0115f3 --- /dev/null +++ b/server/services/meteor/service.ts @@ -0,0 +1,32 @@ +import { Meteor } from 'meteor/meteor'; + +import { ServiceClass } from '../../sdk/types/ServiceClass'; +import { IMeteor, AutoUpdateRecord } from '../../sdk/types/IMeteor'; +import { api } from '../../sdk/api'; + + +const autoUpdateRecords = new Map(); + +Meteor.server.publish_handlers.meteor_autoupdate_clientVersions.call({ + added(_collection: string, id: string, version: AutoUpdateRecord) { + autoUpdateRecords.set(id, version); + }, + changed(_collection: string, id: string, version: AutoUpdateRecord) { + autoUpdateRecords.set(id, version); + api.broadcast('meteor.autoUpdateClientVersionChanged', { record: version }); + }, + onStop() { + // + }, + ready() { + // + }, +}); + +export class MeteorService extends ServiceClass implements IMeteor { + protected name = 'meteor'; + + async getLastAutoUpdateClientVersions(): Promise { + return [...autoUpdateRecords.values()]; + } +} diff --git a/server/services/startup.ts b/server/services/startup.ts index b4a2f3e209d3d..219ded7ac6eb2 100644 --- a/server/services/startup.ts +++ b/server/services/startup.ts @@ -4,8 +4,10 @@ import { Meteor } from 'meteor/meteor'; import { api } from '../sdk/api'; import { Authorization } from './authorization/service'; import { Streamer } from './streamer/service'; +import { MeteorService } from './meteor/service'; api.registerService(new Authorization(MongoInternals.defaultRemoteCollectionDriver().mongo.db)); +api.registerService(new MeteorService()); // TODO how to make typescript know that "Meteor.Streamer" actually exists? api.registerService(new Streamer((Meteor as any).Streamer)); From 754dd41977c6b9df46288f9afc3421884bdbf0aa Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Sat, 26 Sep 2020 16:05:18 -0300 Subject: [PATCH 077/198] Implement publication `meteor.loginServiceConfiguration` --- ee/server/services/DDPStreamer/DDPStreamer.ts | 4 +++ ee/server/services/DDPStreamer/Publication.ts | 4 +++ ee/server/services/DDPStreamer/Server.ts | 10 +++++++ .../services/DDPStreamer/configureServer.ts | 30 +++++++++++++++++-- ee/server/services/DDPStreamer/constants.ts | 1 + ee/server/services/StreamHub/StreamHub.ts | 4 +++ .../watchLoginServiceConfiguration.ts | 28 +++++++++++++++++ server/sdk/lib/Events.ts | 1 + server/sdk/types/IMeteor.ts | 2 ++ server/services/meteor/service.ts | 5 ++++ 10 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 ee/server/services/StreamHub/watchLoginServiceConfiguration.ts diff --git a/ee/server/services/DDPStreamer/DDPStreamer.ts b/ee/server/services/DDPStreamer/DDPStreamer.ts index 446fca76a0aef..0817c11699441 100644 --- a/ee/server/services/DDPStreamer/DDPStreamer.ts +++ b/ee/server/services/DDPStreamer/DDPStreamer.ts @@ -248,6 +248,10 @@ export class DDPStreamer extends ServiceClass { Streamer.streamRoles.emit('roles', payload); }); + this.onEvent('meteor.loginServiceConfiguration', ({ action, record }): void => { + events.emit('meteor.loginServiceConfiguration', action, record); + }); + this.onEvent('meteor.autoUpdateClientVersionChanged', ({ record }): void => { events.emit('meteor.autoUpdateClientVersionChanged', record); }); diff --git a/ee/server/services/DDPStreamer/Publication.ts b/ee/server/services/DDPStreamer/Publication.ts index fbde827d359f6..db762e10eab19 100644 --- a/ee/server/services/DDPStreamer/Publication.ts +++ b/ee/server/services/DDPStreamer/Publication.ts @@ -38,6 +38,10 @@ export class Publication extends EventEmitter { this.server.changed(this.client, collection, id, fields); } + removed(collection: string, id: string): void { + this.server.removed(this.client, collection, id); + } + get uid(): string { return this.client.uid; } diff --git a/ee/server/services/DDPStreamer/Server.ts b/ee/server/services/DDPStreamer/Server.ts index 21baf6ea6f72f..662c25bdf1130 100644 --- a/ee/server/services/DDPStreamer/Server.ts +++ b/ee/server/services/DDPStreamer/Server.ts @@ -144,4 +144,14 @@ export class Server extends EventEmitter { }), ); } + + removed(client: Client, collection: string, id: string): void { + return client.send( + this.serialize({ + [DDP_EVENTS.MSG]: DDP_EVENTS.REMOVED, + [DDP_EVENTS.COLLECTION]: collection, + [DDP_EVENTS.ID]: id, + }), + ); + } } diff --git a/ee/server/services/DDPStreamer/configureServer.ts b/ee/server/services/DDPStreamer/configureServer.ts index 10ccc349575cd..5eea4ab7c9e65 100644 --- a/ee/server/services/DDPStreamer/configureServer.ts +++ b/ee/server/services/DDPStreamer/configureServer.ts @@ -27,8 +27,34 @@ export const events = new EventEmitter(); // publication.ready(); // }); -server.publish('meteor.loginServiceConfiguration', function(pub) { - // TODO implement to be compatible with meteor's web client +const loginServiceConfigurationCollection = 'meteor_accounts_loginServiceConfiguration'; +const loginServiceConfigurationPublication = 'meteor.loginServiceConfiguration'; +const loginServices = new Map(); + +MeteorService.getLoginServiceConfiguration().then((records) => records.forEach((record) => loginServices.set(record._id, record))); + +server.publish(loginServiceConfigurationPublication, async function(pub) { + loginServices.forEach((record) => pub.added(loginServiceConfigurationCollection, record._id, record)); + + const fn = (action: string, record: any): void => { + switch (action) { + case 'added': + case 'changed': + loginServices.set(record._id, record); + pub[action](loginServiceConfigurationCollection, record._id, record); + break; + case 'removed': + loginServices.delete(record._id); + pub[action](loginServiceConfigurationCollection, record._id); + } + }; + + events.on(loginServiceConfigurationPublication, fn); + + pub.once('stop', () => { + events.removeListener(loginServiceConfigurationPublication, fn); + }); + pub.ready(); }); diff --git a/ee/server/services/DDPStreamer/constants.ts b/ee/server/services/DDPStreamer/constants.ts index e64679e57406c..e40f68e0a7606 100644 --- a/ee/server/services/DDPStreamer/constants.ts +++ b/ee/server/services/DDPStreamer/constants.ts @@ -14,6 +14,7 @@ export const DDP_EVENTS = { READY: 'ready', ADDED: 'added', CHANGED: 'changed', + REMOVED: 'removed', RESUME: 'resume', RESULT: 'result', METHOD: 'method', diff --git a/ee/server/services/StreamHub/StreamHub.ts b/ee/server/services/StreamHub/StreamHub.ts index 94b025d5b2b83..5d3d3cb1a918a 100755 --- a/ee/server/services/StreamHub/StreamHub.ts +++ b/ee/server/services/StreamHub/StreamHub.ts @@ -7,6 +7,7 @@ import { watchRoles } from './watchRoles'; import { watchInquiries } from './watchInquiries'; import { getConnection } from '../mongo'; import { ServiceClass, IServiceClass } from '../../../../server/sdk/types/ServiceClass'; +import { watchLoginServiceConfiguration } from './watchLoginServiceConfiguration'; export class StreamHub extends ServiceClass implements IServiceClass { protected name = 'hub'; @@ -22,6 +23,7 @@ export class StreamHub extends ServiceClass implements IServiceClass { const Rooms = db.collection('rocketchat_room'); const Settings = db.collection('rocketchat_settings'); const Inquiry = db.collection('rocketchat_livechat_inquiry'); + const loginServiceConfiguration = db.collection('meteor_accounts_loginServiceConfiguration'); Users.watch([], { fullDocument: 'updateLookup' }).on('change', watchUsers); @@ -58,5 +60,7 @@ export class StreamHub extends ServiceClass implements IServiceClass { }, }, }], { fullDocument: 'updateLookup' }).on('change', watchSettings); + + loginServiceConfiguration.watch([{ $project: { 'fullDocument.secret': 0 } }], { fullDocument: 'updateLookup' }).on('change', watchLoginServiceConfiguration); } } diff --git a/ee/server/services/StreamHub/watchLoginServiceConfiguration.ts b/ee/server/services/StreamHub/watchLoginServiceConfiguration.ts new file mode 100644 index 0000000000000..eddca306c30df --- /dev/null +++ b/ee/server/services/StreamHub/watchLoginServiceConfiguration.ts @@ -0,0 +1,28 @@ +import { ChangeEvent } from 'mongodb'; + +import { api } from '../../../../server/sdk/api'; +import { IRole } from '../../../../definition/IRole'; + +export async function watchLoginServiceConfiguration(event: ChangeEvent): Promise { + switch (event.operationType) { + case 'insert': + case 'update': + if (!event.fullDocument) { + return; + } + + api.broadcast('meteor.loginServiceConfiguration', { + action: event.operationType === 'insert' ? 'added' : 'changed', + record: event.fullDocument, + }); + break; + case 'delete': + api.broadcast('meteor.loginServiceConfiguration', { + action: 'removed', + record: { + _id: event.documentKey._id, + }, + }); + break; + } +} diff --git a/server/sdk/lib/Events.ts b/server/sdk/lib/Events.ts index 2cb5e9d66f61f..42617b6fd702e 100644 --- a/server/sdk/lib/Events.ts +++ b/server/sdk/lib/Events.ts @@ -24,4 +24,5 @@ export type EventSignatures = { 'role'(data: {type: 'changed' | 'removed' } & Partial): void; 'license.module'(data: {module: string; valid: boolean}): void; 'meteor.autoUpdateClientVersionChanged'(data: {record: AutoUpdateRecord}): void; + 'meteor.loginServiceConfiguration'(data: {action: string; record: any}): void; } diff --git a/server/sdk/types/IMeteor.ts b/server/sdk/types/IMeteor.ts index 02197c76f198f..cbb267c7237bf 100644 --- a/server/sdk/types/IMeteor.ts +++ b/server/sdk/types/IMeteor.ts @@ -12,4 +12,6 @@ export type AutoUpdateRecord = { export interface IMeteor extends IServiceClass { getLastAutoUpdateClientVersions(): Promise; + + getLoginServiceConfiguration(): Promise; } diff --git a/server/services/meteor/service.ts b/server/services/meteor/service.ts index d8d580c0115f3..8c15491dd7f66 100644 --- a/server/services/meteor/service.ts +++ b/server/services/meteor/service.ts @@ -1,4 +1,5 @@ import { Meteor } from 'meteor/meteor'; +import { ServiceConfiguration } from 'meteor/service-configuration'; import { ServiceClass } from '../../sdk/types/ServiceClass'; import { IMeteor, AutoUpdateRecord } from '../../sdk/types/IMeteor'; @@ -29,4 +30,8 @@ export class MeteorService extends ServiceClass implements IMeteor { async getLastAutoUpdateClientVersions(): Promise { return [...autoUpdateRecords.values()]; } + + async getLoginServiceConfiguration(): Promise { + return ServiceConfiguration.configurations.find({}, { fields: { secret: 0 } }).fetch(); + } } From 3a93f35c30e2bd734aab7f4bfa27889cf080096b Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Sat, 26 Sep 2020 16:35:17 -0300 Subject: [PATCH 078/198] [FIX] Reactivity of the login services configuration not working --- app/lib/server/startup/userDataStream.js | 25 +++++++++++++++++++ .../models/LoginServiceConfiguration.js | 7 ++++++ 2 files changed, 32 insertions(+) create mode 100644 app/models/server/models/LoginServiceConfiguration.js diff --git a/app/lib/server/startup/userDataStream.js b/app/lib/server/startup/userDataStream.js index f30f745de5001..b7c796d992ede 100644 --- a/app/lib/server/startup/userDataStream.js +++ b/app/lib/server/startup/userDataStream.js @@ -2,6 +2,7 @@ import { MongoInternals } from 'meteor/mongo'; import { Users } from '../../../models/server'; import { Notifications } from '../../../notifications/server'; +import loginServiceConfiguration from '../../../models/server/models/LoginServiceConfiguration'; let processOnChange; // eslint-disable-next-line no-undef @@ -10,6 +11,7 @@ const disableOplog = Package['disable-oplog']; if (disableOplog) { // Stores the callbacks for the disconnection reactivity bellow const userCallbacks = new Map(); + const serviceConfigCallbacks = new Set(); // Overrides the native observe changes to prevent database polling and stores the callbacks // for the users' tokens to re-implement the reactivity based on our database listeners @@ -32,11 +34,17 @@ if (disableOplog) { userCallbacks.set(selector._id, cbs); } } + + if (collectionName === 'meteor_accounts_loginServiceConfiguration') { + serviceConfigCallbacks.add(callbacks); + } + return { stop() { if (cbs) { cbs.delete(callbacks); } + serviceConfigCallbacks.delete(callbacks); }, }; }; @@ -57,6 +65,23 @@ if (disableOplog) { } } }; + + loginServiceConfiguration.on('change', ({ clientAction, id, data, diff }) => { + switch (clientAction) { + case 'inserted': + case 'updated': + const record = { ...data || diff }; + delete record.secret; + serviceConfigCallbacks.forEach((callbacks) => { + callbacks[clientAction === 'inserted' ? 'added' : 'changed']?.(id, record); + }); + break; + case 'removed': + serviceConfigCallbacks.forEach((callbacks) => { + callbacks.removed?.(id); + }); + } + }); } Users.on('change', ({ clientAction, id, data, diff }) => { diff --git a/app/models/server/models/LoginServiceConfiguration.js b/app/models/server/models/LoginServiceConfiguration.js new file mode 100644 index 0000000000000..8d3a31d6ee699 --- /dev/null +++ b/app/models/server/models/LoginServiceConfiguration.js @@ -0,0 +1,7 @@ +import { ServiceConfiguration } from 'meteor/service-configuration'; + +import { Base } from './_Base'; + +export class LoginServiceConfiguration extends Base {} + +export default new LoginServiceConfiguration(ServiceConfiguration.configurations, { preventSetUpdatedAt: true }); From bcc27634673e8e8f56ced6f58ab4d837dd2a4613 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Sun, 27 Sep 2020 17:03:53 -0300 Subject: [PATCH 079/198] Stream: Implement sendEphemeralMessage --- app/apps/server/bridges/messages.js | 17 +- .../server/methods/addUserToRole.js | 4 +- .../server/methods/removeUserFromRole.js | 4 +- app/authorization/server/methods/saveRole.js | 4 +- .../server/streamer/permissions/emitter.js | 6 +- .../server/methods/deleteEmojiCustom.js | 4 +- .../server/methods/insertOrUpdateEmoji.js | 6 +- .../server/methods/uploadEmojiCustom.js | 4 +- app/google-vision/server/googlevision.js | 8 +- app/ldap/server/sync.js | 8 +- app/lib/server/functions/deleteUser.js | 4 +- app/lib/server/functions/setRealName.js | 4 +- app/lib/server/functions/setRoomAvatar.js | 4 +- app/lib/server/functions/setStatusText.js | 6 +- app/lib/server/functions/setUserAvatar.js | 4 +- app/lib/server/functions/setUsername.js | 4 +- app/lib/server/methods/addUsersToRoom.js | 8 +- app/lib/server/methods/filterATAllTag.js | 8 +- app/lib/server/methods/filterATHereTag.js | 8 +- app/lib/server/methods/sendMessage.js | 8 +- app/mentions/server/server.js | 9 +- app/reactions/server/setReaction.js | 8 +- .../server/server.js | 18 +- app/slashcommands-create/server/server.js | 8 +- app/slashcommands-help/server/server.js | 8 +- app/slashcommands-hide/server/hide.js | 18 +- app/slashcommands-invite/server/server.js | 23 +-- app/slashcommands-inviteall/server/server.js | 23 +-- app/slashcommands-join/server/server.js | 8 +- app/slashcommands-kick/server/server.js | 13 +- app/slashcommands-leave/server/leave.js | 8 +- app/slashcommands-msg/server/server.js | 12 +- app/slashcommands-mute/server/mute.js | 13 +- app/slashcommands-mute/server/unmute.js | 13 +- app/slashcommands-status/lib/status.js | 13 +- .../server/server.js | 18 +- app/sms/server/services/twilio.js | 8 +- app/sms/server/services/voxtelesys.js | 8 +- .../server/methods/deleteCustomUserStatus.js | 4 +- .../methods/insertOrUpdateUserStatus.js | 6 +- imports/users-presence/server/activeUsers.js | 4 +- server/main.d.ts | 7 +- server/methods/addRoomLeader.js | 4 +- server/methods/addRoomModerator.js | 4 +- server/methods/addRoomOwner.js | 4 +- server/methods/removeRoomLeader.js | 4 +- server/methods/removeRoomModerator.js | 4 +- server/methods/removeRoomOwner.js | 4 +- server/methods/resetAvatar.js | 4 +- server/publications/settings/emitter.js | 6 +- server/sdk/index.ts | 4 +- .../types/{IStreamer.ts => IStreamService.ts} | 21 ++- server/services/startup.ts | 6 +- server/services/stream/service.ts | 158 ++++++++++++++++++ server/services/streamer/service.ts | 94 ----------- 55 files changed, 310 insertions(+), 380 deletions(-) rename server/sdk/types/{IStreamer.ts => IStreamService.ts} (60%) create mode 100644 server/services/stream/service.ts delete mode 100644 server/services/streamer/service.ts diff --git a/app/apps/server/bridges/messages.js b/app/apps/server/bridges/messages.js index e3f3e722dc650..e710049788df9 100644 --- a/app/apps/server/bridges/messages.js +++ b/app/apps/server/bridges/messages.js @@ -1,9 +1,7 @@ -import { Random } from 'meteor/random'; - import { Messages, Users, Subscriptions } from '../../../models/server'; -import { Notifications } from '../../../notifications'; import { updateMessage } from '../../../lib/server/functions/updateMessage'; import { executeSendMessage } from '../../../lib/server/methods/sendMessage'; +import { StreamService } from '../../../../server/sdk'; export class AppMessageBridge { constructor(orch) { @@ -52,10 +50,8 @@ export class AppMessageBridge { return; } - Notifications.notifyUser(user.id, 'message', { + StreamService.sendEphemeralMessage(user.id, msg.rid, { ...msg, - _id: Random.id(), - ts: new Date(), }); } @@ -67,11 +63,6 @@ export class AppMessageBridge { } const msg = this.orch.getConverters().get('messages').convertAppMessage(message); - const rmsg = Object.assign(msg, { - _id: Random.id(), - rid: room.id, - ts: new Date(), - }); const users = Subscriptions.findByRoomIdWhenUserIdExists(room.id, { fields: { 'u._id': 1 } }) .fetch() @@ -80,7 +71,9 @@ export class AppMessageBridge { Users.findByIds(users, { fields: { _id: 1 } }) .fetch() .forEach(({ _id }) => - Notifications.notifyUser(_id, 'message', rmsg), + StreamService.sendEphemeralMessage(_id, room.id, { + ...msg, + }), ); } } diff --git a/app/authorization/server/methods/addUserToRole.js b/app/authorization/server/methods/addUserToRole.js index 7a5f2df5bdaee..f7b9af4e7adb9 100644 --- a/app/authorization/server/methods/addUserToRole.js +++ b/app/authorization/server/methods/addUserToRole.js @@ -4,7 +4,7 @@ import _ from 'underscore'; import { Users, Roles } from '../../../models/server'; import { settings } from '../../../settings/server'; import { hasPermission } from '../functions/hasPermission'; -import { Streamer } from '../../../../server/sdk'; +import { StreamService } from '../../../../server/sdk'; Meteor.methods({ 'authorization:addUserToRole'(roleName, username, scope) { @@ -50,7 +50,7 @@ Meteor.methods({ const add = Roles.addUserRoles(user._id, roleName, scope); if (settings.get('UI_DisplayRoles')) { - Streamer.sendRoleUpdate({ + StreamService.sendRoleUpdate({ type: 'added', _id: roleName, u: { diff --git a/app/authorization/server/methods/removeUserFromRole.js b/app/authorization/server/methods/removeUserFromRole.js index 4b7c5347b3b8a..ce7f7d7d47c5f 100644 --- a/app/authorization/server/methods/removeUserFromRole.js +++ b/app/authorization/server/methods/removeUserFromRole.js @@ -4,7 +4,7 @@ import _ from 'underscore'; import { Roles } from '../../../models/server'; import { settings } from '../../../settings/server'; import { hasPermission } from '../functions/hasPermission'; -import { Streamer } from '../../../../server/sdk'; +import { StreamService } from '../../../../server/sdk'; Meteor.methods({ 'authorization:removeUserFromRole'(roleName, username, scope) { @@ -55,7 +55,7 @@ Meteor.methods({ const remove = Roles.removeUserRoles(user._id, roleName, scope); if (settings.get('UI_DisplayRoles')) { - Streamer.sendRoleUpdate({ + StreamService.sendRoleUpdate({ type: 'removed', _id: roleName, u: { diff --git a/app/authorization/server/methods/saveRole.js b/app/authorization/server/methods/saveRole.js index da5594f42efa1..c335e19b8598d 100644 --- a/app/authorization/server/methods/saveRole.js +++ b/app/authorization/server/methods/saveRole.js @@ -4,7 +4,7 @@ import { Roles } from '../../../models/server'; import { settings } from '../../../settings/server'; import { hasPermission } from '../functions/hasPermission'; import { rolesStreamer } from '../lib/streamer'; -import { Streamer } from '../../../../server/sdk'; +import { StreamService } from '../../../../server/sdk'; Meteor.methods({ 'authorization:saveRole'(roleData) { @@ -27,7 +27,7 @@ Meteor.methods({ const update = Roles.createOrUpdate(roleData.name, roleData.scope, roleData.description, false, roleData.mandatory2fa); if (settings.get('UI_DisplayRoles')) { - Streamer.sendRoleUpdate({ + StreamService.sendRoleUpdate({ type: 'changed', _id: roleData.name, }); diff --git a/app/authorization/server/streamer/permissions/emitter.js b/app/authorization/server/streamer/permissions/emitter.js index 27e36154fdf5d..b18e00f03e8d4 100644 --- a/app/authorization/server/streamer/permissions/emitter.js +++ b/app/authorization/server/streamer/permissions/emitter.js @@ -2,7 +2,7 @@ import Settings from '../../../../models/server/models/Settings'; import { CONSTANTS } from '../../../lib'; import Permissions from '../../../../models/server/models/Permissions'; import { clearCache } from '../../functions/hasPermission'; -import { Streamer } from '../../../../../server/sdk'; +import { StreamService } from '../../../../../server/sdk'; Permissions.on('change', ({ clientAction, id, data, diff }) => { if (diff && Object.keys(diff).length === 1 && diff._updatedAt) { @@ -22,7 +22,7 @@ Permissions.on('change', ({ clientAction, id, data, diff }) => { clearCache(); - Streamer.sendPermission({ clientAction, data }); + StreamService.sendPermission({ clientAction, data }); if (data.level && data.level === CONSTANTS.SETTINGS_LEVEL) { // if the permission changes, the effect on the visible settings depends on the role affected. @@ -32,6 +32,6 @@ Permissions.on('change', ({ clientAction, id, data, diff }) => { if (!setting) { return; } - Streamer.sendPrivateSetting({ clientAction: 'updated', setting }); + StreamService.sendPrivateSetting({ clientAction: 'updated', setting }); } }); diff --git a/app/emoji-custom/server/methods/deleteEmojiCustom.js b/app/emoji-custom/server/methods/deleteEmojiCustom.js index 34dfee1134878..6be3aef42c6b0 100644 --- a/app/emoji-custom/server/methods/deleteEmojiCustom.js +++ b/app/emoji-custom/server/methods/deleteEmojiCustom.js @@ -1,6 +1,6 @@ import { Meteor } from 'meteor/meteor'; -import { Streamer } from '../../../../server/sdk'; +import { StreamService } from '../../../../server/sdk'; import { hasPermission } from '../../../authorization'; import { EmojiCustom } from '../../../models'; import { RocketChatFileEmojiCustomInstance } from '../startup/emoji-custom'; @@ -18,7 +18,7 @@ Meteor.methods({ RocketChatFileEmojiCustomInstance.deleteFile(encodeURIComponent(`${ emoji.name }.${ emoji.extension }`)); EmojiCustom.removeById(emojiID); - Streamer.sendDeleteCustomEmoji(emoji); + StreamService.sendDeleteCustomEmoji(emoji); return true; }, diff --git a/app/emoji-custom/server/methods/insertOrUpdateEmoji.js b/app/emoji-custom/server/methods/insertOrUpdateEmoji.js index 223ea23f40f4f..fb49e0aa4824c 100644 --- a/app/emoji-custom/server/methods/insertOrUpdateEmoji.js +++ b/app/emoji-custom/server/methods/insertOrUpdateEmoji.js @@ -6,7 +6,7 @@ import limax from 'limax'; import { hasPermission } from '../../../authorization'; import { EmojiCustom } from '../../../models'; import { RocketChatFileEmojiCustomInstance } from '../startup/emoji-custom'; -import { Streamer } from '../../../../server/sdk'; +import { StreamService } from '../../../../server/sdk'; Meteor.methods({ insertOrUpdateEmoji(emojiData) { @@ -73,7 +73,7 @@ Meteor.methods({ const _id = EmojiCustom.create(createEmoji); - Streamer.sendUpdateCustomEmoji(createEmoji); + StreamService.sendUpdateCustomEmoji(createEmoji); return _id; } @@ -107,7 +107,7 @@ Meteor.methods({ EmojiCustom.setAliases(emojiData._id, []); } - Streamer.sendUpdateCustomEmoji(emojiData); + StreamService.sendUpdateCustomEmoji(emojiData); return true; }, diff --git a/app/emoji-custom/server/methods/uploadEmojiCustom.js b/app/emoji-custom/server/methods/uploadEmojiCustom.js index 3c18acf70e53e..f31995dde6530 100644 --- a/app/emoji-custom/server/methods/uploadEmojiCustom.js +++ b/app/emoji-custom/server/methods/uploadEmojiCustom.js @@ -4,7 +4,7 @@ import limax from 'limax'; import { hasPermission } from '../../../authorization'; import { RocketChatFile } from '../../../file'; import { RocketChatFileEmojiCustomInstance } from '../startup/emoji-custom'; -import { Streamer } from '../../../../server/sdk'; +import { StreamService } from '../../../../server/sdk'; Meteor.methods({ uploadEmojiCustom(binaryContent, contentType, emojiData) { @@ -21,7 +21,7 @@ Meteor.methods({ RocketChatFileEmojiCustomInstance.deleteFile(encodeURIComponent(`${ emojiData.name }.${ emojiData.extension }`)); const ws = RocketChatFileEmojiCustomInstance.createWriteStream(encodeURIComponent(`${ emojiData.name }.${ emojiData.extension }`), contentType); ws.on('end', Meteor.bindEnvironment(() => - Meteor.setTimeout(() => Streamer.sendUpdateCustomEmoji(emojiData), 500), + Meteor.setTimeout(() => StreamService.sendUpdateCustomEmoji(emojiData), 500), )); rs.pipe(ws); diff --git a/app/google-vision/server/googlevision.js b/app/google-vision/server/googlevision.js index 33494b398c1ac..245a9846861b2 100644 --- a/app/google-vision/server/googlevision.js +++ b/app/google-vision/server/googlevision.js @@ -1,12 +1,11 @@ import { Meteor } from 'meteor/meteor'; -import { Random } from 'meteor/random'; import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; import { settings } from '../../settings'; import { callbacks } from '../../callbacks'; -import { Notifications } from '../../notifications'; import { Uploads, Settings, Users, Messages } from '../../models'; import { FileUpload } from '../../file-upload'; +import { StreamService } from '../../../server/sdk'; class GoogleVision { constructor() { @@ -67,10 +66,7 @@ class GoogleVision { FileUpload.getStore('Uploads').deleteById(file._id); const user = Users.findOneById(message.u && message.u._id); if (user) { - Notifications.notifyUser(user._id, 'message', { - _id: Random.id(), - rid: message.rid, - ts: new Date(), + StreamService.sendEphemeralMessage(user._id, message.rid, { msg: TAPi18n.__('Adult_images_are_not_allowed', {}, user.language), }); } diff --git a/app/ldap/server/sync.js b/app/ldap/server/sync.js index aea4828a5c9dc..116cf55aef709 100644 --- a/app/ldap/server/sync.js +++ b/app/ldap/server/sync.js @@ -14,7 +14,7 @@ import { _setRealName, _setUsername } from '../../lib'; import { templateVarHandler } from '../../utils'; import { FileUpload } from '../../file-upload'; import { addUserToRoom, removeUserFromRoom, createRoom } from '../../lib/server/functions'; -import { Streamer } from '../../../server/sdk'; +import { StreamService } from '../../../server/sdk'; export const logger = new Logger('LDAPSync', {}); @@ -264,7 +264,7 @@ export function mapLdapGroupsToUserRoles(ldap, ldapUser, user) { const del = Roles.removeUserRoles(user._id, roleName); if (settings.get('UI_DisplayRoles') && del) { - Streamer.sendRoleUpdate({ + StreamService.sendRoleUpdate({ type: 'removed', _id: roleName, u: { @@ -365,7 +365,7 @@ function syncUserAvatar(user, ldapUser) { fileStore.insert(file, rs, (err, result) => { Meteor.setTimeout(function() { Users.setAvatarData(user._id, 'ldap', result.etag); - Streamer.sendUserAvatarUpdate({ username: user.username, etag: result.etag }); + StreamService.sendUserAvatarUpdate({ username: user.username, etag: result.etag }); }, 500); }); }); @@ -412,7 +412,7 @@ export function syncUserData(user, ldapUser, ldap) { for (const roleName of userRoles) { const add = Roles.addUserRoles(user._id, roleName); if (settings.get('UI_DisplayRoles') && add) { - Streamer.sendRoleUpdate({ + StreamService.sendRoleUpdate({ type: 'added', _id: roleName, u: { diff --git a/app/lib/server/functions/deleteUser.js b/app/lib/server/functions/deleteUser.js index da8d35c72fc1a..97b052a14997d 100644 --- a/app/lib/server/functions/deleteUser.js +++ b/app/lib/server/functions/deleteUser.js @@ -8,7 +8,7 @@ import { updateGroupDMsName } from './updateGroupDMsName'; import { relinquishRoomOwnerships } from './relinquishRoomOwnerships'; import { getSubscribedRoomsForUserWithDetails, shouldRemoveOrChangeOwner } from './getRoomsWithSingleOwner'; import { getUserSingleOwnedRooms } from './getUserSingleOwnedRooms'; -import { Streamer } from '../../../../server/sdk'; +import { StreamService } from '../../../../server/sdk'; export const deleteUser = function(userId, confirmRelinquish = false) { const user = Users.findOneById(userId, { @@ -65,7 +65,7 @@ export const deleteUser = function(userId, confirmRelinquish = false) { } Integrations.disableByUserId(userId); // Disables all the integrations which rely on the user being deleted. - Streamer.sendUserDeleted(userId); + StreamService.sendUserDeleted(userId); } // Remove user from users database diff --git a/app/lib/server/functions/setRealName.js b/app/lib/server/functions/setRealName.js index 22e047c734e7d..1c000dd0248b8 100644 --- a/app/lib/server/functions/setRealName.js +++ b/app/lib/server/functions/setRealName.js @@ -5,7 +5,7 @@ import { Users } from '../../../models/server'; import { settings } from '../../../settings'; import { hasPermission } from '../../../authorization'; import { RateLimiter } from '../lib'; -import { Streamer } from '../../../../server/sdk'; +import { StreamService } from '../../../../server/sdk'; export const _setRealName = function(userId, name, fullUser) { name = s.trim(name); @@ -30,7 +30,7 @@ export const _setRealName = function(userId, name, fullUser) { user.name = name; if (settings.get('UI_Use_Real_Name') === true) { - Streamer.sendUserNameChanged({ + StreamService.sendUserNameChanged({ _id: user._id, name: user.name, username: user.username, diff --git a/app/lib/server/functions/setRoomAvatar.js b/app/lib/server/functions/setRoomAvatar.js index 4f3d6ae93fdec..73c4f403b963a 100644 --- a/app/lib/server/functions/setRoomAvatar.js +++ b/app/lib/server/functions/setRoomAvatar.js @@ -3,7 +3,7 @@ import { Meteor } from 'meteor/meteor'; import { RocketChatFile } from '../../../file'; import { FileUpload } from '../../../file-upload'; import { Rooms, Avatars, Messages } from '../../../models/server'; -import { Streamer } from '../../../../server/sdk'; +import { StreamService } from '../../../../server/sdk'; export const setRoomAvatar = function(rid, dataURI, user) { const fileStore = FileUpload.getStore('Avatars'); @@ -33,7 +33,7 @@ export const setRoomAvatar = function(rid, dataURI, user) { } Rooms.setAvatarData(rid, 'upload', result.etag); Messages.createRoomSettingsChangedWithTypeRoomIdMessageAndUser('room_changed_avatar', rid, '', user); - Streamer.sendRoomAvatarUpdate({ rid, etag: result.etag }); + StreamService.sendRoomAvatarUpdate({ rid, etag: result.etag }); }, 500); }); }; diff --git a/app/lib/server/functions/setStatusText.js b/app/lib/server/functions/setStatusText.js index 63d036b60e29e..597b02c4f33af 100644 --- a/app/lib/server/functions/setStatusText.js +++ b/app/lib/server/functions/setStatusText.js @@ -5,7 +5,7 @@ import { Users } from '../../../models/server'; import { Users as UsersRaw } from '../../../models/server/raw'; import { hasPermission } from '../../../authorization/server'; import { RateLimiter } from '../lib'; -import { Streamer } from '../../../../server/sdk'; +import { StreamService } from '../../../../server/sdk'; // mirror of object in /imports/startup/client/listenActiveUsers.js - keep updated const STATUS_MAP = { @@ -29,7 +29,7 @@ export const _setStatusTextPromise = async function(userId, statusText) { await UsersRaw.updateStatusText(user._id, statusText); const { _id: uid, username } = user; - Streamer.sendUserStatus({ + StreamService.sendUserStatus({ uid, username, status: STATUS_MAP[user.status], @@ -61,7 +61,7 @@ export const _setStatusText = function(userId, statusText) { user.statusText = statusText; const { _id: uid, username } = user; - Streamer.sendUserStatus({ + StreamService.sendUserStatus({ uid, username, status: STATUS_MAP[user.status], diff --git a/app/lib/server/functions/setUserAvatar.js b/app/lib/server/functions/setUserAvatar.js index ad19fe44f55c9..5a5fc5b697d09 100644 --- a/app/lib/server/functions/setUserAvatar.js +++ b/app/lib/server/functions/setUserAvatar.js @@ -4,7 +4,7 @@ import { HTTP } from 'meteor/http'; import { RocketChatFile } from '../../../file'; import { FileUpload } from '../../../file-upload'; import { Users } from '../../../models'; -import { Streamer } from '../../../../server/sdk'; +import { StreamService } from '../../../../server/sdk'; export const setUserAvatar = function(user, dataURI, contentType, service) { let encoding; @@ -64,7 +64,7 @@ export const setUserAvatar = function(user, dataURI, contentType, service) { fileStore.insert(file, buffer, (err, result) => { Meteor.setTimeout(function() { Users.setAvatarData(user._id, service, result.etag); - Streamer.sendUserAvatarUpdate({ username: user.username, etag: result.etag }); + StreamService.sendUserAvatarUpdate({ username: user.username, etag: result.etag }); }, 500); }); }; diff --git a/app/lib/server/functions/setUsername.js b/app/lib/server/functions/setUsername.js index 629f3b2930338..ae453f600df13 100644 --- a/app/lib/server/functions/setUsername.js +++ b/app/lib/server/functions/setUsername.js @@ -7,7 +7,7 @@ import { Users, Invites } from '../../../models/server'; import { hasPermission } from '../../../authorization'; import { RateLimiter } from '../lib'; import { addUserToRoom } from './addUserToRoom'; -import { Streamer } from '../../../../server/sdk'; +import { StreamService } from '../../../../server/sdk'; import { checkUsernameAvailability, setUserAvatar, getAvatarSuggestionForUser } from '.'; @@ -76,7 +76,7 @@ export const _setUsername = function(userId, u, fullUser) { } } - Streamer.sendUserNameChanged({ + StreamService.sendUserNameChanged({ _id: user._id, name: user.name, username: user.username, diff --git a/app/lib/server/methods/addUsersToRoom.js b/app/lib/server/methods/addUsersToRoom.js index 63eaa7594a831..dac850df50004 100644 --- a/app/lib/server/methods/addUsersToRoom.js +++ b/app/lib/server/methods/addUsersToRoom.js @@ -1,12 +1,11 @@ import { Meteor } from 'meteor/meteor'; import { Match } from 'meteor/check'; -import { Random } from 'meteor/random'; import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; import { Rooms, Subscriptions, Users } from '../../../models'; import { hasPermission } from '../../../authorization'; import { addUserToRoom } from '../functions'; -import { Notifications } from '../../../notifications'; +import { StreamService } from '../../../../server/sdk'; Meteor.methods({ addUsersToRoom(data = {}) { @@ -73,10 +72,7 @@ Meteor.methods({ if (!subscription) { addUserToRoom(data.rid, newUser, user); } else { - Notifications.notifyUser(userId, 'message', { - _id: Random.id(), - rid: data.rid, - ts: new Date(), + StreamService.sendEphemeralMessage(userId, data.rid, { msg: TAPi18n.__('Username_is_already_in_here', { postProcess: 'sprintf', sprintf: [newUser.username], diff --git a/app/lib/server/methods/filterATAllTag.js b/app/lib/server/methods/filterATAllTag.js index 9ada82426f3ba..396e376e86a5d 100644 --- a/app/lib/server/methods/filterATAllTag.js +++ b/app/lib/server/methods/filterATAllTag.js @@ -1,13 +1,12 @@ import { Meteor } from 'meteor/meteor'; -import { Random } from 'meteor/random'; import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; import _ from 'underscore'; import moment from 'moment'; import { hasPermission } from '../../../authorization'; import { callbacks } from '../../../callbacks'; -import { Notifications } from '../../../notifications'; import { Users } from '../../../models'; +import { StreamService } from '../../../../server/sdk'; callbacks.add('beforeSaveMessage', function(message) { // If the message was edited, or is older than 60 seconds (imported) @@ -27,10 +26,7 @@ callbacks.add('beforeSaveMessage', function(message) { // Add a notification to the chat, informing the user that this // action is not allowed. - Notifications.notifyUser(message.u._id, 'message', { - _id: Random.id(), - rid: message.rid, - ts: new Date(), + StreamService.sendEphemeralMessage(message.u._id, message.rid, { msg: TAPi18n.__('error-action-not-allowed', { action }, language), }); diff --git a/app/lib/server/methods/filterATHereTag.js b/app/lib/server/methods/filterATHereTag.js index 79198e1aaf5d0..fb5607f344c3d 100644 --- a/app/lib/server/methods/filterATHereTag.js +++ b/app/lib/server/methods/filterATHereTag.js @@ -1,13 +1,12 @@ import { Meteor } from 'meteor/meteor'; -import { Random } from 'meteor/random'; import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; import _ from 'underscore'; import moment from 'moment'; import { hasPermission } from '../../../authorization'; import { callbacks } from '../../../callbacks'; -import { Notifications } from '../../../notifications'; import { Users } from '../../../models'; +import { StreamService } from '../../../../server/sdk'; callbacks.add('beforeSaveMessage', function(message) { // If the message was edited, or is older than 60 seconds (imported) @@ -26,10 +25,7 @@ callbacks.add('beforeSaveMessage', function(message) { // Add a notification to the chat, informing the user that this // action is not allowed. - Notifications.notifyUser(message.u._id, 'message', { - _id: Random.id(), - rid: message.rid, - ts: new Date(), + StreamService.sendEphemeralMessage(message.u._id, message.rid, { msg: TAPi18n.__('error-action-not-allowed', { action }, language), }); diff --git a/app/lib/server/methods/sendMessage.js b/app/lib/server/methods/sendMessage.js index 22d30108fcb47..75101f4eb1e09 100644 --- a/app/lib/server/methods/sendMessage.js +++ b/app/lib/server/methods/sendMessage.js @@ -1,19 +1,18 @@ import { Meteor } from 'meteor/meteor'; import { check } from 'meteor/check'; -import { Random } from 'meteor/random'; import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; import moment from 'moment'; import { hasPermission } from '../../../authorization'; import { metrics } from '../../../metrics'; import { settings } from '../../../settings'; -import { Notifications } from '../../../notifications'; import { messageProperties } from '../../../ui-utils'; import { Users, Messages } from '../../../models'; import { sendMessage } from '../functions'; import { RateLimiter } from '../lib'; import { canSendMessage } from '../../../authorization/server'; import { SystemLogger } from '../../../logger/server'; +import { StreamService } from '../../../../server/sdk'; export function executeSendMessage(uid, message) { if (message.tshow && !message.tmid) { @@ -81,10 +80,7 @@ export function executeSendMessage(uid, message) { SystemLogger.error('Error sending message:', error); const errorMessage = typeof error === 'string' ? error : error.error || error.message; - Notifications.notifyUser(uid, 'message', { - _id: Random.id(), - rid: message.rid, - ts: new Date(), + StreamService.sendEphemeralMessage(uid, message.rid, { msg: TAPi18n.__(errorMessage, {}, user.language), }); diff --git a/app/mentions/server/server.js b/app/mentions/server/server.js index 9c515e4ecdecf..9e4cc516f3ae0 100644 --- a/app/mentions/server/server.js +++ b/app/mentions/server/server.js @@ -1,13 +1,12 @@ import { Meteor } from 'meteor/meteor'; -import { Random } from 'meteor/random'; import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; import _ from 'underscore'; import MentionsServer from './Mentions'; import { settings } from '../../settings'; import { callbacks } from '../../callbacks'; -import { Notifications } from '../../notifications'; import { Users, Subscriptions, Rooms } from '../../models'; +import { StreamService } from '../../../server/sdk'; const mention = new MentionsServer({ pattern: () => settings.get('UTF8_Names_Validation'), @@ -21,12 +20,8 @@ const mention = new MentionsServer({ const { language } = this.getUser(sender._id); const msg = TAPi18n.__('Group_mentions_disabled_x_members', { total: this.messageMaxAll }, language); - Notifications.notifyUser(sender._id, 'message', { - _id: Random.id(), - rid, - ts: new Date(), + StreamService.sendEphemeralMessage(sender._id, rid, { msg, - groupable: false, }); // Also throw to stop propagation of 'sendMessage'. diff --git a/app/reactions/server/setReaction.js b/app/reactions/server/setReaction.js index 6729baf00ceef..3e7c8e9395c73 100644 --- a/app/reactions/server/setReaction.js +++ b/app/reactions/server/setReaction.js @@ -1,14 +1,13 @@ import { Meteor } from 'meteor/meteor'; -import { Random } from 'meteor/random'; import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; import _ from 'underscore'; import { Messages, EmojiCustom, Rooms } from '../../models'; -import { Notifications } from '../../notifications'; import { callbacks } from '../../callbacks'; import { emoji } from '../../emoji'; import { isTheLastMessage, msgStream } from '../../lib'; import { hasPermission } from '../../authorization/server/functions/hasPermission'; +import { StreamService } from '../../../server/sdk'; const removeUserReaction = (message, reaction, username) => { message.reactions[reaction].usernames.splice(message.reactions[reaction].usernames.indexOf(username), 1); @@ -112,10 +111,7 @@ Meteor.methods({ return Promise.await(executeSetReaction(reaction, messageId, shouldReact)); } catch (e) { if (e.error === 'error-not-allowed' && e.reason && e.details && e.details.rid) { - Notifications.notifyUser(Meteor.userId(), 'message', { - _id: Random.id(), - rid: e.details.rid, - ts: new Date(), + StreamService.sendEphemeralMessage(Meteor.userId(), e.details.rid, { msg: e.reason, }); diff --git a/app/slashcommands-archiveroom/server/server.js b/app/slashcommands-archiveroom/server/server.js index 6085a25974260..46d61fd634a90 100644 --- a/app/slashcommands-archiveroom/server/server.js +++ b/app/slashcommands-archiveroom/server/server.js @@ -1,11 +1,10 @@ import { Meteor } from 'meteor/meteor'; import { Match } from 'meteor/check'; -import { Random } from 'meteor/random'; import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; import { Rooms, Messages } from '../../models'; import { slashCommands } from '../../utils'; -import { Notifications } from '../../notifications'; +import { StreamService } from '../../../server/sdk'; function Archive(command, params, item) { if (command !== 'archive' || !Match.test(params, String)) { @@ -26,10 +25,7 @@ function Archive(command, params, item) { const user = Meteor.users.findOne(Meteor.userId()); if (!room) { - return Notifications.notifyUser(Meteor.userId(), 'message', { - _id: Random.id(), - rid: item.rid, - ts: new Date(), + StreamService.sendEphemeralMessage(Meteor.userId(), item.rid, { msg: TAPi18n.__('Channel_doesnt_exist', { postProcess: 'sprintf', sprintf: [channel], @@ -43,10 +39,7 @@ function Archive(command, params, item) { } if (room.archived) { - Notifications.notifyUser(Meteor.userId(), 'message', { - _id: Random.id(), - rid: item.rid, - ts: new Date(), + StreamService.sendEphemeralMessage(Meteor.userId(), item.rid, { msg: TAPi18n.__('Duplicate_archived_channel_name', { postProcess: 'sprintf', sprintf: [channel], @@ -57,10 +50,7 @@ function Archive(command, params, item) { Meteor.call('archiveRoom', room._id); Messages.createRoomArchivedByRoomIdAndUser(room._id, Meteor.user()); - Notifications.notifyUser(Meteor.userId(), 'message', { - _id: Random.id(), - rid: item.rid, - ts: new Date(), + StreamService.sendEphemeralMessage(Meteor.userId(), item.rid, { msg: TAPi18n.__('Channel_Archived', { postProcess: 'sprintf', sprintf: [channel], diff --git a/app/slashcommands-create/server/server.js b/app/slashcommands-create/server/server.js index 898d44bdaac12..8c648ef3d6cc7 100644 --- a/app/slashcommands-create/server/server.js +++ b/app/slashcommands-create/server/server.js @@ -1,12 +1,11 @@ import { Meteor } from 'meteor/meteor'; import { Match } from 'meteor/check'; -import { Random } from 'meteor/random'; import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; import { settings } from '../../settings'; -import { Notifications } from '../../notifications'; import { Rooms } from '../../models'; import { slashCommands } from '../../utils'; +import { StreamService } from '../../../server/sdk'; function Create(command, params, item) { function getParams(str) { @@ -36,10 +35,7 @@ function Create(command, params, item) { const user = Meteor.users.findOne(Meteor.userId()); const room = Rooms.findOneByName(channel); if (room != null) { - Notifications.notifyUser(Meteor.userId(), 'message', { - _id: Random.id(), - rid: item.rid, - ts: new Date(), + StreamService.sendEphemeralMessage(Meteor.userId(), item.rid, { msg: TAPi18n.__('Channel_already_exist', { postProcess: 'sprintf', sprintf: [channel], diff --git a/app/slashcommands-help/server/server.js b/app/slashcommands-help/server/server.js index ba76736dc9a57..b5ef61af46743 100644 --- a/app/slashcommands-help/server/server.js +++ b/app/slashcommands-help/server/server.js @@ -1,9 +1,8 @@ import { Meteor } from 'meteor/meteor'; -import { Random } from 'meteor/random'; import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; import { slashCommands } from '../../utils'; -import { Notifications } from '../../notifications'; +import { StreamService } from '../../../server/sdk'; /* * Help is a named function that will replace /join commands @@ -39,10 +38,7 @@ slashCommands.add('help', function Help(command, params, item) { }, ]; keys.forEach((key) => { - Notifications.notifyUser(Meteor.userId(), 'message', { - _id: Random.id(), - rid: item.rid, - ts: new Date(), + StreamService.sendEphemeralMessage(Meteor.userId(), item.rid, { msg: TAPi18n.__(Object.keys(key)[0], { postProcess: 'sprintf', sprintf: [key[Object.keys(key)[0]]], diff --git a/app/slashcommands-hide/server/hide.js b/app/slashcommands-hide/server/hide.js index 2a316417d4d51..c97e21fc83e0d 100644 --- a/app/slashcommands-hide/server/hide.js +++ b/app/slashcommands-hide/server/hide.js @@ -1,11 +1,10 @@ import { Meteor } from 'meteor/meteor'; import { Match } from 'meteor/check'; -import { Random } from 'meteor/random'; import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; import { Rooms, Subscriptions } from '../../models'; -import { Notifications } from '../../notifications'; import { slashCommands } from '../../utils'; +import { StreamService } from '../../../server/sdk'; /* * Hide is a named function that will replace /hide commands @@ -29,10 +28,7 @@ function Hide(command, param, item) { }); if (!roomObject) { - return Notifications.notifyUser(user._id, 'message', { - _id: Random.id(), - rid: item.rid, - ts: new Date(), + StreamService.sendEphemeralMessage(user._id, item.rid, { msg: TAPi18n.__('Channel_doesnt_exist', { postProcess: 'sprintf', sprintf: [room], @@ -41,10 +37,7 @@ function Hide(command, param, item) { } if (!Subscriptions.findOneByRoomIdAndUserId(room._id, user._id, { fields: { _id: 1 } })) { - return Notifications.notifyUser(user._id, 'message', { - _id: Random.id(), - rid: item.rid, - ts: new Date(), + return StreamService.sendEphemeralMessage(user._id, item.rid, { msg: TAPi18n.__('error-logged-user-not-in-room', { postProcess: 'sprintf', sprintf: [room], @@ -56,10 +49,7 @@ function Hide(command, param, item) { Meteor.call('hideRoom', rid, (error) => { if (error) { - return Notifications.notifyUser(user._id, 'message', { - _id: Random.id(), - rid: item.rid, - ts: new Date(), + return StreamService.sendEphemeralMessage(user._id, item.rid, { msg: TAPi18n.__(error, null, user.language), }); } diff --git a/app/slashcommands-invite/server/server.js b/app/slashcommands-invite/server/server.js index eb804329cc5c5..c60d5fb2440f2 100644 --- a/app/slashcommands-invite/server/server.js +++ b/app/slashcommands-invite/server/server.js @@ -1,11 +1,10 @@ import { Meteor } from 'meteor/meteor'; import { Match } from 'meteor/check'; -import { Random } from 'meteor/random'; import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; -import { Notifications } from '../../notifications'; import { slashCommands } from '../../utils'; import { Subscriptions } from '../../models'; +import { StreamService } from '../../../server/sdk'; /* * Invite is a named function that will replace /invite commands @@ -30,10 +29,7 @@ function Invite(command, params, item) { const userId = Meteor.userId(); const currentUser = Meteor.users.findOne(userId); if (users.count() === 0) { - Notifications.notifyUser(userId, 'message', { - _id: Random.id(), - rid: item.rid, - ts: new Date(), + StreamService.sendEphemeralMessage(userId, item.rid, { msg: TAPi18n.__('User_doesnt_exist', { postProcess: 'sprintf', sprintf: [usernames.join(' @')], @@ -46,10 +42,7 @@ function Invite(command, params, item) { if (subscription == null) { return true; } - Notifications.notifyUser(userId, 'message', { - _id: Random.id(), - rid: item.rid, - ts: new Date(), + StreamService.sendEphemeralMessage(userId, item.rid, { msg: TAPi18n.__('Username_is_already_in_here', { postProcess: 'sprintf', sprintf: [user.username], @@ -66,17 +59,11 @@ function Invite(command, params, item) { }); } catch ({ error }) { if (error === 'cant-invite-for-direct-room') { - Notifications.notifyUser(userId, 'message', { - _id: Random.id(), - rid: item.rid, - ts: new Date(), + StreamService.sendEphemeralMessage(userId, item.rid, { msg: TAPi18n.__('Cannot_invite_users_to_direct_rooms', null, currentUser.language), }); } else { - Notifications.notifyUser(userId, 'message', { - _id: Random.id(), - rid: item.rid, - ts: new Date(), + StreamService.sendEphemeralMessage(userId, item.rid, { msg: TAPi18n.__(error, null, currentUser.language), }); } diff --git a/app/slashcommands-inviteall/server/server.js b/app/slashcommands-inviteall/server/server.js index 1a41717293235..c6b78d7c65cc4 100644 --- a/app/slashcommands-inviteall/server/server.js +++ b/app/slashcommands-inviteall/server/server.js @@ -4,13 +4,12 @@ */ import { Meteor } from 'meteor/meteor'; import { Match } from 'meteor/check'; -import { Random } from 'meteor/random'; import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; import { Rooms, Subscriptions } from '../../models'; import { slashCommands } from '../../utils'; import { settings } from '../../settings'; -import { Notifications } from '../../notifications'; +import { StreamService } from '../../../server/sdk'; function inviteAll(type) { return function inviteAll(command, params, item) { @@ -30,10 +29,7 @@ function inviteAll(type) { const targetChannel = type === 'from' ? Rooms.findOneById(item.rid) : Rooms.findOneByName(channel); if (!baseChannel) { - return Notifications.notifyUser(userId, 'message', { - _id: Random.id(), - rid: item.rid, - ts: new Date(), + return StreamService.sendEphemeralMessage(userId, item.rid, { msg: TAPi18n.__('Channel_doesnt_exist', { postProcess: 'sprintf', sprintf: [channel], @@ -52,10 +48,7 @@ function inviteAll(type) { if (!targetChannel && ['c', 'p'].indexOf(baseChannel.t) > -1) { Meteor.call(baseChannel.t === 'c' ? 'createChannel' : 'createPrivateGroup', channel, users); - Notifications.notifyUser(userId, 'message', { - _id: Random.id(), - rid: item.rid, - ts: new Date(), + StreamService.sendEphemeralMessage(userId, item.rid, { msg: TAPi18n.__('Channel_created', { postProcess: 'sprintf', sprintf: [channel], @@ -67,18 +60,12 @@ function inviteAll(type) { users, }); } - return Notifications.notifyUser(userId, 'message', { - _id: Random.id(), - rid: item.rid, - ts: new Date(), + return StreamService.sendEphemeralMessage(userId, item.rid, { msg: TAPi18n.__('Users_added', null, currentUser.language), }); } catch (e) { const msg = e.error === 'cant-invite-for-direct-room' ? 'Cannot_invite_users_to_direct_rooms' : e.error; - Notifications.notifyUser(userId, 'message', { - _id: Random.id(), - rid: item.rid, - ts: new Date(), + StreamService.sendEphemeralMessage(userId, item.rid, { msg: TAPi18n.__(msg, null, currentUser.language), }); } diff --git a/app/slashcommands-join/server/server.js b/app/slashcommands-join/server/server.js index 1b1802da704a2..a49a473b650a7 100644 --- a/app/slashcommands-join/server/server.js +++ b/app/slashcommands-join/server/server.js @@ -1,11 +1,10 @@ import { Meteor } from 'meteor/meteor'; import { Match } from 'meteor/check'; -import { Random } from 'meteor/random'; import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; import { Rooms, Subscriptions } from '../../models'; -import { Notifications } from '../../notifications'; import { slashCommands } from '../../utils'; +import { StreamService } from '../../../server/sdk'; function Join(command, params, item) { if (command !== 'join' || !Match.test(params, String)) { @@ -19,10 +18,7 @@ function Join(command, params, item) { const user = Meteor.users.findOne(Meteor.userId()); const room = Rooms.findOneByNameAndType(channel, 'c'); if (!room) { - Notifications.notifyUser(Meteor.userId(), 'message', { - _id: Random.id(), - rid: item.rid, - ts: new Date(), + StreamService.sendEphemeralMessage(Meteor.userId(), item.rid, { msg: TAPi18n.__('Channel_doesnt_exist', { postProcess: 'sprintf', sprintf: [channel], diff --git a/app/slashcommands-kick/server/server.js b/app/slashcommands-kick/server/server.js index 5bc785cc82144..7f0867abe3f78 100644 --- a/app/slashcommands-kick/server/server.js +++ b/app/slashcommands-kick/server/server.js @@ -2,12 +2,11 @@ // Kick is a named function that will replace /kick commands import { Meteor } from 'meteor/meteor'; import { Match } from 'meteor/check'; -import { Random } from 'meteor/random'; import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; -import { Notifications } from '../../notifications'; import { Users, Subscriptions } from '../../models'; import { slashCommands } from '../../utils'; +import { StreamService } from '../../../server/sdk'; const Kick = function(command, params, { rid }) { if (command !== 'kick' || !Match.test(params, String)) { @@ -22,10 +21,7 @@ const Kick = function(command, params, { rid }) { const kickedUser = Users.findOneByUsernameIgnoringCase(username); if (kickedUser == null) { - return Notifications.notifyUser(userId, 'message', { - _id: Random.id(), - rid, - ts: new Date(), + return StreamService.sendEphemeralMessage(userId, rid, { msg: TAPi18n.__('Username_doesnt_exist', { postProcess: 'sprintf', sprintf: [username], @@ -35,10 +31,7 @@ const Kick = function(command, params, { rid }) { const subscription = Subscriptions.findOneByRoomIdAndUserId(rid, user._id, { fields: { _id: 1 } }); if (!subscription) { - return Notifications.notifyUser(userId, 'message', { - _id: Random.id(), - rid, - ts: new Date(), + return StreamService.sendEphemeralMessage(userId, rid, { msg: TAPi18n.__('Username_is_not_in_this_room', { postProcess: 'sprintf', sprintf: [username], diff --git a/app/slashcommands-leave/server/leave.js b/app/slashcommands-leave/server/leave.js index ce6fc32fb1e6f..bf2e76c0c763d 100644 --- a/app/slashcommands-leave/server/leave.js +++ b/app/slashcommands-leave/server/leave.js @@ -1,9 +1,8 @@ import { Meteor } from 'meteor/meteor'; -import { Random } from 'meteor/random'; import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; -import { Notifications } from '../../notifications'; import { slashCommands } from '../../utils'; +import { StreamService } from '../../../server/sdk'; /* * Leave is a named function that will replace /leave commands @@ -17,10 +16,7 @@ function Leave(command, params, item) { try { Meteor.call('leaveRoom', item.rid); } catch ({ error }) { - Notifications.notifyUser(Meteor.userId(), 'message', { - _id: Random.id(), - rid: item.rid, - ts: new Date(), + StreamService.sendEphemeralMessage(Meteor.userId(), item.rid, { msg: TAPi18n.__(error, null, Meteor.user().language), }); } diff --git a/app/slashcommands-msg/server/server.js b/app/slashcommands-msg/server/server.js index 5fa33fe901e44..41124ed30389f 100644 --- a/app/slashcommands-msg/server/server.js +++ b/app/slashcommands-msg/server/server.js @@ -4,8 +4,8 @@ import { Random } from 'meteor/random'; import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; import { slashCommands } from '../../utils'; -import { Notifications } from '../../notifications'; import { Users } from '../../models'; +import { StreamService } from '../../../server/sdk'; /* * Msg is a named function that will replace /msg commands @@ -19,10 +19,7 @@ function Msg(command, params, item) { const separator = trimmedParams.indexOf(' '); const user = Meteor.users.findOne(Meteor.userId()); if (separator === -1) { - return Notifications.notifyUser(Meteor.userId(), 'message', { - _id: Random.id(), - rid: item.rid, - ts: new Date(), + return StreamService.sendEphemeralMessage(Meteor.userId(), item.rid, { msg: TAPi18n.__('Username_and_message_must_not_be_empty', null, user.language), }); } @@ -31,10 +28,7 @@ function Msg(command, params, item) { const targetUsername = targetUsernameOrig.replace('@', ''); const targetUser = Users.findOneByUsernameIgnoringCase(targetUsername); if (targetUser == null) { - Notifications.notifyUser(Meteor.userId(), 'message', { - _id: Random.id(), - rid: item.rid, - ts: new Date(), + StreamService.sendEphemeralMessage(Meteor.userId(), item.rid, { msg: TAPi18n.__('Username_doesnt_exist', { postProcess: 'sprintf', sprintf: [targetUsernameOrig], diff --git a/app/slashcommands-mute/server/mute.js b/app/slashcommands-mute/server/mute.js index 306edfecde33a..539232322d05b 100644 --- a/app/slashcommands-mute/server/mute.js +++ b/app/slashcommands-mute/server/mute.js @@ -1,11 +1,10 @@ import { Meteor } from 'meteor/meteor'; import { Match } from 'meteor/check'; -import { Random } from 'meteor/random'; import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; import { slashCommands } from '../../utils'; import { Users, Subscriptions } from '../../models'; -import { Notifications } from '../../notifications'; +import { StreamService } from '../../../server/sdk'; /* * Mute is a named function that will replace /mute commands @@ -23,10 +22,7 @@ slashCommands.add('mute', function Mute(command, params, item) { const user = Meteor.users.findOne(userId); const mutedUser = Users.findOneByUsernameIgnoringCase(username); if (mutedUser == null) { - Notifications.notifyUser(userId, 'message', { - _id: Random.id(), - rid: item.rid, - ts: new Date(), + StreamService.sendEphemeralMessage(userId, item.rid, { msg: TAPi18n.__('Username_doesnt_exist', { postProcess: 'sprintf', sprintf: [username], @@ -37,10 +33,7 @@ slashCommands.add('mute', function Mute(command, params, item) { const subscription = Subscriptions.findOneByRoomIdAndUserId(item.rid, mutedUser._id, { fields: { _id: 1 } }); if (!subscription) { - Notifications.notifyUser(userId, 'message', { - _id: Random.id(), - rid: item.rid, - ts: new Date(), + StreamService.sendEphemeralMessage(userId, item.rid, { msg: TAPi18n.__('Username_is_not_in_this_room', { postProcess: 'sprintf', sprintf: [username], diff --git a/app/slashcommands-mute/server/unmute.js b/app/slashcommands-mute/server/unmute.js index a84c518e48b49..e9d42b608a64f 100644 --- a/app/slashcommands-mute/server/unmute.js +++ b/app/slashcommands-mute/server/unmute.js @@ -1,11 +1,10 @@ import { Meteor } from 'meteor/meteor'; import { Match } from 'meteor/check'; -import { Random } from 'meteor/random'; import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; import { slashCommands } from '../../utils'; import { Users, Subscriptions } from '../../models'; -import { Notifications } from '../../notifications'; +import { StreamService } from '../../../server/sdk'; /* * Unmute is a named function that will replace /unmute commands @@ -22,10 +21,7 @@ slashCommands.add('unmute', function Unmute(command, params, item) { const user = Meteor.users.findOne(Meteor.userId()); const unmutedUser = Users.findOneByUsernameIgnoringCase(username); if (unmutedUser == null) { - return Notifications.notifyUser(Meteor.userId(), 'message', { - _id: Random.id(), - rid: item.rid, - ts: new Date(), + return StreamService.sendEphemeralMessage(Meteor.userId(), item.rid, { msg: TAPi18n.__('Username_doesnt_exist', { postProcess: 'sprintf', sprintf: [username], @@ -35,10 +31,7 @@ slashCommands.add('unmute', function Unmute(command, params, item) { const subscription = Subscriptions.findOneByRoomIdAndUserId(item.rid, unmutedUser._id, { fields: { _id: 1 } }); if (!subscription) { - return Notifications.notifyUser(Meteor.userId(), 'message', { - _id: Random.id(), - rid: item.rid, - ts: new Date(), + return StreamService.sendEphemeralMessage(Meteor.userId(), item.rid, { msg: TAPi18n.__('Username_is_not_in_this_room', { postProcess: 'sprintf', sprintf: [username], diff --git a/app/slashcommands-status/lib/status.js b/app/slashcommands-status/lib/status.js index 0141fdc1b1b24..89667fb0841c8 100644 --- a/app/slashcommands-status/lib/status.js +++ b/app/slashcommands-status/lib/status.js @@ -1,9 +1,8 @@ import { Meteor } from 'meteor/meteor'; import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; -import { Random } from 'meteor/random'; import { handleError, slashCommands } from '../../utils'; -import { Notifications } from '../../notifications'; +import { StreamService } from '../../../server/sdk'; function Status(command, params, item) { if (command === 'status') { @@ -16,20 +15,14 @@ function Status(command, params, item) { } if (err.error === 'error-not-allowed') { - Notifications.notifyUser(Meteor.userId(), 'message', { - _id: Random.id(), - rid: item.rid, - ts: new Date(), + StreamService.sendEphemeralMessage(Meteor.userId(), item.rid, { msg: TAPi18n.__('StatusMessage_Change_Disabled', null, user.language), }); } throw err; } else { - Notifications.notifyUser(Meteor.userId(), 'message', { - _id: Random.id(), - rid: item.rid, - ts: new Date(), + StreamService.sendEphemeralMessage(Meteor.userId(), item.rid, { msg: TAPi18n.__('StatusMessage_Changed_Successfully', null, user.language), }); } diff --git a/app/slashcommands-unarchiveroom/server/server.js b/app/slashcommands-unarchiveroom/server/server.js index be2c56e47cb08..09983e48fc1f6 100644 --- a/app/slashcommands-unarchiveroom/server/server.js +++ b/app/slashcommands-unarchiveroom/server/server.js @@ -1,12 +1,11 @@ import { Meteor } from 'meteor/meteor'; import { Match } from 'meteor/check'; -import { Random } from 'meteor/random'; import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; import { Rooms, Messages } from '../../models'; import { slashCommands } from '../../utils'; -import { Notifications } from '../../notifications'; import { roomTypes, RoomMemberActions } from '../../utils/server'; +import { StreamService } from '../../../server/sdk'; function Unarchive(command, params, item) { if (command !== 'unarchive' || !Match.test(params, String)) { @@ -27,10 +26,7 @@ function Unarchive(command, params, item) { const user = Meteor.users.findOne(Meteor.userId()); if (!room) { - return Notifications.notifyUser(Meteor.userId(), 'message', { - _id: Random.id(), - rid: item.rid, - ts: new Date(), + return StreamService.sendEphemeralMessage(Meteor.userId(), item.rid, { msg: TAPi18n.__('Channel_doesnt_exist', { postProcess: 'sprintf', sprintf: [channel], @@ -44,10 +40,7 @@ function Unarchive(command, params, item) { } if (!room.archived) { - Notifications.notifyUser(Meteor.userId(), 'message', { - _id: Random.id(), - rid: item.rid, - ts: new Date(), + StreamService.sendEphemeralMessage(Meteor.userId(), item.rid, { msg: TAPi18n.__('Channel_already_Unarchived', { postProcess: 'sprintf', sprintf: [channel], @@ -59,10 +52,7 @@ function Unarchive(command, params, item) { Meteor.call('unarchiveRoom', room._id); Messages.createRoomUnarchivedByRoomIdAndUser(room._id, Meteor.user()); - Notifications.notifyUser(Meteor.userId(), 'message', { - _id: Random.id(), - rid: item.rid, - ts: new Date(), + StreamService.sendEphemeralMessage(Meteor.userId(), item.rid, { msg: TAPi18n.__('Channel_Unarchived', { postProcess: 'sprintf', sprintf: [channel], diff --git a/app/sms/server/services/twilio.js b/app/sms/server/services/twilio.js index c928ed8a447f8..a3620b9d988b7 100644 --- a/app/sms/server/services/twilio.js +++ b/app/sms/server/services/twilio.js @@ -1,20 +1,16 @@ import { Meteor } from 'meteor/meteor'; import twilio from 'twilio'; -import { Random } from 'meteor/random'; import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; import filesize from 'filesize'; import { settings } from '../../../settings'; import { SMS } from '../SMS'; -import { Notifications } from '../../../notifications'; import { fileUploadIsValidContentType } from '../../../utils/lib/fileUploadRestrictions'; +import { StreamService } from '../../../../server/sdk'; const MAX_FILE_SIZE = 5242880; -const notifyAgent = (userId, rid, msg) => Notifications.notifyUser(userId, 'message', { - _id: Random.id(), - rid, - ts: new Date(), +const notifyAgent = (userId, rid, msg) => StreamService.sendEphemeralMessage(userId, rid, { msg, }); diff --git a/app/sms/server/services/voxtelesys.js b/app/sms/server/services/voxtelesys.js index 7e0afc37eb3ae..5f218b78a5475 100644 --- a/app/sms/server/services/voxtelesys.js +++ b/app/sms/server/services/voxtelesys.js @@ -1,21 +1,17 @@ import { HTTP } from 'meteor/http'; import { Meteor } from 'meteor/meteor'; -import { Random } from 'meteor/random'; import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; import filesize from 'filesize'; import { settings } from '../../../settings'; import { SMS } from '../SMS'; -import { Notifications } from '../../../notifications'; import { fileUploadIsValidContentType } from '../../../utils/lib/fileUploadRestrictions'; import { mime } from '../../../utils/lib/mimeTypes'; +import { StreamService } from '../../../../server/sdk'; const MAX_FILE_SIZE = 5242880; -const notifyAgent = (userId, rid, msg) => Notifications.notifyUser(userId, 'message', { - _id: Random.id(), - rid, - ts: new Date(), +const notifyAgent = (userId, rid, msg) => StreamService.sendEphemeralMessage(userId, rid, { msg, }); diff --git a/app/user-status/server/methods/deleteCustomUserStatus.js b/app/user-status/server/methods/deleteCustomUserStatus.js index 6a92a989d267b..ab5d4be7f9a6d 100644 --- a/app/user-status/server/methods/deleteCustomUserStatus.js +++ b/app/user-status/server/methods/deleteCustomUserStatus.js @@ -2,7 +2,7 @@ import { Meteor } from 'meteor/meteor'; import { hasPermission } from '../../../authorization/server'; import { CustomUserStatus } from '../../../models/server'; -import { Streamer } from '../../../../server/sdk'; +import { StreamService } from '../../../../server/sdk'; Meteor.methods({ deleteCustomUserStatus(userStatusID) { @@ -16,7 +16,7 @@ Meteor.methods({ } CustomUserStatus.removeById(userStatusID); - Streamer.sendDeleteCustomUserStatus(userStatus); + StreamService.sendDeleteCustomUserStatus(userStatus); return true; }, diff --git a/app/user-status/server/methods/insertOrUpdateUserStatus.js b/app/user-status/server/methods/insertOrUpdateUserStatus.js index 6f9e3a8e784c2..8bf8dcf0ea213 100644 --- a/app/user-status/server/methods/insertOrUpdateUserStatus.js +++ b/app/user-status/server/methods/insertOrUpdateUserStatus.js @@ -3,7 +3,7 @@ import s from 'underscore.string'; import { hasPermission } from '../../../authorization'; import { CustomUserStatus } from '../../../models'; -import { Streamer } from '../../../../server/sdk'; +import { StreamService } from '../../../../server/sdk'; Meteor.methods({ insertOrUpdateUserStatus(userStatusData) { @@ -49,7 +49,7 @@ Meteor.methods({ const _id = CustomUserStatus.create(createUserStatus); - Streamer.sendUpdateCustomUserStatus(createUserStatus); + StreamService.sendUpdateCustomUserStatus(createUserStatus); return _id; } @@ -63,7 +63,7 @@ Meteor.methods({ CustomUserStatus.setStatusType(userStatusData._id, userStatusData.statusType); } - Streamer.sendUpdateCustomUserStatus(userStatusData); + StreamService.sendUpdateCustomUserStatus(userStatusData); return true; }, diff --git a/imports/users-presence/server/activeUsers.js b/imports/users-presence/server/activeUsers.js index 62b091ae5c40a..b90aeccb19e8e 100644 --- a/imports/users-presence/server/activeUsers.js +++ b/imports/users-presence/server/activeUsers.js @@ -1,7 +1,7 @@ import { UserPresenceEvents } from 'meteor/konecty:user-presence'; import { settings } from '../../../app/settings/server'; -import { Streamer } from '../../../server/sdk'; +import { StreamService } from '../../../server/sdk'; // mirror of object in /imports/startup/client/listenActiveUsers.js - keep updated export const STATUS_MAP = { @@ -20,7 +20,7 @@ export const setUserStatus = (user, status/* , statusConnection*/) => { // since this callback can be called by only one instance in the cluster // we need to broadcast the change to all instances - Streamer.sendUserStatus({ + StreamService.sendUserStatus({ uid, username, status: STATUS_MAP[status], diff --git a/server/main.d.ts b/server/main.d.ts index 110e86aacabf5..b3cf986be543f 100644 --- a/server/main.d.ts +++ b/server/main.d.ts @@ -1,6 +1,8 @@ import { EJSON } from 'meteor/ejson'; import { Db } from 'mongodb'; +import { IStreamer } from './sdk/types/IStreamService'; + /* eslint-disable @typescript-eslint/interface-name-prefix */ declare module 'meteor/random' { namespace Random { @@ -38,10 +40,7 @@ declare module 'meteor/meteor' { details?: string | undefined | Record; } - interface Streamer { - allowWrite(allow: string): void; - allowRead(allow: string): void; - } + const Streamer: IStreamer; const server: any; diff --git a/server/methods/addRoomLeader.js b/server/methods/addRoomLeader.js index aac0cb3b899b2..828b2c37a926f 100644 --- a/server/methods/addRoomLeader.js +++ b/server/methods/addRoomLeader.js @@ -4,7 +4,7 @@ import { check } from 'meteor/check'; import { hasPermission } from '../../app/authorization'; import { Users, Subscriptions, Messages } from '../../app/models'; import { settings } from '../../app/settings'; -import { Streamer } from '../sdk'; +import { StreamService } from '../sdk'; Meteor.methods({ addRoomLeader(rid, userId) { @@ -58,7 +58,7 @@ Meteor.methods({ }); if (settings.get('UI_DisplayRoles')) { - Streamer.sendRoleUpdate({ + StreamService.sendRoleUpdate({ type: 'added', _id: 'leader', u: { diff --git a/server/methods/addRoomModerator.js b/server/methods/addRoomModerator.js index 7a2d7ed878bac..5ee74b74cdbbe 100644 --- a/server/methods/addRoomModerator.js +++ b/server/methods/addRoomModerator.js @@ -4,7 +4,7 @@ import { check } from 'meteor/check'; import { hasPermission } from '../../app/authorization'; import { Users, Subscriptions, Messages } from '../../app/models'; import { settings } from '../../app/settings'; -import { Streamer } from '../sdk'; +import { StreamService } from '../sdk'; Meteor.methods({ addRoomModerator(rid, userId) { @@ -58,7 +58,7 @@ Meteor.methods({ }); if (settings.get('UI_DisplayRoles')) { - Streamer.sendRoleUpdate({ + StreamService.sendRoleUpdate({ type: 'added', _id: 'moderator', u: { diff --git a/server/methods/addRoomOwner.js b/server/methods/addRoomOwner.js index 9e06f3650caf6..d226b4a42ba93 100644 --- a/server/methods/addRoomOwner.js +++ b/server/methods/addRoomOwner.js @@ -4,7 +4,7 @@ import { check } from 'meteor/check'; import { hasPermission } from '../../app/authorization'; import { Users, Subscriptions, Messages } from '../../app/models'; import { settings } from '../../app/settings'; -import { Streamer } from '../sdk'; +import { StreamService } from '../sdk'; Meteor.methods({ addRoomOwner(rid, userId) { @@ -58,7 +58,7 @@ Meteor.methods({ }); if (settings.get('UI_DisplayRoles')) { - Streamer.sendRoleUpdate({ + StreamService.sendRoleUpdate({ type: 'added', _id: 'owner', u: { diff --git a/server/methods/removeRoomLeader.js b/server/methods/removeRoomLeader.js index 21cff4e995a19..cadb8010963bd 100644 --- a/server/methods/removeRoomLeader.js +++ b/server/methods/removeRoomLeader.js @@ -4,7 +4,7 @@ import { check } from 'meteor/check'; import { hasPermission } from '../../app/authorization'; import { Users, Subscriptions, Messages } from '../../app/models'; import { settings } from '../../app/settings'; -import { Streamer } from '../sdk'; +import { StreamService } from '../sdk'; Meteor.methods({ removeRoomLeader(rid, userId) { @@ -58,7 +58,7 @@ Meteor.methods({ }); if (settings.get('UI_DisplayRoles')) { - Streamer.sendRoleUpdate({ + StreamService.sendRoleUpdate({ type: 'removed', _id: 'leader', u: { diff --git a/server/methods/removeRoomModerator.js b/server/methods/removeRoomModerator.js index dab2c5d990915..632474720134c 100644 --- a/server/methods/removeRoomModerator.js +++ b/server/methods/removeRoomModerator.js @@ -4,7 +4,7 @@ import { check } from 'meteor/check'; import { hasPermission } from '../../app/authorization'; import { Users, Subscriptions, Messages } from '../../app/models'; import { settings } from '../../app/settings'; -import { Streamer } from '../sdk'; +import { StreamService } from '../sdk'; Meteor.methods({ removeRoomModerator(rid, userId) { @@ -58,7 +58,7 @@ Meteor.methods({ }); if (settings.get('UI_DisplayRoles')) { - Streamer.sendRoleUpdate({ + StreamService.sendRoleUpdate({ type: 'removed', _id: 'moderator', u: { diff --git a/server/methods/removeRoomOwner.js b/server/methods/removeRoomOwner.js index 2e88c506ea2e8..4a3f5cfb39298 100644 --- a/server/methods/removeRoomOwner.js +++ b/server/methods/removeRoomOwner.js @@ -4,7 +4,7 @@ import { check } from 'meteor/check'; import { hasPermission, getUsersInRole } from '../../app/authorization'; import { Users, Subscriptions, Messages } from '../../app/models'; import { settings } from '../../app/settings'; -import { Streamer } from '../sdk'; +import { StreamService } from '../sdk'; Meteor.methods({ removeRoomOwner(rid, userId) { @@ -65,7 +65,7 @@ Meteor.methods({ }); if (settings.get('UI_DisplayRoles')) { - Streamer.sendRoleUpdate({ + StreamService.sendRoleUpdate({ type: 'removed', _id: 'owner', u: { diff --git a/server/methods/resetAvatar.js b/server/methods/resetAvatar.js index 569c706beaa3f..378e25b3f3ede 100644 --- a/server/methods/resetAvatar.js +++ b/server/methods/resetAvatar.js @@ -5,7 +5,7 @@ import { FileUpload } from '../../app/file-upload'; import { Users } from '../../app/models/server'; import { settings } from '../../app/settings'; import { hasPermission } from '../../app/authorization/server'; -import { Streamer } from '../sdk'; +import { StreamService } from '../sdk'; Meteor.methods({ resetAvatar(userId) { @@ -43,7 +43,7 @@ Meteor.methods({ FileUpload.getStore('Avatars').deleteByName(user.username); Users.unsetAvatarData(user._id); - Streamer.sendUserAvatarUpdate({ username: user.username, etag: null }); + StreamService.sendUserAvatarUpdate({ username: user.username, etag: null }); }, }); diff --git a/server/publications/settings/emitter.js b/server/publications/settings/emitter.js index 77bfc706cd370..cd9a1765fb6bf 100644 --- a/server/publications/settings/emitter.js +++ b/server/publications/settings/emitter.js @@ -2,7 +2,7 @@ import { Settings } from '../../../app/models/server'; import { Notifications } from '../../../app/notifications/server'; import { hasAtLeastOnePermission } from '../../../app/authorization/server'; import { SettingsEvents } from '../../../app/settings/server/functions/settings'; -import { Streamer } from '../../sdk'; +import { StreamService } from '../../sdk'; Settings.on('change', ({ clientAction, id, data, diff }) => { if (diff && Object.keys(diff).length === 1 && diff._updatedAt) { // avoid useless changes @@ -30,7 +30,7 @@ Settings.on('change', ({ clientAction, id, data, diff }) => { if (setting.public === true) { Notifications.notifyAllInThisInstance('public-settings-changed', clientAction, value); } - Streamer.sendPrivateSetting({ clientAction, setting }); + StreamService.sendPrivateSetting({ clientAction, setting }); break; } @@ -40,7 +40,7 @@ Settings.on('change', ({ clientAction, id, data, diff }) => { if (setting && setting.public === true) { Notifications.notifyAllInThisInstance('public-settings-changed', clientAction, { _id: id }); } - Streamer.sendPrivateSetting({ clientAction, setting: { _id: id } }); + StreamService.sendPrivateSetting({ clientAction, setting: { _id: id } }); break; } } diff --git a/server/sdk/index.ts b/server/sdk/index.ts index c09ebc4bd53e3..d5ac2be37b224 100644 --- a/server/sdk/index.ts +++ b/server/sdk/index.ts @@ -6,7 +6,7 @@ import { IServiceContext } from './types/ServiceClass'; import { IPresence } from './types/IPresence'; import { IAccount } from './types/IAccount'; import { ILicense } from './types/ILicense'; -import { IStreamer } from './types/IStreamer'; +import { IStreamService } from './types/IStreamService'; import { IMeteor } from './types/IMeteor'; // TODO think in a way to not have to pass the service name to proxify here as well @@ -14,7 +14,7 @@ export const Authorization = proxify('authorization'); export const Presence = proxify('presence'); export const Account = proxify('accounts'); export const License = proxify('license'); -export const Streamer = proxify('streamer'); +export const StreamService = proxify('streamer'); export const MeteorService = proxify('meteor'); export const asyncLocalStorage = new AsyncLocalStorage(); diff --git a/server/sdk/types/IStreamer.ts b/server/sdk/types/IStreamService.ts similarity index 60% rename from server/sdk/types/IStreamer.ts rename to server/sdk/types/IStreamService.ts index 1dde967caedf1..0cd7cd3b3feb9 100644 --- a/server/sdk/types/IStreamer.ts +++ b/server/sdk/types/IStreamService.ts @@ -1,4 +1,5 @@ import { IServiceClass } from './ServiceClass'; +import { IMessage } from '../../../definition/IMessage'; export enum STATUS_MAP { OFFLINE = 0, @@ -7,7 +8,24 @@ export enum STATUS_MAP { BUDY = 3, } -export interface IStreamer extends IServiceClass { +export interface IStreamer { + // eslint-disable-next-line @typescript-eslint/no-misused-new + new(name: string): IStreamer; + + allowWrite(allow: string): void; + + allowWrite(allow: (this: {userId?: string}, eventName: string, ...args: any[]) => void): void; + + allowRead(allow: string): void; + + allowRead(allow: (this: {userId?: string}, eventName: string, ...args: any[]) => void): void; + + emit(event: string, ...data: any[]): void; + + emitWithoutBroadcast(event: string, ...data: any[]): void; +} + +export interface IStreamService extends IServiceClass { notifyAll(eventName: string, ...args: any[]): void; notifyUser(uid: string, eventName: string, ...args: any[]): void; sendUserStatus({ uid, username, status, statusText }: { uid: string; username: string; status: STATUS_MAP; statusText?: string }): void; @@ -22,4 +40,5 @@ export interface IStreamer extends IServiceClass { sendUserNameChanged(userData: Record): void; sendDeleteCustomUserStatus(userStatusData: Record): void; sendUpdateCustomUserStatus(userStatusData: Record): void; + sendEphemeralMessage(uid: string, rid: string, message: Partial): void; } diff --git a/server/services/startup.ts b/server/services/startup.ts index 219ded7ac6eb2..f40286c43903b 100644 --- a/server/services/startup.ts +++ b/server/services/startup.ts @@ -3,11 +3,9 @@ import { Meteor } from 'meteor/meteor'; import { api } from '../sdk/api'; import { Authorization } from './authorization/service'; -import { Streamer } from './streamer/service'; +import { StreamService } from './stream/service'; import { MeteorService } from './meteor/service'; api.registerService(new Authorization(MongoInternals.defaultRemoteCollectionDriver().mongo.db)); api.registerService(new MeteorService()); - -// TODO how to make typescript know that "Meteor.Streamer" actually exists? -api.registerService(new Streamer((Meteor as any).Streamer)); +api.registerService(new StreamService(Meteor.Streamer)); diff --git a/server/services/stream/service.ts b/server/services/stream/service.ts new file mode 100644 index 0000000000000..e2ad76fa44a00 --- /dev/null +++ b/server/services/stream/service.ts @@ -0,0 +1,158 @@ +import { ServiceClass } from '../../sdk/types/ServiceClass'; +import { IStreamService, STATUS_MAP, IStreamer } from '../../sdk/types/IStreamService'; +// import { Notifications } from '../../../app/notifications/server'; +import { IMessage } from '../../../definition/IMessage'; + +export class StreamService extends ServiceClass implements IStreamService { + protected name = 'streamer'; + + private streamLogged: IStreamer; + + private streamAll: IStreamer; + + private streamRoom: IStreamer; + + private streamRoomUsers: IStreamer; + + private streamUser: IStreamer; + + constructor(Streamer: IStreamer) { + super(); + + this.streamLogged = new Streamer('notify-logged'); + this.streamAll = new Streamer('notify-all'); + this.streamRoom = new Streamer('notify-room'); + this.streamRoomUsers = new Streamer('notify-room-users'); + this.streamUser = new Streamer('notify-user'); + + this.streamAll.allowWrite('none'); + this.streamLogged.allowWrite('none'); + this.streamRoom.allowWrite('none'); + // TODO: Missing models for this code + // this.streamRoomUsers.allowWrite(function(eventName, ...args) { + // const [roomId, e] = eventName.split('/'); + // if (Subscriptions.findOneByRoomIdAndUserId(roomId, this.userId) != null) { + // const subscriptions = Subscriptions.findByRoomIdAndNotUserId(roomId, this.userId).fetch(); + // subscriptions.forEach((subscription) => self.notifyUser(subscription.u._id, e, ...args)); + // } + // return false; + // }); + this.streamUser.allowWrite('logged'); + this.streamAll.allowRead('all'); + this.streamLogged.allowRead('logged'); + // TODO: Missing models for this code + // this.streamRoom.allowRead(function(eventName, extraData) { + // const [roomId] = eventName.split('/'); + // const room = Rooms.findOneById(roomId); + // if (!room) { + // console.warn(`Invalid streamRoom eventName: "${ eventName }"`); + // return false; + // } + // if (room.t === 'l' && extraData && extraData.token && room.v.token === extraData.token) { + // return true; + // } + // if (this.userId == null) { + // return false; + // } + // const subscription = Subscriptions.findOneByRoomIdAndUserId(roomId, this.userId, { fields: { _id: 1 } }); + // return subscription != null; + // }); + this.streamRoomUsers.allowRead('none'); + this.streamUser.allowRead(function(eventName) { + const [userId] = eventName.split('/'); + return (this.userId != null) && this.userId === userId; + }); + } + + sendDeleteCustomEmoji(emojiData: Record): void { + this.streamLogged.emit('deleteEmojiCustom', { + emojiData, + }); + } + + sendUpdateCustomEmoji(emojiData: Record): void { + this.streamLogged.emit('updateEmojiCustom', { + emojiData, + }); + } + + sendUserDeleted(uid: string): void { + this.streamLogged.emit('Users:Deleted', { + userId: uid, + }); + } + + sendUserNameChanged(userData: Record): void { + this.streamLogged.emit('Users:NameChanged', userData); + } + + sendDeleteCustomUserStatus(userStatusData: Record): void { + this.streamLogged.emit('deleteCustomUserStatus', { + userStatusData, + }); + } + + sendUpdateCustomUserStatus(userStatusData: Record): void { + this.streamLogged.emit('updateCustomUserStatus', { + userStatusData, + }); + } + + sendUserStatus({ uid, username, status, statusText }: { uid: string; username: string; status: STATUS_MAP; statusText?: string }): void { + this.streamLogged.emit('user-status', [ + uid, + username, + status, + statusText, + ]); + } + + sendPermission({ clientAction, data }: any): void { + this.streamLogged.emitWithoutBroadcast('permissions-changed', clientAction, data); + } + + sendPrivateSetting({ clientAction, setting }: any): void { + this.streamLogged.emitWithoutBroadcast('private-settings-changed', clientAction, setting); + } + + sendUserAvatarUpdate({ username, etag }: { username: string; etag?: string }): void { + this.streamLogged.emit('updateAvatar', { + username, + etag, + }); + } + + sendRoomAvatarUpdate({ rid, etag }: { rid: string; etag?: string }): void { + this.streamLogged.emit('updateAvatar', { + rid, + etag, + }); + } + + sendRoleUpdate(update: Record): void { + this.streamLogged.emit('roles-change', update); + } + + sendEphemeralMessage(uid: string, rid: string, message: Partial): void { + this.notifyUser(uid, 'message', { + groupable: false, + ...message, + _id: String(Date.now()), + rid, + ts: new Date(), + }); + } + + notifyAll(eventName: string, ...args: any[]): void { + console.log('notifyAll', eventName, args); + } + + notifyUser(uid: string, eventName: string, ...args: any[]): void { + console.log('notifyUser', uid, eventName, args); + // if (this.debug === true) { + // console.log('notifyUser', [userId, eventName, ...args]); + // } + + return this.streamUser.emit(`${ uid }/${ eventName }`, ...args); + } +} diff --git a/server/services/streamer/service.ts b/server/services/streamer/service.ts deleted file mode 100644 index a35e5c86fbd07..0000000000000 --- a/server/services/streamer/service.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { ServiceClass } from '../../sdk/types/ServiceClass'; -import { IStreamer, STATUS_MAP } from '../../sdk/types/IStreamer'; -// import { Notifications } from '../../../app/notifications/server'; - -export class Streamer extends ServiceClass implements IStreamer { - protected name = 'streamer'; - - private streamLogged: any; - - constructor(Streamer: any) { - super(); - - this.streamLogged = new Streamer('notify-logged'); - this.streamLogged.allowWrite('none'); - this.streamLogged.allowRead('logged'); - } - - sendDeleteCustomEmoji(emojiData: Record): void { - this.streamLogged.emit('deleteEmojiCustom', { - emojiData, - }); - } - - sendUpdateCustomEmoji(emojiData: Record): void { - this.streamLogged.emit('updateEmojiCustom', { - emojiData, - }); - } - - sendUserDeleted(uid: string): void { - this.streamLogged.emit('Users:Deleted', { - userId: uid, - }); - } - - sendUserNameChanged(userData: Record): void { - this.streamLogged.emit('Users:NameChanged', userData); - } - - sendDeleteCustomUserStatus(userStatusData: Record): void { - this.streamLogged.emit('deleteCustomUserStatus', { - userStatusData, - }); - } - - sendUpdateCustomUserStatus(userStatusData: Record): void { - this.streamLogged.emit('updateCustomUserStatus', { - userStatusData, - }); - } - - sendUserStatus({ uid, username, status, statusText }: { uid: string; username: string; status: STATUS_MAP; statusText?: string }): void { - this.streamLogged.emit('user-status', [ - uid, - username, - status, - statusText, - ]); - } - - sendPermission({ clientAction, data }: any): void { - this.streamLogged.emitWithoutBroadcast('permissions-changed', clientAction, data); - } - - sendPrivateSetting({ clientAction, setting }: any): void { - this.streamLogged.emitWithoutBroadcast('private-settings-changed', clientAction, setting); - } - - sendUserAvatarUpdate({ username, etag }: { username: string; etag?: string }): void { - this.streamLogged.emit('updateAvatar', { - username, - etag, - }); - } - - sendRoomAvatarUpdate({ rid, etag }: { rid: string; etag?: string }): void { - this.streamLogged.emit('updateAvatar', { - rid, - etag, - }); - } - - sendRoleUpdate(update: Record): void { - this.streamLogged.emit('roles-change', update); - } - - notifyAll(eventName: string, ...args: any[]): void { - console.log('notifyAll', eventName, args); - } - - notifyUser(uid: string, eventName: string, ...args: any[]): void { - console.log('notifyUser', uid, eventName, args); - } -} From 8e4805f50f171575894c489f9187d278f2781ed6 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Mon, 28 Sep 2020 09:22:56 -0300 Subject: [PATCH 080/198] Move most of the notifications and streams code to a isolated module file --- app/apps/server/communication/websockets.js | 15 +- app/authorization/server/lib/streamer.js | 5 - .../server/methods/deleteRole.js | 4 +- app/authorization/server/methods/saveRole.js | 4 +- .../server/classes/ImporterWebsocket.js | 7 +- .../server/methods/clearIntegrationHistory.js | 4 +- app/integrations/server/streamer.js | 11 +- app/livechat/server/lib/Helper.js | 3 +- app/livechat/server/lib/Livechat.js | 9 +- .../server/lib/stream/queueManager.js | 10 +- app/logger/server/streamer.js | 7 +- app/notifications/server/lib/Notifications.js | 92 +--------- .../server/hooks/onRemoveAgentDepartment.ts | 4 +- .../server/hooks/onSaveAgentDepartment.ts | 4 +- .../server/methods/removeCannedResponse.js | 4 +- .../server/methods/saveCannedResponse.js | 4 +- ee/app/canned-responses/server/streamer.js | 8 +- .../livechat-enterprise/server/lib/Helper.js | 4 +- .../notifications/notifications.module.ts | 170 ++++++++++++++++++ server/sdk/types/IStreamService.ts | 6 +- server/stream/rooms/index.js | 10 +- 21 files changed, 225 insertions(+), 160 deletions(-) delete mode 100644 app/authorization/server/lib/streamer.js create mode 100644 server/modules/notifications/notifications.module.ts diff --git a/app/apps/server/communication/websockets.js b/app/apps/server/communication/websockets.js index e5615c0e2d91a..ead46d67e59d9 100644 --- a/app/apps/server/communication/websockets.js +++ b/app/apps/server/communication/websockets.js @@ -1,6 +1,7 @@ -import { Meteor } from 'meteor/meteor'; import { AppStatusUtils } from '@rocket.chat/apps-engine/definition/AppStatus'; +import notifications from '../../../notifications/server/lib/Notifications'; + export const AppEvents = Object.freeze({ APP_ADDED: 'app/added', APP_REMOVED: 'app/removed', @@ -101,18 +102,10 @@ export class AppServerListener { export class AppServerNotifier { constructor(orch) { - this.engineStreamer = new Meteor.Streamer('apps-engine', { retransmit: false }); - this.engineStreamer.serverOnly = true; - this.engineStreamer.allowRead('none'); - this.engineStreamer.allowEmit('all'); - this.engineStreamer.allowWrite('none'); + this.engineStreamer = notifications.streamAppsEngine; // This is used to broadcast to the web clients - this.clientStreamer = new Meteor.Streamer('apps', { retransmit: false }); - this.clientStreamer.serverOnly = true; - this.clientStreamer.allowRead('all'); - this.clientStreamer.allowEmit('all'); - this.clientStreamer.allowWrite('none'); + this.clientStreamer = notifications.streamApps; this.received = new Map(); this.listener = new AppServerListener(orch, this.engineStreamer, this.clientStreamer, this.received); diff --git a/app/authorization/server/lib/streamer.js b/app/authorization/server/lib/streamer.js deleted file mode 100644 index f00103832be8e..0000000000000 --- a/app/authorization/server/lib/streamer.js +++ /dev/null @@ -1,5 +0,0 @@ -import { Meteor } from 'meteor/meteor'; - -export const rolesStreamer = new Meteor.Streamer('roles'); -rolesStreamer.allowWrite('none'); -rolesStreamer.allowRead('logged'); diff --git a/app/authorization/server/methods/deleteRole.js b/app/authorization/server/methods/deleteRole.js index 56ab719fe981c..89af2485e575a 100644 --- a/app/authorization/server/methods/deleteRole.js +++ b/app/authorization/server/methods/deleteRole.js @@ -2,7 +2,7 @@ import { Meteor } from 'meteor/meteor'; import * as Models from '../../../models/server'; import { hasPermission } from '../functions/hasPermission'; -import { rolesStreamer } from '../lib/streamer'; +import notifications from '../../../notifications/server/lib/Notifications'; Meteor.methods({ 'authorization:deleteRole'(roleName) { @@ -38,7 +38,7 @@ Meteor.methods({ const removed = Models.Roles.remove(role.name); if (removed) { - rolesStreamer.emit('roles', { + notifications.streamRoles.emit('roles', { type: 'removed', name: roleName, }); diff --git a/app/authorization/server/methods/saveRole.js b/app/authorization/server/methods/saveRole.js index c335e19b8598d..ccb5e9b3f1567 100644 --- a/app/authorization/server/methods/saveRole.js +++ b/app/authorization/server/methods/saveRole.js @@ -3,8 +3,8 @@ import { Meteor } from 'meteor/meteor'; import { Roles } from '../../../models/server'; import { settings } from '../../../settings/server'; import { hasPermission } from '../functions/hasPermission'; -import { rolesStreamer } from '../lib/streamer'; import { StreamService } from '../../../../server/sdk'; +import notifications from '../../../notifications/server/lib/Notifications'; Meteor.methods({ 'authorization:saveRole'(roleData) { @@ -32,7 +32,7 @@ Meteor.methods({ _id: roleData.name, }); } - rolesStreamer.emit('roles', { + notifications.streamRoles.emit('roles', { type: 'changed', ...roleData, }); diff --git a/app/importer/server/classes/ImporterWebsocket.js b/app/importer/server/classes/ImporterWebsocket.js index 07e23cb77e59c..ba067fda38675 100644 --- a/app/importer/server/classes/ImporterWebsocket.js +++ b/app/importer/server/classes/ImporterWebsocket.js @@ -1,11 +1,8 @@ -import { Meteor } from 'meteor/meteor'; +import notifications from '../../../notifications/server/lib/Notifications'; class ImporterWebsocketDef { constructor() { - this.streamer = new Meteor.Streamer('importers', { retransmit: false }); - this.streamer.allowRead('all'); - this.streamer.allowEmit('all'); - this.streamer.allowWrite('none'); + this.streamer = notifications.streamImporters; } /** diff --git a/app/integrations/server/methods/clearIntegrationHistory.js b/app/integrations/server/methods/clearIntegrationHistory.js index 4bd3f2a5cdc68..87eec581e37aa 100644 --- a/app/integrations/server/methods/clearIntegrationHistory.js +++ b/app/integrations/server/methods/clearIntegrationHistory.js @@ -2,7 +2,7 @@ import { Meteor } from 'meteor/meteor'; import { hasPermission } from '../../../authorization'; import { IntegrationHistory, Integrations } from '../../../models'; -import { integrationHistoryStreamer } from '../streamer'; +import notifications from '../../../notifications/server/lib/Notifications'; Meteor.methods({ clearIntegrationHistory(integrationId) { @@ -22,7 +22,7 @@ Meteor.methods({ IntegrationHistory.removeByIntegrationId(integrationId); - integrationHistoryStreamer.emit(integrationId, { type: 'removed' }); + notifications.streamIntegrationHistory.emit(integrationId, { type: 'removed' }); return true; }, diff --git a/app/integrations/server/streamer.js b/app/integrations/server/streamer.js index 557c4f871ffeb..812898eba253a 100644 --- a/app/integrations/server/streamer.js +++ b/app/integrations/server/streamer.js @@ -1,11 +1,8 @@ -import { Meteor } from 'meteor/meteor'; - import { hasAtLeastOnePermission } from '../../authorization/server'; import { IntegrationHistory } from '../../models/server'; +import notifications from '../../notifications/server/lib/Notifications'; -export const integrationHistoryStreamer = new Meteor.Streamer('integrationHistory'); -integrationHistoryStreamer.allowWrite('none'); -integrationHistoryStreamer.allowRead(function() { +notifications.streamIntegrationHistory.allowRead(function() { return this.userId && hasAtLeastOnePermission(this.userId, [ 'manage-outgoing-integrations', 'manage-own-outgoing-integrations', @@ -19,11 +16,11 @@ IntegrationHistory.on('change', ({ clientAction, id, data, diff }) => { if (!history && !history.integration) { return; } - integrationHistoryStreamer.emit(history.integration._id, { id, diff, type: clientAction }); + notifications.streamIntegrationHistory.emit(history.integration._id, { id, diff, type: clientAction }); break; } case 'inserted': { - integrationHistoryStreamer.emit(data.integration._id, { data, type: clientAction }); + notifications.streamIntegrationHistory.emit(data.integration._id, { data, type: clientAction }); break; } } diff --git a/app/livechat/server/lib/Helper.js b/app/livechat/server/lib/Helper.js index 42b6e890da1fa..f612f435e7185 100644 --- a/app/livechat/server/lib/Helper.js +++ b/app/livechat/server/lib/Helper.js @@ -9,6 +9,7 @@ import { RoutingManager } from './RoutingManager'; import { callbacks } from '../../../callbacks/server'; import { settings } from '../../../settings'; import { Apps, AppEvents } from '../../../apps/server'; +import notifications from '../../../notifications/server/lib/Notifications'; export const createLivechatRoom = (rid, name, guest, roomInfo = {}, extraData = {}) => { check(rid, String); @@ -225,7 +226,7 @@ export const normalizeAgent = (agentId) => { export const dispatchAgentDelegated = (rid, agentId) => { const agent = normalizeAgent(agentId); - Livechat.stream.emit(rid, { + notifications.streamLivechatRoom.emit(rid, { type: 'agentData', data: agent, }); diff --git a/app/livechat/server/lib/Livechat.js b/app/livechat/server/lib/Livechat.js index bdcb69c15cbc7..83dc9cc1e44af 100644 --- a/app/livechat/server/lib/Livechat.js +++ b/app/livechat/server/lib/Livechat.js @@ -38,6 +38,7 @@ import { FileUpload } from '../../../file-upload/server'; import { normalizeTransferredByData, parseAgentCustomFields, updateDepartmentAgents } from './Helper'; import { Apps, AppEvents } from '../../../apps/server'; import { businessHourManager } from '../business-hour'; +import notifications from '../../../notifications/server/lib/Notifications'; export const Livechat = { Analytics, @@ -1107,7 +1108,7 @@ export const Livechat = { } LivechatRooms.findOpenByAgent(userId).forEach((room) => { - Livechat.stream.emit(room._id, { + notifications.streamLivechatRoom.emit(room._id, { type: 'agentStatus', status, }); @@ -1123,7 +1124,7 @@ export const Livechat = { }, notifyRoomVisitorChange(roomId, visitor) { - Livechat.stream.emit(roomId, { + notifications.streamLivechatRoom.emit(roomId, { type: 'visitorData', visitor, }); @@ -1156,9 +1157,7 @@ export const Livechat = { }, }; -Livechat.stream = new Meteor.Streamer('livechat-room'); - -Livechat.stream.allowRead((roomId, extraData) => { +notifications.streamLivechatRoom.allowRead((roomId, extraData) => { const room = LivechatRooms.findOneById(roomId); if (!room) { diff --git a/app/livechat/server/lib/stream/queueManager.js b/app/livechat/server/lib/stream/queueManager.js index bf30b8397ff89..b1fc913668778 100644 --- a/app/livechat/server/lib/stream/queueManager.js +++ b/app/livechat/server/lib/stream/queueManager.js @@ -1,17 +1,13 @@ -import { Meteor } from 'meteor/meteor'; - import { hasPermission } from '../../../../authorization/server'; import { LivechatInquiry } from '../../../../models/server'; -import { LIVECHAT_INQUIRY_QUEUE_STREAM_OBSERVER } from '../../../lib/stream/constants'; import { RoutingManager } from '../RoutingManager'; +import notifications from '../../../../notifications/server/lib/Notifications'; -const queueDataStreamer = new Meteor.Streamer(LIVECHAT_INQUIRY_QUEUE_STREAM_OBSERVER); -queueDataStreamer.allowWrite('none'); -queueDataStreamer.allowRead(function() { +notifications.streamLivechatQueueData.allowRead(function() { return this.userId ? hasPermission(this.userId, 'view-l-room') : false; }); -const emitQueueDataEvent = (event, data) => queueDataStreamer.emitWithoutBroadcast(event, data); +const emitQueueDataEvent = (event, data) => notifications.streamLivechatQueueData.emitWithoutBroadcast(event, data); const mountDataToEmit = (type, data) => ({ type, ...data }); LivechatInquiry.on('change', ({ clientAction, id: _id, data: record }) => { diff --git a/app/logger/server/streamer.js b/app/logger/server/streamer.js index 475c7a183efd0..269848072492b 100644 --- a/app/logger/server/streamer.js +++ b/app/logger/server/streamer.js @@ -7,6 +7,7 @@ import { Log } from 'meteor/logging'; import { settings } from '../../settings'; import { hasPermission } from '../../authorization/server'; +import notifications from '../../notifications/server/lib/Notifications'; export const processString = function(string, date) { let obj; @@ -52,15 +53,13 @@ export const StdOut = new class extends EventEmitter { } }(); -const stdoutStreamer = new Meteor.Streamer('stdout'); -stdoutStreamer.allowWrite('none'); -stdoutStreamer.allowRead(function() { +notifications.streamStdout.allowRead(function() { return this.userId ? hasPermission(this.userId, 'view-logs') : false; }); Meteor.startup(() => { const handler = (string, item) => { - stdoutStreamer.emitWithoutBroadcast('stdout', { + notifications.streamStdout.emitWithoutBroadcast('stdout', { ...item, }); }; diff --git a/app/notifications/server/lib/Notifications.js b/app/notifications/server/lib/Notifications.js index 003db8f22f143..08ce19371c9fc 100644 --- a/app/notifications/server/lib/Notifications.js +++ b/app/notifications/server/lib/Notifications.js @@ -4,6 +4,7 @@ import { DDPCommon } from 'meteor/ddp-common'; import { WEB_RTC_EVENTS } from '../../../webrtc'; import { Subscriptions, Rooms } from '../../../models/server'; import { settings } from '../../../settings/server'; +import { NotificationsModule } from '../../../../server/modules/notifications/notifications.module'; const changedPayload = function(collection, id, fields) { return DDPCommon.stringifyDDP({ @@ -58,19 +59,11 @@ class RoomStreamer extends Meteor.Streamer { } } -class Notifications { - constructor() { +class Notifications extends NotificationsModule { + constructor(Streamer, RoomStreamer) { + super(Streamer, RoomStreamer); + const self = this; - this.debug = false; - this.notifyUser = this.notifyUser.bind(this); - this.streamAll = new Meteor.Streamer('notify-all'); - this.streamLogged = new Meteor.Streamer('notify-logged'); - this.streamRoom = new Meteor.Streamer('notify-room'); - this.streamRoomUsers = new Meteor.Streamer('notify-room-users'); - this.streamUser = new RoomStreamer('notify-user'); - this.streamAll.allowWrite('none'); - this.streamLogged.allowWrite('none'); - this.streamRoom.allowWrite('none'); this.streamRoomUsers.allowWrite(function(eventName, ...args) { const [roomId, e] = eventName.split('/'); // const user = Meteor.users.findOne(this.userId, { @@ -84,9 +77,7 @@ class Notifications { } return false; }); - this.streamUser.allowWrite('logged'); - this.streamAll.allowRead('all'); - this.streamLogged.allowRead('logged'); + this.streamRoom.allowRead(function(eventName, extraData) { const [roomId] = eventName.split('/'); const room = Rooms.findOneById(roomId); @@ -103,79 +94,10 @@ class Notifications { const subscription = Subscriptions.findOneByRoomIdAndUserId(roomId, this.userId, { fields: { _id: 1 } }); return subscription != null; }); - this.streamRoomUsers.allowRead('none'); - this.streamUser.allowRead(function(eventName) { - const [userId] = eventName.split('/'); - return (this.userId != null) && this.userId === userId; - }); - } - - notifyAll(eventName, ...args) { - if (this.debug === true) { - console.log('notifyAll', [eventName, ...args]); - } - args.unshift(eventName); - return this.streamAll.emit.apply(this.streamAll, args); - } - - notifyLogged(eventName, ...args) { - if (this.debug === true) { - console.log('notifyLogged', [eventName, ...args]); - } - args.unshift(eventName); - return this.streamLogged.emit.apply(this.streamLogged, args); - } - - notifyRoom(room, eventName, ...args) { - if (this.debug === true) { - console.log('notifyRoom', [room, eventName, ...args]); - } - args.unshift(`${ room }/${ eventName }`); - return this.streamRoom.emit.apply(this.streamRoom, args); - } - - notifyUser(userId, eventName, ...args) { - if (this.debug === true) { - console.log('notifyUser', [userId, eventName, ...args]); - } - args.unshift(`${ userId }/${ eventName }`); - return this.streamUser.emit.apply(this.streamUser, args); - } - - notifyAllInThisInstance(eventName, ...args) { - if (this.debug === true) { - console.log('notifyAll', [eventName, ...args]); - } - args.unshift(eventName); - return this.streamAll.emitWithoutBroadcast.apply(this.streamAll, args); - } - - notifyLoggedInThisInstance(eventName, ...args) { - if (this.debug === true) { - console.log('notifyLogged', [eventName, ...args]); - } - args.unshift(eventName); - return this.streamLogged.emitWithoutBroadcast.apply(this.streamLogged, args); - } - - notifyRoomInThisInstance(room, eventName, ...args) { - if (this.debug === true) { - console.log('notifyRoomAndBroadcast', [room, eventName, ...args]); - } - args.unshift(`${ room }/${ eventName }`); - return this.streamRoom.emitWithoutBroadcast.apply(this.streamRoom, args); - } - - notifyUserInThisInstance(userId, eventName, ...args) { - if (this.debug === true) { - console.log('notifyUserAndBroadcast', [userId, eventName, ...args]); - } - args.unshift(`${ userId }/${ eventName }`); - return this.streamUser.emitWithoutBroadcast.apply(this.streamUser, args); } } -const notifications = new Notifications(); +const notifications = new Notifications(Meteor.Streamer, RoomStreamer); notifications.streamRoom.allowWrite(function(eventName, username, typing, extraData) { const [roomId, e] = eventName.split('/'); diff --git a/ee/app/canned-responses/server/hooks/onRemoveAgentDepartment.ts b/ee/app/canned-responses/server/hooks/onRemoveAgentDepartment.ts index e08e23a115cb4..a56acd9825400 100644 --- a/ee/app/canned-responses/server/hooks/onRemoveAgentDepartment.ts +++ b/ee/app/canned-responses/server/hooks/onRemoveAgentDepartment.ts @@ -1,12 +1,12 @@ import { callbacks } from '../../../../../app/callbacks/server'; import CannedResponse from '../../../models/server/models/CannedResponse'; -import { cannedResponsesStreamer } from '../streamer'; +import notifications from '../../../../../app/notifications/server/lib/Notifications'; callbacks.add('livechat.removeAgentDepartment', async (options: Record): Promise => { const { departmentId, agentsId } = options; CannedResponse.findByDepartmentId(departmentId, { fields: { _id: 1 } }).forEach((response: any) => { const { _id } = response; - cannedResponsesStreamer.emit('canned-responses', { type: 'removed', _id }, { agentsId }); + notifications.streamCannedResponses.emit('canned-responses', { type: 'removed', _id }, { agentsId }); }); return options; diff --git a/ee/app/canned-responses/server/hooks/onSaveAgentDepartment.ts b/ee/app/canned-responses/server/hooks/onSaveAgentDepartment.ts index d74f80f582c6c..e301b12c95408 100644 --- a/ee/app/canned-responses/server/hooks/onSaveAgentDepartment.ts +++ b/ee/app/canned-responses/server/hooks/onSaveAgentDepartment.ts @@ -1,11 +1,11 @@ import { callbacks } from '../../../../../app/callbacks/server'; import CannedResponse from '../../../models/server/models/CannedResponse'; -import { cannedResponsesStreamer } from '../streamer'; +import notifications from '../../../../../app/notifications/server/lib/Notifications'; callbacks.add('livechat.saveAgentDepartment', async (options: Record): Promise => { const { departmentId, agentsId } = options; CannedResponse.findByDepartmentId(departmentId, {}).forEach((response: any) => { - cannedResponsesStreamer.emit('canned-responses', { type: 'changed', ...response }, { agentsId }); + notifications.streamCannedResponses.emit('canned-responses', { type: 'changed', ...response }, { agentsId }); }); return options; diff --git a/ee/app/canned-responses/server/methods/removeCannedResponse.js b/ee/app/canned-responses/server/methods/removeCannedResponse.js index 6fbab487a1fb2..c7c4329fcfc6f 100644 --- a/ee/app/canned-responses/server/methods/removeCannedResponse.js +++ b/ee/app/canned-responses/server/methods/removeCannedResponse.js @@ -3,7 +3,7 @@ import { check } from 'meteor/check'; import { hasPermission } from '../../../../../app/authorization'; import CannedResponse from '../../../models/server/models/CannedResponse'; -import { cannedResponsesStreamer } from '../streamer'; +import notifications from '../../../../../app/notifications/server/lib/Notifications'; Meteor.methods({ 'removeCannedResponse'(_id) { @@ -18,7 +18,7 @@ Meteor.methods({ throw new Meteor.Error('error-canned-response-not-found', 'Canned Response not found', { method: 'removeCannedResponse' }); } - cannedResponsesStreamer.emit('canned-responses', { type: 'removed', _id }); + notifications.streamCannedResponses.emit('canned-responses', { type: 'removed', _id }); return CannedResponse.removeById(_id); }, diff --git a/ee/app/canned-responses/server/methods/saveCannedResponse.js b/ee/app/canned-responses/server/methods/saveCannedResponse.js index 6d04c467bb7fa..cca82b4eba99e 100644 --- a/ee/app/canned-responses/server/methods/saveCannedResponse.js +++ b/ee/app/canned-responses/server/methods/saveCannedResponse.js @@ -4,7 +4,7 @@ import { Match, check } from 'meteor/check'; import { hasPermission } from '../../../../../app/authorization'; import CannedResponse from '../../../models/server/models/CannedResponse'; import { Users } from '../../../../../app/models'; -import { cannedResponsesStreamer } from '../streamer'; +import notifications from '../../../../../app/notifications/server/lib/Notifications'; Meteor.methods({ saveCannedResponse(_id, responseData) { @@ -42,7 +42,7 @@ Meteor.methods({ responseData.createdBy = user.username; } const createdCannedResponse = CannedResponse.createOrUpdateCannedResponse(_id, responseData); - cannedResponsesStreamer.emit('canned-responses', { type: 'changed', ...createdCannedResponse }); + notifications.streamCannedResponses.emit('canned-responses', { type: 'changed', ...createdCannedResponse }); return createdCannedResponse; }, diff --git a/ee/app/canned-responses/server/streamer.js b/ee/app/canned-responses/server/streamer.js index ee34be29ed504..21e88d032fe86 100644 --- a/ee/app/canned-responses/server/streamer.js +++ b/ee/app/canned-responses/server/streamer.js @@ -1,11 +1,7 @@ -import { Meteor } from 'meteor/meteor'; - import { hasPermission } from '../../../../app/authorization'; import { settings } from '../../../../app/settings'; +import notifications from '../../../../app/notifications/server/lib/Notifications'; -export const cannedResponsesStreamer = new Meteor.Streamer('canned-responses'); - -cannedResponsesStreamer.allowWrite('none'); -cannedResponsesStreamer.allowRead(function() { +notifications.streamCannedResponses.allowRead(function() { return this.userId && settings.get('Canned_Responses_Enable') && hasPermission(this.userId, 'view-canned-responses'); }); diff --git a/ee/app/livechat-enterprise/server/lib/Helper.js b/ee/app/livechat-enterprise/server/lib/Helper.js index af42c8fce8078..df486b2865369 100644 --- a/ee/app/livechat-enterprise/server/lib/Helper.js +++ b/ee/app/livechat-enterprise/server/lib/Helper.js @@ -13,9 +13,9 @@ import { } from '../../../../../app/models/server'; import { Rooms as RoomRaw } from '../../../../../app/models/server/raw'; import { settings } from '../../../../../app/settings'; -import { Livechat } from '../../../../../app/livechat/server/lib/Livechat'; import { RoutingManager } from '../../../../../app/livechat/server/lib/RoutingManager'; import { dispatchAgentDelegated } from '../../../../../app/livechat/server/lib/Helper'; +import notifications from '../../../../../app/notifications/server/lib/Notifications'; export const getMaxNumberSimultaneousChat = ({ agentId, departmentId }) => { if (agentId) { @@ -82,7 +82,7 @@ export const dispatchInquiryPosition = async (inquiry, queueInfo) => { const { position, department } = inquiry; const data = await normalizeQueueInfo({ position, queueInfo, department }); const propagateInquiryPosition = Meteor.bindEnvironment((inquiry) => { - Livechat.stream.emit(inquiry.rid, { + notifications.streamLivechatRoom.emit(inquiry.rid, { type: 'queueData', data, }); diff --git a/server/modules/notifications/notifications.module.ts b/server/modules/notifications/notifications.module.ts new file mode 100644 index 0000000000000..158803c537079 --- /dev/null +++ b/server/modules/notifications/notifications.module.ts @@ -0,0 +1,170 @@ +import { IStreamer } from '../../sdk/types/IStreamService'; + +export class NotificationsModule { + private debug = false + + public readonly streamLogged: IStreamer; + + public readonly streamAll: IStreamer; + + public readonly streamRoom: IStreamer; + + public readonly streamRoomUsers: IStreamer; + + public readonly streamUser: IStreamer; + + public readonly streamImporters: IStreamer; + + public readonly streamRoles: IStreamer; + + public readonly streamApps: IStreamer; + + public readonly streamAppsEngine: IStreamer; + + public readonly streamCannedResponses: IStreamer; + + public readonly streamIntegrationHistory: IStreamer; + + public readonly streamLivechatRoom: IStreamer; + + public readonly streamLivechatQueueData: IStreamer; + + public readonly streamStdout: IStreamer; + + public readonly streamRoomData: IStreamer; + + constructor( + private Streamer: IStreamer, + private RoomStreamer: IStreamer, + ) { + this.notifyUser = this.notifyUser.bind(this); + + this.streamAll = new this.Streamer('notify-all'); + this.streamAll.allowWrite('none'); + this.streamAll.allowRead('all'); + + this.streamLogged = new this.Streamer('notify-logged'); + this.streamLogged.allowWrite('none'); + this.streamLogged.allowRead('logged'); + + this.streamRoom = new this.Streamer('notify-room'); + this.streamRoom.allowWrite('none'); + // this.streamRoom.allowRead(function(eventName, extraData) { // Implemented outside + // notifications.streamRoom.allowWrite(function(eventName, username, typing, extraData) { // Implemented outside + + this.streamRoomUsers = new this.Streamer('notify-room-users'); + this.streamRoomUsers.allowRead('none'); + // this.streamRoomUsers.allowWrite(function(eventName, ...args) { // Implemented outside + + this.streamUser = new this.RoomStreamer('notify-user'); + this.streamUser.allowWrite('logged'); + this.streamUser.allowRead(function(eventName) { + const [userId] = eventName.split('/'); + return (this.userId != null) && this.userId === userId; + }); + + this.streamImporters = new this.Streamer('importers', { retransmit: false }); + this.streamImporters.allowRead('all'); + this.streamImporters.allowEmit('all'); + this.streamImporters.allowWrite('none'); + + this.streamRoles = new this.Streamer('roles'); + this.streamRoles.allowWrite('none'); + this.streamRoles.allowRead('logged'); + + this.streamApps = new this.Streamer('apps', { retransmit: false }); + this.streamApps.serverOnly = true; + this.streamApps.allowRead('all'); + this.streamApps.allowEmit('all'); + this.streamApps.allowWrite('none'); + + this.streamAppsEngine = new this.Streamer('apps-engine', { retransmit: false }); + this.streamAppsEngine.serverOnly = true; + this.streamAppsEngine.allowRead('none'); + this.streamAppsEngine.allowEmit('all'); + this.streamAppsEngine.allowWrite('none'); + + this.streamCannedResponses = new this.Streamer('canned-responses'); + this.streamCannedResponses.allowWrite('none'); + // this.streamCannedResponses.allowRead(function() { // Implemented outside + + this.streamIntegrationHistory = new this.Streamer('integrationHistory'); + this.streamIntegrationHistory.allowWrite('none'); + // this.streamIntegrationHistory.allowRead(function() { // Implemented outside + + this.streamLivechatRoom = new this.Streamer('livechat-room'); + // this.streamLivechatRoom.allowRead((roomId, extraData) => { // Implemented outside + + this.streamLivechatQueueData = new this.Streamer('livechat-inquiry-queue-observer'); + this.streamLivechatQueueData.allowWrite('none'); + // this.streamLivechatQueueData.allowRead(function() { // Implemented outside + + this.streamStdout = new this.Streamer('stdout'); + this.streamStdout.allowWrite('none'); + // this.streamStdout.allowRead(function() { // Implemented outside + + this.streamRoomData = new this.Streamer('room-data'); + this.streamRoomData.allowWrite('none'); + // this.streamRoomData.allowRead(function(rid) { // Implemented outside + } + + notifyAll(eventName: string, ...args: any[]): void { + if (this.debug === true) { + console.log('notifyAll', [eventName, ...args]); + } + return this.streamAll.emit(eventName, ...args); + } + + notifyLogged(eventName: string, ...args: any[]): void { + if (this.debug === true) { + console.log('notifyLogged', [eventName, ...args]); + } + return this.streamLogged.emit(eventName, ...args); + } + + notifyRoom(room: string, eventName: string, ...args: any[]): void { + if (this.debug === true) { + console.log('notifyRoom', [room, eventName, ...args]); + } + return this.streamRoom.emit(`${ room }/${ eventName }`, ...args); + } + + notifyUser(userId: string, eventName: string, ...args: any[]): void { + if (this.debug === true) { + console.log('notifyUser', [userId, eventName, ...args]); + } + return this.streamUser.emit(`${ userId }/${ eventName }`, ...args); + } + + notifyAllInThisInstance(eventName: string, ...args: any[]): void { + if (this.debug === true) { + console.log('notifyAll', [eventName, ...args]); + } + return this.streamAll.emitWithoutBroadcast(eventName, ...args); + } + + notifyLoggedInThisInstance(eventName: string, ...args: any[]): void { + if (this.debug === true) { + console.log('notifyLogged', [eventName, ...args]); + } + return this.streamLogged.emitWithoutBroadcast(eventName, ...args); + } + + notifyRoomInThisInstance(room: string, eventName: string, ...args: any[]): void { + if (this.debug === true) { + console.log('notifyRoomAndBroadcast', [room, eventName, ...args]); + } + return this.streamRoom.emitWithoutBroadcast(`${ room }/${ eventName }`, ...args); + } + + notifyUserInThisInstance(userId: string, eventName: string, ...args: any[]): void { + if (this.debug === true) { + console.log('notifyUserAndBroadcast', [userId, eventName, ...args]); + } + return this.streamUser.emitWithoutBroadcast(`${ userId }/${ eventName }`, ...args); + } + + progressUpdated(progress: {rate: number}): void { + this.streamImporters.emit('progress', progress); + } +} diff --git a/server/sdk/types/IStreamService.ts b/server/sdk/types/IStreamService.ts index 0cd7cd3b3feb9..f5b3884aaa872 100644 --- a/server/sdk/types/IStreamService.ts +++ b/server/sdk/types/IStreamService.ts @@ -9,8 +9,12 @@ export enum STATUS_MAP { } export interface IStreamer { + serverOnly: boolean; + // eslint-disable-next-line @typescript-eslint/no-misused-new - new(name: string): IStreamer; + new(name: string, options?: {retransmit: boolean}): IStreamer; + + allowEmit(allow: string): void; allowWrite(allow: string): void; diff --git a/server/stream/rooms/index.js b/server/stream/rooms/index.js index 7ea683f290a2d..7de1b91db3de0 100644 --- a/server/stream/rooms/index.js +++ b/server/stream/rooms/index.js @@ -1,13 +1,9 @@ import { Meteor } from 'meteor/meteor'; import { roomTypes } from '../../../app/utils'; -import { ROOM_DATA_STREAM } from '../../../app/utils/stream/constants'; +import notifications from '../../../app/notifications/server/lib/Notifications'; -export const roomDataStream = new Meteor.Streamer(ROOM_DATA_STREAM); - -roomDataStream.allowWrite('none'); - -roomDataStream.allowRead(function(rid) { +notifications.streamRoomData.allowRead(function(rid) { try { const room = Meteor.call('canAccessRoom', rid, this.userId); if (!room) { @@ -29,5 +25,5 @@ export function emitRoomDataEvent(id, data) { return; } - roomDataStream.emitWithoutBroadcast(id, data); + notifications.streamRoomData.emitWithoutBroadcast(id, data); } From a61552c4a3cd31c75525e4263cb6e61133b5a137 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Mon, 28 Sep 2020 13:57:24 -0300 Subject: [PATCH 081/198] Init streamer de duplication --- app/apps/server/bridges/messages.js | 6 +- ee/server/services/DDPStreamer/Client.ts | 2 +- ee/server/services/DDPStreamer/DDPStreamer.ts | 39 +++-- ee/server/services/DDPStreamer/Publication.ts | 4 +- ee/server/services/DDPStreamer/Streamer.ts | 36 ++-- .../services/DDPStreamer/configureServer.ts | 18 +- ee/server/services/DDPStreamer/constants.ts | 13 -- ee/server/services/DDPStreamer/streams/all.ts | 5 - .../services/DDPStreamer/streams/index.ts | 156 +++++++++++++++++- .../DDPStreamer/streams/livechatInquiry.ts | 8 - .../services/DDPStreamer/streams/missing.ts | 17 -- .../DDPStreamer/streams/notifyLogged.ts | 7 - .../DDPStreamer/streams/notifyUser.ts | 71 -------- .../services/DDPStreamer/streams/room.ts | 9 - .../DDPStreamer/streams/roomMessages.ts | 2 +- .../services/DDPStreamer/streams/roomUsers.ts | 6 - server/main.d.ts | 4 +- .../notifications/notifications.module.ts | 6 +- server/sdk/lib/Events.ts | 1 + server/sdk/types/IStreamService.ts | 21 ++- server/services/stream/service.ts | 4 +- 21 files changed, 234 insertions(+), 201 deletions(-) delete mode 100644 ee/server/services/DDPStreamer/streams/livechatInquiry.ts delete mode 100644 ee/server/services/DDPStreamer/streams/missing.ts delete mode 100644 ee/server/services/DDPStreamer/streams/notifyLogged.ts delete mode 100644 ee/server/services/DDPStreamer/streams/notifyUser.ts delete mode 100644 ee/server/services/DDPStreamer/streams/room.ts delete mode 100644 ee/server/services/DDPStreamer/streams/roomUsers.ts diff --git a/app/apps/server/bridges/messages.js b/app/apps/server/bridges/messages.js index e710049788df9..a838b4c12f524 100644 --- a/app/apps/server/bridges/messages.js +++ b/app/apps/server/bridges/messages.js @@ -1,7 +1,7 @@ import { Messages, Users, Subscriptions } from '../../../models/server'; import { updateMessage } from '../../../lib/server/functions/updateMessage'; import { executeSendMessage } from '../../../lib/server/methods/sendMessage'; -import { StreamService } from '../../../../server/sdk'; +import { api } from '../../../../server/sdk/api'; export class AppMessageBridge { constructor(orch) { @@ -50,7 +50,7 @@ export class AppMessageBridge { return; } - StreamService.sendEphemeralMessage(user.id, msg.rid, { + api.broadcast('stream.ephemeralMessage', user.id, msg.rid, { ...msg, }); } @@ -71,7 +71,7 @@ export class AppMessageBridge { Users.findByIds(users, { fields: { _id: 1 } }) .fetch() .forEach(({ _id }) => - StreamService.sendEphemeralMessage(_id, room.id, { + api.broadcast('stream.ephemeralMessage', _id, room.id, { ...msg, }), ); diff --git a/ee/server/services/DDPStreamer/Client.ts b/ee/server/services/DDPStreamer/Client.ts index 23083bea8ae3c..4eabb257c52eb 100644 --- a/ee/server/services/DDPStreamer/Client.ts +++ b/ee/server/services/DDPStreamer/Client.ts @@ -21,7 +21,7 @@ export class Client extends EventEmitter { public wait = false; - public uid: string; + public userId: string; constructor( public ws: WebSocket, diff --git a/ee/server/services/DDPStreamer/DDPStreamer.ts b/ee/server/services/DDPStreamer/DDPStreamer.ts index 0817c11699441..88f02baf4a7fb 100644 --- a/ee/server/services/DDPStreamer/DDPStreamer.ts +++ b/ee/server/services/DDPStreamer/DDPStreamer.ts @@ -10,6 +10,7 @@ import { Client, MeteorClient } from './Client'; import { isEmpty } from './lib/utils'; import { ServiceClass } from '../../../../server/sdk/types/ServiceClass'; import { events } from './configureServer'; +import notifications from './streams/index'; const { PORT: port = 4000, @@ -111,11 +112,11 @@ export class DDPStreamer extends ServiceClass { // [STREAM_NAMES.LIVECHAT_INQUIRY]({ action, inquiry }) { this.onEvent('livechat-inquiry-queue-observer', ({ action, inquiry }): void => { if (!inquiry.department) { - Streamer.streamLivechatInquiry.emit('public', action, inquiry); + notifications.streamLivechatQueueData.emit('public', action, inquiry); return; } - Streamer.streamLivechatInquiry.emit(`department/${ inquiry.department }`, action, inquiry); - Streamer.streamLivechatInquiry.emit(inquiry._id, action, inquiry); + notifications.streamLivechatQueueData.emit(`department/${ inquiry.department }`, action, inquiry); + notifications.streamLivechatQueueData.emit(inquiry._id, action, inquiry); }); // [STREAMER_EVENTS.STREAM]([streamer, eventName, payload]) { @@ -144,7 +145,7 @@ export class DDPStreamer extends ServiceClass { } = payload; // Streamer.userpresence.emit(_id, status); if (status) { - Streamer.notifyLogged.emit('user-status', [_id, username, STATUS_MAP[status], statusText]); + notifications.notifyLogged('user-status', [_id, username, STATUS_MAP[status], statusText]); } // User.emit(`${ STREAMER_EVENTS.USER_CHANGED }/${ _id }`, _id, user); // use this method }); @@ -182,8 +183,9 @@ export class DDPStreamer extends ServiceClass { break; } - Streamer.notifyUser.emit( - `${ _id }/userData`, + _id && notifications.notifyUser( + _id, + 'userData', data, ); @@ -196,7 +198,7 @@ export class DDPStreamer extends ServiceClass { user: { _id, name, username }, } = payload; // User.emit(`${ STREAMER_EVENTS.USER_CHANGED }/${ _id }`, _id, user); - Streamer.notifyLogged.emit('Users:NameChanged', { _id, name, username }); + notifications.notifyLogged('Users:NameChanged', { _id, name, username }); }); // 'setting'() { }, @@ -206,13 +208,14 @@ export class DDPStreamer extends ServiceClass { return; } - Streamer.notifyUser.emit( - `${ subscription.u._id }/subscriptions-changed`, + notifications.notifyUser( + subscription.u._id, + 'subscriptions-changed', action, subscription, ); - Streamer.notifyUser.__emit(subscription.u._id, action, subscription); + notifications.streamUser.__emit(subscription.u._id, action, subscription); // RocketChat.Notifications.notifyUserInThisInstance( // subscription.u._id, @@ -233,8 +236,8 @@ export class DDPStreamer extends ServiceClass { if (!room._id) { return; } - Streamer.notifyUser.__emit(room._id, action, room); - Streamer.streamRoomData.emit(room._id, action, room); // TODO REMOVE + notifications.streamUser.__emit(room._id, action, room); + notifications.streamRoomData.emit(room._id, action, room); // TODO REMOVE }); // stream: { // group: 'streamer', @@ -245,7 +248,7 @@ export class DDPStreamer extends ServiceClass { // }, // role(payload) { this.onEvent('role', (payload): void => { - Streamer.streamRoles.emit('roles', payload); + notifications.streamRoles.emit('roles', payload); }); this.onEvent('meteor.loginServiceConfiguration', ({ action, record }): void => { @@ -255,6 +258,16 @@ export class DDPStreamer extends ServiceClass { this.onEvent('meteor.autoUpdateClientVersionChanged', ({ record }): void => { events.emit('meteor.autoUpdateClientVersionChanged', record); }); + + this.onEvent('stream.ephemeralMessage', (uid, rid, message): void => { + notifications.notifyUser(uid, 'message', { + groupable: false, + ...message, + _id: String(Date.now()), + rid, + ts: new Date(), + }); + }); } } diff --git a/ee/server/services/DDPStreamer/Publication.ts b/ee/server/services/DDPStreamer/Publication.ts index db762e10eab19..ec2820ada008b 100644 --- a/ee/server/services/DDPStreamer/Publication.ts +++ b/ee/server/services/DDPStreamer/Publication.ts @@ -42,8 +42,8 @@ export class Publication extends EventEmitter { this.server.removed(this.client, collection, id); } - get uid(): string { - return this.client.uid; + get userId(): string { + return this.client.userId; } } diff --git a/ee/server/services/DDPStreamer/Streamer.ts b/ee/server/services/DDPStreamer/Streamer.ts index afefa7f3fd62d..6c722eb1cea00 100644 --- a/ee/server/services/DDPStreamer/Streamer.ts +++ b/ee/server/services/DDPStreamer/Streamer.ts @@ -7,8 +7,9 @@ import { isEmpty } from './lib/utils'; import { Publication } from './Publication'; import { Client } from './Client'; import { api } from '../../../../server/sdk/api'; +import { StreamerClass } from '../../../../server/sdk/types/IStreamService'; -type Rule = (this: Publication, eventName: string, ...args: any) => Promise; +type Rule = (this: Publication, eventName: string, ...args: any) => boolean | Promise; interface IRules { [k: string]: Rule; @@ -40,12 +41,14 @@ export const publish = Symbol('publish'); export const Streams = new Map(); -export class Stream extends EventEmitter { +export class Stream extends EventEmitter implements StreamerClass { subscriptionName: string; - // retransmit = true; + serverOnly: boolean; - // retransmitToSelf = false; + retransmit = true; + + retransmitToSelf = false; private subscriptionsByEventName = new Map>(); @@ -53,15 +56,17 @@ export class Stream extends EventEmitter { private _allowWrite = {}; + private _allowEmit = {}; + constructor( private name: string, - // { retransmit = true, retransmitToSelf = false }: {retransmit?: boolean; retransmitToSelf?: boolean } = {}, + options: {retransmit: boolean; retransmitToSelf: boolean } = { retransmit: true, retransmitToSelf: false }, ) { super(); this.subscriptionName = `${ STREAM_NAMES.STREAMER_PREFIX }${ name }`; - // this.retransmit = retransmit; - // this.retransmitToSelf = retransmitToSelf; + this.retransmit = options.retransmit; + this.retransmitToSelf = options.retransmitToSelf; // this.subscriptionsByEventName = new Map(); @@ -106,20 +111,24 @@ export class Stream extends EventEmitter { if (eventName === 'logged') { rules[_eventName] = async function(): Promise { - return Boolean(this.uid); + return Boolean(this.userId); }; } }; } - allowRead(eventName: string | boolean | Rule, fn?: Rule): boolean | undefined { + allowRead(eventName: string | boolean | Rule, fn?: Rule): Promise | boolean | undefined { return this[allow](this._allowRead, 'allowRead')(eventName, fn); } - allowWrite(eventName: string | boolean | Rule, fn?: Rule): boolean | undefined { + allowWrite(eventName: string | boolean | Rule, fn?: Rule): Promise | boolean | undefined { return this[allow](this._allowWrite, 'allowWrite')(eventName, fn); } + allowEmit(eventName: string | boolean | Rule, fn?: Rule): Promise | boolean | undefined { + return this[allow](this._allowEmit, 'allowEmit')(eventName, fn); + } + [isAllowed](rules: IRules, defaultPermission = false) { return async (scope: Publication, eventName: string, args: any): Promise => { if (rules[eventName]) { @@ -269,7 +278,8 @@ export class Stream extends EventEmitter { return super.emit(event, ...args); } - // emitWithoutBroadcast(event: string, ...args: any[]): boolean { - // return this._emit(event, args, undefined, false); - // } + emitWithoutBroadcast(event: string, ...args: any[]): boolean { + // On microservices all emit needs to be broadcasted. + return this.emit(event, ...args); + } } diff --git a/ee/server/services/DDPStreamer/configureServer.ts b/ee/server/services/DDPStreamer/configureServer.ts index 5eea4ab7c9e65..5ae40efbc0511 100644 --- a/ee/server/services/DDPStreamer/configureServer.ts +++ b/ee/server/services/DDPStreamer/configureServer.ts @@ -89,7 +89,7 @@ server.methods({ throw new Error('login error'); } - this.uid = result.uid; + this.userId = result.uid; this.emit(DDP_EVENTS.LOGGED); @@ -103,20 +103,20 @@ server.methods({ }; }, 'UserPresence:setDefaultStatus'(status) { - const { uid } = this; - return Presence.setStatus(uid, status); + const { userId } = this; + return Presence.setStatus(userId, status); }, 'UserPresence:online'() { - const { uid, session } = this; - return Presence.setConnectionStatus(uid, USER_STATUS.ONLINE, session); + const { userId, session } = this; + return Presence.setConnectionStatus(userId, USER_STATUS.ONLINE, session); }, 'UserPresence:away'() { - const { uid, session } = this; - return Presence.setConnectionStatus(uid, USER_STATUS.AWAY, session); + const { userId, session } = this; + return Presence.setConnectionStatus(userId, USER_STATUS.AWAY, session); }, 'setUserStatus'(status, statusText) { - const { uid } = this; - return Presence.setStatus(uid, status, statusText); + const { userId } = this; + return Presence.setStatus(userId, status, statusText); }, }); diff --git a/ee/server/services/DDPStreamer/constants.ts b/ee/server/services/DDPStreamer/constants.ts index e40f68e0a7606..4af8bf22d2548 100644 --- a/ee/server/services/DDPStreamer/constants.ts +++ b/ee/server/services/DDPStreamer/constants.ts @@ -52,21 +52,8 @@ export const STREAM_NAMES = { ROOMS_CHANGED: 'rooms-changed', ROOM_DATA: 'room-data', // TODO both data are the same plx merge them - ROOM_MESSAGES: 'room-messages', - NOTIFY_ALL: 'notify-all', - NOTIFY_LOGGED: 'notify-logged', - NOTIFY_ROOM: 'notify-room', - NOTIFY_USER: 'notify-user', PRESENCE: 'userpresence', - IMPORTERS: 'importers', - ROLES: 'roles', - APPS: 'apps', - CANNED_RESPONSES: 'canned-responses', - LIVECHAT_INQUIRY: 'livechat-inquiry-queue-observer', - LIVECHAT_ROOM: 'livechat-room', - - NOTIFY_ROOM_USERS: 'notify-room-users', 'my-message': '__my_messages__', }; diff --git a/ee/server/services/DDPStreamer/streams/all.ts b/ee/server/services/DDPStreamer/streams/all.ts index 8492b28ddc68e..e69de29bb2d1d 100644 --- a/ee/server/services/DDPStreamer/streams/all.ts +++ b/ee/server/services/DDPStreamer/streams/all.ts @@ -1,5 +0,0 @@ -import { Stream } from '../Streamer'; -import { STREAM_NAMES } from '../constants'; - -export const streamAll = new Stream(STREAM_NAMES.NOTIFY_ALL); -streamAll.allowRead('all'); diff --git a/ee/server/services/DDPStreamer/streams/index.ts b/ee/server/services/DDPStreamer/streams/index.ts index 8e945eb57b18d..790a0c56bd598 100644 --- a/ee/server/services/DDPStreamer/streams/index.ts +++ b/ee/server/services/DDPStreamer/streams/index.ts @@ -1,10 +1,150 @@ -export * from '../Streamer'; -export * from './all'; -export * from './livechatInquiry'; -export * from './missing'; -export * from './notifyLogged'; -export * from './notifyUser'; -export * from './room'; +import { Stream, send, changedPayload, publish } from '../Streamer'; +import { NotificationsModule } from '../../../../../server/modules/notifications/notifications.module'; +import { ISubscription } from '../../../../../definition/ISubscription'; +import { getCollection, Collections } from '../../mongo'; +import { Publication } from '../Publication'; +import { Authorization } from '../../../../../server/sdk'; + export * from './roomMessages'; -export * from './roomUsers'; export * from './presence'; + +export class RoomStreamer extends Stream { + async [publish](publication: Publication, eventName = '', options: boolean | {useCollection?: boolean; args?: any} = false): Promise { + super[publish](publication, eventName, options); + // const uid = Meteor.userId(); + const { userId } = publication.client; + if (/rooms-changed/.test(eventName)) { + // TODO: change this to serialize only once + const roomEvent = (...args: any[]): void => { + const payload = changedPayload(this.subscriptionName, { + eventName: `${ userId }/rooms-changed`, + args, + }); + + payload && send( + publication, + payload, + ); + }; + + const Subscription = await getCollection(Collections.Subscriptions); + + const subscriptions = await Subscription.find>( + { 'u._id': userId }, + { projection: { rid: 1 } }, + ).toArray(); + + subscriptions.forEach(({ rid }) => { + this.on(rid, roomEvent); + }); + + const userEvent = (clientAction: string, { rid }: Partial = {}): void => { + if (!rid) { + return; + } + + switch (clientAction) { + case 'inserted': + subscriptions.push({ rid }); + this.on(rid, roomEvent); + break; + + case 'removed': + this.removeListener(rid, roomEvent); + break; + } + }; + this.on(userId, userEvent); + + publication.once('stop', () => { + this.removeListener(userId, userEvent); + subscriptions.forEach(({ rid }) => this.removeListener(rid, roomEvent)); + }); + } + } +} + +const notifications = new NotificationsModule(Stream, RoomStreamer); + +export default notifications; + +// export const streamRoomData = new Stream(STREAM_NAMES.ROOM_DATA); +notifications.streamRoomData.allowRead(function(rid) { + return !!this.userId && Authorization.canAccessRoom({ _id: rid }, { _id: this.userId }); +}); + +notifications.streamLivechatQueueData.allowRead(function() { + return !!this.userId && Authorization.hasPermission(this.userId, 'view-l-room'); +}); + +// TODO: Implement permission +// this.streamCannedResponses.allowRead(function() { // Implemented outside +// this.streamLivechatRoom.allowRead((roomId, extraData) => { // Implemented outside + + +// TODO: Implement permission +// const self = this; +// notifications.streamRoomUsers.allowWrite(function(eventName, ...args) { +// const [roomId, e] = eventName.split('/'); +// // const user = Meteor.users.findOne(this.userId, { +// // fields: { +// // username: 1 +// // } +// // }); +// if (Subscriptions.findOneByRoomIdAndUserId(roomId, this.userId) != null) { +// const subscriptions = Subscriptions.findByRoomIdAndNotUserId(roomId, this.userId).fetch(); +// subscriptions.forEach((subscription) => self.notifyUser(subscription.u._id, e, ...args)); +// } +// return false; +// }); + +// TODO: Implement permission +// notifications.streamRoom.allowRead(function(eventName, extraData) { +// const [roomId] = eventName.split('/'); +// const room = Rooms.findOneById(roomId); +// if (!room) { +// console.warn(`Invalid streamRoom eventName: "${ eventName }"`); +// return false; +// } +// if (room.t === 'l' && extraData && extraData.token && room.v.token === extraData.token) { +// return true; +// } +// if (this.userId == null) { +// return false; +// } +// const subscription = Subscriptions.findOneByRoomIdAndUserId(roomId, this.userId, { fields: { _id: 1 } }); +// return subscription != null; +// }); + +// TODO: Implement permission +// notifications.streamRoom.allowWrite(function(eventName, username, typing, extraData) { +// const [roomId, e] = eventName.split('/'); + +// if (isNaN(e) ? e === WEB_RTC_EVENTS.WEB_RTC : parseFloat(e) === WEB_RTC_EVENTS.WEB_RTC) { +// return true; +// } + +// if (e === 'typing') { +// const key = settings.get('UI_Use_Real_Name') ? 'name' : 'username'; +// // typing from livechat widget +// if (extraData && extraData.token) { +// const room = Rooms.findOneById(roomId); +// if (room && room.t === 'l' && room.v.token === extraData.token) { +// return true; +// } +// } + +// const user = Meteor.users.findOne(this.userId, { +// fields: { +// [key]: 1, +// }, +// }); + +// if (!user) { +// return false; +// } + +// return user[key] === username; +// } +// return false; +// }); diff --git a/ee/server/services/DDPStreamer/streams/livechatInquiry.ts b/ee/server/services/DDPStreamer/streams/livechatInquiry.ts deleted file mode 100644 index f9d81a497ad05..0000000000000 --- a/ee/server/services/DDPStreamer/streams/livechatInquiry.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Stream } from '../Streamer'; -import { STREAM_NAMES } from '../constants'; -import { Authorization } from '../../../../../server/sdk'; - -export const streamLivechatInquiry = new Stream(STREAM_NAMES.LIVECHAT_INQUIRY); -streamLivechatInquiry.allowRead(function() { - return Authorization.hasPermission(this.uid, 'view-l-room'); -}); diff --git a/ee/server/services/DDPStreamer/streams/missing.ts b/ee/server/services/DDPStreamer/streams/missing.ts deleted file mode 100644 index 40add6bd38788..0000000000000 --- a/ee/server/services/DDPStreamer/streams/missing.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Stream } from '../Streamer'; -import { STREAM_NAMES } from '../constants'; - -export const streamApps = new Stream(STREAM_NAMES.APPS); -streamApps.allowRead('all'); - -export const streamImporters = new Stream(STREAM_NAMES.IMPORTERS); -streamImporters.allowRead('all'); - -export const streamRoles = new Stream(STREAM_NAMES.ROLES); -streamRoles.allowRead('all'); - -export const streamCannedresponses = new Stream(STREAM_NAMES.CANNED_RESPONSES); -streamCannedresponses.allowRead('all'); - -export const streamLivechatRoom = new Stream(STREAM_NAMES.LIVECHAT_ROOM); -streamLivechatRoom.allowRead('all'); diff --git a/ee/server/services/DDPStreamer/streams/notifyLogged.ts b/ee/server/services/DDPStreamer/streams/notifyLogged.ts deleted file mode 100644 index 79bfcb36d3b2c..0000000000000 --- a/ee/server/services/DDPStreamer/streams/notifyLogged.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Stream } from '../Streamer'; -import { STREAM_NAMES } from '../constants'; - -export const notifyLogged = new Stream(STREAM_NAMES.NOTIFY_LOGGED); - -notifyLogged.allowWrite('none'); -notifyLogged.allowRead('logged'); diff --git a/ee/server/services/DDPStreamer/streams/notifyUser.ts b/ee/server/services/DDPStreamer/streams/notifyUser.ts deleted file mode 100644 index 47b38872800ab..0000000000000 --- a/ee/server/services/DDPStreamer/streams/notifyUser.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { Stream, send, changedPayload, publish } from '../Streamer'; -import { STREAM_NAMES } from '../constants'; -import { ISubscription } from '../../../../../definition/ISubscription'; -import { getCollection, Collections } from '../../mongo'; -import { Publication } from '../Publication'; -import { Authorization } from '../../../../../server/sdk'; - -class RoomStreamer extends Stream { - async [publish](publication: Publication, eventName = '', options: boolean | {useCollection?: boolean; args?: any} = false): Promise { - super[publish](publication, eventName, options); - // const uid = Meteor.userId(); - const { uid } = publication.client; - if (/rooms-changed/.test(eventName)) { - // TODO: change this to serialize only once - const roomEvent = (...args: any[]): void => { - const payload = changedPayload(this.subscriptionName, { - eventName: `${ uid }/rooms-changed`, - args, - }); - - payload && send( - publication, - payload, - ); - }; - - const Subscription = await getCollection(Collections.Subscriptions); - - const subscriptions = await Subscription.find>( - { 'u._id': uid }, - { projection: { rid: 1 } }, - ).toArray(); - - subscriptions.forEach(({ rid }) => { - this.on(rid, roomEvent); - }); - - const userEvent = (clientAction: string, { rid }: Partial = {}): void => { - if (!rid) { - return; - } - - switch (clientAction) { - case 'inserted': - subscriptions.push({ rid }); - this.on(rid, roomEvent); - break; - - case 'removed': - this.removeListener(rid, roomEvent); - break; - } - }; - this.on(uid, userEvent); - - publication.once('stop', () => { - this.removeListener(uid, userEvent); - subscriptions.forEach(({ rid }) => this.removeListener(rid, roomEvent)); - }); - } - } -} - -export const notifyUser = new RoomStreamer(STREAM_NAMES.NOTIFY_USER); -notifyUser.allowWrite('none'); -notifyUser.allowRead('logged'); - -export const streamRoomData = new Stream(STREAM_NAMES.ROOM_DATA); -streamRoomData.allowRead(function(rid) { - return Authorization.canAccessRoom({ _id: rid }, { _id: this.uid }); -}); diff --git a/ee/server/services/DDPStreamer/streams/room.ts b/ee/server/services/DDPStreamer/streams/room.ts deleted file mode 100644 index 075bbf5c431c2..0000000000000 --- a/ee/server/services/DDPStreamer/streams/room.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Stream } from '../Streamer'; -import { STREAM_NAMES } from '../constants'; - -export const streamRoom = new Stream(STREAM_NAMES.NOTIFY_ROOM); - -streamRoom.allowWrite('none'); - -// TODO canAccessRoom? -streamRoom.allowRead('all'); diff --git a/ee/server/services/DDPStreamer/streams/roomMessages.ts b/ee/server/services/DDPStreamer/streams/roomMessages.ts index a5f3caaee6862..3bc5c510703a2 100644 --- a/ee/server/services/DDPStreamer/streams/roomMessages.ts +++ b/ee/server/services/DDPStreamer/streams/roomMessages.ts @@ -5,5 +5,5 @@ import { Authorization } from '../../../../../server/sdk'; export const roomMessages = new Stream(STREAM_NAMES.ROOM_MESSAGES); roomMessages.allowWrite('none'); roomMessages.allowRead(function(rid) { - return Authorization.canAccessRoom({ _id: rid }, { _id: this.uid }); + return Authorization.canAccessRoom({ _id: rid }, { _id: this.userId }); }); diff --git a/ee/server/services/DDPStreamer/streams/roomUsers.ts b/ee/server/services/DDPStreamer/streams/roomUsers.ts deleted file mode 100644 index 0d5d0bbba515d..0000000000000 --- a/ee/server/services/DDPStreamer/streams/roomUsers.ts +++ /dev/null @@ -1,6 +0,0 @@ -// mangola stream detected??? -import { Stream } from '../Streamer'; -import { STREAM_NAMES } from '../constants'; - -export const streamRoomUsers = new Stream(STREAM_NAMES.NOTIFY_ROOM_USERS); -streamRoomUsers.allowRead('none'); diff --git a/server/main.d.ts b/server/main.d.ts index b3cf986be543f..0499c4912ea3e 100644 --- a/server/main.d.ts +++ b/server/main.d.ts @@ -1,7 +1,7 @@ import { EJSON } from 'meteor/ejson'; import { Db } from 'mongodb'; -import { IStreamer } from './sdk/types/IStreamService'; +import { StreamerClass } from './sdk/types/IStreamService'; /* eslint-disable @typescript-eslint/interface-name-prefix */ declare module 'meteor/random' { @@ -40,7 +40,7 @@ declare module 'meteor/meteor' { details?: string | undefined | Record; } - const Streamer: IStreamer; + const Streamer: StreamerClass; const server: any; diff --git a/server/modules/notifications/notifications.module.ts b/server/modules/notifications/notifications.module.ts index 158803c537079..46f4fb4e7dfe6 100644 --- a/server/modules/notifications/notifications.module.ts +++ b/server/modules/notifications/notifications.module.ts @@ -1,4 +1,4 @@ -import { IStreamer } from '../../sdk/types/IStreamService'; +import { IStreamer, StreamerClass } from '../../sdk/types/IStreamService'; export class NotificationsModule { private debug = false @@ -34,8 +34,8 @@ export class NotificationsModule { public readonly streamRoomData: IStreamer; constructor( - private Streamer: IStreamer, - private RoomStreamer: IStreamer, + private Streamer: StreamerClass, + private RoomStreamer: StreamerClass, ) { this.notifyUser = this.notifyUser.bind(this); diff --git a/server/sdk/lib/Events.ts b/server/sdk/lib/Events.ts index 42617b6fd702e..643b93a0e7e57 100644 --- a/server/sdk/lib/Events.ts +++ b/server/sdk/lib/Events.ts @@ -25,4 +25,5 @@ export type EventSignatures = { 'license.module'(data: {module: string; valid: boolean}): void; 'meteor.autoUpdateClientVersionChanged'(data: {record: AutoUpdateRecord}): void; 'meteor.loginServiceConfiguration'(data: {action: string; record: any}): void; + 'stream.ephemeralMessage'(uid: string, rid: string, message: Partial): void; } diff --git a/server/sdk/types/IStreamService.ts b/server/sdk/types/IStreamService.ts index f5b3884aaa872..db6376d4f5be9 100644 --- a/server/sdk/types/IStreamService.ts +++ b/server/sdk/types/IStreamService.ts @@ -8,27 +8,32 @@ export enum STATUS_MAP { BUDY = 3, } +type Publication = { + userId?: string; +} +type Rule = (this: Publication, eventName: string, ...args: any) => boolean | Promise; + export interface IStreamer { serverOnly: boolean; // eslint-disable-next-line @typescript-eslint/no-misused-new - new(name: string, options?: {retransmit: boolean}): IStreamer; - - allowEmit(allow: string): void; + // new(name: string, options?: {retransmit?: boolean; retransmitToSelf?: boolean}): IStreamer; - allowWrite(allow: string): void; + allowEmit(eventName: string | boolean | Rule, fn?: Rule): Promise | boolean | undefined; - allowWrite(allow: (this: {userId?: string}, eventName: string, ...args: any[]) => void): void; + allowWrite(eventName: string | boolean | Rule, fn?: Rule): Promise | boolean | undefined; - allowRead(allow: string): void; - - allowRead(allow: (this: {userId?: string}, eventName: string, ...args: any[]) => void): void; + allowRead(eventName: string | boolean | Rule, fn?: Rule): Promise | boolean | undefined; emit(event: string, ...data: any[]): void; + __emit(...data: any[]): void; + emitWithoutBroadcast(event: string, ...data: any[]): void; } +export type StreamerClass = new (name: string, options?: {retransmit?: boolean; retransmitToSelf?: boolean}) => IStreamer; + export interface IStreamService extends IServiceClass { notifyAll(eventName: string, ...args: any[]): void; notifyUser(uid: string, eventName: string, ...args: any[]): void; diff --git a/server/services/stream/service.ts b/server/services/stream/service.ts index e2ad76fa44a00..b43bba4072a6a 100644 --- a/server/services/stream/service.ts +++ b/server/services/stream/service.ts @@ -1,5 +1,5 @@ import { ServiceClass } from '../../sdk/types/ServiceClass'; -import { IStreamService, STATUS_MAP, IStreamer } from '../../sdk/types/IStreamService'; +import { IStreamService, STATUS_MAP, IStreamer, StreamerClass } from '../../sdk/types/IStreamService'; // import { Notifications } from '../../../app/notifications/server'; import { IMessage } from '../../../definition/IMessage'; @@ -16,7 +16,7 @@ export class StreamService extends ServiceClass implements IStreamService { private streamUser: IStreamer; - constructor(Streamer: IStreamer) { + constructor(Streamer: StreamerClass) { super(); this.streamLogged = new Streamer('notify-logged'); From 2de16b4e5e4b3b4d48d826a91d0c047d0b3a549a Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Mon, 28 Sep 2020 17:47:24 -0300 Subject: [PATCH 082/198] Resolve IStreamer type error --- app/lib/server/lib/msgStream.js | 58 +------------ app/notifications/server/lib/Notifications.js | 85 ++++++++++++++++++- ee/server/services/DDPStreamer/DDPStreamer.ts | 6 +- ee/server/services/DDPStreamer/Streamer.ts | 10 +-- ee/server/services/DDPStreamer/constants.ts | 3 - ee/server/services/DDPStreamer/streams/all.ts | 0 .../services/DDPStreamer/streams/index.ts | 49 ++++++++++- .../services/DDPStreamer/streams/presence.ts | 7 -- .../DDPStreamer/streams/roomMessages.ts | 9 -- server/main.d.ts | 4 +- .../notifications/notifications.module.ts | 12 ++- server/sdk/types/IStreamService.ts | 8 +- server/services/stream/service.ts | 4 +- 13 files changed, 154 insertions(+), 101 deletions(-) delete mode 100644 ee/server/services/DDPStreamer/streams/all.ts delete mode 100644 ee/server/services/DDPStreamer/streams/presence.ts delete mode 100644 ee/server/services/DDPStreamer/streams/roomMessages.ts diff --git a/app/lib/server/lib/msgStream.js b/app/lib/server/lib/msgStream.js index 94f657a2691ff..208c3ab14f549 100644 --- a/app/lib/server/lib/msgStream.js +++ b/app/lib/server/lib/msgStream.js @@ -1,57 +1,3 @@ -import { Meteor } from 'meteor/meteor'; -import { DDPCommon } from 'meteor/ddp-common'; +import notifications from '../../../notifications/server/lib/Notifications'; -const changedPayload = function(collection, id, fields) { - return DDPCommon.stringifyDDP({ - msg: 'changed', - collection, - id, - fields, - }); -}; - -const send = function(self, msg) { - if (!self.socket) { - return; - } - self.socket.send(msg); -}; - -class MessageStream extends Meteor.Streamer { - getSubscriptionByUserIdAndRoomId(userId, rid) { - return this.subscriptions.find((sub) => sub.eventName === rid && sub.subscription.userId === userId); - } - - _publish(publication, eventName, options) { - super._publish(publication, eventName, options); - const uid = Meteor.userId(); - - const userEvent = (clientAction, { rid }) => { - switch (clientAction) { - case 'removed': - this.removeListener(uid, userEvent); - this.removeSubscription(this.getSubscriptionByUserIdAndRoomId(uid, rid), eventName); - break; - } - }; - this.on(uid, userEvent); - } - - mymessage = (eventName, args) => { - const subscriptions = this.subscriptionsByEventName[eventName]; - if (!Array.isArray(subscriptions)) { - return; - } - subscriptions.forEach(({ subscription }) => { - const options = this.isEmitAllowed(subscription, eventName, args); - if (options) { - send(subscription._session, changedPayload(this.subscriptionName, 'id', { - eventName, - args: [args, options], - })); - } - }); - } -} - -export const msgStream = new MessageStream('room-messages'); +export const msgStream = notifications.streamRoomMessage; diff --git a/app/notifications/server/lib/Notifications.js b/app/notifications/server/lib/Notifications.js index 08ce19371c9fc..134425ec1fc38 100644 --- a/app/notifications/server/lib/Notifications.js +++ b/app/notifications/server/lib/Notifications.js @@ -5,6 +5,7 @@ import { WEB_RTC_EVENTS } from '../../../webrtc'; import { Subscriptions, Rooms } from '../../../models/server'; import { settings } from '../../../settings/server'; import { NotificationsModule } from '../../../../server/modules/notifications/notifications.module'; +import { hasPermission } from '../../../authorization/client'; const changedPayload = function(collection, id, fields) { return DDPCommon.stringifyDDP({ @@ -59,9 +60,46 @@ class RoomStreamer extends Meteor.Streamer { } } +class MessageStream extends Meteor.Streamer { + getSubscriptionByUserIdAndRoomId(userId, rid) { + return this.subscriptions.find((sub) => sub.eventName === rid && sub.subscription.userId === userId); + } + + _publish(publication, eventName, options) { + super._publish(publication, eventName, options); + const uid = Meteor.userId(); + + const userEvent = (clientAction, { rid }) => { + switch (clientAction) { + case 'removed': + this.removeListener(uid, userEvent); + this.removeSubscription(this.getSubscriptionByUserIdAndRoomId(uid, rid), eventName); + break; + } + }; + this.on(uid, userEvent); + } + + mymessage = (eventName, args) => { + const subscriptions = this.subscriptionsByEventName[eventName]; + if (!Array.isArray(subscriptions)) { + return; + } + subscriptions.forEach(({ subscription }) => { + const options = this.isEmitAllowed(subscription, eventName, args); + if (options) { + send(subscription._session, changedPayload(this.subscriptionName, 'id', { + eventName, + args: [args, options], + })); + } + }); + } +} + class Notifications extends NotificationsModule { - constructor(Streamer, RoomStreamer) { - super(Streamer, RoomStreamer); + constructor(Streamer, RoomStreamer, MessageStream) { + super(Streamer, RoomStreamer, MessageStream); const self = this; this.streamRoomUsers.allowWrite(function(eventName, ...args) { @@ -97,7 +135,7 @@ class Notifications extends NotificationsModule { } } -const notifications = new Notifications(Meteor.Streamer, RoomStreamer); +const notifications = new Notifications(Meteor.Streamer, RoomStreamer, MessageStream); notifications.streamRoom.allowWrite(function(eventName, username, typing, extraData) { const [roomId, e] = eventName.split('/'); @@ -132,3 +170,44 @@ notifications.streamRoom.allowWrite(function(eventName, username, typing, extraD }); export default notifications; + + +notifications.streamRoomMessage.allowRead(function(eventName, args) { + try { + const room = Meteor.call('canAccessRoom', eventName, this.userId, args); + + if (!room) { + return false; + } + + if (room.t === 'c' && !hasPermission(this.userId, 'preview-c-room') && !Subscriptions.findOneByRoomIdAndUserId(room._id, this.userId, { fields: { _id: 1 } })) { + return false; + } + + return true; + } catch (error) { + /* error*/ + return false; + } +}); + +notifications.streamRoomMessage.allowRead('__my_messages__', 'all'); + +notifications.streamRoomMessage.allowEmit('__my_messages__', function(eventName, msg) { + try { + const room = Meteor.call('canAccessRoom', msg.rid, this.userId); + + if (!room) { + return false; + } + + return { + roomParticipant: Subscriptions.findOneByRoomIdAndUserId(room._id, this.userId, { fields: { _id: 1 } }) != null, + roomType: room.t, + roomName: room.name, + }; + } catch (error) { + /* error*/ + return false; + } +}); diff --git a/ee/server/services/DDPStreamer/DDPStreamer.ts b/ee/server/services/DDPStreamer/DDPStreamer.ts index 88f02baf4a7fb..b8949bdc90e09 100644 --- a/ee/server/services/DDPStreamer/DDPStreamer.ts +++ b/ee/server/services/DDPStreamer/DDPStreamer.ts @@ -4,7 +4,7 @@ import url from 'url'; import WebSocket from 'ws'; // import PromService from 'moleculer-prometheus'; -import * as Streamer from './streams'; +import { Streams } from './Streamer'; import { Client, MeteorClient } from './Client'; // import { STREAMER_EVENTS, STREAM_NAMES } from './constants'; import { isEmpty } from './lib/utils'; @@ -121,14 +121,14 @@ export class DDPStreamer extends ServiceClass { // [STREAMER_EVENTS.STREAM]([streamer, eventName, payload]) { this.onEvent('stream', ([streamer, eventName, payload]): void => { - const stream = Streamer.Streams.get(streamer); + const stream = Streams.get(streamer); return stream && stream.emitPayload(eventName, payload); }); // message({ message }) { this.onEvent('message', ({ message }): void => { // roomMessages.emitWithoutBroadcast('__my_messages__', record, {}); - Streamer.roomMessages.emit(message.rid, message); + notifications.streamRoomMessage.emit(message.rid, message); }); // userpresence(payload) { diff --git a/ee/server/services/DDPStreamer/Streamer.ts b/ee/server/services/DDPStreamer/Streamer.ts index 6c722eb1cea00..2541a7789a772 100644 --- a/ee/server/services/DDPStreamer/Streamer.ts +++ b/ee/server/services/DDPStreamer/Streamer.ts @@ -7,7 +7,7 @@ import { isEmpty } from './lib/utils'; import { Publication } from './Publication'; import { Client } from './Client'; import { api } from '../../../../server/sdk/api'; -import { StreamerClass } from '../../../../server/sdk/types/IStreamService'; +import { IStreamer } from '../../../../server/sdk/types/IStreamService'; type Rule = (this: Publication, eventName: string, ...args: any) => boolean | Promise; @@ -41,7 +41,7 @@ export const publish = Symbol('publish'); export const Streams = new Map(); -export class Stream extends EventEmitter implements StreamerClass { +export class Stream extends EventEmitter implements IStreamer { subscriptionName: string; serverOnly: boolean; @@ -60,13 +60,13 @@ export class Stream extends EventEmitter implements StreamerClass { constructor( private name: string, - options: {retransmit: boolean; retransmitToSelf: boolean } = { retransmit: true, retransmitToSelf: false }, + { retransmit = true, retransmitToSelf = false }: {retransmit?: boolean; retransmitToSelf?: boolean } = { }, ) { super(); this.subscriptionName = `${ STREAM_NAMES.STREAMER_PREFIX }${ name }`; - this.retransmit = options.retransmit; - this.retransmitToSelf = options.retransmitToSelf; + this.retransmit = retransmit; + this.retransmitToSelf = retransmitToSelf; // this.subscriptionsByEventName = new Map(); diff --git a/ee/server/services/DDPStreamer/constants.ts b/ee/server/services/DDPStreamer/constants.ts index 4af8bf22d2548..54d0dcf093fd3 100644 --- a/ee/server/services/DDPStreamer/constants.ts +++ b/ee/server/services/DDPStreamer/constants.ts @@ -52,8 +52,5 @@ export const STREAM_NAMES = { ROOMS_CHANGED: 'rooms-changed', ROOM_DATA: 'room-data', // TODO both data are the same plx merge them - ROOM_MESSAGES: 'room-messages', - PRESENCE: 'userpresence', - 'my-message': '__my_messages__', }; diff --git a/ee/server/services/DDPStreamer/streams/all.ts b/ee/server/services/DDPStreamer/streams/all.ts deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/ee/server/services/DDPStreamer/streams/index.ts b/ee/server/services/DDPStreamer/streams/index.ts index 790a0c56bd598..0a928db9639de 100644 --- a/ee/server/services/DDPStreamer/streams/index.ts +++ b/ee/server/services/DDPStreamer/streams/index.ts @@ -5,9 +5,6 @@ import { getCollection, Collections } from '../../mongo'; import { Publication } from '../Publication'; import { Authorization } from '../../../../../server/sdk'; -export * from './roomMessages'; -export * from './presence'; - export class RoomStreamer extends Stream { async [publish](publication: Publication, eventName = '', options: boolean | {useCollection?: boolean; args?: any} = false): Promise { super[publish](publication, eventName, options); @@ -64,10 +61,54 @@ export class RoomStreamer extends Stream { } } -const notifications = new NotificationsModule(Stream, RoomStreamer); +class MessageStream extends Stream { + // TODO: implement the code bellow + // getSubscriptionByUserIdAndRoomId(userId, rid) { + // return this.subscriptions.find((sub) => sub.eventName === rid && sub.subscription.userId === userId); + // } + + // _publish(publication, eventName, options) { + // super._publish(publication, eventName, options); + // const uid = Meteor.userId(); + + // const userEvent = (clientAction, { rid }) => { + // switch (clientAction) { + // case 'removed': + // this.removeListener(uid, userEvent); + // this.removeSubscription(this.getSubscriptionByUserIdAndRoomId(uid, rid), eventName); + // break; + // } + // }; + // this.on(uid, userEvent); + // } + + // mymessage = (eventName, args) => { + // const subscriptions = this.subscriptionsByEventName[eventName]; + // if (!Array.isArray(subscriptions)) { + // return; + // } + // subscriptions.forEach(({ subscription }) => { + // const options = this.isEmitAllowed(subscription, eventName, args); + // if (options) { + // send(subscription._session, changedPayload(this.subscriptionName, 'id', { + // eventName, + // args: [args, options], + // })); + // } + // }); + // } +} + +const notifications = new NotificationsModule(Stream, RoomStreamer, MessageStream); export default notifications; +// TODO: Implementation not complete +notifications.streamRoomMessage.allowRead(function(rid) { + return !!this.userId && Authorization.canAccessRoom({ _id: rid }, { _id: this.userId }); +}); + + // export const streamRoomData = new Stream(STREAM_NAMES.ROOM_DATA); notifications.streamRoomData.allowRead(function(rid) { return !!this.userId && Authorization.canAccessRoom({ _id: rid }, { _id: this.userId }); diff --git a/ee/server/services/DDPStreamer/streams/presence.ts b/ee/server/services/DDPStreamer/streams/presence.ts deleted file mode 100644 index fb3c7e87d77c7..0000000000000 --- a/ee/server/services/DDPStreamer/streams/presence.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Stream } from '../Streamer'; -import { STREAM_NAMES } from '../constants'; - -export const userpresence = new Stream(STREAM_NAMES.PRESENCE); - -// TODO remove, not used -userpresence.allowRead('all'); diff --git a/ee/server/services/DDPStreamer/streams/roomMessages.ts b/ee/server/services/DDPStreamer/streams/roomMessages.ts deleted file mode 100644 index 3bc5c510703a2..0000000000000 --- a/ee/server/services/DDPStreamer/streams/roomMessages.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Stream } from '../Streamer'; -import { STREAM_NAMES } from '../constants'; -import { Authorization } from '../../../../../server/sdk'; - -export const roomMessages = new Stream(STREAM_NAMES.ROOM_MESSAGES); -roomMessages.allowWrite('none'); -roomMessages.allowRead(function(rid) { - return Authorization.canAccessRoom({ _id: rid }, { _id: this.userId }); -}); diff --git a/server/main.d.ts b/server/main.d.ts index 0499c4912ea3e..29ca2958f6736 100644 --- a/server/main.d.ts +++ b/server/main.d.ts @@ -1,7 +1,7 @@ import { EJSON } from 'meteor/ejson'; import { Db } from 'mongodb'; -import { StreamerClass } from './sdk/types/IStreamService'; +import { IStreamerConstructor } from './sdk/types/IStreamService'; /* eslint-disable @typescript-eslint/interface-name-prefix */ declare module 'meteor/random' { @@ -40,7 +40,7 @@ declare module 'meteor/meteor' { details?: string | undefined | Record; } - const Streamer: StreamerClass; + const Streamer: IStreamerConstructor; const server: any; diff --git a/server/modules/notifications/notifications.module.ts b/server/modules/notifications/notifications.module.ts index 46f4fb4e7dfe6..a22593d701486 100644 --- a/server/modules/notifications/notifications.module.ts +++ b/server/modules/notifications/notifications.module.ts @@ -1,4 +1,4 @@ -import { IStreamer, StreamerClass } from '../../sdk/types/IStreamService'; +import { IStreamer, IStreamerConstructor } from '../../sdk/types/IStreamService'; export class NotificationsModule { private debug = false @@ -13,6 +13,8 @@ export class NotificationsModule { public readonly streamUser: IStreamer; + public readonly streamRoomMessage: IStreamer; + public readonly streamImporters: IStreamer; public readonly streamRoles: IStreamer; @@ -34,8 +36,9 @@ export class NotificationsModule { public readonly streamRoomData: IStreamer; constructor( - private Streamer: StreamerClass, - private RoomStreamer: StreamerClass, + private Streamer: IStreamerConstructor, + private RoomStreamer: IStreamerConstructor, + private MessageStreamer: IStreamerConstructor, ) { this.notifyUser = this.notifyUser.bind(this); @@ -56,6 +59,9 @@ export class NotificationsModule { this.streamRoomUsers.allowRead('none'); // this.streamRoomUsers.allowWrite(function(eventName, ...args) { // Implemented outside + this.streamRoomMessage = new this.MessageStreamer('room-messages'); + this.streamRoomMessage.allowWrite('none'); + this.streamUser = new this.RoomStreamer('notify-user'); this.streamUser.allowWrite('logged'); this.streamUser.allowRead(function(eventName) { diff --git a/server/sdk/types/IStreamService.ts b/server/sdk/types/IStreamService.ts index db6376d4f5be9..16023983a40cb 100644 --- a/server/sdk/types/IStreamService.ts +++ b/server/sdk/types/IStreamService.ts @@ -16,9 +16,6 @@ type Rule = (this: Publication, eventName: string, ...args: any) => boolean | Pr export interface IStreamer { serverOnly: boolean; - // eslint-disable-next-line @typescript-eslint/no-misused-new - // new(name: string, options?: {retransmit?: boolean; retransmitToSelf?: boolean}): IStreamer; - allowEmit(eventName: string | boolean | Rule, fn?: Rule): Promise | boolean | undefined; allowWrite(eventName: string | boolean | Rule, fn?: Rule): Promise | boolean | undefined; @@ -32,7 +29,10 @@ export interface IStreamer { emitWithoutBroadcast(event: string, ...data: any[]): void; } -export type StreamerClass = new (name: string, options?: {retransmit?: boolean; retransmitToSelf?: boolean}) => IStreamer; +export interface IStreamerConstructor { + // eslint-disable-next-line @typescript-eslint/no-misused-new + new(name: string, options?: {retransmit?: boolean; retransmitToSelf?: boolean}): IStreamer; +} export interface IStreamService extends IServiceClass { notifyAll(eventName: string, ...args: any[]): void; diff --git a/server/services/stream/service.ts b/server/services/stream/service.ts index b43bba4072a6a..efd670536c94f 100644 --- a/server/services/stream/service.ts +++ b/server/services/stream/service.ts @@ -1,5 +1,5 @@ import { ServiceClass } from '../../sdk/types/ServiceClass'; -import { IStreamService, STATUS_MAP, IStreamer, StreamerClass } from '../../sdk/types/IStreamService'; +import { IStreamService, STATUS_MAP, IStreamer, IStreamerConstructor } from '../../sdk/types/IStreamService'; // import { Notifications } from '../../../app/notifications/server'; import { IMessage } from '../../../definition/IMessage'; @@ -16,7 +16,7 @@ export class StreamService extends ServiceClass implements IStreamService { private streamUser: IStreamer; - constructor(Streamer: StreamerClass) { + constructor(Streamer: IStreamerConstructor) { super(); this.streamLogged = new Streamer('notify-logged'); From d9d01d1b2d9e351d2ed710df28d4e35be13a1b98 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Mon, 28 Sep 2020 18:14:24 -0300 Subject: [PATCH 083/198] Fix import --- app/notifications/server/lib/Notifications.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/notifications/server/lib/Notifications.js b/app/notifications/server/lib/Notifications.js index 134425ec1fc38..f739abb76ad91 100644 --- a/app/notifications/server/lib/Notifications.js +++ b/app/notifications/server/lib/Notifications.js @@ -5,7 +5,7 @@ import { WEB_RTC_EVENTS } from '../../../webrtc'; import { Subscriptions, Rooms } from '../../../models/server'; import { settings } from '../../../settings/server'; import { NotificationsModule } from '../../../../server/modules/notifications/notifications.module'; -import { hasPermission } from '../../../authorization/client'; +import { hasPermission } from '../../../authorization/server'; const changedPayload = function(collection, id, fields) { return DDPCommon.stringifyDDP({ From 67219c2b02a7c50797c48ef7334bfb67d0464a94 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Mon, 28 Sep 2020 19:28:17 -0300 Subject: [PATCH 084/198] Broadcast user presence --- app/lib/server/functions/setStatusText.js | 28 ++++---------------- imports/users-presence/server/activeUsers.js | 9 ++----- server/sdk/types/IStreamService.ts | 8 ------ server/services/listeners/notification.ts | 28 ++++++++++++++++++++ server/services/startup.ts | 2 ++ server/services/stream/service.ts | 9 ------- 6 files changed, 37 insertions(+), 47 deletions(-) create mode 100644 server/services/listeners/notification.ts diff --git a/app/lib/server/functions/setStatusText.js b/app/lib/server/functions/setStatusText.js index 597b02c4f33af..d6705468ab393 100644 --- a/app/lib/server/functions/setStatusText.js +++ b/app/lib/server/functions/setStatusText.js @@ -5,15 +5,7 @@ import { Users } from '../../../models/server'; import { Users as UsersRaw } from '../../../models/server/raw'; import { hasPermission } from '../../../authorization/server'; import { RateLimiter } from '../lib'; -import { StreamService } from '../../../../server/sdk'; - -// mirror of object in /imports/startup/client/listenActiveUsers.js - keep updated -const STATUS_MAP = { - offline: 0, - online: 1, - away: 2, - busy: 3, -}; +import { api } from '../../../../server/sdk/api'; export const _setStatusTextPromise = async function(userId, statusText) { if (!userId) { return false; } @@ -28,13 +20,8 @@ export const _setStatusTextPromise = async function(userId, statusText) { await UsersRaw.updateStatusText(user._id, statusText); - const { _id: uid, username } = user; - StreamService.sendUserStatus({ - uid, - username, - status: STATUS_MAP[user.status], - statusText, - }); + const { _id, username, status } = user; + api.broadcast('userpresence', { user: { _id, username, status, statusText } }); return true; }; @@ -60,13 +47,8 @@ export const _setStatusText = function(userId, statusText) { Users.updateStatusText(user._id, statusText); user.statusText = statusText; - const { _id: uid, username } = user; - StreamService.sendUserStatus({ - uid, - username, - status: STATUS_MAP[user.status], - statusText, - }); + const { _id, username, status } = user; + api.broadcast('userpresence', { user: { _id, username, status, statusText } }); return true; }; diff --git a/imports/users-presence/server/activeUsers.js b/imports/users-presence/server/activeUsers.js index b90aeccb19e8e..48ddf7626f4c7 100644 --- a/imports/users-presence/server/activeUsers.js +++ b/imports/users-presence/server/activeUsers.js @@ -1,7 +1,7 @@ import { UserPresenceEvents } from 'meteor/konecty:user-presence'; import { settings } from '../../../app/settings/server'; -import { StreamService } from '../../../server/sdk'; +import { api } from '../../../server/sdk/api'; // mirror of object in /imports/startup/client/listenActiveUsers.js - keep updated export const STATUS_MAP = { @@ -20,12 +20,7 @@ export const setUserStatus = (user, status/* , statusConnection*/) => { // since this callback can be called by only one instance in the cluster // we need to broadcast the change to all instances - StreamService.sendUserStatus({ - uid, - username, - status: STATUS_MAP[status], - statusText, - }); + api.broadcast('userpresence', { user: { status, _id: uid, username, statusText } }); // remove username }; let TroubleshootDisablePresenceBroadcast; diff --git a/server/sdk/types/IStreamService.ts b/server/sdk/types/IStreamService.ts index 16023983a40cb..a05a8949a4373 100644 --- a/server/sdk/types/IStreamService.ts +++ b/server/sdk/types/IStreamService.ts @@ -1,13 +1,6 @@ import { IServiceClass } from './ServiceClass'; import { IMessage } from '../../../definition/IMessage'; -export enum STATUS_MAP { - OFFLINE = 0, - ONLINE = 1, - AWAY = 2, - BUDY = 3, -} - type Publication = { userId?: string; } @@ -37,7 +30,6 @@ export interface IStreamerConstructor { export interface IStreamService extends IServiceClass { notifyAll(eventName: string, ...args: any[]): void; notifyUser(uid: string, eventName: string, ...args: any[]): void; - sendUserStatus({ uid, username, status, statusText }: { uid: string; username: string; status: STATUS_MAP; statusText?: string }): void; sendPermission({ clientAction, data }: any): void; sendPrivateSetting({ clientAction, setting }: any): void; sendUserAvatarUpdate({ username, etag }: { username: string; etag?: string }): void; diff --git a/server/services/listeners/notification.ts b/server/services/listeners/notification.ts new file mode 100644 index 0000000000000..6160c5c74ac60 --- /dev/null +++ b/server/services/listeners/notification.ts @@ -0,0 +1,28 @@ +import { ServiceClass } from '../../sdk/types/ServiceClass'; +import notifications from '../../../app/notifications/server/lib/Notifications'; + +const STATUS_MAP: {[k: string]: number} = { + offline: 0, + online: 1, + away: 2, + busy: 3, +}; + +export class NotificationService extends ServiceClass { + protected name = 'notification'; + + constructor() { + super(); + + this.onEvent('userpresence', ({ user }) => { + const { + _id, username, status, statusText, + } = user; + if (!status) { + return; + } + + notifications.notifyLogged('user-status', [_id, username, STATUS_MAP[status], statusText]); + }); + } +} diff --git a/server/services/startup.ts b/server/services/startup.ts index f40286c43903b..e52b4dd5c45b1 100644 --- a/server/services/startup.ts +++ b/server/services/startup.ts @@ -5,7 +5,9 @@ import { api } from '../sdk/api'; import { Authorization } from './authorization/service'; import { StreamService } from './stream/service'; import { MeteorService } from './meteor/service'; +import { NotificationService } from './listeners/notification'; api.registerService(new Authorization(MongoInternals.defaultRemoteCollectionDriver().mongo.db)); api.registerService(new MeteorService()); api.registerService(new StreamService(Meteor.Streamer)); +api.registerService(new NotificationService()); diff --git a/server/services/stream/service.ts b/server/services/stream/service.ts index efd670536c94f..3ec0dc2a6484b 100644 --- a/server/services/stream/service.ts +++ b/server/services/stream/service.ts @@ -98,15 +98,6 @@ export class StreamService extends ServiceClass implements IStreamService { }); } - sendUserStatus({ uid, username, status, statusText }: { uid: string; username: string; status: STATUS_MAP; statusText?: string }): void { - this.streamLogged.emit('user-status', [ - uid, - username, - status, - statusText, - ]); - } - sendPermission({ clientAction, data }: any): void { this.streamLogged.emitWithoutBroadcast('permissions-changed', clientAction, data); } From b53b0a54ba5d5a7b1732259881929ea7cd757d5f Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Tue, 29 Sep 2020 13:35:48 -0300 Subject: [PATCH 085/198] Development instructions --- ee/server/services/DDPStreamer/DDPStreamer.ts | 2 +- ee/server/services/README.md | 39 +++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/ee/server/services/DDPStreamer/DDPStreamer.ts b/ee/server/services/DDPStreamer/DDPStreamer.ts index b8949bdc90e09..9eab3c030f388 100644 --- a/ee/server/services/DDPStreamer/DDPStreamer.ts +++ b/ee/server/services/DDPStreamer/DDPStreamer.ts @@ -42,7 +42,7 @@ const proxy = function(req: IncomingMessage, res: ServerResponse): void { const httpServer = http.createServer((req, res) => { res.setHeader('Access-Control-Allow-Origin', '*'); - if (!/^\/sockjs\/info\?cb=/.test(req.url || '')) { + if (process.env.NODE_ENV !== 'production' && !/^\/sockjs\/info\?cb=/.test(req.url || '')) { return proxy(req, res); // res.writeHead(404); diff --git a/ee/server/services/README.md b/ee/server/services/README.md index a23f6e8e85d89..57f6fcea6106d 100644 --- a/ee/server/services/README.md +++ b/ee/server/services/README.md @@ -1,5 +1,44 @@ # Rocket.Chat Micro-Services +## PM2 + +This usually suits better for development purposes. + +Start NATS first, you can it via Docker: + +``` +docker run --rm -d -p 4222:4222 nats +``` + +Then run Rocket.Chat as usual with an additional `TRANSPORTER` env var: + +``` +TRANSPORTER=nats://localhost:4222 MOLECULER_LOG_LEVEL=debug meteor +``` + +Set up an Enterprise license going to Admin > Enterprise. + +Then you can spin up micro services. From this folder, first install dependencies: + +``` +meteor npm i +``` + +You can run on `dev` to have hot reloading: + +``` +MONGO_URL=mongodb://localhost:3001/meteor \ +MOLECULER_LOG_LEVEL=debug \ +TRANSPORTER=nats://localhost:4222 \ +meteor npm run dev +``` + +To see process logs, do: + +``` +meteor npm run pm2 -- logs +``` + ## Docker Compose The `docker-compose.yml` file contais a setup of the micro-services plus some extra tools: From 46b467f14b61ae7d92c79df7d4daa21462cd8d96 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Tue, 29 Sep 2020 14:05:54 -0300 Subject: [PATCH 086/198] Remove STATUS_MAP --- server/services/stream/service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/services/stream/service.ts b/server/services/stream/service.ts index 3ec0dc2a6484b..13c568779a4ad 100644 --- a/server/services/stream/service.ts +++ b/server/services/stream/service.ts @@ -1,5 +1,5 @@ import { ServiceClass } from '../../sdk/types/ServiceClass'; -import { IStreamService, STATUS_MAP, IStreamer, IStreamerConstructor } from '../../sdk/types/IStreamService'; +import { IStreamService, IStreamer, IStreamerConstructor } from '../../sdk/types/IStreamService'; // import { Notifications } from '../../../app/notifications/server'; import { IMessage } from '../../../definition/IMessage'; From 85ee26e1c8e8dff519be83fcd3c60249c42aaaea Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Tue, 29 Sep 2020 17:22:17 -0300 Subject: [PATCH 087/198] Use correct @types/node version --- package-lock.json | 48 +++++++++++++++++++++++------------------------ package.json | 6 +++--- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6ab3b6014f1e2..acabf430e0bcf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8453,9 +8453,9 @@ } }, "@types/node": { - "version": "14.6.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.6.4.tgz", - "integrity": "sha512-Wk7nG1JSaMfMpoMJDKUsWYugliB2Vy55pdjLpmLixeyMi7HizW2I/9QoxsPCkXl3dO+ZOVqPumKaDUv5zJu2uQ==" + "version": "10.12.14", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.14.tgz", + "integrity": "sha512-0rVcFRhM93kRGAU88ASCjX9Y3FWDCh+33G5Z5evpKOea4xcpLqDGwmo64+DjgaSezTN5j9KdnUzvxhOw7fNciQ==" }, "@types/normalize-package-data": { "version": "2.4.0", @@ -18002,7 +18002,7 @@ }, "chownr": { "version": "1.1.1", - "resolved": "", + "resolved": false, "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==", "dev": true, "optional": true @@ -18037,7 +18037,7 @@ }, "debug": { "version": "4.1.1", - "resolved": "", + "resolved": false, "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "dev": true, "optional": true, @@ -18068,7 +18068,7 @@ }, "fs-minipass": { "version": "1.2.5", - "resolved": "", + "resolved": false, "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==", "dev": true, "optional": true, @@ -18102,7 +18102,7 @@ }, "glob": { "version": "7.1.3", - "resolved": "", + "resolved": false, "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "dev": true, "optional": true, @@ -18134,7 +18134,7 @@ }, "ignore-walk": { "version": "3.0.1", - "resolved": "", + "resolved": false, "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", "dev": true, "optional": true, @@ -18155,7 +18155,7 @@ }, "inherits": { "version": "2.0.3", - "resolved": "", + "resolved": false, "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", "dev": true, "optional": true @@ -18196,14 +18196,14 @@ }, "minimist": { "version": "0.0.8", - "resolved": "", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "dev": true, "optional": true }, "minipass": { "version": "2.3.5", - "resolved": "", + "resolved": false, "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", "dev": true, "optional": true, @@ -18214,7 +18214,7 @@ }, "minizlib": { "version": "1.2.1", - "resolved": "", + "resolved": false, "integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==", "dev": true, "optional": true, @@ -18224,7 +18224,7 @@ }, "mkdirp": { "version": "0.5.1", - "resolved": "", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "dev": true, "optional": true, @@ -18234,7 +18234,7 @@ }, "ms": { "version": "2.1.1", - "resolved": "", + "resolved": false, "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", "dev": true, "optional": true @@ -18248,7 +18248,7 @@ }, "needle": { "version": "2.3.0", - "resolved": "", + "resolved": false, "integrity": "sha512-QBZu7aAFR0522EyaXZM0FZ9GLpq6lvQ3uq8gteiDUp7wKdy0lSd2hPlgFwVuW1CBkfEs9PfDQsQzZghLs/psdg==", "dev": true, "optional": true, @@ -18260,7 +18260,7 @@ }, "node-pre-gyp": { "version": "0.12.0", - "resolved": "", + "resolved": false, "integrity": "sha512-4KghwV8vH5k+g2ylT+sLTjy5wmUOb9vPhnM8NHvRf9dHmnW/CndrFXy2aRPaPST6dugXSdHXfeaHQm77PIz/1A==", "dev": true, "optional": true, @@ -18290,14 +18290,14 @@ }, "npm-bundled": { "version": "1.0.6", - "resolved": "", + "resolved": false, "integrity": "sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==", "dev": true, "optional": true }, "npm-packlist": { "version": "1.4.1", - "resolved": "", + "resolved": false, "integrity": "sha512-+TcdO7HJJ8peiiYhvPxsEDhF3PJFGUGRcFsGve3vxvxdcpO2Z4Z7rkosRM0kWj6LfbK/P0gu3dzk5RU1ffvFcw==", "dev": true, "optional": true, @@ -18377,7 +18377,7 @@ }, "process-nextick-args": { "version": "2.0.0", - "resolved": "", + "resolved": false, "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", "dev": true, "optional": true @@ -18397,7 +18397,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true, "optional": true @@ -18422,7 +18422,7 @@ }, "rimraf": { "version": "2.6.3", - "resolved": "", + "resolved": false, "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", "dev": true, "optional": true, @@ -18453,7 +18453,7 @@ }, "semver": { "version": "5.7.0", - "resolved": "", + "resolved": false, "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", "dev": true, "optional": true @@ -18513,7 +18513,7 @@ }, "tar": { "version": "4.4.8", - "resolved": "", + "resolved": false, "integrity": "sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ==", "dev": true, "optional": true, @@ -18553,7 +18553,7 @@ }, "yallist": { "version": "3.0.3", - "resolved": "", + "resolved": false, "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", "dev": true, "optional": true diff --git a/package.json b/package.json index 9667adcd84365..ac80e477d463d 100644 --- a/package.json +++ b/package.json @@ -67,12 +67,12 @@ "@types/chai-spies": "^1.0.1", "@types/ejson": "^2.1.2", "@types/meteor": "^1.4.49", - "@types/msgpack5": "^3.4.1", "@types/mocha": "^8.0.3", "@types/mock-require": "^2.0.0", "@types/moment-timezone": "^0.5.30", "@types/mongodb": "^3.5.26", - "@types/node": "^14.6.4", + "@types/msgpack5": "^3.4.1", + "@types/node": "^10.12.14", "@types/react-dom": "^16.9.8", "@types/semver": "^7.3.3", "@types/toastr": "^2.1.38", @@ -210,7 +210,6 @@ "mailparser": "^2.8.1", "marked": "^0.7.0", "mem": "^6.1.0", - "msgpack5": "^4.2.1", "meteor-node-stubs": "^1.0.1", "mime-db": "^1.44.0", "mime-type": "^3.1.0", @@ -219,6 +218,7 @@ "moment": "^2.27.0", "moment-timezone": "^0.5.31", "mongodb": "^3.6.0", + "msgpack5": "^4.2.1", "nats": "^1.4.8", "node-dogstatsd": "^0.0.7", "node-gcm": "0.14.4", From fa728b26e38b70cf0a1c7ae764fd8600586ffa0d Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Tue, 29 Sep 2020 18:03:04 -0300 Subject: [PATCH 088/198] Add missing typescript module for async_hooks --- server/main.d.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/server/main.d.ts b/server/main.d.ts index 29ca2958f6736..162d4f72a1ae0 100644 --- a/server/main.d.ts +++ b/server/main.d.ts @@ -107,3 +107,21 @@ declare module 'meteor/mongo' { function defaultRemoteCollectionDriver(): RemoteCollectionDriver; } } + +declare module 'async_hooks' { + export class AsyncLocalStorage { + disable(): void; + + getStore(): T | undefined; + + run(store: T, callback: (...args: any[]) => void, ...args: any[]): void; + + exit(callback: (...args: any[]) => void, ...args: any[]): void; + + runSyncAndReturn(store: T, callback: (...args: any[]) => R, ...args: any[]): R; + + exitSyncAndReturn(callback: (...args: any[]) => R, ...args: any[]): R; + + enterWith(store: T): void; + } +} From cc64b66add8cce672e87bfb77b4dc00cb89d0dec Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Tue, 29 Sep 2020 21:28:06 -0300 Subject: [PATCH 089/198] Import Meteor.Streamer code and unify with DDP Streamer --- .meteor/packages | 1 - .meteor/versions | 1 - app/models/server/models/Subscriptions.js | 4 +- app/notifications/server/lib/Notifications.js | 213 ----------- app/notifications/server/lib/Notifications.ts | 224 ++++++++++++ ee/server/services/DDPStreamer/DDPStreamer.ts | 8 +- ee/server/services/DDPStreamer/Publication.ts | 81 +---- ee/server/services/DDPStreamer/Server.ts | 27 +- ee/server/services/DDPStreamer/Streamer.ts | 306 +++------------- .../services/DDPStreamer/configureServer.ts | 22 +- .../services/DDPStreamer/lib/sendBroadcast.ts | 34 -- .../services/DDPStreamer/streams/index.ts | 20 +- server/main.d.ts | 2 +- .../notifications/notifications.module.ts | 4 +- server/modules/streamer/streamer.module.ts | 341 ++++++++++++++++++ server/sdk/types/IStreamService.ts | 26 -- server/services/startup.ts | 4 +- server/services/stream/service.ts | 5 +- server/stream/messages/index.js | 6 +- server/stream/streamBroadcast.js | 11 +- 20 files changed, 676 insertions(+), 664 deletions(-) delete mode 100644 app/notifications/server/lib/Notifications.js create mode 100644 app/notifications/server/lib/Notifications.ts delete mode 100644 ee/server/services/DDPStreamer/lib/sendBroadcast.ts create mode 100644 server/modules/streamer/streamer.module.ts diff --git a/.meteor/packages b/.meteor/packages index 2a2c903bbd211..65fdab1114b31 100644 --- a/.meteor/packages +++ b/.meteor/packages @@ -41,7 +41,6 @@ tracker@1.2.0 #rocketchat:google-natural-language rocketchat:livechat -rocketchat:streamer rocketchat:version konecty:multiple-instances-status diff --git a/.meteor/versions b/.meteor/versions index 811fbd386ad1d..8c01810d4d3d8 100644 --- a/.meteor/versions +++ b/.meteor/versions @@ -121,7 +121,6 @@ rocketchat:livechat@0.0.1 rocketchat:mongo-config@0.0.1 rocketchat:oauth2-server@2.1.0 rocketchat:postcss@1.0.0 -rocketchat:streamer@1.1.0 rocketchat:tap-i18n@1.9.1 rocketchat:version@1.0.0 routepolicy@1.1.0 diff --git a/app/models/server/models/Subscriptions.js b/app/models/server/models/Subscriptions.js index 3615dbf2ae20a..4de33a2aa92ab 100644 --- a/app/models/server/models/Subscriptions.js +++ b/app/models/server/models/Subscriptions.js @@ -439,7 +439,7 @@ export class Subscriptions extends Base { } // FIND ONE - findOneByRoomIdAndUserId(roomId, userId, options) { + findOneByRoomIdAndUserId(roomId, userId, options = {}) { const query = { rid: roomId, 'u._id': userId, @@ -547,7 +547,7 @@ export class Subscriptions extends Base { return this.find(query, options); } - findByRoomIdAndNotUserId(roomId, userId, options) { + findByRoomIdAndNotUserId(roomId, userId, options = {}) { const query = { rid: roomId, 'u._id': { diff --git a/app/notifications/server/lib/Notifications.js b/app/notifications/server/lib/Notifications.js deleted file mode 100644 index f739abb76ad91..0000000000000 --- a/app/notifications/server/lib/Notifications.js +++ /dev/null @@ -1,213 +0,0 @@ -import { Meteor } from 'meteor/meteor'; -import { DDPCommon } from 'meteor/ddp-common'; - -import { WEB_RTC_EVENTS } from '../../../webrtc'; -import { Subscriptions, Rooms } from '../../../models/server'; -import { settings } from '../../../settings/server'; -import { NotificationsModule } from '../../../../server/modules/notifications/notifications.module'; -import { hasPermission } from '../../../authorization/server'; - -const changedPayload = function(collection, id, fields) { - return DDPCommon.stringifyDDP({ - msg: 'changed', - collection, - id, - fields, - }); -}; -const send = function(self, msg) { - if (!self.socket) { - return; - } - self.socket.send(msg); -}; -class RoomStreamer extends Meteor.Streamer { - _publish(publication, eventName, options) { - super._publish(publication, eventName, options); - const uid = Meteor.userId(); - if (/rooms-changed/.test(eventName)) { - const roomEvent = (...args) => send(publication._session, changedPayload(this.subscriptionName, 'id', { - eventName: `${ uid }/rooms-changed`, - args, - })); - const rooms = Subscriptions.find({ 'u._id': uid }, { fields: { rid: 1 } }).fetch(); - rooms.forEach(({ rid }) => { - this.on(rid, roomEvent); - }); - - const userEvent = (clientAction, { rid }) => { - switch (clientAction) { - case 'inserted': - rooms.push({ rid }); - this.on(rid, roomEvent); - - // after a subscription is added need to emit the room again - roomEvent('inserted', Rooms.findOneById(rid)); - break; - - case 'removed': - this.removeListener(rid, roomEvent); - break; - } - }; - this.on(uid, userEvent); - - publication.onStop(() => { - this.removeListener(uid, userEvent); - rooms.forEach(({ rid }) => this.removeListener(rid, roomEvent)); - }); - } - } -} - -class MessageStream extends Meteor.Streamer { - getSubscriptionByUserIdAndRoomId(userId, rid) { - return this.subscriptions.find((sub) => sub.eventName === rid && sub.subscription.userId === userId); - } - - _publish(publication, eventName, options) { - super._publish(publication, eventName, options); - const uid = Meteor.userId(); - - const userEvent = (clientAction, { rid }) => { - switch (clientAction) { - case 'removed': - this.removeListener(uid, userEvent); - this.removeSubscription(this.getSubscriptionByUserIdAndRoomId(uid, rid), eventName); - break; - } - }; - this.on(uid, userEvent); - } - - mymessage = (eventName, args) => { - const subscriptions = this.subscriptionsByEventName[eventName]; - if (!Array.isArray(subscriptions)) { - return; - } - subscriptions.forEach(({ subscription }) => { - const options = this.isEmitAllowed(subscription, eventName, args); - if (options) { - send(subscription._session, changedPayload(this.subscriptionName, 'id', { - eventName, - args: [args, options], - })); - } - }); - } -} - -class Notifications extends NotificationsModule { - constructor(Streamer, RoomStreamer, MessageStream) { - super(Streamer, RoomStreamer, MessageStream); - - const self = this; - this.streamRoomUsers.allowWrite(function(eventName, ...args) { - const [roomId, e] = eventName.split('/'); - // const user = Meteor.users.findOne(this.userId, { - // fields: { - // username: 1 - // } - // }); - if (Subscriptions.findOneByRoomIdAndUserId(roomId, this.userId) != null) { - const subscriptions = Subscriptions.findByRoomIdAndNotUserId(roomId, this.userId).fetch(); - subscriptions.forEach((subscription) => self.notifyUser(subscription.u._id, e, ...args)); - } - return false; - }); - - this.streamRoom.allowRead(function(eventName, extraData) { - const [roomId] = eventName.split('/'); - const room = Rooms.findOneById(roomId); - if (!room) { - console.warn(`Invalid streamRoom eventName: "${ eventName }"`); - return false; - } - if (room.t === 'l' && extraData && extraData.token && room.v.token === extraData.token) { - return true; - } - if (this.userId == null) { - return false; - } - const subscription = Subscriptions.findOneByRoomIdAndUserId(roomId, this.userId, { fields: { _id: 1 } }); - return subscription != null; - }); - } -} - -const notifications = new Notifications(Meteor.Streamer, RoomStreamer, MessageStream); - -notifications.streamRoom.allowWrite(function(eventName, username, typing, extraData) { - const [roomId, e] = eventName.split('/'); - - if (isNaN(e) ? e === WEB_RTC_EVENTS.WEB_RTC : parseFloat(e) === WEB_RTC_EVENTS.WEB_RTC) { - return true; - } - - if (e === 'typing') { - const key = settings.get('UI_Use_Real_Name') ? 'name' : 'username'; - // typing from livechat widget - if (extraData && extraData.token) { - const room = Rooms.findOneById(roomId); - if (room && room.t === 'l' && room.v.token === extraData.token) { - return true; - } - } - - const user = Meteor.users.findOne(this.userId, { - fields: { - [key]: 1, - }, - }); - - if (!user) { - return false; - } - - return user[key] === username; - } - return false; -}); - -export default notifications; - - -notifications.streamRoomMessage.allowRead(function(eventName, args) { - try { - const room = Meteor.call('canAccessRoom', eventName, this.userId, args); - - if (!room) { - return false; - } - - if (room.t === 'c' && !hasPermission(this.userId, 'preview-c-room') && !Subscriptions.findOneByRoomIdAndUserId(room._id, this.userId, { fields: { _id: 1 } })) { - return false; - } - - return true; - } catch (error) { - /* error*/ - return false; - } -}); - -notifications.streamRoomMessage.allowRead('__my_messages__', 'all'); - -notifications.streamRoomMessage.allowEmit('__my_messages__', function(eventName, msg) { - try { - const room = Meteor.call('canAccessRoom', msg.rid, this.userId); - - if (!room) { - return false; - } - - return { - roomParticipant: Subscriptions.findOneByRoomIdAndUserId(room._id, this.userId, { fields: { _id: 1 } }) != null, - roomType: room.t, - roomName: room.name, - }; - } catch (error) { - /* error*/ - return false; - } -}); diff --git a/app/notifications/server/lib/Notifications.ts b/app/notifications/server/lib/Notifications.ts new file mode 100644 index 0000000000000..68f601b0dd241 --- /dev/null +++ b/app/notifications/server/lib/Notifications.ts @@ -0,0 +1,224 @@ +import { Meteor } from 'meteor/meteor'; +import { DDPCommon } from 'meteor/ddp-common'; + +import { WEB_RTC_EVENTS } from '../../../webrtc'; +import { Subscriptions, Rooms } from '../../../models/server'; +import { settings } from '../../../settings/server'; +import { NotificationsModule } from '../../../../server/modules/notifications/notifications.module'; +import { hasPermission } from '../../../authorization/server'; +import { Streamer, Publication, DDPSubscription } from '../../../../server/modules/streamer/streamer.module'; +import { ISubscription } from '../../../../definition/ISubscription'; +import { IUser } from '../../../../definition/IUser'; + +export class Stream extends Streamer { + registerPublication(name: string, fn: (eventName: string, options: boolean | {useCollection?: boolean; args?: any}) => void): void { + Meteor.publish(name, fn); + } + + registerMethod(methods: Record any>): void { + Meteor.methods(methods); + } + + changedPayload(collection: string, id: string, fields: Record): string | false { + return DDPCommon.stringifyDDP({ + msg: 'changed', + collection, + id, + fields, + }); + } +} + +class RoomStreamer extends Stream { + async _publish(publication: Publication, eventName: string, options: boolean | {useCollection?: boolean; args?: any} = false): Promise { + super._publish(publication, eventName, options); + const uid = Meteor.userId(); + if (!uid) { + return; + } + + if (/rooms-changed/.test(eventName)) { + const roomEvent = (...args: any[]): void => publication._session.socket?.send(this.changedPayload(this.subscriptionName, 'id', { + eventName: `${ uid }/rooms-changed`, + args, + })); + const rooms: Pick[] = Subscriptions.find({ 'u._id': uid }, { fields: { rid: 1 } }).fetch(); + rooms.forEach(({ rid }) => { + this.on(rid, roomEvent); + }); + + const userEvent = (clientAction: string, { rid }: {rid: string}): void => { + switch (clientAction) { + case 'inserted': + rooms.push({ rid }); + this.on(rid, roomEvent); + + // after a subscription is added need to emit the room again + roomEvent('inserted', Rooms.findOneById(rid)); + break; + + case 'removed': + this.removeListener(rid, roomEvent); + break; + } + }; + this.on(uid, userEvent); + + publication.onStop(() => { + this.removeListener(uid, userEvent); + rooms.forEach(({ rid }) => this.removeListener(rid, roomEvent)); + }); + } + } +} + +class MessageStream extends Stream { + getSubscriptionByUserIdAndRoomId(userId: string, rid: string): DDPSubscription | undefined { + return [...this.subscriptions].find((sub) => sub.eventName === rid && sub.subscription.userId === userId); + } + + async _publish(publication: Publication, eventName: string, options: boolean | {useCollection?: boolean; args?: any} = false): Promise { + super._publish(publication, eventName, options); + const uid = Meteor.userId(); + if (!uid) { + return; + } + + const userEvent = (clientAction: string, { rid }: {rid: string}): void => { + switch (clientAction) { + case 'removed': + this.removeListener(uid, userEvent); + const sub = this.getSubscriptionByUserIdAndRoomId(uid, rid); + sub && this.removeSubscription(sub, eventName); + break; + } + }; + this.on(uid, userEvent); + } + + mymessage(eventName: string, args: any[]): void { + const subscriptions = this.subscriptionsByEventName.get(eventName); + if (!Array.isArray(subscriptions)) { + return; + } + subscriptions.forEach(async ({ subscription }) => { + // TODO: bring back the options + const options = await this.isEmitAllowed(subscription, eventName, args); + if (options) { + subscription._session.socket?.send(this.changedPayload(this.subscriptionName, 'id', { + eventName, + args: [args, options], + })); + } + }); + } +} + +const notifications = new NotificationsModule(Stream, RoomStreamer, MessageStream); + +notifications.streamRoomUsers.allowWrite(async function(eventName, ...args) { + const [roomId, e] = eventName.split('/'); + // const user = Meteor.users.findOne(this.userId, { + // fields: { + // username: 1 + // } + // }); + if (Subscriptions.findOneByRoomIdAndUserId(roomId, this.userId) != null) { + const subscriptions: ISubscription[] = Subscriptions.findByRoomIdAndNotUserId(roomId, this.userId).fetch(); + subscriptions.forEach((subscription) => notifications.notifyUser(subscription.u._id, e, ...args)); + } + return false; +}); + +notifications.streamRoom.allowRead(async function(eventName, extraData) { + const [roomId] = eventName.split('/'); + const room = Rooms.findOneById(roomId); + if (!room) { + console.warn(`Invalid streamRoom eventName: "${ eventName }"`); + return false; + } + if (room.t === 'l' && extraData && extraData.token && room.v.token === extraData.token) { + return true; + } + if (this.userId == null) { + return false; + } + const subscription = Subscriptions.findOneByRoomIdAndUserId(roomId, this.userId, { fields: { _id: 1 } }); + return subscription != null; +}); + +notifications.streamRoom.allowWrite(async function(eventName, username, _typing, extraData) { + const [roomId, e] = eventName.split('/'); + + // if (isNaN(parseFloat(e)) ? e === WEB_RTC_EVENTS.WEB_RTC : parseFloat(e) === WEB_RTC_EVENTS.WEB_RTC) { + if (e === WEB_RTC_EVENTS.WEB_RTC) { + return true; + } + + if (e === 'typing') { + const key = settings.get('UI_Use_Real_Name') ? 'name' : 'username'; + // typing from livechat widget + if (extraData && extraData.token) { + const room = Rooms.findOneById(roomId); + if (room && room.t === 'l' && room.v.token === extraData.token) { + return true; + } + } + + const user = Meteor.users.findOne(this.userId, { + fields: { + [key]: 1, + }, + }) as IUser; + + if (!user) { + return false; + } + + return user[key] === username; + } + return false; +}); + +export default notifications; + + +notifications.streamRoomMessage.allowRead(async function(eventName, args) { + try { + const room = Meteor.call('canAccessRoom', eventName, this.userId, args); + + if (!room) { + return false; + } + + if (room.t === 'c' && !hasPermission(this.userId, 'preview-c-room') && !Subscriptions.findOneByRoomIdAndUserId(room._id, this.userId, { fields: { _id: 1 } })) { + return false; + } + + return true; + } catch (error) { + /* error*/ + return false; + } +}); + +notifications.streamRoomMessage.allowRead('__my_messages__', 'all'); + +notifications.streamRoomMessage.allowEmit('__my_messages__', async function(_eventName, msg) { + try { + const room = Meteor.call('canAccessRoom', msg.rid, this.userId); + + if (!room) { + return false; + } + + return { + roomParticipant: Subscriptions.findOneByRoomIdAndUserId(room._id, this.userId, { fields: { _id: 1 } }) != null, + roomType: room.t, + roomName: room.name, + }; + } catch (error) { + /* error*/ + return false; + } +}); diff --git a/ee/server/services/DDPStreamer/DDPStreamer.ts b/ee/server/services/DDPStreamer/DDPStreamer.ts index 9eab3c030f388..e330570a17d80 100644 --- a/ee/server/services/DDPStreamer/DDPStreamer.ts +++ b/ee/server/services/DDPStreamer/DDPStreamer.ts @@ -4,13 +4,13 @@ import url from 'url'; import WebSocket from 'ws'; // import PromService from 'moleculer-prometheus'; -import { Streams } from './Streamer'; import { Client, MeteorClient } from './Client'; // import { STREAMER_EVENTS, STREAM_NAMES } from './constants'; import { isEmpty } from './lib/utils'; import { ServiceClass } from '../../../../server/sdk/types/ServiceClass'; import { events } from './configureServer'; import notifications from './streams/index'; +import { StreamerCentral } from '../../../../server/modules/streamer/streamer.module'; const { PORT: port = 4000, @@ -120,9 +120,9 @@ export class DDPStreamer extends ServiceClass { }); // [STREAMER_EVENTS.STREAM]([streamer, eventName, payload]) { - this.onEvent('stream', ([streamer, eventName, payload]): void => { - const stream = Streams.get(streamer); - return stream && stream.emitPayload(eventName, payload); + this.onEvent('stream', ([streamer, eventName, args]): void => { + const stream = StreamerCentral.instances[streamer]; + return stream && stream.emitWithoutBroadcast(eventName, ...args); }); // message({ message }) { diff --git a/ee/server/services/DDPStreamer/Publication.ts b/ee/server/services/DDPStreamer/Publication.ts index ec2820ada008b..82906acdb565a 100644 --- a/ee/server/services/DDPStreamer/Publication.ts +++ b/ee/server/services/DDPStreamer/Publication.ts @@ -1,14 +1,18 @@ import { EventEmitter } from 'events'; -import { DDP_EVENTS } from './constants'; import { Server } from './Server'; -import { server } from './configureServer'; -import { sendBroadcast } from './lib/sendBroadcast'; import { Client } from './Client'; import { IPacket } from './types/IPacket'; -import { ISubscription } from '../../../../definition/ISubscription'; + +interface ISession { + socket?: { + send: Function; + }; +} export class Publication extends EventEmitter { + _session: ISession; + constructor( public client: Client, private packet: IPacket, @@ -19,6 +23,10 @@ export class Publication extends EventEmitter { client.subscriptions.set(packet.id, this); client.once('close', () => this.emit('stop', this.client, this.packet)); this.once('stop', () => client.subscriptions.delete(packet.id)); + + this._session = { + socket: client, + }; } ready(): void { @@ -30,6 +38,10 @@ export class Publication extends EventEmitter { this.emit('stop', this.client, this.packet); } + onStop(fn: (...args: any[]) => void): void { + this.once('stop', fn); + } + added(collection: string, id: string, fields: any): void { this.server.added(this.client, collection, id, fields); } @@ -46,64 +58,3 @@ export class Publication extends EventEmitter { return this.client.userId; } } - -export const changedPayload = (collection: string, id: string, fields: object, cleared: string[]): string => - server.serialize({ - [DDP_EVENTS.MSG]: DDP_EVENTS.CHANGED, - [DDP_EVENTS.COLLECTION]: collection, - [DDP_EVENTS.ID]: id, - [DDP_EVENTS.FIELDS]: fields, - [DDP_EVENTS.CLEARED]: cleared, - }); - - -export class Publish { - subscriptionsByEventName = new Map(); - - constructor(private name: string) { - // - } - - addSubscription(subscription: ISubscription, eventName: string): void { - // this.subscriptions.add(subscription); - if (!this.subscriptionsByEventName.has(eventName)) { - this.subscriptionsByEventName.set( - eventName, - new Set([subscription]), - ); - return; - } - - this.subscriptionsByEventName - .get(eventName) - .add(subscription); - } - - removeSubscription(subscription: ISubscription, eventName: string): void { - // this.subscriptions.delete(subscription); - const subscriptions = this.subscriptionsByEventName.get( - eventName, - ); - if (subscriptions) { - subscriptions.delete(subscription); - if (!subscriptions.size) { - this.subscriptionsByEventName.delete(eventName); - } - } - } - - emit(eventName: string, id: string, fields: object, cleared: string[]): void { - const subscriptions = this.subscriptionsByEventName.get(eventName); - if (!subscriptions || !subscriptions.size) { - return; - } - - const msg = changedPayload(this.name, id, fields, cleared); - - if (!msg) { - return; - } - - sendBroadcast(subscriptions, msg); - } -} diff --git a/ee/server/services/DDPStreamer/Server.ts b/ee/server/services/DDPStreamer/Server.ts index 662c25bdf1130..503cba7e4556a 100644 --- a/ee/server/services/DDPStreamer/Server.ts +++ b/ee/server/services/DDPStreamer/Server.ts @@ -7,7 +7,7 @@ import { Publication } from './Publication'; import { Client } from './Client'; import { IPacket } from './types/IPacket'; -type SubscriptionFn = (publication: Publication, client: Client, eventName: string, options: object) => void; +type SubscriptionFn = (this: Publication, eventName: string, options: object) => void; type MethodFn = (this: Client, ...args: any[]) => any; type Methods = { [k: string]: MethodFn; @@ -16,16 +16,13 @@ type Methods = { // eslint-disable-next-line @typescript-eslint/camelcase export const SERVER_ID = ejson.stringify({ server_id: '0' }); -const methods = Symbol('methods'); -const subscriptions = Symbol('subscriptions'); - // TODO: remove, web-client still receives the object of the logged in user, verify if that works // export const User = new Publish('user'); export class Server extends EventEmitter { - [subscriptions] = new Map(); + private _subscriptions = new Map(); - [methods] = new Map(); + private _methods = new Map(); serialize = ejson.stringify; @@ -36,10 +33,10 @@ export class Server extends EventEmitter { async call(client: Client, packet: IPacket): Promise { try { - if (!this[methods].has(packet.method)) { + if (!this._methods.has(packet.method)) { throw new Error(`Method '${ packet.method }' doesn't exist`); } - const fn = this[methods].get(packet.method); + const fn = this._methods.get(packet.method); if (!fn) { throw Error('method not found'); @@ -54,36 +51,36 @@ export class Server extends EventEmitter { methods(obj: Methods): void { Object.entries(obj).forEach(([name, fn]) => { - if (this[methods].has(name)) { + if (this._methods.has(name)) { return; } - this[methods].set(name, fn); + this._methods.set(name, fn); }); } async subscribe(client: Client, packet: IPacket): Promise { try { - if (!this[subscriptions].has(packet.name)) { + if (!this._subscriptions.has(packet.name)) { throw new Error(`Subscription '${ packet.name }' doesn't exist`); } - const fn = this[subscriptions].get(packet.name); + const fn = this._subscriptions.get(packet.name); if (!fn) { throw new Error('subscription not found'); } const publication = new Publication(client, packet, this); const [eventName, ...options] = packet.params; - await fn(publication, client, eventName, options); + await fn.call(publication, eventName, options); } catch (error) { this.nosub(client, packet, error.toString()); } } publish(name: string, fn: SubscriptionFn): void { - if (this[subscriptions].has(name)) { + if (this._subscriptions.has(name)) { return; } - this[subscriptions].set(name, fn); + this._subscriptions.set(name, fn); } stream(stream: string, fn: SubscriptionFn): void { diff --git a/ee/server/services/DDPStreamer/Streamer.ts b/ee/server/services/DDPStreamer/Streamer.ts index 2541a7789a772..568005ba51abc 100644 --- a/ee/server/services/DDPStreamer/Streamer.ts +++ b/ee/server/services/DDPStreamer/Streamer.ts @@ -1,285 +1,61 @@ -import { EventEmitter } from 'events'; +import WebSocket from 'ws'; import { server } from './configureServer'; -import { DDP_EVENTS, STREAM_NAMES } from './constants'; -import { sendBroadcast } from './lib/sendBroadcast'; +import { DDP_EVENTS } from './constants'; import { isEmpty } from './lib/utils'; -import { Publication } from './Publication'; -import { Client } from './Client'; +import { Streamer, DDPSubscription, Connection, StreamerCentral } from '../../../../server/modules/streamer/streamer.module'; import { api } from '../../../../server/sdk/api'; -import { IStreamer } from '../../../../server/sdk/types/IStreamService'; -type Rule = (this: Publication, eventName: string, ...args: any) => boolean | Promise; - -interface IRules { - [k: string]: Rule; -} - -export type ISubscription = { - client: Client; -} - -export const send = function(self: Publication, msg: string): void { - if (!self.client) { - return; - } - self.client.send(msg); -}; - -export const changedPayload = (collection: string, fields: object): string | false => !isEmpty(fields) && server.serialize({ - [DDP_EVENTS.MSG]: DDP_EVENTS.CHANGED, - [DDP_EVENTS.COLLECTION]: collection, - [DDP_EVENTS.ID]: 'id', - [DDP_EVENTS.FIELDS]: fields, +StreamerCentral.on('broadcast', (name, eventName, args) => { + api.broadcast('stream', [ + name, + eventName, + args, + ]); }); -// PRIVATE METHODS - -const allow = Symbol('allow'); -const isAllowed = Symbol('isAllowed'); -export const publish = Symbol('publish'); - -export const Streams = new Map(); - -export class Stream extends EventEmitter implements IStreamer { - subscriptionName: string; - - serverOnly: boolean; - - retransmit = true; - - retransmitToSelf = false; - - private subscriptionsByEventName = new Map>(); - - private _allowRead = {}; - - private _allowWrite = {}; - - private _allowEmit = {}; - - constructor( - private name: string, - { retransmit = true, retransmitToSelf = false }: {retransmit?: boolean; retransmitToSelf?: boolean } = { }, - ) { - super(); - - this.subscriptionName = `${ STREAM_NAMES.STREAMER_PREFIX }${ name }`; - this.retransmit = retransmit; - this.retransmitToSelf = retransmitToSelf; - - // this.subscriptionsByEventName = new Map(); - - this.iniPublication(); - - this.allowRead('none'); - this.allowWrite('none'); - - Streams.set(name, this); - } - - [allow](rules: IRules, name: string) { - return (eventName: string | boolean | Rule, fn?: Rule): boolean | undefined => { - const _eventName: string = typeof eventName === 'string' ? eventName : '__all__'; - - if (typeof eventName === 'function') { - fn = eventName; - } - - if (fn) { - rules[_eventName] = fn; - return; - } - - if (!['all', 'none', 'logged'].includes(_eventName)) { - console.error(`${ name } shortcut '${ fn }' is invalid`); - } - - if (eventName === 'all' || eventName === true) { - rules[_eventName] = async function(): Promise { - return true; - }; - return; - } - - if (eventName === 'none' || eventName === false) { - rules[_eventName] = async function(): Promise { - return false; - }; - return; - } - - if (eventName === 'logged') { - rules[_eventName] = async function(): Promise { - return Boolean(this.userId); - }; - } - }; - } - - allowRead(eventName: string | boolean | Rule, fn?: Rule): Promise | boolean | undefined { - return this[allow](this._allowRead, 'allowRead')(eventName, fn); - } - - allowWrite(eventName: string | boolean | Rule, fn?: Rule): Promise | boolean | undefined { - return this[allow](this._allowWrite, 'allowWrite')(eventName, fn); - } - - allowEmit(eventName: string | boolean | Rule, fn?: Rule): Promise | boolean | undefined { - return this[allow](this._allowEmit, 'allowEmit')(eventName, fn); +export class Stream extends Streamer { + registerPublication(name: string, fn: (eventName: string, options: boolean | {useCollection?: boolean; args?: any}) => void): void { + server.publish(name, fn); } - [isAllowed](rules: IRules, defaultPermission = false) { - return async (scope: Publication, eventName: string, args: any): Promise => { - if (rules[eventName]) { - return rules[eventName].call(scope, eventName, ...args); - } - - if (rules.__all__) { - return rules.__all__.call(scope, eventName, ...args); - } - - // TODO: Check this since we have permissions not defined here yet - return defaultPermission; - }; - } - - async isReadAllowed(scope: Publication, eventName: string, args: any): Promise { - // TODO: Check this since we have permissions not defined here yet - return this[isAllowed](this._allowRead, true)(scope, eventName, args); - } - - async isWriteAllowed(scope: Publication, eventName: string, args: any): Promise { - return this[isAllowed](this._allowWrite)(scope, eventName, args); - } - - addSubscription(subscription: ISubscription, eventName: string): void { - // this.subscriptions.add(subscription); - if (!this.subscriptionsByEventName.has(eventName)) { - this.subscriptionsByEventName.set( - eventName, - new Set([subscription]), - ); - return; - } - - this.subscriptionsByEventName.get(eventName)?.add(subscription); + registerMethod(methods: Record any>): void { + server.methods(methods); } - removeSubscription(subscription: ISubscription, eventName: string): void { - // this.subscriptions.delete(subscription); - const subscriptions = this.subscriptionsByEventName.get(eventName); - if (subscriptions) { - subscriptions.delete(subscription); - if (!subscriptions.size) { - this.subscriptionsByEventName.delete(eventName); - } - } + changedPayload(collection: string, id: string, fields: Record): string | false { + return !isEmpty(fields) && server.serialize({ + [DDP_EVENTS.MSG]: DDP_EVENTS.CHANGED, + [DDP_EVENTS.COLLECTION]: collection, + [DDP_EVENTS.ID]: id, + [DDP_EVENTS.FIELDS]: fields, + }); } - async [publish](publication: Publication, eventName = '', options: boolean | {useCollection?: boolean; args?: any} = false): Promise { - let args = []; + async sendToManySubscriptions(subscriptions: Set, origin: Connection | undefined, eventName: string, args: any[], msg: string): Promise { + // TODO: missing typing + const data = Buffer.concat((WebSocket as any).Sender.frame(Buffer.from(`a${ JSON.stringify([msg]) }`), { + fin: true, // sending a single fragment message + rsv1: false, // don"t set rsv1 bit (no compression) + opcode: 1, // opcode for a text frame + mask: false, // set false for client-side + readOnly: false, // the data can be modified as needed + })); - if (typeof options === 'boolean') { - // useCollection = options; - } else { - if (options.useCollection) { - // useCollection = options.useCollection; + for await (const { subscription } of subscriptions) { + if (this.retransmitToSelf === false && origin && origin === subscription.connection) { + return; } - if (options.args) { - args = options.args; + if (this.isEmitAllowed(subscription, eventName, ...args)) { + await new Promise((resolve) => { + // TODO: missing typing + (subscription.client.ws as any)._sender.sendFrame( + data, + resolve, + ); + }); } } - if (!eventName || eventName.length === 0) { - throw new Error('invalid-event-name'); - } - - const isAllowed = await this.isReadAllowed(publication, eventName, args); - if (isAllowed !== true) { - throw new Error('not-allowed'); - } - - this.addSubscription(publication, eventName); - publication.once('stop', () => - this.removeSubscription(publication, eventName), - ); - publication.ready(); - } - - async iniPublication(): Promise { - const p = this[publish].bind(this); - const initMethod = this.initMethod.bind(this); - server.publish(this.subscriptionName, function(publication, _client, eventName, options) { - initMethod(publication); - return p(publication, eventName, options); - }); - } - - async initMethod(publication: Publication): Promise { - const { name, subscriptionName } = this; - const isWriteAllowed = this.isWriteAllowed.bind(this); - - const method = { - async [this.subscriptionName](this: Client, eventName: string, ...args: any[]): Promise { - const isAllowed = await isWriteAllowed(publication, eventName, args); - if (isAllowed !== true) { - return; - } - - const payload = changedPayload(subscriptionName, { eventName, args }); - - if (!payload) { - return; - } - - api.broadcast('stream', [ - name, - eventName, - payload, - ]); - }, - }; - - server.methods(method); - } - - async emitPayload(eventName: string, payload: string): Promise { - if (!payload) { - return; - } - const subscriptions = this.subscriptionsByEventName.get(eventName); - if (!subscriptions || !subscriptions.size) { - return; - } - return sendBroadcast(subscriptions, payload); - } - - emit(eventName: string, ...args: any[]): boolean { - const subscriptions = this.subscriptionsByEventName.get(eventName); - if (!subscriptions || !subscriptions.size) { - return false; - } - - const msg = changedPayload(this.subscriptionName, { - eventName, - args, - }); - - if (!msg) { - return false; - } - - sendBroadcast(subscriptions, msg); - return true; - } - - __emit(event: string, ...args: any[]): boolean { - return super.emit(event, ...args); - } - - emitWithoutBroadcast(event: string, ...args: any[]): boolean { - // On microservices all emit needs to be broadcasted. - return this.emit(event, ...args); } } diff --git a/ee/server/services/DDPStreamer/configureServer.ts b/ee/server/services/DDPStreamer/configureServer.ts index 5ae40efbc0511..daaa84715552f 100644 --- a/ee/server/services/DDPStreamer/configureServer.ts +++ b/ee/server/services/DDPStreamer/configureServer.ts @@ -33,29 +33,29 @@ const loginServices = new Map(); MeteorService.getLoginServiceConfiguration().then((records) => records.forEach((record) => loginServices.set(record._id, record))); -server.publish(loginServiceConfigurationPublication, async function(pub) { - loginServices.forEach((record) => pub.added(loginServiceConfigurationCollection, record._id, record)); +server.publish(loginServiceConfigurationPublication, async function() { + loginServices.forEach((record) => this.added(loginServiceConfigurationCollection, record._id, record)); const fn = (action: string, record: any): void => { switch (action) { case 'added': case 'changed': loginServices.set(record._id, record); - pub[action](loginServiceConfigurationCollection, record._id, record); + this[action](loginServiceConfigurationCollection, record._id, record); break; case 'removed': loginServices.delete(record._id); - pub[action](loginServiceConfigurationCollection, record._id); + this[action](loginServiceConfigurationCollection, record._id); } }; events.on(loginServiceConfigurationPublication, fn); - pub.once('stop', () => { + this.onStop(() => { events.removeListener(loginServiceConfigurationPublication, fn); }); - pub.ready(); + this.ready(); }); const autoUpdateRecords = new Map(); @@ -65,21 +65,21 @@ MeteorService.getLastAutoUpdateClientVersions().then((records) => { }); const autoUpdateCollection = 'meteor_autoupdate_clientVersions'; -server.publish(autoUpdateCollection, function(pub) { - autoUpdateRecords.forEach((record) => pub.added(autoUpdateCollection, record._id, record)); +server.publish(autoUpdateCollection, function() { + autoUpdateRecords.forEach((record) => this.added(autoUpdateCollection, record._id, record)); const fn = (record: any): void => { autoUpdateRecords.set(record._id, record); - pub.changed(autoUpdateCollection, record._id, record); + this.changed(autoUpdateCollection, record._id, record); }; events.on('meteor.autoUpdateClientVersionChanged', fn); - pub.once('stop', () => { + this.onStop(() => { events.removeListener('meteor.autoUpdateClientVersionChanged', fn); }); - pub.ready(); + this.ready(); }); server.methods({ diff --git a/ee/server/services/DDPStreamer/lib/sendBroadcast.ts b/ee/server/services/DDPStreamer/lib/sendBroadcast.ts deleted file mode 100644 index d5ee2dcd90ef3..0000000000000 --- a/ee/server/services/DDPStreamer/lib/sendBroadcast.ts +++ /dev/null @@ -1,34 +0,0 @@ -import WebSocket from 'ws'; - -import { ISubscription } from '../Streamer'; - -const broadcastData = (message: string): Buffer[] => { - // TODO: missing typing - const data = (WebSocket as any).Sender.frame(Buffer.from(message), { - fin: true, // sending a single fragment message - rsv1: false, // don"t set rsv1 bit (no compression) - opcode: 1, // opcode for a text frame - mask: false, // set false for client-side - readOnly: false, // the data can be modified as needed - }); - - return [Buffer.concat(data)]; -}; - -export const sendBroadcast = async (subscriptions: Set, message: string): Promise => { - const frames: {[k: string]: Buffer[]} = { - default: broadcastData(message), - meteor: broadcastData(`a${ JSON.stringify([message]) }`), - }; - - for (const subscription of subscriptions) { - // eslint-disable-next-line - await new Promise((resolve) => { - // TODO: missing typing - (subscription.client.ws as any)._sender.sendFrame( - frames[subscription.client.kind], - resolve, - ); - }); - } -}; diff --git a/ee/server/services/DDPStreamer/streams/index.ts b/ee/server/services/DDPStreamer/streams/index.ts index 0a928db9639de..e0c1f66d1cce9 100644 --- a/ee/server/services/DDPStreamer/streams/index.ts +++ b/ee/server/services/DDPStreamer/streams/index.ts @@ -1,24 +1,24 @@ -import { Stream, send, changedPayload, publish } from '../Streamer'; +import { Stream } from '../Streamer'; import { NotificationsModule } from '../../../../../server/modules/notifications/notifications.module'; import { ISubscription } from '../../../../../definition/ISubscription'; import { getCollection, Collections } from '../../mongo'; -import { Publication } from '../Publication'; import { Authorization } from '../../../../../server/sdk'; +import { Publication } from '../../../../../server/modules/streamer/streamer.module'; export class RoomStreamer extends Stream { - async [publish](publication: Publication, eventName = '', options: boolean | {useCollection?: boolean; args?: any} = false): Promise { - super[publish](publication, eventName, options); + async _publish(publication: Publication, eventName = '', options: boolean | {useCollection?: boolean; args?: any} = false): Promise { + super._publish(publication, eventName, options); // const uid = Meteor.userId(); const { userId } = publication.client; if (/rooms-changed/.test(eventName)) { // TODO: change this to serialize only once const roomEvent = (...args: any[]): void => { - const payload = changedPayload(this.subscriptionName, { + const payload = this.changedPayload(this.subscriptionName, 'id', { eventName: `${ userId }/rooms-changed`, args, }); - payload && send( + payload && publication.client?.send( publication, payload, ); @@ -53,7 +53,7 @@ export class RoomStreamer extends Stream { }; this.on(userId, userEvent); - publication.once('stop', () => { + publication.onStop(() => { this.removeListener(userId, userEvent); subscriptions.forEach(({ rid }) => this.removeListener(rid, roomEvent)); }); @@ -104,17 +104,17 @@ const notifications = new NotificationsModule(Stream, RoomStreamer, MessageStrea export default notifications; // TODO: Implementation not complete -notifications.streamRoomMessage.allowRead(function(rid) { +notifications.streamRoomMessage.allowRead(async function(rid) { return !!this.userId && Authorization.canAccessRoom({ _id: rid }, { _id: this.userId }); }); // export const streamRoomData = new Stream(STREAM_NAMES.ROOM_DATA); -notifications.streamRoomData.allowRead(function(rid) { +notifications.streamRoomData.allowRead(async function(rid) { return !!this.userId && Authorization.canAccessRoom({ _id: rid }, { _id: this.userId }); }); -notifications.streamLivechatQueueData.allowRead(function() { +notifications.streamLivechatQueueData.allowRead(async function() { return !!this.userId && Authorization.hasPermission(this.userId, 'view-l-room'); }); diff --git a/server/main.d.ts b/server/main.d.ts index 29ca2958f6736..072f5f63508db 100644 --- a/server/main.d.ts +++ b/server/main.d.ts @@ -1,7 +1,7 @@ import { EJSON } from 'meteor/ejson'; import { Db } from 'mongodb'; -import { IStreamerConstructor } from './sdk/types/IStreamService'; +import { IStreamerConstructor } from './modules/streamer/streamer.module'; /* eslint-disable @typescript-eslint/interface-name-prefix */ declare module 'meteor/random' { diff --git a/server/modules/notifications/notifications.module.ts b/server/modules/notifications/notifications.module.ts index a22593d701486..a185fe94eaea3 100644 --- a/server/modules/notifications/notifications.module.ts +++ b/server/modules/notifications/notifications.module.ts @@ -1,4 +1,4 @@ -import { IStreamer, IStreamerConstructor } from '../../sdk/types/IStreamService'; +import { IStreamer, IStreamerConstructor } from '../streamer/streamer.module'; export class NotificationsModule { private debug = false @@ -64,7 +64,7 @@ export class NotificationsModule { this.streamUser = new this.RoomStreamer('notify-user'); this.streamUser.allowWrite('logged'); - this.streamUser.allowRead(function(eventName) { + this.streamUser.allowRead(async function(eventName) { const [userId] = eventName.split('/'); return (this.userId != null) && this.userId === userId; }); diff --git a/server/modules/streamer/streamer.module.ts b/server/modules/streamer/streamer.module.ts new file mode 100644 index 0000000000000..6158fa3f5186e --- /dev/null +++ b/server/modules/streamer/streamer.module.ts @@ -0,0 +1,341 @@ +import { EventEmitter } from 'events'; + +class StreamerCentralClass extends EventEmitter { + public instances: Record = {}; + + constructor() { + super(); + } +} + +export const StreamerCentral = new StreamerCentralClass(); + +export type Client = { + ws: any; + kind: any; + userId: string; + send: Function; +} + +export type Publication = { + onStop: Function; + stop: Function; + connection: Connection; + _session: { + sendAdded(publicationName: string, id: string, fields: Record): void; + socket?: { + send: Function; + }; + }; + ready: Function; + userId: string; + client: Client; +} + +type Rule = (this: Publication, eventName: string, ...args: any) => Promise; + +interface IRules { + [k: string]: Rule; +} + +export type Connection = any; + +export type DDPSubscription = { + eventName: string; + subscription: Publication; +} + +export interface IStreamer { + serverOnly: boolean; + + allowEmit(eventName: string | boolean | Rule, fn?: Rule | 'all' | 'none' | 'logged'): void; + + allowWrite(eventName: string | boolean | Rule, fn?: Rule | 'all' | 'none' | 'logged'): void; + + allowRead(eventName: string | boolean | Rule, fn?: Rule | 'all' | 'none' | 'logged'): void; + + emit(event: string, ...data: any[]): void; + + __emit(...data: any[]): void; + + emitWithoutBroadcast(event: string, ...data: any[]): void; +} + +export interface IStreamerConstructor { + // eslint-disable-next-line @typescript-eslint/no-misused-new + new(name: string, options?: {retransmit?: boolean; retransmitToSelf?: boolean}): IStreamer; +} + +export abstract class Streamer extends EventEmitter implements IStreamer { + protected subscriptions = new Set(); + + protected subscriptionsByEventName = new Map>(); + + public retransmit = true; + + public retransmitToSelf = false; + + public serverOnly = false; + + private _allowRead: IRules = {}; + + private _allowWrite: IRules = {}; + + private _allowEmit: IRules = {}; + + constructor( + public name: string, + { retransmit = true, retransmitToSelf = false }: {retransmit?: boolean; retransmitToSelf?: boolean } = { }, + ) { + super(); + + if (StreamerCentral.instances[name]) { + console.warn('Streamer instance already exists:', name); + return StreamerCentral.instances[name]; + } + + StreamerCentral.instances[name] = this; + + this.retransmit = retransmit; + this.retransmitToSelf = retransmitToSelf; + + this.iniPublication(); + // DDPStreamer doesn't have this + this.initMethod(); + + this.allowRead('none'); + this.allowEmit('all'); + this.allowWrite('none'); + } + + get subscriptionName(): string { + return `stream-${ this.name }`; + } + + private allow(rules: IRules, name: string) { + return (eventName: string | boolean | Rule, fn?: string | boolean | Rule): void => { + if (fn === undefined) { + fn = eventName; + eventName = '__all__'; + } + + if (typeof eventName !== 'string') { + return; + } + + if (typeof fn === 'function') { + rules[eventName] = fn; + return; + } + + if (typeof fn === 'string' && ['all', 'none', 'logged'].indexOf(fn) === -1) { + console.error(`${ name } shortcut '${ fn }' is invalid`); + } + + if (fn === 'all' || fn === true) { + rules[eventName] = async function(): Promise { + return true; + }; + return; + } + + if (fn === 'none' || fn === false) { + rules[eventName] = async function(): Promise { + return false; + }; + return; + } + + if (fn === 'logged') { + rules[eventName] = async function(): Promise { + return Boolean(this.userId); + }; + } + }; + } + + allowRead(eventName: string | boolean | Rule, fn?: Rule | 'all' | 'none' | 'logged'): void { + this.allow(this._allowRead, 'allowRead')(eventName, fn); + } + + allowWrite(eventName: string | boolean | Rule, fn?: Rule | 'all' | 'none' | 'logged'): void { + this.allow(this._allowWrite, 'allowWrite')(eventName, fn); + } + + allowEmit(eventName: string | boolean | Rule, fn?: Rule | 'all' | 'none' | 'logged'): void { + this.allow(this._allowEmit, 'allowEmit')(eventName, fn); + } + + private isAllowed(rules: IRules) { + return async (scope: Publication, eventName: string, args: any): Promise => { + if (rules[eventName]) { + return rules[eventName].call(scope, eventName, ...args); + } + + return rules.__all__.call(scope, eventName, ...args); + }; + } + + async isReadAllowed(scope: Publication, eventName: string, args: any): Promise { + return this.isAllowed(this._allowRead)(scope, eventName, args); + } + + async isEmitAllowed(scope: Publication, eventName: string, ...args: any[]): Promise { + return this.isAllowed(this._allowEmit)(scope, eventName, args); + } + + async isWriteAllowed(scope: Publication, eventName: string, args: any): Promise { + return this.isAllowed(this._allowWrite)(scope, eventName, args); + } + + addSubscription(subscription: DDPSubscription, eventName: string): void { + this.subscriptions.add(subscription); + + const subByEventName = this.subscriptionsByEventName.get(eventName) || new Set(); + subByEventName.add(subscription); + + this.subscriptionsByEventName.set(eventName, subByEventName); + } + + removeSubscription(subscription: DDPSubscription, eventName: string): void { + this.subscriptions.delete(subscription); + + const subByEventName = this.subscriptionsByEventName.get(eventName); + if (subByEventName) { + subByEventName.delete(subscription); + } + } + + async _publish(publication: Publication, eventName: string, options: boolean | {useCollection?: boolean; args?: any} = false): Promise { + let useCollection; + let args = []; + + if (typeof options === 'boolean') { + useCollection = options; + } else { + if (options.useCollection) { + useCollection = options.useCollection; + } + + if (options.args) { + args = options.args; + } + } + + if (eventName.length === 0) { + publication.stop(); + throw new Error('invalid-event-name'); + } + + if (await this.isReadAllowed(publication, eventName, args) !== true) { + publication.stop(); + throw new Error('not-allowed'); + } + + const subscription = { + subscription: publication, + eventName, + }; + + this.addSubscription(subscription, eventName); + + publication.onStop(() => { + this.removeSubscription(subscription, eventName); + }); + + // DDPStreamer doesn't have this + if (useCollection === true) { + // Collection compatibility + publication._session.sendAdded(this.subscriptionName, 'id', { + eventName, + }); + } + + publication.ready(); + } + + abstract registerPublication(name: string, fn: (eventName: string, options: boolean | {useCollection?: boolean; args?: any}) => void): void; + + iniPublication(): void { + const { _publish } = this; + this.registerPublication(this.subscriptionName, function(this: Publication, eventName: string, options: boolean | {useCollection?: boolean; args?: any}) { + return _publish(this, eventName, options); + }); + } + + abstract registerMethod(methods: Record any>): void; + + initMethod(): void { + const { isWriteAllowed, __emit, _emit, retransmit } = this; + + const method: Record any> = { + async [this.subscriptionName](this: Publication, eventName, ...args): Promise { + if (await isWriteAllowed(this, eventName, args) !== true) { + return; + } + + __emit(eventName, ...args); + + if (retransmit === true) { + _emit(eventName, args, this.connection, true); + } + }, + }; + + try { + this.registerMethod(method); + } catch (e) { + console.error(e); + } + } + + abstract changedPayload(collection: string, id: string, fields: Record): string | false; + + _emit(eventName: string, args: any[], origin: Connection | undefined, broadcast: boolean): boolean { + if (broadcast === true) { + StreamerCentral.emit('broadcast', this.name, eventName, args); + } + + const subscriptions = this.subscriptionsByEventName.get(eventName); + if (!subscriptions || !subscriptions.size) { + return false; + } + + const msg = this.changedPayload(this.subscriptionName, 'id', { + eventName, + args, + }); + + if (!msg) { + return false; + } + + this.sendToManySubscriptions(subscriptions, origin, eventName, args, msg); + + return true; + } + + async sendToManySubscriptions(subscriptions: Set, origin: Connection | undefined, eventName: string, args: any[], msg: string): Promise { + subscriptions.forEach((subscription) => { + if (this.retransmitToSelf === false && origin && origin === subscription.subscription.connection) { + return; + } + + if (this.isEmitAllowed(subscription.subscription, eventName, ...args)) { + subscription.subscription._session.socket?.send(msg); + } + }); + } + + emit(eventName: string, ...args: any[]): boolean { + return this._emit(eventName, args, undefined, true); + } + + __emit(eventName: string, ...args: any[]): boolean { + return super.emit(eventName, ...args); + } + + emitWithoutBroadcast(eventName: string, ...args: any[]): void { + this._emit(eventName, args, undefined, false); + } +} diff --git a/server/sdk/types/IStreamService.ts b/server/sdk/types/IStreamService.ts index a05a8949a4373..77ce44f62ff22 100644 --- a/server/sdk/types/IStreamService.ts +++ b/server/sdk/types/IStreamService.ts @@ -1,32 +1,6 @@ import { IServiceClass } from './ServiceClass'; import { IMessage } from '../../../definition/IMessage'; -type Publication = { - userId?: string; -} -type Rule = (this: Publication, eventName: string, ...args: any) => boolean | Promise; - -export interface IStreamer { - serverOnly: boolean; - - allowEmit(eventName: string | boolean | Rule, fn?: Rule): Promise | boolean | undefined; - - allowWrite(eventName: string | boolean | Rule, fn?: Rule): Promise | boolean | undefined; - - allowRead(eventName: string | boolean | Rule, fn?: Rule): Promise | boolean | undefined; - - emit(event: string, ...data: any[]): void; - - __emit(...data: any[]): void; - - emitWithoutBroadcast(event: string, ...data: any[]): void; -} - -export interface IStreamerConstructor { - // eslint-disable-next-line @typescript-eslint/no-misused-new - new(name: string, options?: {retransmit?: boolean; retransmitToSelf?: boolean}): IStreamer; -} - export interface IStreamService extends IServiceClass { notifyAll(eventName: string, ...args: any[]): void; notifyUser(uid: string, eventName: string, ...args: any[]): void; diff --git a/server/services/startup.ts b/server/services/startup.ts index e52b4dd5c45b1..01f58a2e15b4f 100644 --- a/server/services/startup.ts +++ b/server/services/startup.ts @@ -1,13 +1,13 @@ import { MongoInternals } from 'meteor/mongo'; -import { Meteor } from 'meteor/meteor'; import { api } from '../sdk/api'; import { Authorization } from './authorization/service'; import { StreamService } from './stream/service'; import { MeteorService } from './meteor/service'; import { NotificationService } from './listeners/notification'; +import { Stream } from '../../app/notifications/server/lib/Notifications'; api.registerService(new Authorization(MongoInternals.defaultRemoteCollectionDriver().mongo.db)); api.registerService(new MeteorService()); -api.registerService(new StreamService(Meteor.Streamer)); +api.registerService(new StreamService(Stream)); api.registerService(new NotificationService()); diff --git a/server/services/stream/service.ts b/server/services/stream/service.ts index 13c568779a4ad..0116242418794 100644 --- a/server/services/stream/service.ts +++ b/server/services/stream/service.ts @@ -1,7 +1,8 @@ import { ServiceClass } from '../../sdk/types/ServiceClass'; -import { IStreamService, IStreamer, IStreamerConstructor } from '../../sdk/types/IStreamService'; +import { IStreamService } from '../../sdk/types/IStreamService'; // import { Notifications } from '../../../app/notifications/server'; import { IMessage } from '../../../definition/IMessage'; +import { IStreamer, IStreamerConstructor } from '../../modules/streamer/streamer.module'; export class StreamService extends ServiceClass implements IStreamService { protected name = 'streamer'; @@ -58,7 +59,7 @@ export class StreamService extends ServiceClass implements IStreamService { // return subscription != null; // }); this.streamRoomUsers.allowRead('none'); - this.streamUser.allowRead(function(eventName) { + this.streamUser.allowRead(async function(eventName) { const [userId] = eventName.split('/'); return (this.userId != null) && this.userId === userId; }); diff --git a/server/stream/messages/index.js b/server/stream/messages/index.js index 9c5e839cf1c7e..50d5ebaaf9e5a 100644 --- a/server/stream/messages/index.js +++ b/server/stream/messages/index.js @@ -5,11 +5,6 @@ import { Subscriptions } from '../../../app/models'; import { msgStream } from '../../../app/lib/server'; import './emitter'; - -export const MY_MESSAGE = '__my_messages__'; - -msgStream.allowWrite('none'); - msgStream.allowRead(function(eventName, args) { try { const room = Meteor.call('canAccessRoom', eventName, this.userId, args); @@ -29,6 +24,7 @@ msgStream.allowRead(function(eventName, args) { } }); +export const MY_MESSAGE = '__my_messages__'; msgStream.allowRead(MY_MESSAGE, 'all'); msgStream.allowEmit(MY_MESSAGE, function(eventName, msg) { diff --git a/server/stream/streamBroadcast.js b/server/stream/streamBroadcast.js index b615e1b5d6853..4048c0890e25d 100644 --- a/server/stream/streamBroadcast.js +++ b/server/stream/streamBroadcast.js @@ -12,6 +12,7 @@ import { settings } from '../../app/settings'; import { isDocker, getURL } from '../../app/utils'; import { Users } from '../../app/models/server'; import InstanceStatusModel from '../../app/models/server/models/InstanceStatus'; +import { StreamerCentral } from '../modules/streamer/streamer.module'; process.env.PORT = String(process.env.PORT).trim(); process.env.INSTANCE_IP = String(process.env.INSTANCE_IP).trim(); @@ -182,7 +183,7 @@ Meteor.methods({ return 'not-authorized'; } - const instance = Meteor.StreamerCentral.instances[streamName]; + const instance = StreamerCentral.instances[streamName]; if (!instance) { return 'stream-not-exists'; } @@ -190,7 +191,7 @@ Meteor.methods({ if (instance.serverOnly) { instance.__emit(eventName, ...args); } else { - Meteor.StreamerCentral.instances[streamName]._emit(eventName, args); + StreamerCentral.instances[streamName]._emit(eventName, args); } }, }); @@ -233,7 +234,7 @@ function startStreamCastBroadcast(value) { return 'not-authorized'; } - const instance = Meteor.StreamerCentral.instances[streamName]; + const instance = StreamerCentral.instances[streamName]; if (!instance) { return 'stream-not-exists'; } @@ -319,10 +320,10 @@ function startStreamBroadcast() { TroubleshootDisableInstanceBroadcast = value; if (value) { - return Meteor.StreamerCentral.removeListener('broadcast', onBroadcast); + return StreamerCentral.removeListener('broadcast', onBroadcast); } - Meteor.StreamerCentral.on('broadcast', onBroadcast); + StreamerCentral.on('broadcast', onBroadcast); }); } From 2a7f6ef5b25ce175d1b4b797d967d3a75839b20d Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Wed, 30 Sep 2020 08:38:58 -0300 Subject: [PATCH 090/198] Fix initial errors after the refactoring --- .meteor/packages | 1 + .meteor/versions | 1 + app/notifications/server/lib/Notifications.ts | 5 ++++- server/modules/streamer/streamer.module.ts | 7 +++++-- 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/.meteor/packages b/.meteor/packages index 65fdab1114b31..2a2c903bbd211 100644 --- a/.meteor/packages +++ b/.meteor/packages @@ -41,6 +41,7 @@ tracker@1.2.0 #rocketchat:google-natural-language rocketchat:livechat +rocketchat:streamer rocketchat:version konecty:multiple-instances-status diff --git a/.meteor/versions b/.meteor/versions index 8c01810d4d3d8..811fbd386ad1d 100644 --- a/.meteor/versions +++ b/.meteor/versions @@ -121,6 +121,7 @@ rocketchat:livechat@0.0.1 rocketchat:mongo-config@0.0.1 rocketchat:oauth2-server@2.1.0 rocketchat:postcss@1.0.0 +rocketchat:streamer@1.1.0 rocketchat:tap-i18n@1.9.1 rocketchat:version@1.0.0 routepolicy@1.1.0 diff --git a/app/notifications/server/lib/Notifications.ts b/app/notifications/server/lib/Notifications.ts index 68f601b0dd241..65860e5465462 100644 --- a/app/notifications/server/lib/Notifications.ts +++ b/app/notifications/server/lib/Notifications.ts @@ -1,4 +1,5 @@ import { Meteor } from 'meteor/meteor'; +import { Promise } from 'meteor/promise'; import { DDPCommon } from 'meteor/ddp-common'; import { WEB_RTC_EVENTS } from '../../../webrtc'; @@ -12,7 +13,9 @@ import { IUser } from '../../../../definition/IUser'; export class Stream extends Streamer { registerPublication(name: string, fn: (eventName: string, options: boolean | {useCollection?: boolean; args?: any}) => void): void { - Meteor.publish(name, fn); + Meteor.publish(name, function(eventName, options) { + return Promise.await(fn.call(this, eventName, options)); + }); } registerMethod(methods: Record any>): void { diff --git a/server/modules/streamer/streamer.module.ts b/server/modules/streamer/streamer.module.ts index 6158fa3f5186e..2cd8aa5df266c 100644 --- a/server/modules/streamer/streamer.module.ts +++ b/server/modules/streamer/streamer.module.ts @@ -257,7 +257,7 @@ export abstract class Streamer extends EventEmitter implements IStreamer { abstract registerPublication(name: string, fn: (eventName: string, options: boolean | {useCollection?: boolean; args?: any}) => void): void; iniPublication(): void { - const { _publish } = this; + const _publish = this._publish.bind(this); this.registerPublication(this.subscriptionName, function(this: Publication, eventName: string, options: boolean | {useCollection?: boolean; args?: any}) { return _publish(this, eventName, options); }); @@ -266,7 +266,10 @@ export abstract class Streamer extends EventEmitter implements IStreamer { abstract registerMethod(methods: Record any>): void; initMethod(): void { - const { isWriteAllowed, __emit, _emit, retransmit } = this; + const isWriteAllowed = this.isWriteAllowed.bind(this); + const __emit = this.__emit.bind(this); + const _emit = this._emit.bind(this); + const { retransmit } = this; const method: Record any> = { async [this.subscriptionName](this: Publication, eventName, ...args): Promise { From 70a8b1882d92fb436712e5e52fc68bb6898775de Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Wed, 30 Sep 2020 09:11:24 -0300 Subject: [PATCH 091/198] Resolve alerts from lgtm --- ee/server/services/Account/lib/utils.ts | 2 +- ee/server/services/DDPStreamer/Client.ts | 4 ---- ee/server/services/DDPStreamer/Streamer.ts | 2 +- server/modules/streamer/streamer.module.ts | 5 ++--- 4 files changed, 4 insertions(+), 9 deletions(-) diff --git a/ee/server/services/Account/lib/utils.ts b/ee/server/services/Account/lib/utils.ts index 56f202ddcf109..af8e4df00c23d 100644 --- a/ee/server/services/Account/lib/utils.ts +++ b/ee/server/services/Account/lib/utils.ts @@ -20,7 +20,7 @@ type Password = string | { export const getPassword = (password: Password): string => { if (typeof password === 'string') { - return crypto.createHash('sha256').update(password).digest('hex'); + return crypto.createHash('sha256').update(password).digest('hex'); // lgtm [js/insufficient-password-hash] } if (typeof password.digest === 'undefined') { throw new Error('invalid password'); diff --git a/ee/server/services/DDPStreamer/Client.ts b/ee/server/services/DDPStreamer/Client.ts index 4eabb257c52eb..1bb1177d74c62 100644 --- a/ee/server/services/DDPStreamer/Client.ts +++ b/ee/server/services/DDPStreamer/Client.ts @@ -13,8 +13,6 @@ export class Client extends EventEmitter { protected timeout: NodeJS.Timeout; - public kind = 'default'; - public readonly session = uuidv1(); public subscriptions = new Map(); @@ -149,8 +147,6 @@ export class Client extends EventEmitter { } export class MeteorClient extends Client { - public kind = 'meteor'; - // TODO implement meteor errors // a["{\"msg\":\"result\",\"id\":\"12\",\"error\":{\"isClientSafe\":true,\"error\":403,\"reason\":\"User has no password set\",\"message\":\"User has no password set [403]\",\"errorType\":\"Meteor.Error\"}}"] diff --git a/ee/server/services/DDPStreamer/Streamer.ts b/ee/server/services/DDPStreamer/Streamer.ts index 568005ba51abc..cc5a65c834595 100644 --- a/ee/server/services/DDPStreamer/Streamer.ts +++ b/ee/server/services/DDPStreamer/Streamer.ts @@ -47,7 +47,7 @@ export class Stream extends Streamer { return; } - if (this.isEmitAllowed(subscription, eventName, ...args)) { + if (await this.isEmitAllowed(subscription, eventName, ...args)) { await new Promise((resolve) => { // TODO: missing typing (subscription.client.ws as any)._sender.sendFrame( diff --git a/server/modules/streamer/streamer.module.ts b/server/modules/streamer/streamer.module.ts index 2cd8aa5df266c..4bc3cffc4998d 100644 --- a/server/modules/streamer/streamer.module.ts +++ b/server/modules/streamer/streamer.module.ts @@ -12,7 +12,6 @@ export const StreamerCentral = new StreamerCentralClass(); export type Client = { ws: any; - kind: any; userId: string; send: Function; } @@ -319,12 +318,12 @@ export abstract class Streamer extends EventEmitter implements IStreamer { } async sendToManySubscriptions(subscriptions: Set, origin: Connection | undefined, eventName: string, args: any[], msg: string): Promise { - subscriptions.forEach((subscription) => { + subscriptions.forEach(async (subscription) => { if (this.retransmitToSelf === false && origin && origin === subscription.subscription.connection) { return; } - if (this.isEmitAllowed(subscription.subscription, eventName, ...args)) { + if (await this.isEmitAllowed(subscription.subscription, eventName, ...args)) { subscription.subscription._session.socket?.send(msg); } }); From d0169f941290dabfa01cbad0762b6ad36976388b Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Wed, 30 Sep 2020 10:34:18 -0300 Subject: [PATCH 092/198] gracefully closes oplog connection on SIGTERM --- app/models/server/models/_oplogHandle.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/app/models/server/models/_oplogHandle.ts b/app/models/server/models/_oplogHandle.ts index 8775a40e903a2..f069536cd41bb 100644 --- a/app/models/server/models/_oplogHandle.ts +++ b/app/models/server/models/_oplogHandle.ts @@ -83,6 +83,10 @@ class CustomOplogHandle { return this; } + async stop(): Promise { + return this.client?.close(); + } + async startOplog(): Promise { const isMasterDoc = await this.db.admin().command({ ismaster: 1 }); if (!isMasterDoc || !isMasterDoc.setName) { @@ -197,3 +201,13 @@ export const getOplogHandle = async (): Promise { + if (!oplogHandle) { + process.exit(0); + } + + // gracefully closes oplog connection on SIGTERM + await (await oplogHandle).stop(); + process.exit(0); +}); From 07ff2ad46a568728c98ac919178d4aa882577cb5 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Wed, 30 Sep 2020 14:26:47 -0300 Subject: [PATCH 093/198] Fix unhandled promise exception --- app/notifications/server/lib/Notifications.ts | 4 ++-- server/modules/streamer/streamer.module.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/notifications/server/lib/Notifications.ts b/app/notifications/server/lib/Notifications.ts index 65860e5465462..c98d9d0a8f0d5 100644 --- a/app/notifications/server/lib/Notifications.ts +++ b/app/notifications/server/lib/Notifications.ts @@ -34,7 +34,7 @@ export class Stream extends Streamer { class RoomStreamer extends Stream { async _publish(publication: Publication, eventName: string, options: boolean | {useCollection?: boolean; args?: any} = false): Promise { - super._publish(publication, eventName, options); + await super._publish(publication, eventName, options); const uid = Meteor.userId(); if (!uid) { return; @@ -81,7 +81,7 @@ class MessageStream extends Stream { } async _publish(publication: Publication, eventName: string, options: boolean | {useCollection?: boolean; args?: any} = false): Promise { - super._publish(publication, eventName, options); + await super._publish(publication, eventName, options); const uid = Meteor.userId(); if (!uid) { return; diff --git a/server/modules/streamer/streamer.module.ts b/server/modules/streamer/streamer.module.ts index 4bc3cffc4998d..71f791d03b7d8 100644 --- a/server/modules/streamer/streamer.module.ts +++ b/server/modules/streamer/streamer.module.ts @@ -253,11 +253,11 @@ export abstract class Streamer extends EventEmitter implements IStreamer { publication.ready(); } - abstract registerPublication(name: string, fn: (eventName: string, options: boolean | {useCollection?: boolean; args?: any}) => void): void; + abstract registerPublication(name: string, fn: (eventName: string, options: boolean | {useCollection?: boolean; args?: any}) => Promise): void; iniPublication(): void { const _publish = this._publish.bind(this); - this.registerPublication(this.subscriptionName, function(this: Publication, eventName: string, options: boolean | {useCollection?: boolean; args?: any}) { + this.registerPublication(this.subscriptionName, async function(this: Publication, eventName: string, options: boolean | {useCollection?: boolean; args?: any}) { return _publish(this, eventName, options); }); } From 270c581c0af4691a75c16efb5758aa3c902c7eff Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Wed, 30 Sep 2020 17:19:39 -0300 Subject: [PATCH 094/198] Move all allow calls to the same file --- app/integrations/server/streamer.js | 8 -- app/livechat/server/lib/Livechat.js | 14 ---- .../server/lib/stream/queueManager.js | 5 -- app/logger/server/streamer.js | 5 -- app/models/server/models/LivechatRooms.js | 2 +- app/notifications/server/lib/Notifications.ts | 77 ++++++++++++++++++- ee/app/canned-responses/server/streamer.js | 7 -- server/publications/settings/emitter.js | 8 -- server/stream/messages/emitter.js | 4 +- server/stream/messages/index.js | 46 ----------- server/stream/rooms/index.js | 15 ---- 11 files changed, 76 insertions(+), 115 deletions(-) delete mode 100644 ee/app/canned-responses/server/streamer.js diff --git a/app/integrations/server/streamer.js b/app/integrations/server/streamer.js index 812898eba253a..cb72c9a0bebc4 100644 --- a/app/integrations/server/streamer.js +++ b/app/integrations/server/streamer.js @@ -1,14 +1,6 @@ -import { hasAtLeastOnePermission } from '../../authorization/server'; import { IntegrationHistory } from '../../models/server'; import notifications from '../../notifications/server/lib/Notifications'; -notifications.streamIntegrationHistory.allowRead(function() { - return this.userId && hasAtLeastOnePermission(this.userId, [ - 'manage-outgoing-integrations', - 'manage-own-outgoing-integrations', - ]); -}); - IntegrationHistory.on('change', ({ clientAction, id, data, diff }) => { switch (clientAction) { case 'updated': { diff --git a/app/livechat/server/lib/Livechat.js b/app/livechat/server/lib/Livechat.js index 83dc9cc1e44af..d5fd07399ebf5 100644 --- a/app/livechat/server/lib/Livechat.js +++ b/app/livechat/server/lib/Livechat.js @@ -1157,20 +1157,6 @@ export const Livechat = { }, }; -notifications.streamLivechatRoom.allowRead((roomId, extraData) => { - const room = LivechatRooms.findOneById(roomId); - - if (!room) { - console.warn(`Invalid eventName: "${ roomId }"`); - return false; - } - - if (room.t === 'l' && extraData && extraData.visitorToken && room.v.token === extraData.visitorToken) { - return true; - } - return false; -}); - settings.get('Livechat_history_monitor_type', (key, value) => { Livechat.historyMonitorType = value; }); diff --git a/app/livechat/server/lib/stream/queueManager.js b/app/livechat/server/lib/stream/queueManager.js index b1fc913668778..18a759312788e 100644 --- a/app/livechat/server/lib/stream/queueManager.js +++ b/app/livechat/server/lib/stream/queueManager.js @@ -1,12 +1,7 @@ -import { hasPermission } from '../../../../authorization/server'; import { LivechatInquiry } from '../../../../models/server'; import { RoutingManager } from '../RoutingManager'; import notifications from '../../../../notifications/server/lib/Notifications'; -notifications.streamLivechatQueueData.allowRead(function() { - return this.userId ? hasPermission(this.userId, 'view-l-room') : false; -}); - const emitQueueDataEvent = (event, data) => notifications.streamLivechatQueueData.emitWithoutBroadcast(event, data); const mountDataToEmit = (type, data) => ({ type, ...data }); diff --git a/app/logger/server/streamer.js b/app/logger/server/streamer.js index 269848072492b..ae0a930dbb99c 100644 --- a/app/logger/server/streamer.js +++ b/app/logger/server/streamer.js @@ -6,7 +6,6 @@ import { EJSON } from 'meteor/ejson'; import { Log } from 'meteor/logging'; import { settings } from '../../settings'; -import { hasPermission } from '../../authorization/server'; import notifications from '../../notifications/server/lib/Notifications'; export const processString = function(string, date) { @@ -53,10 +52,6 @@ export const StdOut = new class extends EventEmitter { } }(); -notifications.streamStdout.allowRead(function() { - return this.userId ? hasPermission(this.userId, 'view-logs') : false; -}); - Meteor.startup(() => { const handler = (string, item) => { notifications.streamStdout.emitWithoutBroadcast('stdout', { diff --git a/app/models/server/models/LivechatRooms.js b/app/models/server/models/LivechatRooms.js index 8a114d68c2610..dcaeff7d7721e 100644 --- a/app/models/server/models/LivechatRooms.js +++ b/app/models/server/models/LivechatRooms.js @@ -137,7 +137,7 @@ export class LivechatRooms extends Base { return this.find(query, options); } - findOneById(_id, fields) { + findOneById(_id, fields = {}) { const options = {}; if (fields) { diff --git a/app/notifications/server/lib/Notifications.ts b/app/notifications/server/lib/Notifications.ts index c98d9d0a8f0d5..43706ba35ba46 100644 --- a/app/notifications/server/lib/Notifications.ts +++ b/app/notifications/server/lib/Notifications.ts @@ -3,13 +3,14 @@ import { Promise } from 'meteor/promise'; import { DDPCommon } from 'meteor/ddp-common'; import { WEB_RTC_EVENTS } from '../../../webrtc'; -import { Subscriptions, Rooms } from '../../../models/server'; +import { Subscriptions, Rooms, LivechatRooms } from '../../../models/server'; import { settings } from '../../../settings/server'; import { NotificationsModule } from '../../../../server/modules/notifications/notifications.module'; -import { hasPermission } from '../../../authorization/server'; +import { hasPermission, hasAtLeastOnePermission } from '../../../authorization/server'; import { Streamer, Publication, DDPSubscription } from '../../../../server/modules/streamer/streamer.module'; import { ISubscription } from '../../../../definition/ISubscription'; import { IUser } from '../../../../definition/IUser'; +import { roomTypes } from '../../../utils/server'; export class Stream extends Streamer { registerPublication(name: string, fn: (eventName: string, options: boolean | {useCollection?: boolean; args?: any}) => void): void { @@ -118,6 +119,7 @@ class MessageStream extends Stream { } const notifications = new NotificationsModule(Stream, RoomStreamer, MessageStream); +export default notifications; notifications.streamRoomUsers.allowWrite(async function(eventName, ...args) { const [roomId, e] = eventName.split('/'); @@ -183,8 +185,58 @@ notifications.streamRoom.allowWrite(async function(eventName, username, _typing, return false; }); -export default notifications; +notifications.streamLivechatQueueData.allowRead(function() { + return this.userId ? hasPermission(this.userId, 'view-l-room') : false; +}); + +notifications.streamIntegrationHistory.allowRead(function() { + return this.userId && hasAtLeastOnePermission(this.userId, [ + 'manage-outgoing-integrations', + 'manage-own-outgoing-integrations', + ]); +}); + +notifications.streamLivechatRoom.allowRead(async function(roomId, extraData) { + const room = LivechatRooms.findOneById(roomId); + + if (!room) { + console.warn(`Invalid eventName: "${ roomId }"`); + return false; + } + + if (room.t === 'l' && extraData && extraData.visitorToken && room.v.token === extraData.visitorToken) { + return true; + } + return false; +}); + +notifications.streamStdout.allowRead(function() { + return this.userId ? hasPermission(this.userId, 'view-logs') : false; +}); + +notifications.streamCannedResponses.allowRead(function() { + return this.userId && settings.get('Canned_Responses_Enable') && hasPermission(this.userId, 'view-canned-responses'); +}); + +notifications.streamAll.allowRead('private-settings-changed', function() { + if (this.userId == null) { + return false; + } + return hasAtLeastOnePermission(this.userId, ['view-privileged-setting', 'edit-privileged-setting', 'manage-selected-settings']); +}); + +notifications.streamRoomData.allowRead(function(rid) { + try { + const room = Meteor.call('canAccessRoom', rid, this.userId); + if (!room) { + return false; + } + return roomTypes.getConfig(room.t).isEmitAllowed(); + } catch (error) { + return false; + } +}); notifications.streamRoomMessage.allowRead(async function(eventName, args) { try { @@ -225,3 +277,22 @@ notifications.streamRoomMessage.allowEmit('__my_messages__', async function(_eve return false; } }); + +notifications.streamRoomMessage.allowRead(async function(eventName, args) { + try { + const room = Meteor.call('canAccessRoom', eventName, this.userId, args); + + if (!room) { + return false; + } + + if (room.t === 'c' && !hasPermission(this.userId, 'preview-c-room') && !Subscriptions.findOneByRoomIdAndUserId(room._id, this.userId, { fields: { _id: 1 } })) { + return false; + } + + return true; + } catch (error) { + /* error*/ + return false; + } +}); diff --git a/ee/app/canned-responses/server/streamer.js b/ee/app/canned-responses/server/streamer.js deleted file mode 100644 index 21e88d032fe86..0000000000000 --- a/ee/app/canned-responses/server/streamer.js +++ /dev/null @@ -1,7 +0,0 @@ -import { hasPermission } from '../../../../app/authorization'; -import { settings } from '../../../../app/settings'; -import notifications from '../../../../app/notifications/server/lib/Notifications'; - -notifications.streamCannedResponses.allowRead(function() { - return this.userId && settings.get('Canned_Responses_Enable') && hasPermission(this.userId, 'view-canned-responses'); -}); diff --git a/server/publications/settings/emitter.js b/server/publications/settings/emitter.js index cd9a1765fb6bf..ebfa7d4cab3ff 100644 --- a/server/publications/settings/emitter.js +++ b/server/publications/settings/emitter.js @@ -1,6 +1,5 @@ import { Settings } from '../../../app/models/server'; import { Notifications } from '../../../app/notifications/server'; -import { hasAtLeastOnePermission } from '../../../app/authorization/server'; import { SettingsEvents } from '../../../app/settings/server/functions/settings'; import { StreamService } from '../../sdk'; @@ -45,10 +44,3 @@ Settings.on('change', ({ clientAction, id, data, diff }) => { } } }); - -Notifications.streamAll.allowRead('private-settings-changed', function() { - if (this.userId == null) { - return false; - } - return hasAtLeastOnePermission(this.userId, ['view-privileged-setting', 'edit-privileged-setting', 'manage-selected-settings']); -}); diff --git a/server/stream/messages/emitter.js b/server/stream/messages/emitter.js index d86dea2639f5d..d90038eb388cf 100644 --- a/server/stream/messages/emitter.js +++ b/server/stream/messages/emitter.js @@ -4,8 +4,6 @@ import { settings } from '../../../app/settings'; import { Users, Messages } from '../../../app/models'; import { msgStream } from '../../../app/lib/server'; -import { MY_MESSAGE } from '.'; - Meteor.startup(function() { function publishMessage(type, record) { if (record._hidden !== true && (record.imported == null)) { @@ -22,7 +20,7 @@ Meteor.startup(function() { mention.name = user && user.name; }); } - msgStream.mymessage(MY_MESSAGE, record); + msgStream.mymessage('__my_messages__', record); msgStream.emitWithoutBroadcast(record.rid, record); } } diff --git a/server/stream/messages/index.js b/server/stream/messages/index.js index 50d5ebaaf9e5a..625877ba64089 100644 --- a/server/stream/messages/index.js +++ b/server/stream/messages/index.js @@ -1,47 +1 @@ -import { Meteor } from 'meteor/meteor'; - -import { hasPermission } from '../../../app/authorization'; -import { Subscriptions } from '../../../app/models'; -import { msgStream } from '../../../app/lib/server'; import './emitter'; - -msgStream.allowRead(function(eventName, args) { - try { - const room = Meteor.call('canAccessRoom', eventName, this.userId, args); - - if (!room) { - return false; - } - - if (room.t === 'c' && !hasPermission(this.userId, 'preview-c-room') && !Subscriptions.findOneByRoomIdAndUserId(room._id, this.userId, { fields: { _id: 1 } })) { - return false; - } - - return true; - } catch (error) { - /* error*/ - return false; - } -}); - -export const MY_MESSAGE = '__my_messages__'; -msgStream.allowRead(MY_MESSAGE, 'all'); - -msgStream.allowEmit(MY_MESSAGE, function(eventName, msg) { - try { - const room = Meteor.call('canAccessRoom', msg.rid, this.userId); - - if (!room) { - return false; - } - - return { - roomParticipant: Subscriptions.findOneByRoomIdAndUserId(room._id, this.userId, { fields: { _id: 1 } }) != null, - roomType: room.t, - roomName: room.name, - }; - } catch (error) { - /* error*/ - return false; - } -}); diff --git a/server/stream/rooms/index.js b/server/stream/rooms/index.js index 7de1b91db3de0..625184d4769e2 100644 --- a/server/stream/rooms/index.js +++ b/server/stream/rooms/index.js @@ -1,21 +1,6 @@ -import { Meteor } from 'meteor/meteor'; - import { roomTypes } from '../../../app/utils'; import notifications from '../../../app/notifications/server/lib/Notifications'; -notifications.streamRoomData.allowRead(function(rid) { - try { - const room = Meteor.call('canAccessRoom', rid, this.userId); - if (!room) { - return false; - } - - return roomTypes.getConfig(room.t).isEmitAllowed(); - } catch (error) { - return false; - } -}); - export function emitRoomDataEvent(id, data) { if (!data || !data.t) { return; From 68025fe0406c64f7b41c25ad1afaa887174f8f5a Mon Sep 17 00:00:00 2001 From: Alan Sikora Date: Wed, 30 Sep 2020 18:00:08 -0300 Subject: [PATCH 095/198] emoji.deleteCustom --- app/emoji-custom/server/methods/deleteEmojiCustom.js | 4 ++-- definition/IEmoji.ts | 3 +++ server/sdk/lib/Events.ts | 2 ++ server/sdk/types/IStreamService.ts | 1 - server/services/listeners/notification.ts | 6 ++++++ server/services/stream/service.ts | 6 ------ 6 files changed, 13 insertions(+), 9 deletions(-) create mode 100644 definition/IEmoji.ts diff --git a/app/emoji-custom/server/methods/deleteEmojiCustom.js b/app/emoji-custom/server/methods/deleteEmojiCustom.js index 6be3aef42c6b0..77b54236c9f01 100644 --- a/app/emoji-custom/server/methods/deleteEmojiCustom.js +++ b/app/emoji-custom/server/methods/deleteEmojiCustom.js @@ -1,6 +1,6 @@ import { Meteor } from 'meteor/meteor'; -import { StreamService } from '../../../../server/sdk'; +import { api } from '../../../../server/sdk/api'; import { hasPermission } from '../../../authorization'; import { EmojiCustom } from '../../../models'; import { RocketChatFileEmojiCustomInstance } from '../startup/emoji-custom'; @@ -18,7 +18,7 @@ Meteor.methods({ RocketChatFileEmojiCustomInstance.deleteFile(encodeURIComponent(`${ emoji.name }.${ emoji.extension }`)); EmojiCustom.removeById(emojiID); - StreamService.sendDeleteCustomEmoji(emoji); + api.broadcast('emoji.deleteCustom', { emoji }); return true; }, diff --git a/definition/IEmoji.ts b/definition/IEmoji.ts new file mode 100644 index 0000000000000..af9cb8d8fc701 --- /dev/null +++ b/definition/IEmoji.ts @@ -0,0 +1,3 @@ +export interface IEmoji { + [x: string]: any; +} diff --git a/server/sdk/lib/Events.ts b/server/sdk/lib/Events.ts index 643b93a0e7e57..b8f431999484a 100644 --- a/server/sdk/lib/Events.ts +++ b/server/sdk/lib/Events.ts @@ -8,6 +8,7 @@ import { ISetting } from '../../../definition/ISetting'; import { ISubscription } from '../../../definition/ISubscription'; import { IUser } from '../../../definition/IUser'; import { AutoUpdateRecord } from '../types/IMeteor'; +import { IEmoji } from '../../../definition/IEmoji'; export type BufferList = ReturnType; @@ -26,4 +27,5 @@ export type EventSignatures = { 'meteor.autoUpdateClientVersionChanged'(data: {record: AutoUpdateRecord}): void; 'meteor.loginServiceConfiguration'(data: {action: string; record: any}): void; 'stream.ephemeralMessage'(uid: string, rid: string, message: Partial): void; + 'emoji.deleteCustom'(emoji: IEmoji): void; } diff --git a/server/sdk/types/IStreamService.ts b/server/sdk/types/IStreamService.ts index 77ce44f62ff22..68d4fc7c0edc4 100644 --- a/server/sdk/types/IStreamService.ts +++ b/server/sdk/types/IStreamService.ts @@ -9,7 +9,6 @@ export interface IStreamService extends IServiceClass { sendUserAvatarUpdate({ username, etag }: { username: string; etag?: string }): void; sendRoomAvatarUpdate({ rid, etag }: { rid: string; etag?: string }): void; sendRoleUpdate(update: Record): void; - sendDeleteCustomEmoji(emojiData: Record): void; sendUpdateCustomEmoji(emojiData: Record): void; sendUserDeleted(uid: string): void; sendUserNameChanged(userData: Record): void; diff --git a/server/services/listeners/notification.ts b/server/services/listeners/notification.ts index 6160c5c74ac60..e6e5d374866ad 100644 --- a/server/services/listeners/notification.ts +++ b/server/services/listeners/notification.ts @@ -24,5 +24,11 @@ export class NotificationService extends ServiceClass { notifications.notifyLogged('user-status', [_id, username, STATUS_MAP[status], statusText]); }); + + this.onEvent('emoji.deleteCustom', ({ emoji }) => { + notifications.notifyLogged('deleteEmojiCustom', { + emojiData: emoji, + }); + }); } } diff --git a/server/services/stream/service.ts b/server/services/stream/service.ts index 0116242418794..a21e62655d0c6 100644 --- a/server/services/stream/service.ts +++ b/server/services/stream/service.ts @@ -65,12 +65,6 @@ export class StreamService extends ServiceClass implements IStreamService { }); } - sendDeleteCustomEmoji(emojiData: Record): void { - this.streamLogged.emit('deleteEmojiCustom', { - emojiData, - }); - } - sendUpdateCustomEmoji(emojiData: Record): void { this.streamLogged.emit('updateEmojiCustom', { emojiData, From 712e1c18193d007638c91b195d022afed85b0a7b Mon Sep 17 00:00:00 2001 From: Alan Sikora Date: Wed, 30 Sep 2020 18:19:55 -0300 Subject: [PATCH 096/198] emoji.updateCustom --- app/emoji-custom/server/methods/insertOrUpdateEmoji.js | 5 +++-- app/emoji-custom/server/methods/uploadEmojiCustom.js | 4 ++-- server/sdk/lib/Events.ts | 1 + server/sdk/types/IStreamService.ts | 1 - server/services/listeners/notification.ts | 6 ++++++ server/services/stream/service.ts | 6 ------ 6 files changed, 12 insertions(+), 11 deletions(-) diff --git a/app/emoji-custom/server/methods/insertOrUpdateEmoji.js b/app/emoji-custom/server/methods/insertOrUpdateEmoji.js index fb49e0aa4824c..db31d85dd9a57 100644 --- a/app/emoji-custom/server/methods/insertOrUpdateEmoji.js +++ b/app/emoji-custom/server/methods/insertOrUpdateEmoji.js @@ -7,6 +7,7 @@ import { hasPermission } from '../../../authorization'; import { EmojiCustom } from '../../../models'; import { RocketChatFileEmojiCustomInstance } from '../startup/emoji-custom'; import { StreamService } from '../../../../server/sdk'; +import { api } from '/server/sdk/api'; Meteor.methods({ insertOrUpdateEmoji(emojiData) { @@ -73,7 +74,7 @@ Meteor.methods({ const _id = EmojiCustom.create(createEmoji); - StreamService.sendUpdateCustomEmoji(createEmoji); + api.broadcast('emoji.updateCustom', { emoji: createEmoji }) return _id; } @@ -107,7 +108,7 @@ Meteor.methods({ EmojiCustom.setAliases(emojiData._id, []); } - StreamService.sendUpdateCustomEmoji(emojiData); + api.broadcast('emoji.updateCustom', { emoji: emojiData }) return true; }, diff --git a/app/emoji-custom/server/methods/uploadEmojiCustom.js b/app/emoji-custom/server/methods/uploadEmojiCustom.js index f31995dde6530..c66202bb00b3f 100644 --- a/app/emoji-custom/server/methods/uploadEmojiCustom.js +++ b/app/emoji-custom/server/methods/uploadEmojiCustom.js @@ -4,7 +4,7 @@ import limax from 'limax'; import { hasPermission } from '../../../authorization'; import { RocketChatFile } from '../../../file'; import { RocketChatFileEmojiCustomInstance } from '../startup/emoji-custom'; -import { StreamService } from '../../../../server/sdk'; +import { api } from '../../../../server/sdk/api'; Meteor.methods({ uploadEmojiCustom(binaryContent, contentType, emojiData) { @@ -21,7 +21,7 @@ Meteor.methods({ RocketChatFileEmojiCustomInstance.deleteFile(encodeURIComponent(`${ emojiData.name }.${ emojiData.extension }`)); const ws = RocketChatFileEmojiCustomInstance.createWriteStream(encodeURIComponent(`${ emojiData.name }.${ emojiData.extension }`), contentType); ws.on('end', Meteor.bindEnvironment(() => - Meteor.setTimeout(() => StreamService.sendUpdateCustomEmoji(emojiData), 500), + Meteor.setTimeout(() => api.broadcast('emoji.updateCustom', { emoji: emojiData }), 500), )); rs.pipe(ws); diff --git a/server/sdk/lib/Events.ts b/server/sdk/lib/Events.ts index b8f431999484a..5ce1dd8f24af2 100644 --- a/server/sdk/lib/Events.ts +++ b/server/sdk/lib/Events.ts @@ -28,4 +28,5 @@ export type EventSignatures = { 'meteor.loginServiceConfiguration'(data: {action: string; record: any}): void; 'stream.ephemeralMessage'(uid: string, rid: string, message: Partial): void; 'emoji.deleteCustom'(emoji: IEmoji): void; + 'emoji.updateCustom'(emoji: IEmoji): void; } diff --git a/server/sdk/types/IStreamService.ts b/server/sdk/types/IStreamService.ts index 68d4fc7c0edc4..e54ef13ca2995 100644 --- a/server/sdk/types/IStreamService.ts +++ b/server/sdk/types/IStreamService.ts @@ -9,7 +9,6 @@ export interface IStreamService extends IServiceClass { sendUserAvatarUpdate({ username, etag }: { username: string; etag?: string }): void; sendRoomAvatarUpdate({ rid, etag }: { rid: string; etag?: string }): void; sendRoleUpdate(update: Record): void; - sendUpdateCustomEmoji(emojiData: Record): void; sendUserDeleted(uid: string): void; sendUserNameChanged(userData: Record): void; sendDeleteCustomUserStatus(userStatusData: Record): void; diff --git a/server/services/listeners/notification.ts b/server/services/listeners/notification.ts index e6e5d374866ad..a3e40a74bae8a 100644 --- a/server/services/listeners/notification.ts +++ b/server/services/listeners/notification.ts @@ -30,5 +30,11 @@ export class NotificationService extends ServiceClass { emojiData: emoji, }); }); + + this.onEvent('emoji.updateCustom', ({ emoji }) => { + notifications.notifyLogged('updateEmojiCustom', { + emojiData: emoji, + }); + }); } } diff --git a/server/services/stream/service.ts b/server/services/stream/service.ts index a21e62655d0c6..cf930ace9b3b5 100644 --- a/server/services/stream/service.ts +++ b/server/services/stream/service.ts @@ -65,12 +65,6 @@ export class StreamService extends ServiceClass implements IStreamService { }); } - sendUpdateCustomEmoji(emojiData: Record): void { - this.streamLogged.emit('updateEmojiCustom', { - emojiData, - }); - } - sendUserDeleted(uid: string): void { this.streamLogged.emit('Users:Deleted', { userId: uid, From b4e2879013b6c211a366a2876c6e0fbff93477ba Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Wed, 30 Sep 2020 18:49:06 -0300 Subject: [PATCH 097/198] Fix Streamer sendFrame --- ee/server/services/DDPStreamer/Streamer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ee/server/services/DDPStreamer/Streamer.ts b/ee/server/services/DDPStreamer/Streamer.ts index cc5a65c834595..8f0dc998e7e47 100644 --- a/ee/server/services/DDPStreamer/Streamer.ts +++ b/ee/server/services/DDPStreamer/Streamer.ts @@ -34,13 +34,13 @@ export class Stream extends Streamer { async sendToManySubscriptions(subscriptions: Set, origin: Connection | undefined, eventName: string, args: any[], msg: string): Promise { // TODO: missing typing - const data = Buffer.concat((WebSocket as any).Sender.frame(Buffer.from(`a${ JSON.stringify([msg]) }`), { + const data = [Buffer.concat((WebSocket as any).Sender.frame(Buffer.from(`a${ JSON.stringify([msg]) }`), { fin: true, // sending a single fragment message rsv1: false, // don"t set rsv1 bit (no compression) opcode: 1, // opcode for a text frame mask: false, // set false for client-side readOnly: false, // the data can be modified as needed - })); + }))]; for await (const { subscription } of subscriptions) { if (this.retransmitToSelf === false && origin && origin === subscription.connection) { From a073e1db0a07d3c10ed8d8ec16e616a19b85487a Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Wed, 30 Sep 2020 19:06:44 -0300 Subject: [PATCH 098/198] roomMessages streamer --- app/notifications/server/lib/Notifications.ts | 66 ++----------- .../services/DDPStreamer/streams/index.ts | 33 ++++--- .../notifications/notifications.module.ts | 97 +++++++++++++++---- 3 files changed, 105 insertions(+), 91 deletions(-) diff --git a/app/notifications/server/lib/Notifications.ts b/app/notifications/server/lib/Notifications.ts index 43706ba35ba46..8c62461904855 100644 --- a/app/notifications/server/lib/Notifications.ts +++ b/app/notifications/server/lib/Notifications.ts @@ -4,6 +4,7 @@ import { DDPCommon } from 'meteor/ddp-common'; import { WEB_RTC_EVENTS } from '../../../webrtc'; import { Subscriptions, Rooms, LivechatRooms } from '../../../models/server'; +import { Subscriptions as SubscriptionsRaw, Rooms as RoomsRaw } from '../../../models/server/raw'; import { settings } from '../../../settings/server'; import { NotificationsModule } from '../../../../server/modules/notifications/notifications.module'; import { hasPermission, hasAtLeastOnePermission } from '../../../authorization/server'; @@ -119,6 +120,12 @@ class MessageStream extends Stream { } const notifications = new NotificationsModule(Stream, RoomStreamer, MessageStream); + +notifications.configure({ + Rooms: RoomsRaw, + Subscriptions: SubscriptionsRaw, +}); + export default notifications; notifications.streamRoomUsers.allowWrite(async function(eventName, ...args) { @@ -237,62 +244,3 @@ notifications.streamRoomData.allowRead(function(rid) { return false; } }); - -notifications.streamRoomMessage.allowRead(async function(eventName, args) { - try { - const room = Meteor.call('canAccessRoom', eventName, this.userId, args); - - if (!room) { - return false; - } - - if (room.t === 'c' && !hasPermission(this.userId, 'preview-c-room') && !Subscriptions.findOneByRoomIdAndUserId(room._id, this.userId, { fields: { _id: 1 } })) { - return false; - } - - return true; - } catch (error) { - /* error*/ - return false; - } -}); - -notifications.streamRoomMessage.allowRead('__my_messages__', 'all'); - -notifications.streamRoomMessage.allowEmit('__my_messages__', async function(_eventName, msg) { - try { - const room = Meteor.call('canAccessRoom', msg.rid, this.userId); - - if (!room) { - return false; - } - - return { - roomParticipant: Subscriptions.findOneByRoomIdAndUserId(room._id, this.userId, { fields: { _id: 1 } }) != null, - roomType: room.t, - roomName: room.name, - }; - } catch (error) { - /* error*/ - return false; - } -}); - -notifications.streamRoomMessage.allowRead(async function(eventName, args) { - try { - const room = Meteor.call('canAccessRoom', eventName, this.userId, args); - - if (!room) { - return false; - } - - if (room.t === 'c' && !hasPermission(this.userId, 'preview-c-room') && !Subscriptions.findOneByRoomIdAndUserId(room._id, this.userId, { fields: { _id: 1 } })) { - return false; - } - - return true; - } catch (error) { - /* error*/ - return false; - } -}); diff --git a/ee/server/services/DDPStreamer/streams/index.ts b/ee/server/services/DDPStreamer/streams/index.ts index e0c1f66d1cce9..75d7dd77bdaf1 100644 --- a/ee/server/services/DDPStreamer/streams/index.ts +++ b/ee/server/services/DDPStreamer/streams/index.ts @@ -1,9 +1,12 @@ import { Stream } from '../Streamer'; import { NotificationsModule } from '../../../../../server/modules/notifications/notifications.module'; import { ISubscription } from '../../../../../definition/ISubscription'; -import { getCollection, Collections } from '../../mongo'; -import { Authorization } from '../../../../../server/sdk'; +import { IRoom } from '../../../../../definition/IRoom'; +import { getCollection, Collections, getConnection } from '../../mongo'; +// import { Authorization } from '../../../../../server/sdk'; import { Publication } from '../../../../../server/modules/streamer/streamer.module'; +import { RoomsRaw } from '../../../../../app/models/server/raw/Rooms'; +import { SubscriptionsRaw } from '../../../../../app/models/server/raw/Subscriptions'; export class RoomStreamer extends Stream { async _publish(publication: Publication, eventName = '', options: boolean | {useCollection?: boolean; args?: any} = false): Promise { @@ -101,22 +104,30 @@ class MessageStream extends Stream { const notifications = new NotificationsModule(Stream, RoomStreamer, MessageStream); +getConnection() + .then((db) => { + notifications.configure({ + Rooms: new RoomsRaw(db.collection(Collections.Rooms)), + Subscriptions: new SubscriptionsRaw(db.collection(Collections.Subscriptions)), + }); + }); + export default notifications; // TODO: Implementation not complete -notifications.streamRoomMessage.allowRead(async function(rid) { - return !!this.userId && Authorization.canAccessRoom({ _id: rid }, { _id: this.userId }); -}); +// notifications.streamRoomMessage.allowRead(async function(rid) { +// return !!this.userId && Authorization.canAccessRoom({ _id: rid }, { _id: this.userId }); +// }); // export const streamRoomData = new Stream(STREAM_NAMES.ROOM_DATA); -notifications.streamRoomData.allowRead(async function(rid) { - return !!this.userId && Authorization.canAccessRoom({ _id: rid }, { _id: this.userId }); -}); +// notifications.streamRoomData.allowRead(async function(rid) { +// return !!this.userId && Authorization.canAccessRoom({ _id: rid }, { _id: this.userId }); +// }); -notifications.streamLivechatQueueData.allowRead(async function() { - return !!this.userId && Authorization.hasPermission(this.userId, 'view-l-room'); -}); +// notifications.streamLivechatQueueData.allowRead(async function() { +// return !!this.userId && Authorization.hasPermission(this.userId, 'view-l-room'); +// }); // TODO: Implement permission // this.streamCannedResponses.allowRead(function() { // Implemented outside diff --git a/server/modules/notifications/notifications.module.ts b/server/modules/notifications/notifications.module.ts index a185fe94eaea3..3f138fc0de096 100644 --- a/server/modules/notifications/notifications.module.ts +++ b/server/modules/notifications/notifications.module.ts @@ -1,4 +1,12 @@ import { IStreamer, IStreamerConstructor } from '../streamer/streamer.module'; +import { Authorization } from '../../sdk'; +import { RoomsRaw } from '../../../app/models/server/raw/Rooms'; +import { SubscriptionsRaw } from '../../../app/models/server/raw/Subscriptions'; + +interface IModelsParam { + Rooms: RoomsRaw; + Subscriptions: SubscriptionsRaw; +} export class NotificationsModule { private debug = false @@ -40,78 +48,125 @@ export class NotificationsModule { private RoomStreamer: IStreamerConstructor, private MessageStreamer: IStreamerConstructor, ) { - this.notifyUser = this.notifyUser.bind(this); + // this.notifyUser = this.notifyUser.bind(this); this.streamAll = new this.Streamer('notify-all'); + this.streamLogged = new this.Streamer('notify-logged'); + this.streamRoom = new this.Streamer('notify-room'); + this.streamRoomUsers = new this.Streamer('notify-room-users'); + this.streamRoomMessage = new this.MessageStreamer('room-messages'); + this.streamUser = new this.RoomStreamer('notify-user'); + this.streamImporters = new this.Streamer('importers', { retransmit: false }); + this.streamRoles = new this.Streamer('roles'); + this.streamApps = new this.Streamer('apps', { retransmit: false }); + this.streamAppsEngine = new this.Streamer('apps-engine', { retransmit: false }); + this.streamCannedResponses = new this.Streamer('canned-responses'); + this.streamIntegrationHistory = new this.Streamer('integrationHistory'); + this.streamLivechatRoom = new this.Streamer('livechat-room'); + this.streamLivechatQueueData = new this.Streamer('livechat-inquiry-queue-observer'); + this.streamStdout = new this.Streamer('stdout'); + this.streamRoomData = new this.Streamer('room-data'); + } + + async configure({ Rooms, Subscriptions }: IModelsParam): Promise { + this.streamRoomMessage.allowWrite('none'); + this.streamRoomMessage.allowRead(async function(eventName /* , args*/) { + const room = await Rooms.findOneById(eventName); + if (!room) { + return false; + } + + const canAccess = await Authorization.canAccessRoom(room, { _id: this.userId }); + if (!canAccess) { + // verify if can preview messages from public channels + if (room.t === 'c') { + return Authorization.hasPermission(this.userId, 'preview-c-room'); + } + return false; + } + + return true; + }); + + // TODO need to test + this.streamRoomMessage.allowRead('__my_messages__', 'all'); + this.streamRoomMessage.allowEmit('__my_messages__', async function(_eventName, { rid }) { + try { + const room = await Rooms.findOneById(rid); + if (!room) { + return false; + } + + const canAccess = await Authorization.canAccessRoom(room, { _id: this.userId }); + if (!canAccess) { + return false; + } + + const roomParticipant = await Subscriptions.countByRoomIdAndUserId(room._id, this.userId); + + return { + roomParticipant: roomParticipant > 0, + roomType: room.t, + roomName: room.name, + }; + } catch (error) { + /* error*/ + return false; + } + }); + this.streamAll.allowWrite('none'); this.streamAll.allowRead('all'); - this.streamLogged = new this.Streamer('notify-logged'); this.streamLogged.allowWrite('none'); this.streamLogged.allowRead('logged'); - this.streamRoom = new this.Streamer('notify-room'); this.streamRoom.allowWrite('none'); // this.streamRoom.allowRead(function(eventName, extraData) { // Implemented outside // notifications.streamRoom.allowWrite(function(eventName, username, typing, extraData) { // Implemented outside - this.streamRoomUsers = new this.Streamer('notify-room-users'); this.streamRoomUsers.allowRead('none'); // this.streamRoomUsers.allowWrite(function(eventName, ...args) { // Implemented outside - this.streamRoomMessage = new this.MessageStreamer('room-messages'); - this.streamRoomMessage.allowWrite('none'); - - this.streamUser = new this.RoomStreamer('notify-user'); this.streamUser.allowWrite('logged'); this.streamUser.allowRead(async function(eventName) { const [userId] = eventName.split('/'); return (this.userId != null) && this.userId === userId; }); - this.streamImporters = new this.Streamer('importers', { retransmit: false }); this.streamImporters.allowRead('all'); this.streamImporters.allowEmit('all'); this.streamImporters.allowWrite('none'); - this.streamRoles = new this.Streamer('roles'); - this.streamRoles.allowWrite('none'); - this.streamRoles.allowRead('logged'); - - this.streamApps = new this.Streamer('apps', { retransmit: false }); this.streamApps.serverOnly = true; this.streamApps.allowRead('all'); this.streamApps.allowEmit('all'); this.streamApps.allowWrite('none'); - this.streamAppsEngine = new this.Streamer('apps-engine', { retransmit: false }); this.streamAppsEngine.serverOnly = true; this.streamAppsEngine.allowRead('none'); this.streamAppsEngine.allowEmit('all'); this.streamAppsEngine.allowWrite('none'); - this.streamCannedResponses = new this.Streamer('canned-responses'); this.streamCannedResponses.allowWrite('none'); // this.streamCannedResponses.allowRead(function() { // Implemented outside - this.streamIntegrationHistory = new this.Streamer('integrationHistory'); this.streamIntegrationHistory.allowWrite('none'); // this.streamIntegrationHistory.allowRead(function() { // Implemented outside - this.streamLivechatRoom = new this.Streamer('livechat-room'); // this.streamLivechatRoom.allowRead((roomId, extraData) => { // Implemented outside - this.streamLivechatQueueData = new this.Streamer('livechat-inquiry-queue-observer'); this.streamLivechatQueueData.allowWrite('none'); // this.streamLivechatQueueData.allowRead(function() { // Implemented outside - this.streamStdout = new this.Streamer('stdout'); this.streamStdout.allowWrite('none'); // this.streamStdout.allowRead(function() { // Implemented outside - this.streamRoomData = new this.Streamer('room-data'); this.streamRoomData.allowWrite('none'); // this.streamRoomData.allowRead(function(rid) { // Implemented outside + + this.streamRoles.allowWrite('none'); + this.streamRoles.allowRead('logged'); } notifyAll(eventName: string, ...args: any[]): void { From 3a9401e803f90a270f9a54b700681eb0379d61fe Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Thu, 1 Oct 2020 10:17:37 -0300 Subject: [PATCH 099/198] Fix lint --- .eslintignore | 1 + app/emoji-custom/server/methods/insertOrUpdateEmoji.js | 7 +++---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.eslintignore b/.eslintignore index ebdb084375a0e..2a39e35ae2b25 100644 --- a/.eslintignore +++ b/.eslintignore @@ -18,3 +18,4 @@ public/pdf.worker.min.js public/workers/**/* imports/client/ !/.storybook/ +ee/server/services/dist/** diff --git a/app/emoji-custom/server/methods/insertOrUpdateEmoji.js b/app/emoji-custom/server/methods/insertOrUpdateEmoji.js index db31d85dd9a57..4fc2c04a73147 100644 --- a/app/emoji-custom/server/methods/insertOrUpdateEmoji.js +++ b/app/emoji-custom/server/methods/insertOrUpdateEmoji.js @@ -6,8 +6,7 @@ import limax from 'limax'; import { hasPermission } from '../../../authorization'; import { EmojiCustom } from '../../../models'; import { RocketChatFileEmojiCustomInstance } from '../startup/emoji-custom'; -import { StreamService } from '../../../../server/sdk'; -import { api } from '/server/sdk/api'; +import { api } from '../../../../server/sdk/api'; Meteor.methods({ insertOrUpdateEmoji(emojiData) { @@ -74,7 +73,7 @@ Meteor.methods({ const _id = EmojiCustom.create(createEmoji); - api.broadcast('emoji.updateCustom', { emoji: createEmoji }) + api.broadcast('emoji.updateCustom', { emoji: createEmoji }); return _id; } @@ -108,7 +107,7 @@ Meteor.methods({ EmojiCustom.setAliases(emojiData._id, []); } - api.broadcast('emoji.updateCustom', { emoji: emojiData }) + api.broadcast('emoji.updateCustom', { emoji: emojiData }); return true; }, From afe82a15ffbffcea41440afc8a30be9c70457814 Mon Sep 17 00:00:00 2001 From: Alan Sikora Date: Thu, 1 Oct 2020 13:33:22 -0300 Subject: [PATCH 100/198] user.deleted && user.deleteCustomStatus --- .../server/methods/deleteEmojiCustom.js | 2 +- .../server/methods/insertOrUpdateEmoji.js | 4 +- .../server/methods/uploadEmojiCustom.js | 2 +- app/lib/server/functions/deleteUser.js | 4 +- app/lib/server/functions/setRealName.js | 4 +- app/lib/server/functions/setUsername.js | 4 +- .../server/methods/deleteCustomUserStatus.js | 4 +- definition/IUserStatus.ts | 3 ++ server/sdk/lib/Events.ts | 26 +++++++----- server/sdk/types/IStreamService.ts | 3 -- server/services/listeners/notification.ts | 40 +++++++++++++------ server/services/stream/service.ts | 16 -------- 12 files changed, 58 insertions(+), 54 deletions(-) create mode 100644 definition/IUserStatus.ts diff --git a/app/emoji-custom/server/methods/deleteEmojiCustom.js b/app/emoji-custom/server/methods/deleteEmojiCustom.js index 77b54236c9f01..7393f245b459e 100644 --- a/app/emoji-custom/server/methods/deleteEmojiCustom.js +++ b/app/emoji-custom/server/methods/deleteEmojiCustom.js @@ -18,7 +18,7 @@ Meteor.methods({ RocketChatFileEmojiCustomInstance.deleteFile(encodeURIComponent(`${ emoji.name }.${ emoji.extension }`)); EmojiCustom.removeById(emojiID); - api.broadcast('emoji.deleteCustom', { emoji }); + api.broadcast('emoji.deleteCustom', emoji); return true; }, diff --git a/app/emoji-custom/server/methods/insertOrUpdateEmoji.js b/app/emoji-custom/server/methods/insertOrUpdateEmoji.js index 4fc2c04a73147..72a0a6a0694fa 100644 --- a/app/emoji-custom/server/methods/insertOrUpdateEmoji.js +++ b/app/emoji-custom/server/methods/insertOrUpdateEmoji.js @@ -73,7 +73,7 @@ Meteor.methods({ const _id = EmojiCustom.create(createEmoji); - api.broadcast('emoji.updateCustom', { emoji: createEmoji }); + api.broadcast('emoji.updateCustom', createEmoji); return _id; } @@ -107,7 +107,7 @@ Meteor.methods({ EmojiCustom.setAliases(emojiData._id, []); } - api.broadcast('emoji.updateCustom', { emoji: emojiData }); + api.broadcast('emoji.updateCustom', emojiData); return true; }, diff --git a/app/emoji-custom/server/methods/uploadEmojiCustom.js b/app/emoji-custom/server/methods/uploadEmojiCustom.js index c66202bb00b3f..1f3d4cf4d1c43 100644 --- a/app/emoji-custom/server/methods/uploadEmojiCustom.js +++ b/app/emoji-custom/server/methods/uploadEmojiCustom.js @@ -21,7 +21,7 @@ Meteor.methods({ RocketChatFileEmojiCustomInstance.deleteFile(encodeURIComponent(`${ emojiData.name }.${ emojiData.extension }`)); const ws = RocketChatFileEmojiCustomInstance.createWriteStream(encodeURIComponent(`${ emojiData.name }.${ emojiData.extension }`), contentType); ws.on('end', Meteor.bindEnvironment(() => - Meteor.setTimeout(() => api.broadcast('emoji.updateCustom', { emoji: emojiData }), 500), + Meteor.setTimeout(() => api.broadcast('emoji.updateCustom', emojiData), 500), )); rs.pipe(ws); diff --git a/app/lib/server/functions/deleteUser.js b/app/lib/server/functions/deleteUser.js index 97b052a14997d..32146767639f6 100644 --- a/app/lib/server/functions/deleteUser.js +++ b/app/lib/server/functions/deleteUser.js @@ -8,7 +8,7 @@ import { updateGroupDMsName } from './updateGroupDMsName'; import { relinquishRoomOwnerships } from './relinquishRoomOwnerships'; import { getSubscribedRoomsForUserWithDetails, shouldRemoveOrChangeOwner } from './getRoomsWithSingleOwner'; import { getUserSingleOwnedRooms } from './getUserSingleOwnedRooms'; -import { StreamService } from '../../../../server/sdk'; +import { api } from '../../../../server/sdk/api'; export const deleteUser = function(userId, confirmRelinquish = false) { const user = Users.findOneById(userId, { @@ -65,7 +65,7 @@ export const deleteUser = function(userId, confirmRelinquish = false) { } Integrations.disableByUserId(userId); // Disables all the integrations which rely on the user being deleted. - StreamService.sendUserDeleted(userId); + api.broadcast('user.deleted', user); } // Remove user from users database diff --git a/app/lib/server/functions/setRealName.js b/app/lib/server/functions/setRealName.js index 1c000dd0248b8..3a7051d68adfc 100644 --- a/app/lib/server/functions/setRealName.js +++ b/app/lib/server/functions/setRealName.js @@ -5,7 +5,7 @@ import { Users } from '../../../models/server'; import { settings } from '../../../settings'; import { hasPermission } from '../../../authorization'; import { RateLimiter } from '../lib'; -import { StreamService } from '../../../../server/sdk'; +import { api } from '../../../../server/sdk/api'; export const _setRealName = function(userId, name, fullUser) { name = s.trim(name); @@ -30,7 +30,7 @@ export const _setRealName = function(userId, name, fullUser) { user.name = name; if (settings.get('UI_Use_Real_Name') === true) { - StreamService.sendUserNameChanged({ + api.broadcast('user.nameChanged', { _id: user._id, name: user.name, username: user.username, diff --git a/app/lib/server/functions/setUsername.js b/app/lib/server/functions/setUsername.js index ae453f600df13..35b46122cbc0d 100644 --- a/app/lib/server/functions/setUsername.js +++ b/app/lib/server/functions/setUsername.js @@ -7,7 +7,7 @@ import { Users, Invites } from '../../../models/server'; import { hasPermission } from '../../../authorization'; import { RateLimiter } from '../lib'; import { addUserToRoom } from './addUserToRoom'; -import { StreamService } from '../../../../server/sdk'; +import { api } from '../../../../server/sdk/api'; import { checkUsernameAvailability, setUserAvatar, getAvatarSuggestionForUser } from '.'; @@ -76,7 +76,7 @@ export const _setUsername = function(userId, u, fullUser) { } } - StreamService.sendUserNameChanged({ + api.broadcast('user.nameChanged', { _id: user._id, name: user.name, username: user.username, diff --git a/app/user-status/server/methods/deleteCustomUserStatus.js b/app/user-status/server/methods/deleteCustomUserStatus.js index ab5d4be7f9a6d..e81a8140d7180 100644 --- a/app/user-status/server/methods/deleteCustomUserStatus.js +++ b/app/user-status/server/methods/deleteCustomUserStatus.js @@ -2,7 +2,7 @@ import { Meteor } from 'meteor/meteor'; import { hasPermission } from '../../../authorization/server'; import { CustomUserStatus } from '../../../models/server'; -import { StreamService } from '../../../../server/sdk'; +import { api } from '../../../../server/sdk/api'; Meteor.methods({ deleteCustomUserStatus(userStatusID) { @@ -16,7 +16,7 @@ Meteor.methods({ } CustomUserStatus.removeById(userStatusID); - StreamService.sendDeleteCustomUserStatus(userStatus); + api.broadcast('user.deleteCustomStatus', userStatus); return true; }, diff --git a/definition/IUserStatus.ts b/definition/IUserStatus.ts new file mode 100644 index 0000000000000..26b3610bde8f0 --- /dev/null +++ b/definition/IUserStatus.ts @@ -0,0 +1,3 @@ +export interface IUserStatus { + [x: string]: any; +} diff --git a/server/sdk/lib/Events.ts b/server/sdk/lib/Events.ts index 5ce1dd8f24af2..15962210388af 100644 --- a/server/sdk/lib/Events.ts +++ b/server/sdk/lib/Events.ts @@ -9,24 +9,28 @@ import { ISubscription } from '../../../definition/ISubscription'; import { IUser } from '../../../definition/IUser'; import { AutoUpdateRecord } from '../types/IMeteor'; import { IEmoji } from '../../../definition/IEmoji'; +import { IUserStatus } from '../../../definition/IUserStatus'; export type BufferList = ReturnType; export type EventSignatures = { + 'emoji.deleteCustom'(emoji: IEmoji): void; + 'emoji.updateCustom'(emoji: IEmoji): void; + 'license.module'(data: {module: string; valid: boolean}): void; 'livechat-inquiry-queue-observer'(data: {action: string; inquiry: IInquiry}): void; - 'stream'([streamer, eventName, payload]: [string, string, string]): void; - 'subscription'(data: { action: string; subscription: Partial }): void; - 'room'(data: { action: string; room: Partial }): void; 'message'(data: { action: string; message: IMessage }): void; - 'setting'(data: { action: string; setting: Partial }): void; - 'userpresence'(data: { action: string; user: Partial }): void; - 'user'(data: { action: string; user: Partial }): void; - 'user.name'(data: { action: string; user: Partial }): void; - 'role'(data: {type: 'changed' | 'removed' } & Partial): void; - 'license.module'(data: {module: string; valid: boolean}): void; 'meteor.autoUpdateClientVersionChanged'(data: {record: AutoUpdateRecord}): void; 'meteor.loginServiceConfiguration'(data: {action: string; record: any}): void; + 'role'(data: {type: 'changed' | 'removed' } & Partial): void; + 'room'(data: { action: string; room: Partial }): void; + 'setting'(data: { action: string; setting: Partial }): void; + 'stream'([streamer, eventName, payload]: [string, string, string]): void; 'stream.ephemeralMessage'(uid: string, rid: string, message: Partial): void; - 'emoji.deleteCustom'(emoji: IEmoji): void; - 'emoji.updateCustom'(emoji: IEmoji): void; + 'subscription'(data: { action: string; subscription: Partial }): void; + 'user'(data: { action: string; user: Partial }): void; + 'user.deleted'(user: Partial): void; + 'user.deleteCustomStatus'(userStatus: IUserStatus): void; + 'user.nameChanged'(user: Partial): void; + 'user.name'(data: { action: string; user: Partial }): void; + 'userpresence'(data: { action: string; user: Partial }): void; } diff --git a/server/sdk/types/IStreamService.ts b/server/sdk/types/IStreamService.ts index e54ef13ca2995..c0c33790bccfa 100644 --- a/server/sdk/types/IStreamService.ts +++ b/server/sdk/types/IStreamService.ts @@ -9,9 +9,6 @@ export interface IStreamService extends IServiceClass { sendUserAvatarUpdate({ username, etag }: { username: string; etag?: string }): void; sendRoomAvatarUpdate({ rid, etag }: { rid: string; etag?: string }): void; sendRoleUpdate(update: Record): void; - sendUserDeleted(uid: string): void; - sendUserNameChanged(userData: Record): void; - sendDeleteCustomUserStatus(userStatusData: Record): void; sendUpdateCustomUserStatus(userStatusData: Record): void; sendEphemeralMessage(uid: string, rid: string, message: Partial): void; } diff --git a/server/services/listeners/notification.ts b/server/services/listeners/notification.ts index a3e40a74bae8a..8dd7486f89a3e 100644 --- a/server/services/listeners/notification.ts +++ b/server/services/listeners/notification.ts @@ -14,6 +14,34 @@ export class NotificationService extends ServiceClass { constructor() { super(); + this.onEvent('emoji.deleteCustom', (emoji) => { + notifications.notifyLogged('deleteEmojiCustom', { + emojiData: emoji, + }); + }); + + this.onEvent('emoji.updateCustom', (emoji) => { + notifications.notifyLogged('updateEmojiCustom', { + emojiData: emoji, + }); + }); + + this.onEvent('user.deleteCustomStatus', (userStatus) => { + notifications.notifyLogged('deleteCustomUserStatus', { + userStatusData: userStatus, + }); + }); + + this.onEvent('user.deleted', ({ _id: userId }) => { + notifications.notifyLogged('Users:Deleted', { + userId, + }); + }); + + this.onEvent('user.nameChanged', (user) => { + notifications.notifyLogged('Users:NameChanged', user); + }); + this.onEvent('userpresence', ({ user }) => { const { _id, username, status, statusText, @@ -24,17 +52,5 @@ export class NotificationService extends ServiceClass { notifications.notifyLogged('user-status', [_id, username, STATUS_MAP[status], statusText]); }); - - this.onEvent('emoji.deleteCustom', ({ emoji }) => { - notifications.notifyLogged('deleteEmojiCustom', { - emojiData: emoji, - }); - }); - - this.onEvent('emoji.updateCustom', ({ emoji }) => { - notifications.notifyLogged('updateEmojiCustom', { - emojiData: emoji, - }); - }); } } diff --git a/server/services/stream/service.ts b/server/services/stream/service.ts index cf930ace9b3b5..2ce400f38e586 100644 --- a/server/services/stream/service.ts +++ b/server/services/stream/service.ts @@ -65,22 +65,6 @@ export class StreamService extends ServiceClass implements IStreamService { }); } - sendUserDeleted(uid: string): void { - this.streamLogged.emit('Users:Deleted', { - userId: uid, - }); - } - - sendUserNameChanged(userData: Record): void { - this.streamLogged.emit('Users:NameChanged', userData); - } - - sendDeleteCustomUserStatus(userStatusData: Record): void { - this.streamLogged.emit('deleteCustomUserStatus', { - userStatusData, - }); - } - sendUpdateCustomUserStatus(userStatusData: Record): void { this.streamLogged.emit('updateCustomUserStatus', { userStatusData, From c9532ae1f70ed2ae9440f982c2b322c1c416d957 Mon Sep 17 00:00:00 2001 From: Alan Sikora Date: Thu, 1 Oct 2020 13:40:24 -0300 Subject: [PATCH 101/198] user.updateCustomStatus --- app/user-status/server/methods/insertOrUpdateUserStatus.js | 6 +++--- server/sdk/lib/Events.ts | 1 + server/sdk/types/IStreamService.ts | 1 - server/services/listeners/notification.ts | 6 ++++++ server/services/stream/service.ts | 6 ------ 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/app/user-status/server/methods/insertOrUpdateUserStatus.js b/app/user-status/server/methods/insertOrUpdateUserStatus.js index 8bf8dcf0ea213..a01cc751c525d 100644 --- a/app/user-status/server/methods/insertOrUpdateUserStatus.js +++ b/app/user-status/server/methods/insertOrUpdateUserStatus.js @@ -3,7 +3,7 @@ import s from 'underscore.string'; import { hasPermission } from '../../../authorization'; import { CustomUserStatus } from '../../../models'; -import { StreamService } from '../../../../server/sdk'; +import { api } from '../../../../server/sdk/api'; Meteor.methods({ insertOrUpdateUserStatus(userStatusData) { @@ -49,7 +49,7 @@ Meteor.methods({ const _id = CustomUserStatus.create(createUserStatus); - StreamService.sendUpdateCustomUserStatus(createUserStatus); + api.broadcast('user.updateCustomStatus', createUserStatus); return _id; } @@ -63,7 +63,7 @@ Meteor.methods({ CustomUserStatus.setStatusType(userStatusData._id, userStatusData.statusType); } - StreamService.sendUpdateCustomUserStatus(userStatusData); + api.broadcast('user.updateCustomStatus', userStatusData); return true; }, diff --git a/server/sdk/lib/Events.ts b/server/sdk/lib/Events.ts index 15962210388af..0bce76f122a3d 100644 --- a/server/sdk/lib/Events.ts +++ b/server/sdk/lib/Events.ts @@ -30,6 +30,7 @@ export type EventSignatures = { 'user'(data: { action: string; user: Partial }): void; 'user.deleted'(user: Partial): void; 'user.deleteCustomStatus'(userStatus: IUserStatus): void; + 'user.updateCustomStatus'(userStatus: IUserStatus): void; 'user.nameChanged'(user: Partial): void; 'user.name'(data: { action: string; user: Partial }): void; 'userpresence'(data: { action: string; user: Partial }): void; diff --git a/server/sdk/types/IStreamService.ts b/server/sdk/types/IStreamService.ts index c0c33790bccfa..2d72aff6b4da4 100644 --- a/server/sdk/types/IStreamService.ts +++ b/server/sdk/types/IStreamService.ts @@ -9,6 +9,5 @@ export interface IStreamService extends IServiceClass { sendUserAvatarUpdate({ username, etag }: { username: string; etag?: string }): void; sendRoomAvatarUpdate({ rid, etag }: { rid: string; etag?: string }): void; sendRoleUpdate(update: Record): void; - sendUpdateCustomUserStatus(userStatusData: Record): void; sendEphemeralMessage(uid: string, rid: string, message: Partial): void; } diff --git a/server/services/listeners/notification.ts b/server/services/listeners/notification.ts index 8dd7486f89a3e..c29453de6a561 100644 --- a/server/services/listeners/notification.ts +++ b/server/services/listeners/notification.ts @@ -32,6 +32,12 @@ export class NotificationService extends ServiceClass { }); }); + this.onEvent('user.updateCustomStatus', (userStatus) => { + notifications.notifyLogged('updateCustomUserStatus', { + userStatusData: userStatus, + }); + }); + this.onEvent('user.deleted', ({ _id: userId }) => { notifications.notifyLogged('Users:Deleted', { userId, diff --git a/server/services/stream/service.ts b/server/services/stream/service.ts index 2ce400f38e586..4447cfd38280e 100644 --- a/server/services/stream/service.ts +++ b/server/services/stream/service.ts @@ -65,12 +65,6 @@ export class StreamService extends ServiceClass implements IStreamService { }); } - sendUpdateCustomUserStatus(userStatusData: Record): void { - this.streamLogged.emit('updateCustomUserStatus', { - userStatusData, - }); - } - sendPermission({ clientAction, data }: any): void { this.streamLogged.emitWithoutBroadcast('permissions-changed', clientAction, data); } From c9b9f07d531297230534bb5c335fc3896309797c Mon Sep 17 00:00:00 2001 From: Alan Sikora Date: Thu, 1 Oct 2020 13:47:59 -0300 Subject: [PATCH 102/198] permission.changed --- app/authorization/server/streamer/permissions/emitter.js | 4 ++-- server/sdk/lib/Events.ts | 9 +++++---- server/services/listeners/notification.ts | 4 ++++ server/services/stream/service.ts | 4 ---- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/app/authorization/server/streamer/permissions/emitter.js b/app/authorization/server/streamer/permissions/emitter.js index b18e00f03e8d4..7baec757f0102 100644 --- a/app/authorization/server/streamer/permissions/emitter.js +++ b/app/authorization/server/streamer/permissions/emitter.js @@ -2,7 +2,7 @@ import Settings from '../../../../models/server/models/Settings'; import { CONSTANTS } from '../../../lib'; import Permissions from '../../../../models/server/models/Permissions'; import { clearCache } from '../../functions/hasPermission'; -import { StreamService } from '../../../../../server/sdk'; +import { api } from '../../../../../server/sdk/api'; Permissions.on('change', ({ clientAction, id, data, diff }) => { if (diff && Object.keys(diff).length === 1 && diff._updatedAt) { @@ -22,7 +22,7 @@ Permissions.on('change', ({ clientAction, id, data, diff }) => { clearCache(); - StreamService.sendPermission({ clientAction, data }); + api.broadcast('permission.changed', { clientAction, data }); if (data.level && data.level === CONSTANTS.SETTINGS_LEVEL) { // if the permission changes, the effect on the visible settings depends on the role affected. diff --git a/server/sdk/lib/Events.ts b/server/sdk/lib/Events.ts index 0bce76f122a3d..ae19aa205d42a 100644 --- a/server/sdk/lib/Events.ts +++ b/server/sdk/lib/Events.ts @@ -16,11 +16,12 @@ export type BufferList = ReturnType; export type EventSignatures = { 'emoji.deleteCustom'(emoji: IEmoji): void; 'emoji.updateCustom'(emoji: IEmoji): void; - 'license.module'(data: {module: string; valid: boolean}): void; - 'livechat-inquiry-queue-observer'(data: {action: string; inquiry: IInquiry}): void; + 'license.module'(data: { module: string; valid: boolean }): void; + 'livechat-inquiry-queue-observer'(data: { action: string; inquiry: IInquiry }): void; 'message'(data: { action: string; message: IMessage }): void; - 'meteor.autoUpdateClientVersionChanged'(data: {record: AutoUpdateRecord}): void; - 'meteor.loginServiceConfiguration'(data: {action: string; record: any}): void; + 'meteor.autoUpdateClientVersionChanged'(data: {record: AutoUpdateRecord }): void; + 'meteor.loginServiceConfiguration'(data: { action: string; record: any }): void; + 'permission.changed'(data: { clientAction: string; data: any }): void; 'role'(data: {type: 'changed' | 'removed' } & Partial): void; 'room'(data: { action: string; room: Partial }): void; 'setting'(data: { action: string; setting: Partial }): void; diff --git a/server/services/listeners/notification.ts b/server/services/listeners/notification.ts index c29453de6a561..2e49a5757a01d 100644 --- a/server/services/listeners/notification.ts +++ b/server/services/listeners/notification.ts @@ -26,6 +26,10 @@ export class NotificationService extends ServiceClass { }); }); + this.onEvent('permission.changed', ({ clientAction, data }) => { + notifications.notifyLogged('permissions-changed', clientAction, data); + }); + this.onEvent('user.deleteCustomStatus', (userStatus) => { notifications.notifyLogged('deleteCustomUserStatus', { userStatusData: userStatus, diff --git a/server/services/stream/service.ts b/server/services/stream/service.ts index 4447cfd38280e..d47365e47fd0c 100644 --- a/server/services/stream/service.ts +++ b/server/services/stream/service.ts @@ -65,10 +65,6 @@ export class StreamService extends ServiceClass implements IStreamService { }); } - sendPermission({ clientAction, data }: any): void { - this.streamLogged.emitWithoutBroadcast('permissions-changed', clientAction, data); - } - sendPrivateSetting({ clientAction, setting }: any): void { this.streamLogged.emitWithoutBroadcast('private-settings-changed', clientAction, setting); } From d09daa78d565369898ae2cef8ff5a4d02a3c3e61 Mon Sep 17 00:00:00 2001 From: Alan Sikora Date: Thu, 1 Oct 2020 13:51:14 -0300 Subject: [PATCH 103/198] setting.privateChanged --- app/authorization/server/streamer/permissions/emitter.js | 2 +- server/publications/settings/emitter.js | 6 +++--- server/sdk/lib/Events.ts | 1 + server/sdk/types/IStreamService.ts | 2 -- server/services/listeners/notification.ts | 4 ++++ server/services/stream/service.ts | 4 ---- 6 files changed, 9 insertions(+), 10 deletions(-) diff --git a/app/authorization/server/streamer/permissions/emitter.js b/app/authorization/server/streamer/permissions/emitter.js index 7baec757f0102..468e94cbfb818 100644 --- a/app/authorization/server/streamer/permissions/emitter.js +++ b/app/authorization/server/streamer/permissions/emitter.js @@ -32,6 +32,6 @@ Permissions.on('change', ({ clientAction, id, data, diff }) => { if (!setting) { return; } - StreamService.sendPrivateSetting({ clientAction: 'updated', setting }); + api.broadcast('setting.privateChanged', { clientAction: 'updated', setting }); } }); diff --git a/server/publications/settings/emitter.js b/server/publications/settings/emitter.js index ebfa7d4cab3ff..a6e2d0eb20718 100644 --- a/server/publications/settings/emitter.js +++ b/server/publications/settings/emitter.js @@ -1,7 +1,7 @@ import { Settings } from '../../../app/models/server'; import { Notifications } from '../../../app/notifications/server'; import { SettingsEvents } from '../../../app/settings/server/functions/settings'; -import { StreamService } from '../../sdk'; +import { api } from '../../sdk/api'; Settings.on('change', ({ clientAction, id, data, diff }) => { if (diff && Object.keys(diff).length === 1 && diff._updatedAt) { // avoid useless changes @@ -29,7 +29,7 @@ Settings.on('change', ({ clientAction, id, data, diff }) => { if (setting.public === true) { Notifications.notifyAllInThisInstance('public-settings-changed', clientAction, value); } - StreamService.sendPrivateSetting({ clientAction, setting }); + api.broadcast('setting.privateChanged', { clientAction, setting }); break; } @@ -39,7 +39,7 @@ Settings.on('change', ({ clientAction, id, data, diff }) => { if (setting && setting.public === true) { Notifications.notifyAllInThisInstance('public-settings-changed', clientAction, { _id: id }); } - StreamService.sendPrivateSetting({ clientAction, setting: { _id: id } }); + api.broadcast('setting.privateChanged', { clientAction, setting: { _id: id } }); break; } } diff --git a/server/sdk/lib/Events.ts b/server/sdk/lib/Events.ts index ae19aa205d42a..4b18716fc5326 100644 --- a/server/sdk/lib/Events.ts +++ b/server/sdk/lib/Events.ts @@ -25,6 +25,7 @@ export type EventSignatures = { 'role'(data: {type: 'changed' | 'removed' } & Partial): void; 'room'(data: { action: string; room: Partial }): void; 'setting'(data: { action: string; setting: Partial }): void; + 'setting.privateChanged'(data: { clientAction: string; setting: any }): void; 'stream'([streamer, eventName, payload]: [string, string, string]): void; 'stream.ephemeralMessage'(uid: string, rid: string, message: Partial): void; 'subscription'(data: { action: string; subscription: Partial }): void; diff --git a/server/sdk/types/IStreamService.ts b/server/sdk/types/IStreamService.ts index 2d72aff6b4da4..2f898932f79d6 100644 --- a/server/sdk/types/IStreamService.ts +++ b/server/sdk/types/IStreamService.ts @@ -4,8 +4,6 @@ import { IMessage } from '../../../definition/IMessage'; export interface IStreamService extends IServiceClass { notifyAll(eventName: string, ...args: any[]): void; notifyUser(uid: string, eventName: string, ...args: any[]): void; - sendPermission({ clientAction, data }: any): void; - sendPrivateSetting({ clientAction, setting }: any): void; sendUserAvatarUpdate({ username, etag }: { username: string; etag?: string }): void; sendRoomAvatarUpdate({ rid, etag }: { rid: string; etag?: string }): void; sendRoleUpdate(update: Record): void; diff --git a/server/services/listeners/notification.ts b/server/services/listeners/notification.ts index 2e49a5757a01d..47f0bca0bb265 100644 --- a/server/services/listeners/notification.ts +++ b/server/services/listeners/notification.ts @@ -30,6 +30,10 @@ export class NotificationService extends ServiceClass { notifications.notifyLogged('permissions-changed', clientAction, data); }); + this.onEvent('setting.privateChanged', ({ clientAction, setting }) => { + notifications.notifyLogged('private-settings-changed', clientAction, setting); + }); + this.onEvent('user.deleteCustomStatus', (userStatus) => { notifications.notifyLogged('deleteCustomUserStatus', { userStatusData: userStatus, diff --git a/server/services/stream/service.ts b/server/services/stream/service.ts index d47365e47fd0c..b3d4cf1de6c34 100644 --- a/server/services/stream/service.ts +++ b/server/services/stream/service.ts @@ -65,10 +65,6 @@ export class StreamService extends ServiceClass implements IStreamService { }); } - sendPrivateSetting({ clientAction, setting }: any): void { - this.streamLogged.emitWithoutBroadcast('private-settings-changed', clientAction, setting); - } - sendUserAvatarUpdate({ username, etag }: { username: string; etag?: string }): void { this.streamLogged.emit('updateAvatar', { username, From 058f201c60c192abae5a4ef4fbc9f87d58626a3b Mon Sep 17 00:00:00 2001 From: Alan Sikora Date: Thu, 1 Oct 2020 14:18:34 -0300 Subject: [PATCH 104/198] notify.ephemeralMessage --- app/google-vision/server/googlevision.js | 4 ++-- app/lib/server/methods/addUsersToRoom.js | 4 ++-- app/lib/server/methods/filterATAllTag.js | 4 ++-- app/lib/server/methods/filterATHereTag.js | 4 ++-- app/lib/server/methods/sendMessage.js | 4 ++-- app/mentions/server/server.js | 4 ++-- app/reactions/server/setReaction.js | 4 ++-- .../server/server.js | 8 +++---- app/slashcommands-create/server/server.js | 4 ++-- app/slashcommands-help/server/server.js | 4 ++-- app/slashcommands-hide/server/hide.js | 8 +++---- app/slashcommands-invite/server/server.js | 10 ++++---- app/slashcommands-inviteall/server/server.js | 10 ++++---- app/slashcommands-join/server/server.js | 4 ++-- app/slashcommands-kick/server/server.js | 6 ++--- app/slashcommands-leave/server/leave.js | 4 ++-- app/slashcommands-msg/server/server.js | 6 ++--- app/slashcommands-mute/server/mute.js | 6 ++--- app/slashcommands-mute/server/unmute.js | 6 ++--- app/slashcommands-status/lib/status.js | 6 ++--- .../server/server.js | 8 +++---- app/sms/server/services/twilio.js | 4 ++-- app/sms/server/services/voxtelesys.js | 4 ++-- server/sdk/lib/Events.ts | 1 + server/sdk/types/IStreamService.ts | 4 ---- server/services/listeners/notification.ts | 10 ++++++++ server/services/stream/service.ts | 24 ------------------- 27 files changed, 74 insertions(+), 91 deletions(-) diff --git a/app/google-vision/server/googlevision.js b/app/google-vision/server/googlevision.js index 245a9846861b2..69729c6c1ddb5 100644 --- a/app/google-vision/server/googlevision.js +++ b/app/google-vision/server/googlevision.js @@ -5,7 +5,7 @@ import { settings } from '../../settings'; import { callbacks } from '../../callbacks'; import { Uploads, Settings, Users, Messages } from '../../models'; import { FileUpload } from '../../file-upload'; -import { StreamService } from '../../../server/sdk'; +import { api } from '../../../server/sdk/api'; class GoogleVision { constructor() { @@ -66,7 +66,7 @@ class GoogleVision { FileUpload.getStore('Uploads').deleteById(file._id); const user = Users.findOneById(message.u && message.u._id); if (user) { - StreamService.sendEphemeralMessage(user._id, message.rid, { + api.broadcast('notify.ephemeralMessage', user._id, message.rid, { msg: TAPi18n.__('Adult_images_are_not_allowed', {}, user.language), }); } diff --git a/app/lib/server/methods/addUsersToRoom.js b/app/lib/server/methods/addUsersToRoom.js index dac850df50004..46556b763715e 100644 --- a/app/lib/server/methods/addUsersToRoom.js +++ b/app/lib/server/methods/addUsersToRoom.js @@ -5,7 +5,7 @@ import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; import { Rooms, Subscriptions, Users } from '../../../models'; import { hasPermission } from '../../../authorization'; import { addUserToRoom } from '../functions'; -import { StreamService } from '../../../../server/sdk'; +import { api } from '../../../../server/sdk/api'; Meteor.methods({ addUsersToRoom(data = {}) { @@ -72,7 +72,7 @@ Meteor.methods({ if (!subscription) { addUserToRoom(data.rid, newUser, user); } else { - StreamService.sendEphemeralMessage(userId, data.rid, { + api.broadcast('notify.ephemeralMessage', userId, data.rid, { msg: TAPi18n.__('Username_is_already_in_here', { postProcess: 'sprintf', sprintf: [newUser.username], diff --git a/app/lib/server/methods/filterATAllTag.js b/app/lib/server/methods/filterATAllTag.js index 396e376e86a5d..183a5075f48da 100644 --- a/app/lib/server/methods/filterATAllTag.js +++ b/app/lib/server/methods/filterATAllTag.js @@ -6,7 +6,7 @@ import moment from 'moment'; import { hasPermission } from '../../../authorization'; import { callbacks } from '../../../callbacks'; import { Users } from '../../../models'; -import { StreamService } from '../../../../server/sdk'; +import { api } from '../../../../server/sdk/api'; callbacks.add('beforeSaveMessage', function(message) { // If the message was edited, or is older than 60 seconds (imported) @@ -26,7 +26,7 @@ callbacks.add('beforeSaveMessage', function(message) { // Add a notification to the chat, informing the user that this // action is not allowed. - StreamService.sendEphemeralMessage(message.u._id, message.rid, { + api.broadcast('notify.ephemeralMessage', message.u._id, message.rid, { msg: TAPi18n.__('error-action-not-allowed', { action }, language), }); diff --git a/app/lib/server/methods/filterATHereTag.js b/app/lib/server/methods/filterATHereTag.js index fb5607f344c3d..e67a0f76c6c45 100644 --- a/app/lib/server/methods/filterATHereTag.js +++ b/app/lib/server/methods/filterATHereTag.js @@ -6,7 +6,7 @@ import moment from 'moment'; import { hasPermission } from '../../../authorization'; import { callbacks } from '../../../callbacks'; import { Users } from '../../../models'; -import { StreamService } from '../../../../server/sdk'; +import { api } from '../../../../server/sdk/api'; callbacks.add('beforeSaveMessage', function(message) { // If the message was edited, or is older than 60 seconds (imported) @@ -25,7 +25,7 @@ callbacks.add('beforeSaveMessage', function(message) { // Add a notification to the chat, informing the user that this // action is not allowed. - StreamService.sendEphemeralMessage(message.u._id, message.rid, { + api.broadcast('notify.ephemeralMessage', message.u._id, message.rid, { msg: TAPi18n.__('error-action-not-allowed', { action }, language), }); diff --git a/app/lib/server/methods/sendMessage.js b/app/lib/server/methods/sendMessage.js index 75101f4eb1e09..ae996bc6cfe16 100644 --- a/app/lib/server/methods/sendMessage.js +++ b/app/lib/server/methods/sendMessage.js @@ -12,7 +12,7 @@ import { sendMessage } from '../functions'; import { RateLimiter } from '../lib'; import { canSendMessage } from '../../../authorization/server'; import { SystemLogger } from '../../../logger/server'; -import { StreamService } from '../../../../server/sdk'; +import { api } from '../../../../server/sdk/api'; export function executeSendMessage(uid, message) { if (message.tshow && !message.tmid) { @@ -80,7 +80,7 @@ export function executeSendMessage(uid, message) { SystemLogger.error('Error sending message:', error); const errorMessage = typeof error === 'string' ? error : error.error || error.message; - StreamService.sendEphemeralMessage(uid, message.rid, { + api.broadcast('notify.ephemeralMessage', uid, message.rid, { msg: TAPi18n.__(errorMessage, {}, user.language), }); diff --git a/app/mentions/server/server.js b/app/mentions/server/server.js index 9e4cc516f3ae0..62c54e2524806 100644 --- a/app/mentions/server/server.js +++ b/app/mentions/server/server.js @@ -6,7 +6,7 @@ import MentionsServer from './Mentions'; import { settings } from '../../settings'; import { callbacks } from '../../callbacks'; import { Users, Subscriptions, Rooms } from '../../models'; -import { StreamService } from '../../../server/sdk'; +import { api } from '../../../server/sdk/api'; const mention = new MentionsServer({ pattern: () => settings.get('UTF8_Names_Validation'), @@ -20,7 +20,7 @@ const mention = new MentionsServer({ const { language } = this.getUser(sender._id); const msg = TAPi18n.__('Group_mentions_disabled_x_members', { total: this.messageMaxAll }, language); - StreamService.sendEphemeralMessage(sender._id, rid, { + api.broadcast('notify.ephemeralMessage', sender._id, rid, { msg, }); diff --git a/app/reactions/server/setReaction.js b/app/reactions/server/setReaction.js index 3e7c8e9395c73..53de3fe70c1fd 100644 --- a/app/reactions/server/setReaction.js +++ b/app/reactions/server/setReaction.js @@ -7,7 +7,7 @@ import { callbacks } from '../../callbacks'; import { emoji } from '../../emoji'; import { isTheLastMessage, msgStream } from '../../lib'; import { hasPermission } from '../../authorization/server/functions/hasPermission'; -import { StreamService } from '../../../server/sdk'; +import { api } from '../../../server/sdk/api'; const removeUserReaction = (message, reaction, username) => { message.reactions[reaction].usernames.splice(message.reactions[reaction].usernames.indexOf(username), 1); @@ -111,7 +111,7 @@ Meteor.methods({ return Promise.await(executeSetReaction(reaction, messageId, shouldReact)); } catch (e) { if (e.error === 'error-not-allowed' && e.reason && e.details && e.details.rid) { - StreamService.sendEphemeralMessage(Meteor.userId(), e.details.rid, { + api.broadcast('notify.ephemeralMessage', Meteor.userId(), e.details.rid, { msg: e.reason, }); diff --git a/app/slashcommands-archiveroom/server/server.js b/app/slashcommands-archiveroom/server/server.js index 46d61fd634a90..6e10f24f6decb 100644 --- a/app/slashcommands-archiveroom/server/server.js +++ b/app/slashcommands-archiveroom/server/server.js @@ -4,7 +4,7 @@ import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; import { Rooms, Messages } from '../../models'; import { slashCommands } from '../../utils'; -import { StreamService } from '../../../server/sdk'; +import { api } from '../../../server/sdk/api'; function Archive(command, params, item) { if (command !== 'archive' || !Match.test(params, String)) { @@ -25,7 +25,7 @@ function Archive(command, params, item) { const user = Meteor.users.findOne(Meteor.userId()); if (!room) { - StreamService.sendEphemeralMessage(Meteor.userId(), item.rid, { + api.broadcast('notify.ephemeralMessage', Meteor.userId(), item.rid, { msg: TAPi18n.__('Channel_doesnt_exist', { postProcess: 'sprintf', sprintf: [channel], @@ -39,7 +39,7 @@ function Archive(command, params, item) { } if (room.archived) { - StreamService.sendEphemeralMessage(Meteor.userId(), item.rid, { + api.broadcast('notify.ephemeralMessage', Meteor.userId(), item.rid, { msg: TAPi18n.__('Duplicate_archived_channel_name', { postProcess: 'sprintf', sprintf: [channel], @@ -50,7 +50,7 @@ function Archive(command, params, item) { Meteor.call('archiveRoom', room._id); Messages.createRoomArchivedByRoomIdAndUser(room._id, Meteor.user()); - StreamService.sendEphemeralMessage(Meteor.userId(), item.rid, { + api.broadcast('notify.ephemeralMessage', Meteor.userId(), item.rid, { msg: TAPi18n.__('Channel_Archived', { postProcess: 'sprintf', sprintf: [channel], diff --git a/app/slashcommands-create/server/server.js b/app/slashcommands-create/server/server.js index 8c648ef3d6cc7..21c26dac750be 100644 --- a/app/slashcommands-create/server/server.js +++ b/app/slashcommands-create/server/server.js @@ -5,7 +5,7 @@ import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; import { settings } from '../../settings'; import { Rooms } from '../../models'; import { slashCommands } from '../../utils'; -import { StreamService } from '../../../server/sdk'; +import { api } from '../../../server/sdk/api'; function Create(command, params, item) { function getParams(str) { @@ -35,7 +35,7 @@ function Create(command, params, item) { const user = Meteor.users.findOne(Meteor.userId()); const room = Rooms.findOneByName(channel); if (room != null) { - StreamService.sendEphemeralMessage(Meteor.userId(), item.rid, { + api.broadcast('notify.ephemeralMessage', Meteor.userId(), item.rid, { msg: TAPi18n.__('Channel_already_exist', { postProcess: 'sprintf', sprintf: [channel], diff --git a/app/slashcommands-help/server/server.js b/app/slashcommands-help/server/server.js index b5ef61af46743..5b4eaec71407f 100644 --- a/app/slashcommands-help/server/server.js +++ b/app/slashcommands-help/server/server.js @@ -2,7 +2,7 @@ import { Meteor } from 'meteor/meteor'; import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; import { slashCommands } from '../../utils'; -import { StreamService } from '../../../server/sdk'; +import { api } from '../../../server/sdk/api'; /* * Help is a named function that will replace /join commands @@ -38,7 +38,7 @@ slashCommands.add('help', function Help(command, params, item) { }, ]; keys.forEach((key) => { - StreamService.sendEphemeralMessage(Meteor.userId(), item.rid, { + api.broadcast('notify.ephemeralMessage', Meteor.userId(), item.rid, { msg: TAPi18n.__(Object.keys(key)[0], { postProcess: 'sprintf', sprintf: [key[Object.keys(key)[0]]], diff --git a/app/slashcommands-hide/server/hide.js b/app/slashcommands-hide/server/hide.js index c97e21fc83e0d..a42041849b643 100644 --- a/app/slashcommands-hide/server/hide.js +++ b/app/slashcommands-hide/server/hide.js @@ -4,7 +4,7 @@ import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; import { Rooms, Subscriptions } from '../../models'; import { slashCommands } from '../../utils'; -import { StreamService } from '../../../server/sdk'; +import { api } from '../../../server/sdk/api'; /* * Hide is a named function that will replace /hide commands @@ -28,7 +28,7 @@ function Hide(command, param, item) { }); if (!roomObject) { - StreamService.sendEphemeralMessage(user._id, item.rid, { + api.broadcast('notify.ephemeralMessage', user._id, item.rid, { msg: TAPi18n.__('Channel_doesnt_exist', { postProcess: 'sprintf', sprintf: [room], @@ -37,7 +37,7 @@ function Hide(command, param, item) { } if (!Subscriptions.findOneByRoomIdAndUserId(room._id, user._id, { fields: { _id: 1 } })) { - return StreamService.sendEphemeralMessage(user._id, item.rid, { + return api.broadcast('notify.ephemeralMessage', user._id, item.rid, { msg: TAPi18n.__('error-logged-user-not-in-room', { postProcess: 'sprintf', sprintf: [room], @@ -49,7 +49,7 @@ function Hide(command, param, item) { Meteor.call('hideRoom', rid, (error) => { if (error) { - return StreamService.sendEphemeralMessage(user._id, item.rid, { + return api.broadcast('notify.ephemeralMessage', user._id, item.rid, { msg: TAPi18n.__(error, null, user.language), }); } diff --git a/app/slashcommands-invite/server/server.js b/app/slashcommands-invite/server/server.js index c60d5fb2440f2..7c3a953abb5a0 100644 --- a/app/slashcommands-invite/server/server.js +++ b/app/slashcommands-invite/server/server.js @@ -4,7 +4,7 @@ import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; import { slashCommands } from '../../utils'; import { Subscriptions } from '../../models'; -import { StreamService } from '../../../server/sdk'; +import { api } from '../../../server/sdk/api'; /* * Invite is a named function that will replace /invite commands @@ -29,7 +29,7 @@ function Invite(command, params, item) { const userId = Meteor.userId(); const currentUser = Meteor.users.findOne(userId); if (users.count() === 0) { - StreamService.sendEphemeralMessage(userId, item.rid, { + api.broadcast('notify.ephemeralMessage', userId, item.rid, { msg: TAPi18n.__('User_doesnt_exist', { postProcess: 'sprintf', sprintf: [usernames.join(' @')], @@ -42,7 +42,7 @@ function Invite(command, params, item) { if (subscription == null) { return true; } - StreamService.sendEphemeralMessage(userId, item.rid, { + api.broadcast('notify.ephemeralMessage', userId, item.rid, { msg: TAPi18n.__('Username_is_already_in_here', { postProcess: 'sprintf', sprintf: [user.username], @@ -59,11 +59,11 @@ function Invite(command, params, item) { }); } catch ({ error }) { if (error === 'cant-invite-for-direct-room') { - StreamService.sendEphemeralMessage(userId, item.rid, { + api.broadcast('notify.ephemeralMessage', userId, item.rid, { msg: TAPi18n.__('Cannot_invite_users_to_direct_rooms', null, currentUser.language), }); } else { - StreamService.sendEphemeralMessage(userId, item.rid, { + api.broadcast('notify.ephemeralMessage', userId, item.rid, { msg: TAPi18n.__(error, null, currentUser.language), }); } diff --git a/app/slashcommands-inviteall/server/server.js b/app/slashcommands-inviteall/server/server.js index aca9fd3679e02..c98a70984835a 100644 --- a/app/slashcommands-inviteall/server/server.js +++ b/app/slashcommands-inviteall/server/server.js @@ -9,7 +9,7 @@ import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; import { Rooms, Subscriptions } from '../../models'; import { slashCommands } from '../../utils'; import { settings } from '../../settings'; -import { StreamService } from '../../../server/sdk'; +import { api } from '../../../server/sdk/api'; function inviteAll(type) { return function inviteAll(command, params, item) { @@ -29,7 +29,7 @@ function inviteAll(type) { const targetChannel = type === 'from' ? Rooms.findOneById(item.rid) : Rooms.findOneByName(channel); if (!baseChannel) { - return StreamService.sendEphemeralMessage(userId, item.rid, { + return api.broadcast('notify.ephemeralMessage', userId, item.rid, { msg: TAPi18n.__('Channel_doesnt_exist', { postProcess: 'sprintf', sprintf: [channel], @@ -48,7 +48,7 @@ function inviteAll(type) { if (!targetChannel && ['c', 'p'].indexOf(baseChannel.t) > -1) { Meteor.call(baseChannel.t === 'c' ? 'createChannel' : 'createPrivateGroup', channel, users); - StreamService.sendEphemeralMessage(userId, item.rid, { + api.broadcast('notify.ephemeralMessage', userId, item.rid, { msg: TAPi18n.__('Channel_created', { postProcess: 'sprintf', sprintf: [channel], @@ -60,12 +60,12 @@ function inviteAll(type) { users, }); } - return StreamService.sendEphemeralMessage(userId, item.rid, { + return api.broadcast('notify.ephemeralMessage', userId, item.rid, { msg: TAPi18n.__('Users_added', null, currentUser.language), }); } catch (e) { const msg = e.error === 'cant-invite-for-direct-room' ? 'Cannot_invite_users_to_direct_rooms' : e.error; - StreamService.sendEphemeralMessage(userId, item.rid, { + api.broadcast('notify.ephemeralMessage', userId, item.rid, { msg: TAPi18n.__(msg, null, currentUser.language), }); } diff --git a/app/slashcommands-join/server/server.js b/app/slashcommands-join/server/server.js index a49a473b650a7..0443c2300c728 100644 --- a/app/slashcommands-join/server/server.js +++ b/app/slashcommands-join/server/server.js @@ -4,7 +4,7 @@ import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; import { Rooms, Subscriptions } from '../../models'; import { slashCommands } from '../../utils'; -import { StreamService } from '../../../server/sdk'; +import { api } from '../../../server/sdk/api'; function Join(command, params, item) { if (command !== 'join' || !Match.test(params, String)) { @@ -18,7 +18,7 @@ function Join(command, params, item) { const user = Meteor.users.findOne(Meteor.userId()); const room = Rooms.findOneByNameAndType(channel, 'c'); if (!room) { - StreamService.sendEphemeralMessage(Meteor.userId(), item.rid, { + api.broadcast('notify.ephemeralMessage', Meteor.userId(), item.rid, { msg: TAPi18n.__('Channel_doesnt_exist', { postProcess: 'sprintf', sprintf: [channel], diff --git a/app/slashcommands-kick/server/server.js b/app/slashcommands-kick/server/server.js index 7f0867abe3f78..9808e86731cba 100644 --- a/app/slashcommands-kick/server/server.js +++ b/app/slashcommands-kick/server/server.js @@ -6,7 +6,7 @@ import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; import { Users, Subscriptions } from '../../models'; import { slashCommands } from '../../utils'; -import { StreamService } from '../../../server/sdk'; +import { api } from '../../../server/sdk/api'; const Kick = function(command, params, { rid }) { if (command !== 'kick' || !Match.test(params, String)) { @@ -21,7 +21,7 @@ const Kick = function(command, params, { rid }) { const kickedUser = Users.findOneByUsernameIgnoringCase(username); if (kickedUser == null) { - return StreamService.sendEphemeralMessage(userId, rid, { + return api.broadcast('notify.ephemeralMessage', userId, rid, { msg: TAPi18n.__('Username_doesnt_exist', { postProcess: 'sprintf', sprintf: [username], @@ -31,7 +31,7 @@ const Kick = function(command, params, { rid }) { const subscription = Subscriptions.findOneByRoomIdAndUserId(rid, user._id, { fields: { _id: 1 } }); if (!subscription) { - return StreamService.sendEphemeralMessage(userId, rid, { + return api.broadcast('notify.ephemeralMessage', userId, rid, { msg: TAPi18n.__('Username_is_not_in_this_room', { postProcess: 'sprintf', sprintf: [username], diff --git a/app/slashcommands-leave/server/leave.js b/app/slashcommands-leave/server/leave.js index bf2e76c0c763d..31436a2346a23 100644 --- a/app/slashcommands-leave/server/leave.js +++ b/app/slashcommands-leave/server/leave.js @@ -2,7 +2,7 @@ import { Meteor } from 'meteor/meteor'; import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; import { slashCommands } from '../../utils'; -import { StreamService } from '../../../server/sdk'; +import { api } from '../../../server/sdk/api'; /* * Leave is a named function that will replace /leave commands @@ -16,7 +16,7 @@ function Leave(command, params, item) { try { Meteor.call('leaveRoom', item.rid); } catch ({ error }) { - StreamService.sendEphemeralMessage(Meteor.userId(), item.rid, { + api.broadcast('notify.ephemeralMessage', Meteor.userId(), item.rid, { msg: TAPi18n.__(error, null, Meteor.user().language), }); } diff --git a/app/slashcommands-msg/server/server.js b/app/slashcommands-msg/server/server.js index 41124ed30389f..74cec48799fdc 100644 --- a/app/slashcommands-msg/server/server.js +++ b/app/slashcommands-msg/server/server.js @@ -5,7 +5,7 @@ import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; import { slashCommands } from '../../utils'; import { Users } from '../../models'; -import { StreamService } from '../../../server/sdk'; +import { api } from '../../../server/sdk/api'; /* * Msg is a named function that will replace /msg commands @@ -19,7 +19,7 @@ function Msg(command, params, item) { const separator = trimmedParams.indexOf(' '); const user = Meteor.users.findOne(Meteor.userId()); if (separator === -1) { - return StreamService.sendEphemeralMessage(Meteor.userId(), item.rid, { + return api.broadcast('notify.ephemeralMessage', Meteor.userId(), item.rid, { msg: TAPi18n.__('Username_and_message_must_not_be_empty', null, user.language), }); } @@ -28,7 +28,7 @@ function Msg(command, params, item) { const targetUsername = targetUsernameOrig.replace('@', ''); const targetUser = Users.findOneByUsernameIgnoringCase(targetUsername); if (targetUser == null) { - StreamService.sendEphemeralMessage(Meteor.userId(), item.rid, { + api.broadcast('notify.ephemeralMessage', Meteor.userId(), item.rid, { msg: TAPi18n.__('Username_doesnt_exist', { postProcess: 'sprintf', sprintf: [targetUsernameOrig], diff --git a/app/slashcommands-mute/server/mute.js b/app/slashcommands-mute/server/mute.js index 539232322d05b..8321e6434b115 100644 --- a/app/slashcommands-mute/server/mute.js +++ b/app/slashcommands-mute/server/mute.js @@ -4,7 +4,7 @@ import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; import { slashCommands } from '../../utils'; import { Users, Subscriptions } from '../../models'; -import { StreamService } from '../../../server/sdk'; +import { api } from '../../../server/sdk/api'; /* * Mute is a named function that will replace /mute commands @@ -22,7 +22,7 @@ slashCommands.add('mute', function Mute(command, params, item) { const user = Meteor.users.findOne(userId); const mutedUser = Users.findOneByUsernameIgnoringCase(username); if (mutedUser == null) { - StreamService.sendEphemeralMessage(userId, item.rid, { + api.broadcast('notify.ephemeralMessage', userId, item.rid, { msg: TAPi18n.__('Username_doesnt_exist', { postProcess: 'sprintf', sprintf: [username], @@ -33,7 +33,7 @@ slashCommands.add('mute', function Mute(command, params, item) { const subscription = Subscriptions.findOneByRoomIdAndUserId(item.rid, mutedUser._id, { fields: { _id: 1 } }); if (!subscription) { - StreamService.sendEphemeralMessage(userId, item.rid, { + api.broadcast('notify.ephemeralMessage', userId, item.rid, { msg: TAPi18n.__('Username_is_not_in_this_room', { postProcess: 'sprintf', sprintf: [username], diff --git a/app/slashcommands-mute/server/unmute.js b/app/slashcommands-mute/server/unmute.js index e9d42b608a64f..e962bf2979f8f 100644 --- a/app/slashcommands-mute/server/unmute.js +++ b/app/slashcommands-mute/server/unmute.js @@ -4,7 +4,7 @@ import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; import { slashCommands } from '../../utils'; import { Users, Subscriptions } from '../../models'; -import { StreamService } from '../../../server/sdk'; +import { api } from '../../../server/sdk/api'; /* * Unmute is a named function that will replace /unmute commands @@ -21,7 +21,7 @@ slashCommands.add('unmute', function Unmute(command, params, item) { const user = Meteor.users.findOne(Meteor.userId()); const unmutedUser = Users.findOneByUsernameIgnoringCase(username); if (unmutedUser == null) { - return StreamService.sendEphemeralMessage(Meteor.userId(), item.rid, { + return api.broadcast('notify.ephemeralMessage', Meteor.userId(), item.rid, { msg: TAPi18n.__('Username_doesnt_exist', { postProcess: 'sprintf', sprintf: [username], @@ -31,7 +31,7 @@ slashCommands.add('unmute', function Unmute(command, params, item) { const subscription = Subscriptions.findOneByRoomIdAndUserId(item.rid, unmutedUser._id, { fields: { _id: 1 } }); if (!subscription) { - return StreamService.sendEphemeralMessage(Meteor.userId(), item.rid, { + return api.broadcast('notify.ephemeralMessage', Meteor.userId(), item.rid, { msg: TAPi18n.__('Username_is_not_in_this_room', { postProcess: 'sprintf', sprintf: [username], diff --git a/app/slashcommands-status/lib/status.js b/app/slashcommands-status/lib/status.js index 89667fb0841c8..9f7a46588460f 100644 --- a/app/slashcommands-status/lib/status.js +++ b/app/slashcommands-status/lib/status.js @@ -2,7 +2,7 @@ import { Meteor } from 'meteor/meteor'; import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; import { handleError, slashCommands } from '../../utils'; -import { StreamService } from '../../../server/sdk'; +import { api } from '../../../server/sdk/api'; function Status(command, params, item) { if (command === 'status') { @@ -15,14 +15,14 @@ function Status(command, params, item) { } if (err.error === 'error-not-allowed') { - StreamService.sendEphemeralMessage(Meteor.userId(), item.rid, { + api.broadcast('notify.ephemeralMessage', Meteor.userId(), item.rid, { msg: TAPi18n.__('StatusMessage_Change_Disabled', null, user.language), }); } throw err; } else { - StreamService.sendEphemeralMessage(Meteor.userId(), item.rid, { + api.broadcast('notify.ephemeralMessage', Meteor.userId(), item.rid, { msg: TAPi18n.__('StatusMessage_Changed_Successfully', null, user.language), }); } diff --git a/app/slashcommands-unarchiveroom/server/server.js b/app/slashcommands-unarchiveroom/server/server.js index 09983e48fc1f6..07096dc8ec78c 100644 --- a/app/slashcommands-unarchiveroom/server/server.js +++ b/app/slashcommands-unarchiveroom/server/server.js @@ -5,7 +5,7 @@ import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; import { Rooms, Messages } from '../../models'; import { slashCommands } from '../../utils'; import { roomTypes, RoomMemberActions } from '../../utils/server'; -import { StreamService } from '../../../server/sdk'; +import { api } from '../../../server/sdk/api'; function Unarchive(command, params, item) { if (command !== 'unarchive' || !Match.test(params, String)) { @@ -26,7 +26,7 @@ function Unarchive(command, params, item) { const user = Meteor.users.findOne(Meteor.userId()); if (!room) { - return StreamService.sendEphemeralMessage(Meteor.userId(), item.rid, { + return api.broadcast('notify.ephemeralMessage', Meteor.userId(), item.rid, { msg: TAPi18n.__('Channel_doesnt_exist', { postProcess: 'sprintf', sprintf: [channel], @@ -40,7 +40,7 @@ function Unarchive(command, params, item) { } if (!room.archived) { - StreamService.sendEphemeralMessage(Meteor.userId(), item.rid, { + api.broadcast('notify.ephemeralMessage', Meteor.userId(), item.rid, { msg: TAPi18n.__('Channel_already_Unarchived', { postProcess: 'sprintf', sprintf: [channel], @@ -52,7 +52,7 @@ function Unarchive(command, params, item) { Meteor.call('unarchiveRoom', room._id); Messages.createRoomUnarchivedByRoomIdAndUser(room._id, Meteor.user()); - StreamService.sendEphemeralMessage(Meteor.userId(), item.rid, { + api.broadcast('notify.ephemeralMessage', Meteor.userId(), item.rid, { msg: TAPi18n.__('Channel_Unarchived', { postProcess: 'sprintf', sprintf: [channel], diff --git a/app/sms/server/services/twilio.js b/app/sms/server/services/twilio.js index a3620b9d988b7..bf52dcd5491f8 100644 --- a/app/sms/server/services/twilio.js +++ b/app/sms/server/services/twilio.js @@ -6,11 +6,11 @@ import filesize from 'filesize'; import { settings } from '../../../settings'; import { SMS } from '../SMS'; import { fileUploadIsValidContentType } from '../../../utils/lib/fileUploadRestrictions'; -import { StreamService } from '../../../../server/sdk'; +import { api } from '../../../../server/sdk/api'; const MAX_FILE_SIZE = 5242880; -const notifyAgent = (userId, rid, msg) => StreamService.sendEphemeralMessage(userId, rid, { +const notifyAgent = (userId, rid, msg) => api.broadcast('notify.ephemeralMessage', userId, rid, { msg, }); diff --git a/app/sms/server/services/voxtelesys.js b/app/sms/server/services/voxtelesys.js index 5f218b78a5475..3b0c7dd0ae929 100644 --- a/app/sms/server/services/voxtelesys.js +++ b/app/sms/server/services/voxtelesys.js @@ -7,11 +7,11 @@ import { settings } from '../../../settings'; import { SMS } from '../SMS'; import { fileUploadIsValidContentType } from '../../../utils/lib/fileUploadRestrictions'; import { mime } from '../../../utils/lib/mimeTypes'; -import { StreamService } from '../../../../server/sdk'; +import { api } from '../../../../server/sdk/api'; const MAX_FILE_SIZE = 5242880; -const notifyAgent = (userId, rid, msg) => StreamService.sendEphemeralMessage(userId, rid, { +const notifyAgent = (userId, rid, msg) => api.broadcast('notify.ephemeralMessage', userId, rid, { msg, }); diff --git a/server/sdk/lib/Events.ts b/server/sdk/lib/Events.ts index 4b18716fc5326..64ae2bfdc3fc2 100644 --- a/server/sdk/lib/Events.ts +++ b/server/sdk/lib/Events.ts @@ -21,6 +21,7 @@ export type EventSignatures = { 'message'(data: { action: string; message: IMessage }): void; 'meteor.autoUpdateClientVersionChanged'(data: {record: AutoUpdateRecord }): void; 'meteor.loginServiceConfiguration'(data: { action: string; record: any }): void; + 'notify.ephemeralMessage'(uid: string, rid: string, message: Partial): void; 'permission.changed'(data: { clientAction: string; data: any }): void; 'role'(data: {type: 'changed' | 'removed' } & Partial): void; 'room'(data: { action: string; room: Partial }): void; diff --git a/server/sdk/types/IStreamService.ts b/server/sdk/types/IStreamService.ts index 2f898932f79d6..d95f17adb512f 100644 --- a/server/sdk/types/IStreamService.ts +++ b/server/sdk/types/IStreamService.ts @@ -1,11 +1,7 @@ import { IServiceClass } from './ServiceClass'; -import { IMessage } from '../../../definition/IMessage'; export interface IStreamService extends IServiceClass { - notifyAll(eventName: string, ...args: any[]): void; - notifyUser(uid: string, eventName: string, ...args: any[]): void; sendUserAvatarUpdate({ username, etag }: { username: string; etag?: string }): void; sendRoomAvatarUpdate({ rid, etag }: { rid: string; etag?: string }): void; sendRoleUpdate(update: Record): void; - sendEphemeralMessage(uid: string, rid: string, message: Partial): void; } diff --git a/server/services/listeners/notification.ts b/server/services/listeners/notification.ts index 47f0bca0bb265..3a7f53c462207 100644 --- a/server/services/listeners/notification.ts +++ b/server/services/listeners/notification.ts @@ -26,6 +26,16 @@ export class NotificationService extends ServiceClass { }); }); + this.onEvent('notify.ephemeralMessage', (uid, rid, message) => { + notifications.notifyLogged(`${ uid }/message`, { + groupable: false, + ...message, + _id: String(Date.now()), + rid, + ts: new Date(), + }); + }); + this.onEvent('permission.changed', ({ clientAction, data }) => { notifications.notifyLogged('permissions-changed', clientAction, data); }); diff --git a/server/services/stream/service.ts b/server/services/stream/service.ts index b3d4cf1de6c34..5cf5f1ad849d0 100644 --- a/server/services/stream/service.ts +++ b/server/services/stream/service.ts @@ -1,7 +1,6 @@ import { ServiceClass } from '../../sdk/types/ServiceClass'; import { IStreamService } from '../../sdk/types/IStreamService'; // import { Notifications } from '../../../app/notifications/server'; -import { IMessage } from '../../../definition/IMessage'; import { IStreamer, IStreamerConstructor } from '../../modules/streamer/streamer.module'; export class StreamService extends ServiceClass implements IStreamService { @@ -82,27 +81,4 @@ export class StreamService extends ServiceClass implements IStreamService { sendRoleUpdate(update: Record): void { this.streamLogged.emit('roles-change', update); } - - sendEphemeralMessage(uid: string, rid: string, message: Partial): void { - this.notifyUser(uid, 'message', { - groupable: false, - ...message, - _id: String(Date.now()), - rid, - ts: new Date(), - }); - } - - notifyAll(eventName: string, ...args: any[]): void { - console.log('notifyAll', eventName, args); - } - - notifyUser(uid: string, eventName: string, ...args: any[]): void { - console.log('notifyUser', uid, eventName, args); - // if (this.debug === true) { - // console.log('notifyUser', [userId, eventName, ...args]); - // } - - return this.streamUser.emit(`${ uid }/${ eventName }`, ...args); - } } From 9d9300d15d4f2a1879dc69227a516478d349f7c1 Mon Sep 17 00:00:00 2001 From: Alan Sikora Date: Thu, 1 Oct 2020 14:26:49 -0300 Subject: [PATCH 105/198] user.avatarUpdate --- app/ldap/server/sync.js | 3 ++- app/lib/server/functions/setUserAvatar.js | 4 ++-- definition/IUser.ts | 1 + server/methods/resetAvatar.js | 4 ++-- server/sdk/lib/Events.ts | 1 + server/sdk/types/IStreamService.ts | 1 - server/services/listeners/notification.ts | 19 +++++++++++++------ server/services/stream/service.ts | 7 ------- 8 files changed, 21 insertions(+), 19 deletions(-) diff --git a/app/ldap/server/sync.js b/app/ldap/server/sync.js index 116cf55aef709..81fbdbeca566c 100644 --- a/app/ldap/server/sync.js +++ b/app/ldap/server/sync.js @@ -14,6 +14,7 @@ import { _setRealName, _setUsername } from '../../lib'; import { templateVarHandler } from '../../utils'; import { FileUpload } from '../../file-upload'; import { addUserToRoom, removeUserFromRoom, createRoom } from '../../lib/server/functions'; +import { api } from '../../../server/sdk/api'; import { StreamService } from '../../../server/sdk'; @@ -365,7 +366,7 @@ function syncUserAvatar(user, ldapUser) { fileStore.insert(file, rs, (err, result) => { Meteor.setTimeout(function() { Users.setAvatarData(user._id, 'ldap', result.etag); - StreamService.sendUserAvatarUpdate({ username: user.username, etag: result.etag }); + api.broadcast('user.avatarUpdate', { username: user.username, avatarETag: result.etag }); }, 500); }); }); diff --git a/app/lib/server/functions/setUserAvatar.js b/app/lib/server/functions/setUserAvatar.js index 5a5fc5b697d09..7229d5a294e4e 100644 --- a/app/lib/server/functions/setUserAvatar.js +++ b/app/lib/server/functions/setUserAvatar.js @@ -4,7 +4,7 @@ import { HTTP } from 'meteor/http'; import { RocketChatFile } from '../../../file'; import { FileUpload } from '../../../file-upload'; import { Users } from '../../../models'; -import { StreamService } from '../../../../server/sdk'; +import { api } from '../../../../server/sdk/api'; export const setUserAvatar = function(user, dataURI, contentType, service) { let encoding; @@ -64,7 +64,7 @@ export const setUserAvatar = function(user, dataURI, contentType, service) { fileStore.insert(file, buffer, (err, result) => { Meteor.setTimeout(function() { Users.setAvatarData(user._id, service, result.etag); - StreamService.sendUserAvatarUpdate({ username: user.username, etag: result.etag }); + api.broadcast('user.avatarUpdate', { username: user.username, avatarETag: result.etag }); }, 500); }); }; diff --git a/definition/IUser.ts b/definition/IUser.ts index 0260047c7774e..cb1a24f70cbf1 100644 --- a/definition/IUser.ts +++ b/definition/IUser.ts @@ -89,6 +89,7 @@ export interface IUser { statusConnection?: string; lastLogin?: Date; avatarOrigin?: string; + avatarETag?: string; utcOffset?: number; language?: string; statusDefault?: USER_STATUS; diff --git a/server/methods/resetAvatar.js b/server/methods/resetAvatar.js index 378e25b3f3ede..3ac38003a72a2 100644 --- a/server/methods/resetAvatar.js +++ b/server/methods/resetAvatar.js @@ -5,7 +5,7 @@ import { FileUpload } from '../../app/file-upload'; import { Users } from '../../app/models/server'; import { settings } from '../../app/settings'; import { hasPermission } from '../../app/authorization/server'; -import { StreamService } from '../sdk'; +import { api } from '../sdk/api'; Meteor.methods({ resetAvatar(userId) { @@ -43,7 +43,7 @@ Meteor.methods({ FileUpload.getStore('Avatars').deleteByName(user.username); Users.unsetAvatarData(user._id); - StreamService.sendUserAvatarUpdate({ username: user.username, etag: null }); + api.broadcast('user.avatarUpdate', { username: user.username, avatarETag: null }); }, }); diff --git a/server/sdk/lib/Events.ts b/server/sdk/lib/Events.ts index 64ae2bfdc3fc2..7124df4680c16 100644 --- a/server/sdk/lib/Events.ts +++ b/server/sdk/lib/Events.ts @@ -31,6 +31,7 @@ export type EventSignatures = { 'stream.ephemeralMessage'(uid: string, rid: string, message: Partial): void; 'subscription'(data: { action: string; subscription: Partial }): void; 'user'(data: { action: string; user: Partial }): void; + 'user.avatarUpdate'(user: Partial): void; 'user.deleted'(user: Partial): void; 'user.deleteCustomStatus'(userStatus: IUserStatus): void; 'user.updateCustomStatus'(userStatus: IUserStatus): void; diff --git a/server/sdk/types/IStreamService.ts b/server/sdk/types/IStreamService.ts index d95f17adb512f..7266ad36a3f39 100644 --- a/server/sdk/types/IStreamService.ts +++ b/server/sdk/types/IStreamService.ts @@ -1,7 +1,6 @@ import { IServiceClass } from './ServiceClass'; export interface IStreamService extends IServiceClass { - sendUserAvatarUpdate({ username, etag }: { username: string; etag?: string }): void; sendRoomAvatarUpdate({ rid, etag }: { rid: string; etag?: string }): void; sendRoleUpdate(update: Record): void; } diff --git a/server/services/listeners/notification.ts b/server/services/listeners/notification.ts index 3a7f53c462207..a179a589cddec 100644 --- a/server/services/listeners/notification.ts +++ b/server/services/listeners/notification.ts @@ -44,6 +44,19 @@ export class NotificationService extends ServiceClass { notifications.notifyLogged('private-settings-changed', clientAction, setting); }); + this.onEvent('user.avatarUpdate', ({ username, avatarETag: etag }) => { + notifications.notifyLogged('updateAvatar', { + username, + etag, + }); + }); + + this.onEvent('user.deleted', ({ _id: userId }) => { + notifications.notifyLogged('Users:Deleted', { + userId, + }); + }); + this.onEvent('user.deleteCustomStatus', (userStatus) => { notifications.notifyLogged('deleteCustomUserStatus', { userStatusData: userStatus, @@ -56,12 +69,6 @@ export class NotificationService extends ServiceClass { }); }); - this.onEvent('user.deleted', ({ _id: userId }) => { - notifications.notifyLogged('Users:Deleted', { - userId, - }); - }); - this.onEvent('user.nameChanged', (user) => { notifications.notifyLogged('Users:NameChanged', user); }); diff --git a/server/services/stream/service.ts b/server/services/stream/service.ts index 5cf5f1ad849d0..c22b2ce9cd863 100644 --- a/server/services/stream/service.ts +++ b/server/services/stream/service.ts @@ -64,13 +64,6 @@ export class StreamService extends ServiceClass implements IStreamService { }); } - sendUserAvatarUpdate({ username, etag }: { username: string; etag?: string }): void { - this.streamLogged.emit('updateAvatar', { - username, - etag, - }); - } - sendRoomAvatarUpdate({ rid, etag }: { rid: string; etag?: string }): void { this.streamLogged.emit('updateAvatar', { rid, From c71df4ebfac642a47037a83f771dee888bdb9ad6 Mon Sep 17 00:00:00 2001 From: Alan Sikora Date: Thu, 1 Oct 2020 14:32:18 -0300 Subject: [PATCH 106/198] user.roleUpdate --- app/authorization/server/methods/addUserToRole.js | 4 ++-- app/authorization/server/methods/removeUserFromRole.js | 4 ++-- app/authorization/server/methods/saveRole.js | 4 ++-- app/ldap/server/sync.js | 5 ++--- server/methods/addRoomLeader.js | 4 ++-- server/methods/addRoomModerator.js | 4 ++-- server/methods/addRoomOwner.js | 4 ++-- server/methods/removeRoomLeader.js | 4 ++-- server/methods/removeRoomModerator.js | 4 ++-- server/methods/removeRoomOwner.js | 4 ++-- server/sdk/lib/Events.ts | 1 + server/sdk/types/IStreamService.ts | 1 - server/services/listeners/notification.ts | 4 ++++ server/services/stream/service.ts | 4 ---- 14 files changed, 25 insertions(+), 26 deletions(-) diff --git a/app/authorization/server/methods/addUserToRole.js b/app/authorization/server/methods/addUserToRole.js index f7b9af4e7adb9..a7fdd21ec24dc 100644 --- a/app/authorization/server/methods/addUserToRole.js +++ b/app/authorization/server/methods/addUserToRole.js @@ -4,7 +4,7 @@ import _ from 'underscore'; import { Users, Roles } from '../../../models/server'; import { settings } from '../../../settings/server'; import { hasPermission } from '../functions/hasPermission'; -import { StreamService } from '../../../../server/sdk'; +import { api } from '../../../../server/sdk/api'; Meteor.methods({ 'authorization:addUserToRole'(roleName, username, scope) { @@ -50,7 +50,7 @@ Meteor.methods({ const add = Roles.addUserRoles(user._id, roleName, scope); if (settings.get('UI_DisplayRoles')) { - StreamService.sendRoleUpdate({ + api.broadcast('user.roleUpdate', { type: 'added', _id: roleName, u: { diff --git a/app/authorization/server/methods/removeUserFromRole.js b/app/authorization/server/methods/removeUserFromRole.js index ce7f7d7d47c5f..9a36a8895870c 100644 --- a/app/authorization/server/methods/removeUserFromRole.js +++ b/app/authorization/server/methods/removeUserFromRole.js @@ -4,7 +4,7 @@ import _ from 'underscore'; import { Roles } from '../../../models/server'; import { settings } from '../../../settings/server'; import { hasPermission } from '../functions/hasPermission'; -import { StreamService } from '../../../../server/sdk'; +import { api } from '../../../../server/sdk/api'; Meteor.methods({ 'authorization:removeUserFromRole'(roleName, username, scope) { @@ -55,7 +55,7 @@ Meteor.methods({ const remove = Roles.removeUserRoles(user._id, roleName, scope); if (settings.get('UI_DisplayRoles')) { - StreamService.sendRoleUpdate({ + api.broadcast('user.roleUpdate', { type: 'removed', _id: roleName, u: { diff --git a/app/authorization/server/methods/saveRole.js b/app/authorization/server/methods/saveRole.js index ccb5e9b3f1567..d0f0acbb45300 100644 --- a/app/authorization/server/methods/saveRole.js +++ b/app/authorization/server/methods/saveRole.js @@ -3,7 +3,7 @@ import { Meteor } from 'meteor/meteor'; import { Roles } from '../../../models/server'; import { settings } from '../../../settings/server'; import { hasPermission } from '../functions/hasPermission'; -import { StreamService } from '../../../../server/sdk'; +import { api } from '../../../../server/sdk/api'; import notifications from '../../../notifications/server/lib/Notifications'; Meteor.methods({ @@ -27,7 +27,7 @@ Meteor.methods({ const update = Roles.createOrUpdate(roleData.name, roleData.scope, roleData.description, false, roleData.mandatory2fa); if (settings.get('UI_DisplayRoles')) { - StreamService.sendRoleUpdate({ + api.broadcast('user.roleUpdate', { type: 'changed', _id: roleData.name, }); diff --git a/app/ldap/server/sync.js b/app/ldap/server/sync.js index 81fbdbeca566c..9993fe54549ed 100644 --- a/app/ldap/server/sync.js +++ b/app/ldap/server/sync.js @@ -15,7 +15,6 @@ import { templateVarHandler } from '../../utils'; import { FileUpload } from '../../file-upload'; import { addUserToRoom, removeUserFromRoom, createRoom } from '../../lib/server/functions'; import { api } from '../../../server/sdk/api'; -import { StreamService } from '../../../server/sdk'; export const logger = new Logger('LDAPSync', {}); @@ -265,7 +264,7 @@ export function mapLdapGroupsToUserRoles(ldap, ldapUser, user) { const del = Roles.removeUserRoles(user._id, roleName); if (settings.get('UI_DisplayRoles') && del) { - StreamService.sendRoleUpdate({ + api.broadcast('user.roleUpdate', { type: 'removed', _id: roleName, u: { @@ -413,7 +412,7 @@ export function syncUserData(user, ldapUser, ldap) { for (const roleName of userRoles) { const add = Roles.addUserRoles(user._id, roleName); if (settings.get('UI_DisplayRoles') && add) { - StreamService.sendRoleUpdate({ + api.broadcast('user.roleUpdate', { type: 'added', _id: roleName, u: { diff --git a/server/methods/addRoomLeader.js b/server/methods/addRoomLeader.js index 828b2c37a926f..b602c75f984e6 100644 --- a/server/methods/addRoomLeader.js +++ b/server/methods/addRoomLeader.js @@ -4,7 +4,7 @@ import { check } from 'meteor/check'; import { hasPermission } from '../../app/authorization'; import { Users, Subscriptions, Messages } from '../../app/models'; import { settings } from '../../app/settings'; -import { StreamService } from '../sdk'; +import { api } from '../sdk/api'; Meteor.methods({ addRoomLeader(rid, userId) { @@ -58,7 +58,7 @@ Meteor.methods({ }); if (settings.get('UI_DisplayRoles')) { - StreamService.sendRoleUpdate({ + api.broadcast('user.roleUpdate', { type: 'added', _id: 'leader', u: { diff --git a/server/methods/addRoomModerator.js b/server/methods/addRoomModerator.js index 5ee74b74cdbbe..8b430cb667a4f 100644 --- a/server/methods/addRoomModerator.js +++ b/server/methods/addRoomModerator.js @@ -4,7 +4,7 @@ import { check } from 'meteor/check'; import { hasPermission } from '../../app/authorization'; import { Users, Subscriptions, Messages } from '../../app/models'; import { settings } from '../../app/settings'; -import { StreamService } from '../sdk'; +import { api } from '../sdk/api'; Meteor.methods({ addRoomModerator(rid, userId) { @@ -58,7 +58,7 @@ Meteor.methods({ }); if (settings.get('UI_DisplayRoles')) { - StreamService.sendRoleUpdate({ + api.broadcast('user.roleUpdate', { type: 'added', _id: 'moderator', u: { diff --git a/server/methods/addRoomOwner.js b/server/methods/addRoomOwner.js index d226b4a42ba93..91473caef901d 100644 --- a/server/methods/addRoomOwner.js +++ b/server/methods/addRoomOwner.js @@ -4,7 +4,7 @@ import { check } from 'meteor/check'; import { hasPermission } from '../../app/authorization'; import { Users, Subscriptions, Messages } from '../../app/models'; import { settings } from '../../app/settings'; -import { StreamService } from '../sdk'; +import { api } from '../sdk/api'; Meteor.methods({ addRoomOwner(rid, userId) { @@ -58,7 +58,7 @@ Meteor.methods({ }); if (settings.get('UI_DisplayRoles')) { - StreamService.sendRoleUpdate({ + api.broadcast('user.roleUpdate', { type: 'added', _id: 'owner', u: { diff --git a/server/methods/removeRoomLeader.js b/server/methods/removeRoomLeader.js index cadb8010963bd..b88a6a7729dc1 100644 --- a/server/methods/removeRoomLeader.js +++ b/server/methods/removeRoomLeader.js @@ -4,7 +4,7 @@ import { check } from 'meteor/check'; import { hasPermission } from '../../app/authorization'; import { Users, Subscriptions, Messages } from '../../app/models'; import { settings } from '../../app/settings'; -import { StreamService } from '../sdk'; +import { api } from '../sdk/api'; Meteor.methods({ removeRoomLeader(rid, userId) { @@ -58,7 +58,7 @@ Meteor.methods({ }); if (settings.get('UI_DisplayRoles')) { - StreamService.sendRoleUpdate({ + api.broadcast('user.roleUpdate', { type: 'removed', _id: 'leader', u: { diff --git a/server/methods/removeRoomModerator.js b/server/methods/removeRoomModerator.js index 632474720134c..ef86a9c176be5 100644 --- a/server/methods/removeRoomModerator.js +++ b/server/methods/removeRoomModerator.js @@ -4,7 +4,7 @@ import { check } from 'meteor/check'; import { hasPermission } from '../../app/authorization'; import { Users, Subscriptions, Messages } from '../../app/models'; import { settings } from '../../app/settings'; -import { StreamService } from '../sdk'; +import { api } from '../sdk/api'; Meteor.methods({ removeRoomModerator(rid, userId) { @@ -58,7 +58,7 @@ Meteor.methods({ }); if (settings.get('UI_DisplayRoles')) { - StreamService.sendRoleUpdate({ + api.broadcast('user.roleUpdate', { type: 'removed', _id: 'moderator', u: { diff --git a/server/methods/removeRoomOwner.js b/server/methods/removeRoomOwner.js index 4a3f5cfb39298..b4dcf08ca26cf 100644 --- a/server/methods/removeRoomOwner.js +++ b/server/methods/removeRoomOwner.js @@ -4,7 +4,7 @@ import { check } from 'meteor/check'; import { hasPermission, getUsersInRole } from '../../app/authorization'; import { Users, Subscriptions, Messages } from '../../app/models'; import { settings } from '../../app/settings'; -import { StreamService } from '../sdk'; +import { api } from '../sdk/api'; Meteor.methods({ removeRoomOwner(rid, userId) { @@ -65,7 +65,7 @@ Meteor.methods({ }); if (settings.get('UI_DisplayRoles')) { - StreamService.sendRoleUpdate({ + api.broadcast('user.roleUpdate', { type: 'removed', _id: 'owner', u: { diff --git a/server/sdk/lib/Events.ts b/server/sdk/lib/Events.ts index 7124df4680c16..dd1032231814d 100644 --- a/server/sdk/lib/Events.ts +++ b/server/sdk/lib/Events.ts @@ -32,6 +32,7 @@ export type EventSignatures = { 'subscription'(data: { action: string; subscription: Partial }): void; 'user'(data: { action: string; user: Partial }): void; 'user.avatarUpdate'(user: Partial): void; + 'user.roleUpdate'(update: Record): void; 'user.deleted'(user: Partial): void; 'user.deleteCustomStatus'(userStatus: IUserStatus): void; 'user.updateCustomStatus'(userStatus: IUserStatus): void; diff --git a/server/sdk/types/IStreamService.ts b/server/sdk/types/IStreamService.ts index 7266ad36a3f39..a88a884dae027 100644 --- a/server/sdk/types/IStreamService.ts +++ b/server/sdk/types/IStreamService.ts @@ -2,5 +2,4 @@ import { IServiceClass } from './ServiceClass'; export interface IStreamService extends IServiceClass { sendRoomAvatarUpdate({ rid, etag }: { rid: string; etag?: string }): void; - sendRoleUpdate(update: Record): void; } diff --git a/server/services/listeners/notification.ts b/server/services/listeners/notification.ts index a179a589cddec..673469614fc00 100644 --- a/server/services/listeners/notification.ts +++ b/server/services/listeners/notification.ts @@ -83,5 +83,9 @@ export class NotificationService extends ServiceClass { notifications.notifyLogged('user-status', [_id, username, STATUS_MAP[status], statusText]); }); + + this.onEvent('user.roleUpdate', (update) => { + notifications.notifyLogged('roles-change', update); + }); } } diff --git a/server/services/stream/service.ts b/server/services/stream/service.ts index c22b2ce9cd863..ab598d5700cea 100644 --- a/server/services/stream/service.ts +++ b/server/services/stream/service.ts @@ -70,8 +70,4 @@ export class StreamService extends ServiceClass implements IStreamService { etag, }); } - - sendRoleUpdate(update: Record): void { - this.streamLogged.emit('roles-change', update); - } } From 1681484a586e99f65c0f1479f345c00671005852 Mon Sep 17 00:00:00 2001 From: Alan Sikora Date: Thu, 1 Oct 2020 14:43:53 -0300 Subject: [PATCH 107/198] room.avatarUpdate and removed StreamService and everything related to it --- app/lib/server/functions/setRoomAvatar.js | 4 +- definition/IRoom.ts | 1 + server/sdk/index.ts | 2 - server/sdk/lib/Events.ts | 5 +- server/sdk/types/IStreamService.ts | 5 -- server/services/listeners/notification.ts | 23 ++++--- server/services/startup.ts | 3 - server/services/stream/service.ts | 73 ----------------------- 8 files changed, 21 insertions(+), 95 deletions(-) delete mode 100644 server/sdk/types/IStreamService.ts delete mode 100644 server/services/stream/service.ts diff --git a/app/lib/server/functions/setRoomAvatar.js b/app/lib/server/functions/setRoomAvatar.js index 73c4f403b963a..50be78186a23d 100644 --- a/app/lib/server/functions/setRoomAvatar.js +++ b/app/lib/server/functions/setRoomAvatar.js @@ -3,7 +3,7 @@ import { Meteor } from 'meteor/meteor'; import { RocketChatFile } from '../../../file'; import { FileUpload } from '../../../file-upload'; import { Rooms, Avatars, Messages } from '../../../models/server'; -import { StreamService } from '../../../../server/sdk'; +import { api } from '../../../../server/sdk/api'; export const setRoomAvatar = function(rid, dataURI, user) { const fileStore = FileUpload.getStore('Avatars'); @@ -33,7 +33,7 @@ export const setRoomAvatar = function(rid, dataURI, user) { } Rooms.setAvatarData(rid, 'upload', result.etag); Messages.createRoomSettingsChangedWithTypeRoomIdMessageAndUser('room_changed_avatar', rid, '', user); - StreamService.sendRoomAvatarUpdate({ rid, etag: result.etag }); + api.broadcast('room.avatarUpdate', { rid, avatarETag: result.etag }); }, 500); }); }; diff --git a/definition/IRoom.ts b/definition/IRoom.ts index 30e567a337cd5..cf89dc2787cfb 100644 --- a/definition/IRoom.ts +++ b/definition/IRoom.ts @@ -2,6 +2,7 @@ export interface IRoom { _id: string; prid: string; t: 'c' | 'p' | 'd' | 'l'; + avatarETag?: string; _updatedAt?: Date; tokenpass?: { require: string; diff --git a/server/sdk/index.ts b/server/sdk/index.ts index d5ac2be37b224..d672c82949fea 100644 --- a/server/sdk/index.ts +++ b/server/sdk/index.ts @@ -6,7 +6,6 @@ import { IServiceContext } from './types/ServiceClass'; import { IPresence } from './types/IPresence'; import { IAccount } from './types/IAccount'; import { ILicense } from './types/ILicense'; -import { IStreamService } from './types/IStreamService'; import { IMeteor } from './types/IMeteor'; // TODO think in a way to not have to pass the service name to proxify here as well @@ -14,7 +13,6 @@ export const Authorization = proxify('authorization'); export const Presence = proxify('presence'); export const Account = proxify('accounts'); export const License = proxify('license'); -export const StreamService = proxify('streamer'); export const MeteorService = proxify('meteor'); export const asyncLocalStorage = new AsyncLocalStorage(); diff --git a/server/sdk/lib/Events.ts b/server/sdk/lib/Events.ts index dd1032231814d..fc8f156378865 100644 --- a/server/sdk/lib/Events.ts +++ b/server/sdk/lib/Events.ts @@ -25,6 +25,7 @@ export type EventSignatures = { 'permission.changed'(data: { clientAction: string; data: any }): void; 'role'(data: {type: 'changed' | 'removed' } & Partial): void; 'room'(data: { action: string; room: Partial }): void; + 'room.avatarUpdate'(room: Partial): void; 'setting'(data: { action: string; setting: Partial }): void; 'setting.privateChanged'(data: { clientAction: string; setting: any }): void; 'stream'([streamer, eventName, payload]: [string, string, string]): void; @@ -32,11 +33,11 @@ export type EventSignatures = { 'subscription'(data: { action: string; subscription: Partial }): void; 'user'(data: { action: string; user: Partial }): void; 'user.avatarUpdate'(user: Partial): void; - 'user.roleUpdate'(update: Record): void; 'user.deleted'(user: Partial): void; 'user.deleteCustomStatus'(userStatus: IUserStatus): void; - 'user.updateCustomStatus'(userStatus: IUserStatus): void; 'user.nameChanged'(user: Partial): void; 'user.name'(data: { action: string; user: Partial }): void; + 'user.roleUpdate'(update: Record): void; + 'user.updateCustomStatus'(userStatus: IUserStatus): void; 'userpresence'(data: { action: string; user: Partial }): void; } diff --git a/server/sdk/types/IStreamService.ts b/server/sdk/types/IStreamService.ts deleted file mode 100644 index a88a884dae027..0000000000000 --- a/server/sdk/types/IStreamService.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { IServiceClass } from './ServiceClass'; - -export interface IStreamService extends IServiceClass { - sendRoomAvatarUpdate({ rid, etag }: { rid: string; etag?: string }): void; -} diff --git a/server/services/listeners/notification.ts b/server/services/listeners/notification.ts index 673469614fc00..d320f5de5b659 100644 --- a/server/services/listeners/notification.ts +++ b/server/services/listeners/notification.ts @@ -40,6 +40,13 @@ export class NotificationService extends ServiceClass { notifications.notifyLogged('permissions-changed', clientAction, data); }); + this.onEvent('room.avatarUpdate', ({ _id: rid, avatarETag: etag }) => { + notifications.notifyLogged('updateAvatar', { + rid, + etag, + }); + }); + this.onEvent('setting.privateChanged', ({ clientAction, setting }) => { notifications.notifyLogged('private-settings-changed', clientAction, setting); }); @@ -63,16 +70,14 @@ export class NotificationService extends ServiceClass { }); }); - this.onEvent('user.updateCustomStatus', (userStatus) => { - notifications.notifyLogged('updateCustomUserStatus', { - userStatusData: userStatus, - }); - }); - this.onEvent('user.nameChanged', (user) => { notifications.notifyLogged('Users:NameChanged', user); }); + this.onEvent('user.roleUpdate', (update) => { + notifications.notifyLogged('roles-change', update); + }); + this.onEvent('userpresence', ({ user }) => { const { _id, username, status, statusText, @@ -84,8 +89,10 @@ export class NotificationService extends ServiceClass { notifications.notifyLogged('user-status', [_id, username, STATUS_MAP[status], statusText]); }); - this.onEvent('user.roleUpdate', (update) => { - notifications.notifyLogged('roles-change', update); + this.onEvent('user.updateCustomStatus', (userStatus) => { + notifications.notifyLogged('updateCustomUserStatus', { + userStatusData: userStatus, + }); }); } } diff --git a/server/services/startup.ts b/server/services/startup.ts index 01f58a2e15b4f..1d2ec402a75de 100644 --- a/server/services/startup.ts +++ b/server/services/startup.ts @@ -2,12 +2,9 @@ import { MongoInternals } from 'meteor/mongo'; import { api } from '../sdk/api'; import { Authorization } from './authorization/service'; -import { StreamService } from './stream/service'; import { MeteorService } from './meteor/service'; import { NotificationService } from './listeners/notification'; -import { Stream } from '../../app/notifications/server/lib/Notifications'; api.registerService(new Authorization(MongoInternals.defaultRemoteCollectionDriver().mongo.db)); api.registerService(new MeteorService()); -api.registerService(new StreamService(Stream)); api.registerService(new NotificationService()); diff --git a/server/services/stream/service.ts b/server/services/stream/service.ts deleted file mode 100644 index ab598d5700cea..0000000000000 --- a/server/services/stream/service.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { ServiceClass } from '../../sdk/types/ServiceClass'; -import { IStreamService } from '../../sdk/types/IStreamService'; -// import { Notifications } from '../../../app/notifications/server'; -import { IStreamer, IStreamerConstructor } from '../../modules/streamer/streamer.module'; - -export class StreamService extends ServiceClass implements IStreamService { - protected name = 'streamer'; - - private streamLogged: IStreamer; - - private streamAll: IStreamer; - - private streamRoom: IStreamer; - - private streamRoomUsers: IStreamer; - - private streamUser: IStreamer; - - constructor(Streamer: IStreamerConstructor) { - super(); - - this.streamLogged = new Streamer('notify-logged'); - this.streamAll = new Streamer('notify-all'); - this.streamRoom = new Streamer('notify-room'); - this.streamRoomUsers = new Streamer('notify-room-users'); - this.streamUser = new Streamer('notify-user'); - - this.streamAll.allowWrite('none'); - this.streamLogged.allowWrite('none'); - this.streamRoom.allowWrite('none'); - // TODO: Missing models for this code - // this.streamRoomUsers.allowWrite(function(eventName, ...args) { - // const [roomId, e] = eventName.split('/'); - // if (Subscriptions.findOneByRoomIdAndUserId(roomId, this.userId) != null) { - // const subscriptions = Subscriptions.findByRoomIdAndNotUserId(roomId, this.userId).fetch(); - // subscriptions.forEach((subscription) => self.notifyUser(subscription.u._id, e, ...args)); - // } - // return false; - // }); - this.streamUser.allowWrite('logged'); - this.streamAll.allowRead('all'); - this.streamLogged.allowRead('logged'); - // TODO: Missing models for this code - // this.streamRoom.allowRead(function(eventName, extraData) { - // const [roomId] = eventName.split('/'); - // const room = Rooms.findOneById(roomId); - // if (!room) { - // console.warn(`Invalid streamRoom eventName: "${ eventName }"`); - // return false; - // } - // if (room.t === 'l' && extraData && extraData.token && room.v.token === extraData.token) { - // return true; - // } - // if (this.userId == null) { - // return false; - // } - // const subscription = Subscriptions.findOneByRoomIdAndUserId(roomId, this.userId, { fields: { _id: 1 } }); - // return subscription != null; - // }); - this.streamRoomUsers.allowRead('none'); - this.streamUser.allowRead(async function(eventName) { - const [userId] = eventName.split('/'); - return (this.userId != null) && this.userId === userId; - }); - } - - sendRoomAvatarUpdate({ rid, etag }: { rid: string; etag?: string }): void { - this.streamLogged.emit('updateAvatar', { - rid, - etag, - }); - } -} From fb27e6f4cfad8d2b11afc52805b57d19cb16a6d2 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Thu, 1 Oct 2020 15:44:22 -0300 Subject: [PATCH 108/198] Move streamRoomUsers to inside notifications module --- app/models/server/raw/Subscriptions.js | 13 +++++++++- app/notifications/server/lib/Notifications.ts | 26 ++++++++----------- .../notifications/notifications.module.ts | 12 ++++++++- 3 files changed, 34 insertions(+), 17 deletions(-) diff --git a/app/models/server/raw/Subscriptions.js b/app/models/server/raw/Subscriptions.js index 8860ebc213283..a437d469aa8f2 100644 --- a/app/models/server/raw/Subscriptions.js +++ b/app/models/server/raw/Subscriptions.js @@ -1,7 +1,7 @@ import { BaseRaw } from './BaseRaw'; export class SubscriptionsRaw extends BaseRaw { - findOneByRoomIdAndUserId(rid, uid, options) { + findOneByRoomIdAndUserId(rid, uid, options = {}) { const query = { rid, 'u._id': uid, @@ -10,6 +10,17 @@ export class SubscriptionsRaw extends BaseRaw { return this.col.findOne(query, options); } + findByRoomIdAndNotUserId(roomId, userId, options = {}) { + const query = { + rid: roomId, + 'u._id': { + $ne: userId, + }, + }; + + return this.col.find(query, options); + } + countByRoomIdAndUserId(rid, uid) { const query = { rid, diff --git a/app/notifications/server/lib/Notifications.ts b/app/notifications/server/lib/Notifications.ts index 8c62461904855..39e7c1b54840c 100644 --- a/app/notifications/server/lib/Notifications.ts +++ b/app/notifications/server/lib/Notifications.ts @@ -8,10 +8,20 @@ import { Subscriptions as SubscriptionsRaw, Rooms as RoomsRaw } from '../../../m import { settings } from '../../../settings/server'; import { NotificationsModule } from '../../../../server/modules/notifications/notifications.module'; import { hasPermission, hasAtLeastOnePermission } from '../../../authorization/server'; -import { Streamer, Publication, DDPSubscription } from '../../../../server/modules/streamer/streamer.module'; +import { Streamer, Publication, DDPSubscription, StreamerCentral } from '../../../../server/modules/streamer/streamer.module'; import { ISubscription } from '../../../../definition/ISubscription'; import { IUser } from '../../../../definition/IUser'; import { roomTypes } from '../../../utils/server'; +import { api } from '../../../../server/sdk/api'; + +// TODO: Replace this in favor of the api.broadcast +StreamerCentral.on('broadcast', (name, eventName, args) => { + api.broadcast('stream', [ + name, + eventName, + args, + ]); +}); export class Stream extends Streamer { registerPublication(name: string, fn: (eventName: string, options: boolean | {useCollection?: boolean; args?: any}) => void): void { @@ -128,20 +138,6 @@ notifications.configure({ export default notifications; -notifications.streamRoomUsers.allowWrite(async function(eventName, ...args) { - const [roomId, e] = eventName.split('/'); - // const user = Meteor.users.findOne(this.userId, { - // fields: { - // username: 1 - // } - // }); - if (Subscriptions.findOneByRoomIdAndUserId(roomId, this.userId) != null) { - const subscriptions: ISubscription[] = Subscriptions.findByRoomIdAndNotUserId(roomId, this.userId).fetch(); - subscriptions.forEach((subscription) => notifications.notifyUser(subscription.u._id, e, ...args)); - } - return false; -}); - notifications.streamRoom.allowRead(async function(eventName, extraData) { const [roomId] = eventName.split('/'); const room = Rooms.findOneById(roomId); diff --git a/server/modules/notifications/notifications.module.ts b/server/modules/notifications/notifications.module.ts index 3f138fc0de096..4c7803e860d03 100644 --- a/server/modules/notifications/notifications.module.ts +++ b/server/modules/notifications/notifications.module.ts @@ -2,6 +2,7 @@ import { IStreamer, IStreamerConstructor } from '../streamer/streamer.module'; import { Authorization } from '../../sdk'; import { RoomsRaw } from '../../../app/models/server/raw/Rooms'; import { SubscriptionsRaw } from '../../../app/models/server/raw/Subscriptions'; +import { ISubscription } from '../../../definition/ISubscription'; interface IModelsParam { Rooms: RoomsRaw; @@ -69,6 +70,8 @@ export class NotificationsModule { } async configure({ Rooms, Subscriptions }: IModelsParam): Promise { + const notifyUser = this.notifyUser.bind(this); + this.streamRoomMessage.allowWrite('none'); this.streamRoomMessage.allowRead(async function(eventName /* , args*/) { const room = await Rooms.findOneById(eventName); @@ -126,7 +129,14 @@ export class NotificationsModule { // notifications.streamRoom.allowWrite(function(eventName, username, typing, extraData) { // Implemented outside this.streamRoomUsers.allowRead('none'); - // this.streamRoomUsers.allowWrite(function(eventName, ...args) { // Implemented outside + this.streamRoomUsers.allowWrite(async function(eventName, ...args) { + const [roomId, e] = eventName.split('/'); + if (await Subscriptions.countByRoomIdAndUserId(roomId, this.userId) > 0) { + const subscriptions: ISubscription[] = await Subscriptions.findByRoomIdAndNotUserId(roomId, this.userId, { project: { 'u._id': 1, _id: 0 } }).toArray(); + subscriptions.forEach((subscription) => notifyUser(subscription.u._id, e, ...args)); + } + return false; + }); this.streamUser.allowWrite('logged'); this.streamUser.allowRead(async function(eventName) { From 84f18d1d80a258b529f48ae60c25949956ddf1fd Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Thu, 1 Oct 2020 17:29:47 -0300 Subject: [PATCH 109/198] notify-room stream permissions --- app/models/server/raw/Subscriptions.js | 2 +- app/notifications/server/lib/Notifications.ts | 9 ++- .../services/DDPStreamer/streams/index.ts | 57 ++-------------- .../notifications/notifications.module.ts | 65 +++++++++++++++++-- 4 files changed, 76 insertions(+), 57 deletions(-) diff --git a/app/models/server/raw/Subscriptions.js b/app/models/server/raw/Subscriptions.js index a437d469aa8f2..d1b6469b429c2 100644 --- a/app/models/server/raw/Subscriptions.js +++ b/app/models/server/raw/Subscriptions.js @@ -27,7 +27,7 @@ export class SubscriptionsRaw extends BaseRaw { 'u._id': uid, }; - const cursor = this.col.find(query); + const cursor = this.col.find(query, { projection: { _id: 0 } }); return cursor.count(); } diff --git a/app/notifications/server/lib/Notifications.ts b/app/notifications/server/lib/Notifications.ts index 39e7c1b54840c..1dbc283211925 100644 --- a/app/notifications/server/lib/Notifications.ts +++ b/app/notifications/server/lib/Notifications.ts @@ -4,7 +4,6 @@ import { DDPCommon } from 'meteor/ddp-common'; import { WEB_RTC_EVENTS } from '../../../webrtc'; import { Subscriptions, Rooms, LivechatRooms } from '../../../models/server'; -import { Subscriptions as SubscriptionsRaw, Rooms as RoomsRaw } from '../../../models/server/raw'; import { settings } from '../../../settings/server'; import { NotificationsModule } from '../../../../server/modules/notifications/notifications.module'; import { hasPermission, hasAtLeastOnePermission } from '../../../authorization/server'; @@ -13,6 +12,12 @@ import { ISubscription } from '../../../../definition/ISubscription'; import { IUser } from '../../../../definition/IUser'; import { roomTypes } from '../../../utils/server'; import { api } from '../../../../server/sdk/api'; +import { + Subscriptions as SubscriptionsRaw, + Rooms as RoomsRaw, + Users as UsersRaw, + Settings as SettingsRaw, +} from '../../../models/server/raw'; // TODO: Replace this in favor of the api.broadcast StreamerCentral.on('broadcast', (name, eventName, args) => { @@ -134,6 +139,8 @@ const notifications = new NotificationsModule(Stream, RoomStreamer, MessageStrea notifications.configure({ Rooms: RoomsRaw, Subscriptions: SubscriptionsRaw, + Users: UsersRaw, + Settings: SettingsRaw, }); export default notifications; diff --git a/ee/server/services/DDPStreamer/streams/index.ts b/ee/server/services/DDPStreamer/streams/index.ts index 75d7dd77bdaf1..6f4ed60b6e3e0 100644 --- a/ee/server/services/DDPStreamer/streams/index.ts +++ b/ee/server/services/DDPStreamer/streams/index.ts @@ -2,11 +2,15 @@ import { Stream } from '../Streamer'; import { NotificationsModule } from '../../../../../server/modules/notifications/notifications.module'; import { ISubscription } from '../../../../../definition/ISubscription'; import { IRoom } from '../../../../../definition/IRoom'; +import { IUser } from '../../../../../definition/IUser'; +import { ISetting } from '../../../../../definition/ISetting'; import { getCollection, Collections, getConnection } from '../../mongo'; // import { Authorization } from '../../../../../server/sdk'; import { Publication } from '../../../../../server/modules/streamer/streamer.module'; import { RoomsRaw } from '../../../../../app/models/server/raw/Rooms'; import { SubscriptionsRaw } from '../../../../../app/models/server/raw/Subscriptions'; +import { UsersRaw } from '../../../../../app/models/server/raw/Users'; +import { SettingsRaw } from '../../../../../app/models/server/raw/Settings'; export class RoomStreamer extends Stream { async _publish(publication: Publication, eventName = '', options: boolean | {useCollection?: boolean; args?: any} = false): Promise { @@ -109,6 +113,8 @@ getConnection() notifications.configure({ Rooms: new RoomsRaw(db.collection(Collections.Rooms)), Subscriptions: new SubscriptionsRaw(db.collection(Collections.Subscriptions)), + Users: new UsersRaw(db.collection(Collections.User)), + Settings: new SettingsRaw(db.collection(Collections.Settings)), }); }); @@ -149,54 +155,3 @@ export default notifications; // } // return false; // }); - -// TODO: Implement permission -// notifications.streamRoom.allowRead(function(eventName, extraData) { -// const [roomId] = eventName.split('/'); -// const room = Rooms.findOneById(roomId); -// if (!room) { -// console.warn(`Invalid streamRoom eventName: "${ eventName }"`); -// return false; -// } -// if (room.t === 'l' && extraData && extraData.token && room.v.token === extraData.token) { -// return true; -// } -// if (this.userId == null) { -// return false; -// } -// const subscription = Subscriptions.findOneByRoomIdAndUserId(roomId, this.userId, { fields: { _id: 1 } }); -// return subscription != null; -// }); - -// TODO: Implement permission -// notifications.streamRoom.allowWrite(function(eventName, username, typing, extraData) { -// const [roomId, e] = eventName.split('/'); - -// if (isNaN(e) ? e === WEB_RTC_EVENTS.WEB_RTC : parseFloat(e) === WEB_RTC_EVENTS.WEB_RTC) { -// return true; -// } - -// if (e === 'typing') { -// const key = settings.get('UI_Use_Real_Name') ? 'name' : 'username'; -// // typing from livechat widget -// if (extraData && extraData.token) { -// const room = Rooms.findOneById(roomId); -// if (room && room.t === 'l' && room.v.token === extraData.token) { -// return true; -// } -// } - -// const user = Meteor.users.findOne(this.userId, { -// fields: { -// [key]: 1, -// }, -// }); - -// if (!user) { -// return false; -// } - -// return user[key] === username; -// } -// return false; -// }); diff --git a/server/modules/notifications/notifications.module.ts b/server/modules/notifications/notifications.module.ts index 4c7803e860d03..56bcce25608f4 100644 --- a/server/modules/notifications/notifications.module.ts +++ b/server/modules/notifications/notifications.module.ts @@ -3,10 +3,14 @@ import { Authorization } from '../../sdk'; import { RoomsRaw } from '../../../app/models/server/raw/Rooms'; import { SubscriptionsRaw } from '../../../app/models/server/raw/Subscriptions'; import { ISubscription } from '../../../definition/ISubscription'; +import { UsersRaw } from '../../../app/models/server/raw/Users'; +import { SettingsRaw } from '../../../app/models/server/raw/Settings'; interface IModelsParam { Rooms: RoomsRaw; Subscriptions: SubscriptionsRaw; + Users: UsersRaw; + Settings: SettingsRaw; } export class NotificationsModule { @@ -69,7 +73,7 @@ export class NotificationsModule { this.streamRoomData = new this.Streamer('room-data'); } - async configure({ Rooms, Subscriptions }: IModelsParam): Promise { + async configure({ Rooms, Subscriptions, Users, Settings }: IModelsParam): Promise { const notifyUser = this.notifyUser.bind(this); this.streamRoomMessage.allowWrite('none'); @@ -124,9 +128,62 @@ export class NotificationsModule { this.streamLogged.allowWrite('none'); this.streamLogged.allowRead('logged'); - this.streamRoom.allowWrite('none'); - // this.streamRoom.allowRead(function(eventName, extraData) { // Implemented outside - // notifications.streamRoom.allowWrite(function(eventName, username, typing, extraData) { // Implemented outside + this.streamRoom.allowRead(async function(eventName, extraData) { + if (!this.userId) { + return false; + } + + const [rid] = eventName.split('/'); + + // typing from livechat widget + if (extraData?.token) { + // TODO improve this to make a query 'v.token' + const room = await Rooms.findOneById(rid, { projection: { t: 1, 'v.token': 1 } }); + return room && room.t === 'l' && room.v.token === extraData.token; + } + + const subsCount = await Subscriptions.countByRoomIdAndUserId(rid, this.userId); + return subsCount > 0; + }); + + this.streamRoom.allowWrite(async function(eventName, username, _typing, extraData) { + const [rid, e] = eventName.split('/'); + + // TODO should this use WEB_RTC_EVENTS enum? + if (e === 'webrtc') { + return true; + } + + if (e !== 'typing') { + return false; + } + + try { + // TODO consider using something to cache settings + const key = await Settings.getValueById('UI_Use_Real_Name') ? 'name' : 'username'; + + // typing from livechat widget + if (extraData?.token) { + // TODO improve this to make a query 'v.token' + const room = await Rooms.findOneById(rid, { projection: { t: 1, 'v.token': 1 } }); + return room && room.t === 'l' && room.v.token === extraData.token; + } + + const user = await Users.findOneById(this.userId, { + projection: { + [key]: 1, + }, + }); + if (!user) { + return false; + } + + return user[key] === username; + } catch (e) { + console.error(e); + return false; + } + }); this.streamRoomUsers.allowRead('none'); this.streamRoomUsers.allowWrite(async function(eventName, ...args) { From fc276c870599dc27ec7aa83f625335e95917aa00 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Thu, 1 Oct 2020 17:30:15 -0300 Subject: [PATCH 110/198] Fix RoomStreamer/rooms-changed --- ee/server/services/DDPStreamer/streams/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/ee/server/services/DDPStreamer/streams/index.ts b/ee/server/services/DDPStreamer/streams/index.ts index 6f4ed60b6e3e0..8202ded09d94c 100644 --- a/ee/server/services/DDPStreamer/streams/index.ts +++ b/ee/server/services/DDPStreamer/streams/index.ts @@ -26,7 +26,6 @@ export class RoomStreamer extends Stream { }); payload && publication.client?.send( - publication, payload, ); }; From e59f547460646dc1fc3c1b703a9904cf10228b10 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Thu, 1 Oct 2020 17:57:52 -0300 Subject: [PATCH 111/198] Move streamCannedResponses to inside Notifications module --- app/notifications/server/lib/Notifications.ts | 4 ---- server/modules/notifications/notifications.module.ts | 6 ++++-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/app/notifications/server/lib/Notifications.ts b/app/notifications/server/lib/Notifications.ts index 1dbc283211925..d81a2b7c55799 100644 --- a/app/notifications/server/lib/Notifications.ts +++ b/app/notifications/server/lib/Notifications.ts @@ -224,10 +224,6 @@ notifications.streamStdout.allowRead(function() { return this.userId ? hasPermission(this.userId, 'view-logs') : false; }); -notifications.streamCannedResponses.allowRead(function() { - return this.userId && settings.get('Canned_Responses_Enable') && hasPermission(this.userId, 'view-canned-responses'); -}); - notifications.streamAll.allowRead('private-settings-changed', function() { if (this.userId == null) { return false; diff --git a/server/modules/notifications/notifications.module.ts b/server/modules/notifications/notifications.module.ts index 56bcce25608f4..679062e2444bd 100644 --- a/server/modules/notifications/notifications.module.ts +++ b/server/modules/notifications/notifications.module.ts @@ -189,7 +189,7 @@ export class NotificationsModule { this.streamRoomUsers.allowWrite(async function(eventName, ...args) { const [roomId, e] = eventName.split('/'); if (await Subscriptions.countByRoomIdAndUserId(roomId, this.userId) > 0) { - const subscriptions: ISubscription[] = await Subscriptions.findByRoomIdAndNotUserId(roomId, this.userId, { project: { 'u._id': 1, _id: 0 } }).toArray(); + const subscriptions: ISubscription[] = await Subscriptions.findByRoomIdAndNotUserId(roomId, this.userId, { projection: { 'u._id': 1, _id: 0 } }).toArray(); subscriptions.forEach((subscription) => notifyUser(subscription.u._id, e, ...args)); } return false; @@ -216,7 +216,9 @@ export class NotificationsModule { this.streamAppsEngine.allowWrite('none'); this.streamCannedResponses.allowWrite('none'); - // this.streamCannedResponses.allowRead(function() { // Implemented outside + this.streamCannedResponses.allowRead(async function() { + return this.userId && await Settings.getValueById('Canned_Responses_Enable') && Authorization.hasPermission(this.userId, 'view-canned-responses'); + }); this.streamIntegrationHistory.allowWrite('none'); // this.streamIntegrationHistory.allowRead(function() { // Implemented outside From cdb77eb300112d8e8ebfc775ba94e3e85a72735a Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Thu, 1 Oct 2020 18:09:00 -0300 Subject: [PATCH 112/198] Implement integrationHistory stream --- app/notifications/server/lib/Notifications.ts | 7 ------- server/modules/notifications/notifications.module.ts | 10 +++++++++- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/app/notifications/server/lib/Notifications.ts b/app/notifications/server/lib/Notifications.ts index d81a2b7c55799..a3ed55f301ebe 100644 --- a/app/notifications/server/lib/Notifications.ts +++ b/app/notifications/server/lib/Notifications.ts @@ -199,13 +199,6 @@ notifications.streamLivechatQueueData.allowRead(function() { return this.userId ? hasPermission(this.userId, 'view-l-room') : false; }); -notifications.streamIntegrationHistory.allowRead(function() { - return this.userId && hasAtLeastOnePermission(this.userId, [ - 'manage-outgoing-integrations', - 'manage-own-outgoing-integrations', - ]); -}); - notifications.streamLivechatRoom.allowRead(async function(roomId, extraData) { const room = LivechatRooms.findOneById(roomId); diff --git a/server/modules/notifications/notifications.module.ts b/server/modules/notifications/notifications.module.ts index 679062e2444bd..4655f6bc7de63 100644 --- a/server/modules/notifications/notifications.module.ts +++ b/server/modules/notifications/notifications.module.ts @@ -221,7 +221,15 @@ export class NotificationsModule { }); this.streamIntegrationHistory.allowWrite('none'); - // this.streamIntegrationHistory.allowRead(function() { // Implemented outside + this.streamIntegrationHistory.allowRead(async function() { + if (!this.userId) { + return false; + } + return Authorization.hasAtLeastOnePermission(this.userId, [ + 'manage-outgoing-integrations', + 'manage-own-outgoing-integrations', + ]); + }); // this.streamLivechatRoom.allowRead((roomId, extraData) => { // Implemented outside From 29898a5ded20aa7671a0bcf78c1ca81a30491422 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Thu, 1 Oct 2020 18:19:17 -0300 Subject: [PATCH 113/198] Remove old streamRoom allowRead/allowWrite --- app/notifications/server/lib/Notifications.ts | 53 ------------------- 1 file changed, 53 deletions(-) diff --git a/app/notifications/server/lib/Notifications.ts b/app/notifications/server/lib/Notifications.ts index a3ed55f301ebe..9074ac3b5d824 100644 --- a/app/notifications/server/lib/Notifications.ts +++ b/app/notifications/server/lib/Notifications.ts @@ -2,14 +2,11 @@ import { Meteor } from 'meteor/meteor'; import { Promise } from 'meteor/promise'; import { DDPCommon } from 'meteor/ddp-common'; -import { WEB_RTC_EVENTS } from '../../../webrtc'; import { Subscriptions, Rooms, LivechatRooms } from '../../../models/server'; -import { settings } from '../../../settings/server'; import { NotificationsModule } from '../../../../server/modules/notifications/notifications.module'; import { hasPermission, hasAtLeastOnePermission } from '../../../authorization/server'; import { Streamer, Publication, DDPSubscription, StreamerCentral } from '../../../../server/modules/streamer/streamer.module'; import { ISubscription } from '../../../../definition/ISubscription'; -import { IUser } from '../../../../definition/IUser'; import { roomTypes } from '../../../utils/server'; import { api } from '../../../../server/sdk/api'; import { @@ -145,56 +142,6 @@ notifications.configure({ export default notifications; -notifications.streamRoom.allowRead(async function(eventName, extraData) { - const [roomId] = eventName.split('/'); - const room = Rooms.findOneById(roomId); - if (!room) { - console.warn(`Invalid streamRoom eventName: "${ eventName }"`); - return false; - } - if (room.t === 'l' && extraData && extraData.token && room.v.token === extraData.token) { - return true; - } - if (this.userId == null) { - return false; - } - const subscription = Subscriptions.findOneByRoomIdAndUserId(roomId, this.userId, { fields: { _id: 1 } }); - return subscription != null; -}); - -notifications.streamRoom.allowWrite(async function(eventName, username, _typing, extraData) { - const [roomId, e] = eventName.split('/'); - - // if (isNaN(parseFloat(e)) ? e === WEB_RTC_EVENTS.WEB_RTC : parseFloat(e) === WEB_RTC_EVENTS.WEB_RTC) { - if (e === WEB_RTC_EVENTS.WEB_RTC) { - return true; - } - - if (e === 'typing') { - const key = settings.get('UI_Use_Real_Name') ? 'name' : 'username'; - // typing from livechat widget - if (extraData && extraData.token) { - const room = Rooms.findOneById(roomId); - if (room && room.t === 'l' && room.v.token === extraData.token) { - return true; - } - } - - const user = Meteor.users.findOne(this.userId, { - fields: { - [key]: 1, - }, - }) as IUser; - - if (!user) { - return false; - } - - return user[key] === username; - } - return false; -}); - notifications.streamLivechatQueueData.allowRead(function() { return this.userId ? hasPermission(this.userId, 'view-l-room') : false; }); From 051af39067a52624b9321759bc492e3282dc54f7 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Thu, 1 Oct 2020 19:15:09 -0300 Subject: [PATCH 114/198] Fix room access validators --- app/livechat/server/roomAccessValidator.internalService.ts | 7 ++++--- .../server/roomAccessValidator.internalService.ts | 7 ++++--- server/services/authorization/canAccessRoomLivechat.ts | 2 +- server/services/authorization/canAccessRoomTokenpass.ts | 2 +- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/app/livechat/server/roomAccessValidator.internalService.ts b/app/livechat/server/roomAccessValidator.internalService.ts index 9e8df40c4a63a..9a7c8c79396b9 100644 --- a/app/livechat/server/roomAccessValidator.internalService.ts +++ b/app/livechat/server/roomAccessValidator.internalService.ts @@ -1,13 +1,14 @@ import { ServiceClass } from '../../../server/sdk/types/ServiceClass'; import { IAuthorizationLivechat } from '../../../server/sdk/types/IAuthorizationLivechat'; import { validators } from './roomAccessValidator.compatibility'; -import { RoomAccessValidator } from '../../../server/sdk/types/IAuthorization'; import { api } from '../../../server/sdk/api'; +import { IRoom } from '../../../definition/IRoom'; +import { IUser } from '../../../definition/IUser'; class AuthorizationLivechat extends ServiceClass implements IAuthorizationLivechat { - protected name = 'authorization.livechat'; + protected name = 'authorization-livechat'; - canAccessRoom: RoomAccessValidator = async (room, user, extraData): Promise => { + async canAccessRoom(room: Partial, user: Pick, extraData?: object): Promise { for (const validator of validators) { if (validator(room, user, extraData)) { return true; diff --git a/app/tokenpass/server/roomAccessValidator.internalService.ts b/app/tokenpass/server/roomAccessValidator.internalService.ts index b0a9a59eea329..0b5b364040cfa 100644 --- a/app/tokenpass/server/roomAccessValidator.internalService.ts +++ b/app/tokenpass/server/roomAccessValidator.internalService.ts @@ -1,13 +1,14 @@ import { ServiceClass } from '../../../server/sdk/types/ServiceClass'; import { validators } from './roomAccessValidator.compatibility'; -import { RoomAccessValidator } from '../../../server/sdk/types/IAuthorization'; import { api } from '../../../server/sdk/api'; import { IAuthorizationTokenpass } from '../../../server/sdk/types/IAuthorizationTokenpass'; +import { IRoom } from '../../../definition/IRoom'; +import { IUser } from '../../../definition/IUser'; class AuthorizationTokenpass extends ServiceClass implements IAuthorizationTokenpass { - protected name = 'authorization.livechat'; + protected name = 'authorization-tokenpass'; - canAccessRoom: RoomAccessValidator = async (room, user): Promise => { + async canAccessRoom(room: Partial, user: Pick): Promise { for (const validator of validators) { if (validator(room, user)) { return true; diff --git a/server/services/authorization/canAccessRoomLivechat.ts b/server/services/authorization/canAccessRoomLivechat.ts index a184dc100db5b..bf5d02ac2455a 100644 --- a/server/services/authorization/canAccessRoomLivechat.ts +++ b/server/services/authorization/canAccessRoomLivechat.ts @@ -2,7 +2,7 @@ import { IAuthorizationLivechat } from '../../sdk/types/IAuthorizationLivechat'; import { proxify } from '../../sdk/lib/proxify'; import { RoomAccessValidator } from '../../sdk/types/IAuthorization'; -export const AuthorizationLivechat = proxify('authorization.livechat'); +export const AuthorizationLivechat = proxify('authorization-livechat'); export const canAccessRoomLivechat: RoomAccessValidator = async (room, user): Promise => { if (room.t !== 'l') { diff --git a/server/services/authorization/canAccessRoomTokenpass.ts b/server/services/authorization/canAccessRoomTokenpass.ts index 2250e3c469fc7..d5fe6728f20f1 100644 --- a/server/services/authorization/canAccessRoomTokenpass.ts +++ b/server/services/authorization/canAccessRoomTokenpass.ts @@ -2,7 +2,7 @@ import { IAuthorizationTokenpass } from '../../sdk/types/IAuthorizationTokenpass import { proxify } from '../../sdk/lib/proxify'; import { RoomAccessValidator } from '../../sdk/types/IAuthorization'; -export const AuthorizationTokenpass = proxify('authorization.tokenpass'); +export const AuthorizationTokenpass = proxify('authorization-tokenpass'); export const canAccessRoomTokenpass: RoomAccessValidator = async (room, user): Promise => { if (!room.tokenpass) { From 872233126072c1a567f319fd4f3a52d4e36f2ec7 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Thu, 1 Oct 2020 19:18:38 -0300 Subject: [PATCH 115/198] Implement stream room-data --- app/notifications/server/lib/Notifications.ts | 13 ------------- .../services/DDPStreamer/streams/index.ts | 6 ------ .../notifications/notifications.module.ts | 18 +++++++++++++++++- server/publications/room/emitter.js | 9 +++++---- server/stream/rooms/index.js | 14 -------------- 5 files changed, 22 insertions(+), 38 deletions(-) delete mode 100644 server/stream/rooms/index.js diff --git a/app/notifications/server/lib/Notifications.ts b/app/notifications/server/lib/Notifications.ts index 9074ac3b5d824..eea3a814c3364 100644 --- a/app/notifications/server/lib/Notifications.ts +++ b/app/notifications/server/lib/Notifications.ts @@ -170,16 +170,3 @@ notifications.streamAll.allowRead('private-settings-changed', function() { } return hasAtLeastOnePermission(this.userId, ['view-privileged-setting', 'edit-privileged-setting', 'manage-selected-settings']); }); - -notifications.streamRoomData.allowRead(function(rid) { - try { - const room = Meteor.call('canAccessRoom', rid, this.userId); - if (!room) { - return false; - } - - return roomTypes.getConfig(room.t).isEmitAllowed(); - } catch (error) { - return false; - } -}); diff --git a/ee/server/services/DDPStreamer/streams/index.ts b/ee/server/services/DDPStreamer/streams/index.ts index 8202ded09d94c..a0e2590aa1589 100644 --- a/ee/server/services/DDPStreamer/streams/index.ts +++ b/ee/server/services/DDPStreamer/streams/index.ts @@ -124,12 +124,6 @@ export default notifications; // return !!this.userId && Authorization.canAccessRoom({ _id: rid }, { _id: this.userId }); // }); - -// export const streamRoomData = new Stream(STREAM_NAMES.ROOM_DATA); -// notifications.streamRoomData.allowRead(async function(rid) { -// return !!this.userId && Authorization.canAccessRoom({ _id: rid }, { _id: this.userId }); -// }); - // notifications.streamLivechatQueueData.allowRead(async function() { // return !!this.userId && Authorization.hasPermission(this.userId, 'view-l-room'); // }); diff --git a/server/modules/notifications/notifications.module.ts b/server/modules/notifications/notifications.module.ts index 4655f6bc7de63..1fe98f78ad6cb 100644 --- a/server/modules/notifications/notifications.module.ts +++ b/server/modules/notifications/notifications.module.ts @@ -240,7 +240,23 @@ export class NotificationsModule { // this.streamStdout.allowRead(function() { // Implemented outside this.streamRoomData.allowWrite('none'); - // this.streamRoomData.allowRead(function(rid) { // Implemented outside + this.streamRoomData.allowRead(async function(rid) { + try { + const room = await Rooms.findOneById(rid); + if (!room) { + return false; + } + + const canAccess = await Authorization.canAccessRoom(room, { _id: this.userId }); + if (!canAccess) { + return false; + } + + return true; + } catch (error) { + return false; + } + }); this.streamRoles.allowWrite('none'); this.streamRoles.allowRead('logged'); diff --git a/server/publications/room/emitter.js b/server/publications/room/emitter.js index ad5dabb524fe6..0c47dad371696 100644 --- a/server/publications/room/emitter.js +++ b/server/publications/room/emitter.js @@ -1,6 +1,6 @@ -import { emitRoomDataEvent } from '../../stream/rooms'; -import { Rooms, Subscriptions } from '../../../app/models'; -import { Notifications } from '../../../app/notifications'; +import { Rooms, Subscriptions } from '../../../app/models/server'; +import { Notifications } from '../../../app/notifications/server'; +import notifications from '../../../app/notifications/server/lib/Notifications'; import { fields } from '.'; @@ -38,5 +38,6 @@ Rooms.on('change', ({ clientAction, id, data }) => { Notifications.streamUser.__emit(id, clientAction, data); - emitRoomDataEvent(id, data); + // TODO validate emitWithoutBroadcast + notifications.streamRoomData.emitWithoutBroadcast(id, clientAction, data); }); diff --git a/server/stream/rooms/index.js b/server/stream/rooms/index.js deleted file mode 100644 index 625184d4769e2..0000000000000 --- a/server/stream/rooms/index.js +++ /dev/null @@ -1,14 +0,0 @@ -import { roomTypes } from '../../../app/utils'; -import notifications from '../../../app/notifications/server/lib/Notifications'; - -export function emitRoomDataEvent(id, data) { - if (!data || !data.t) { - return; - } - - if (!roomTypes.getConfig(data.t).isEmitAllowed()) { - return; - } - - notifications.streamRoomData.emitWithoutBroadcast(id, data); -} From fde5d37dd4603987689d14eb3e49dc7bd6082f9c Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Thu, 1 Oct 2020 19:18:38 -0300 Subject: [PATCH 116/198] Implement stream room-data --- app/notifications/server/lib/Notifications.ts | 13 ------------- .../services/DDPStreamer/streams/index.ts | 6 ------ server/main.js | 1 - .../notifications/notifications.module.ts | 18 +++++++++++++++++- server/publications/room/emitter.js | 9 +++++---- server/stream/rooms/index.js | 14 -------------- 6 files changed, 22 insertions(+), 39 deletions(-) delete mode 100644 server/stream/rooms/index.js diff --git a/app/notifications/server/lib/Notifications.ts b/app/notifications/server/lib/Notifications.ts index 9074ac3b5d824..eea3a814c3364 100644 --- a/app/notifications/server/lib/Notifications.ts +++ b/app/notifications/server/lib/Notifications.ts @@ -170,16 +170,3 @@ notifications.streamAll.allowRead('private-settings-changed', function() { } return hasAtLeastOnePermission(this.userId, ['view-privileged-setting', 'edit-privileged-setting', 'manage-selected-settings']); }); - -notifications.streamRoomData.allowRead(function(rid) { - try { - const room = Meteor.call('canAccessRoom', rid, this.userId); - if (!room) { - return false; - } - - return roomTypes.getConfig(room.t).isEmitAllowed(); - } catch (error) { - return false; - } -}); diff --git a/ee/server/services/DDPStreamer/streams/index.ts b/ee/server/services/DDPStreamer/streams/index.ts index 8202ded09d94c..a0e2590aa1589 100644 --- a/ee/server/services/DDPStreamer/streams/index.ts +++ b/ee/server/services/DDPStreamer/streams/index.ts @@ -124,12 +124,6 @@ export default notifications; // return !!this.userId && Authorization.canAccessRoom({ _id: rid }, { _id: this.userId }); // }); - -// export const streamRoomData = new Stream(STREAM_NAMES.ROOM_DATA); -// notifications.streamRoomData.allowRead(async function(rid) { -// return !!this.userId && Authorization.canAccessRoom({ _id: rid }, { _id: this.userId }); -// }); - // notifications.streamLivechatQueueData.allowRead(async function() { // return !!this.userId && Authorization.hasPermission(this.userId, 'view-l-room'); // }); diff --git a/server/main.js b/server/main.js index 2517271373492..d7c048ba99fc8 100644 --- a/server/main.js +++ b/server/main.js @@ -76,5 +76,4 @@ import './publications/spotlight'; import './publications/subscription'; import './routes/avatar'; import './stream/messages'; -import './stream/rooms'; import './stream/streamBroadcast'; diff --git a/server/modules/notifications/notifications.module.ts b/server/modules/notifications/notifications.module.ts index 4655f6bc7de63..1fe98f78ad6cb 100644 --- a/server/modules/notifications/notifications.module.ts +++ b/server/modules/notifications/notifications.module.ts @@ -240,7 +240,23 @@ export class NotificationsModule { // this.streamStdout.allowRead(function() { // Implemented outside this.streamRoomData.allowWrite('none'); - // this.streamRoomData.allowRead(function(rid) { // Implemented outside + this.streamRoomData.allowRead(async function(rid) { + try { + const room = await Rooms.findOneById(rid); + if (!room) { + return false; + } + + const canAccess = await Authorization.canAccessRoom(room, { _id: this.userId }); + if (!canAccess) { + return false; + } + + return true; + } catch (error) { + return false; + } + }); this.streamRoles.allowWrite('none'); this.streamRoles.allowRead('logged'); diff --git a/server/publications/room/emitter.js b/server/publications/room/emitter.js index ad5dabb524fe6..0c47dad371696 100644 --- a/server/publications/room/emitter.js +++ b/server/publications/room/emitter.js @@ -1,6 +1,6 @@ -import { emitRoomDataEvent } from '../../stream/rooms'; -import { Rooms, Subscriptions } from '../../../app/models'; -import { Notifications } from '../../../app/notifications'; +import { Rooms, Subscriptions } from '../../../app/models/server'; +import { Notifications } from '../../../app/notifications/server'; +import notifications from '../../../app/notifications/server/lib/Notifications'; import { fields } from '.'; @@ -38,5 +38,6 @@ Rooms.on('change', ({ clientAction, id, data }) => { Notifications.streamUser.__emit(id, clientAction, data); - emitRoomDataEvent(id, data); + // TODO validate emitWithoutBroadcast + notifications.streamRoomData.emitWithoutBroadcast(id, clientAction, data); }); diff --git a/server/stream/rooms/index.js b/server/stream/rooms/index.js deleted file mode 100644 index 625184d4769e2..0000000000000 --- a/server/stream/rooms/index.js +++ /dev/null @@ -1,14 +0,0 @@ -import { roomTypes } from '../../../app/utils'; -import notifications from '../../../app/notifications/server/lib/Notifications'; - -export function emitRoomDataEvent(id, data) { - if (!data || !data.t) { - return; - } - - if (!roomTypes.getConfig(data.t).isEmitAllowed()) { - return; - } - - notifications.streamRoomData.emitWithoutBroadcast(id, data); -} From d9da8b94b6d19bfbfbe718a9d4e2fb6eebc63336 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Thu, 1 Oct 2020 19:56:39 -0300 Subject: [PATCH 117/198] Parse both websocket formats correctly --- ee/server/services/DDPStreamer/Client.ts | 1 + ee/server/services/DDPStreamer/DDPStreamer.ts | 6 +----- ee/server/services/DDPStreamer/Server.ts | 2 +- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/ee/server/services/DDPStreamer/Client.ts b/ee/server/services/DDPStreamer/Client.ts index 1bb1177d74c62..3794ba2d4231e 100644 --- a/ee/server/services/DDPStreamer/Client.ts +++ b/ee/server/services/DDPStreamer/Client.ts @@ -134,6 +134,7 @@ export class Client extends EventEmitter { } this.process(packet.msg, packet); } catch (err) { + console.error(err); return this.ws.close( WS_ERRORS.UNSUPPORTED_DATA, WS_ERRORS_MESSAGES.UNSUPPORTED_DATA, diff --git a/ee/server/services/DDPStreamer/DDPStreamer.ts b/ee/server/services/DDPStreamer/DDPStreamer.ts index e330570a17d80..abf809becbeff 100644 --- a/ee/server/services/DDPStreamer/DDPStreamer.ts +++ b/ee/server/services/DDPStreamer/DDPStreamer.ts @@ -58,11 +58,7 @@ httpServer.listen(port); const wss = new WebSocket.Server({ server: httpServer }); -wss.on('connection', (ws, req) => { - const isMobile = /^RC Mobile/.test(req.headers['user-agent'] || ''); - - return isMobile ? new Client(ws) : new MeteorClient(ws); -}); +wss.on('connection', (ws, req) => (req.url === '/websocket' ? new Client(ws) : new MeteorClient(ws))); // export default { // name: 'streamer', diff --git a/ee/server/services/DDPStreamer/Server.ts b/ee/server/services/DDPStreamer/Server.ts index 503cba7e4556a..d21eeb06e7929 100644 --- a/ee/server/services/DDPStreamer/Server.ts +++ b/ee/server/services/DDPStreamer/Server.ts @@ -27,7 +27,7 @@ export class Server extends EventEmitter { serialize = ejson.stringify; parse = (packet: string): IPacket => { - const [payload] = JSON.parse(packet); + const payload = packet.startsWith('[') ? JSON.parse(packet)[0] : packet; return ejson.parse(payload); } From 7967d7dbcf51e66def4ff3de44a39e2690910e38 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Thu, 1 Oct 2020 19:56:57 -0300 Subject: [PATCH 118/198] PM2: Watch modules folder --- ee/server/services/ecosystem.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ee/server/services/ecosystem.config.js b/ee/server/services/ecosystem.config.js index 9a7670901b37e..1c61906d6fc08 100644 --- a/ee/server/services/ecosystem.config.js +++ b/ee/server/services/ecosystem.config.js @@ -14,7 +14,7 @@ module.exports = { name: 'DDPStreamer', }].map((app) => Object.assign(app, { script: app.script || `ts-node ${ app.name }/service.ts`, - watch: app.watch || ['.', '../broker.ts', '../../../server/sdk'], + watch: app.watch || ['.', '../broker.ts', '../../../server/sdk', '../../../server/modules'], instances: 1, env: { MOLECULER_LOG_LEVEL: 'info', From 80398bf73cafe45bdad11f1139510e3f9bc3c04f Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Thu, 1 Oct 2020 20:22:32 -0300 Subject: [PATCH 119/198] Add broker method to call methods on monolith --- app/models/server/raw/Users.js | 9 +++++++++ ee/server/services/DDPStreamer/Client.ts | 2 ++ ee/server/services/DDPStreamer/Server.ts | 6 ++++++ ee/server/services/DDPStreamer/configureServer.ts | 1 + server/main.d.ts | 2 ++ server/sdk/types/IMeteor.ts | 2 ++ server/services/meteor/service.ts | 14 ++++++++++++++ 7 files changed, 36 insertions(+) diff --git a/app/models/server/raw/Users.js b/app/models/server/raw/Users.js index f3b02ad63c84e..527fee492f24d 100644 --- a/app/models/server/raw/Users.js +++ b/app/models/server/raw/Users.js @@ -48,6 +48,15 @@ export class UsersRaw extends BaseRaw { return this.findOne(query, options); } + findOneByIdAndLoginHashedToken(_id, token, options = {}) { + const query = { + _id, + 'services.resume.loginTokens.hashedToken': token, + }; + + return this.findOne(query, options); + } + findByActiveUsersExcept(searchTerm, exceptions, options, searchFields, extraQuery = [], { startsWith = false, endsWith = false } = {}) { if (exceptions == null) { exceptions = []; } if (options == null) { options = {}; } diff --git a/ee/server/services/DDPStreamer/Client.ts b/ee/server/services/DDPStreamer/Client.ts index 3794ba2d4231e..1cef8a54920d4 100644 --- a/ee/server/services/DDPStreamer/Client.ts +++ b/ee/server/services/DDPStreamer/Client.ts @@ -21,6 +21,8 @@ export class Client extends EventEmitter { public userId: string; + public userToken: string; + constructor( public ws: WebSocket, ) { diff --git a/ee/server/services/DDPStreamer/Server.ts b/ee/server/services/DDPStreamer/Server.ts index d21eeb06e7929..4bbf3d7c2d835 100644 --- a/ee/server/services/DDPStreamer/Server.ts +++ b/ee/server/services/DDPStreamer/Server.ts @@ -6,6 +6,7 @@ import { DDP_EVENTS } from './constants'; import { Publication } from './Publication'; import { Client } from './Client'; import { IPacket } from './types/IPacket'; +import { MeteorService } from '../../../../server/sdk'; type SubscriptionFn = (this: Publication, eventName: string, options: object) => void; type MethodFn = (this: Client, ...args: any[]) => any; @@ -34,6 +35,11 @@ export class Server extends EventEmitter { async call(client: Client, packet: IPacket): Promise { try { if (!this._methods.has(packet.method)) { + const result = await MeteorService.callMethodWithToken(client.userId, client.userToken, packet.method, packet.params); + if (result?.result) { + return this.result(client, packet, result.result); + } + throw new Error(`Method '${ packet.method }' doesn't exist`); } const fn = this._methods.get(packet.method); diff --git a/ee/server/services/DDPStreamer/configureServer.ts b/ee/server/services/DDPStreamer/configureServer.ts index daaa84715552f..82afc5edd5658 100644 --- a/ee/server/services/DDPStreamer/configureServer.ts +++ b/ee/server/services/DDPStreamer/configureServer.ts @@ -90,6 +90,7 @@ server.methods({ } this.userId = result.uid; + this.userToken = result.token; this.emit(DDP_EVENTS.LOGGED); diff --git a/server/main.d.ts b/server/main.d.ts index 11c7270856f8d..833263a549335 100644 --- a/server/main.d.ts +++ b/server/main.d.ts @@ -44,6 +44,8 @@ declare module 'meteor/meteor' { const server: any; + const runAsUser: (userId: string, scope: Function) => any; + interface MethodThisType { twoFactorChecked: boolean | undefined; } diff --git a/server/sdk/types/IMeteor.ts b/server/sdk/types/IMeteor.ts index cbb267c7237bf..1f951107feaba 100644 --- a/server/sdk/types/IMeteor.ts +++ b/server/sdk/types/IMeteor.ts @@ -14,4 +14,6 @@ export interface IMeteor extends IServiceClass { getLastAutoUpdateClientVersions(): Promise; getLoginServiceConfiguration(): Promise; + + callMethodWithToken(userId: string, token: string, method: string, args: any[]): Promise; } diff --git a/server/services/meteor/service.ts b/server/services/meteor/service.ts index 8c15491dd7f66..e30ff4798fc79 100644 --- a/server/services/meteor/service.ts +++ b/server/services/meteor/service.ts @@ -4,6 +4,7 @@ import { ServiceConfiguration } from 'meteor/service-configuration'; import { ServiceClass } from '../../sdk/types/ServiceClass'; import { IMeteor, AutoUpdateRecord } from '../../sdk/types/IMeteor'; import { api } from '../../sdk/api'; +import { Users } from '../../../app/models/server/raw/index'; const autoUpdateRecords = new Map(); @@ -34,4 +35,17 @@ export class MeteorService extends ServiceClass implements IMeteor { async getLoginServiceConfiguration(): Promise { return ServiceConfiguration.configurations.find({}, { fields: { secret: 0 } }).fetch(); } + + async callMethodWithToken(userId: string, token: string, method: string, args: any[]): Promise { + const user = await Users.findOneByIdAndLoginHashedToken(userId, token, { projection: { _id: 1 } }); + if (!user) { + return { + result: Meteor.call(method, ...args), + }; + } + + return { + result: Meteor.runAsUser(userId, () => Meteor.call(method, ...args)), + }; + } } From 66ec7a9aaea28ab0f60f2ddb862e497b863fbf9a Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Fri, 2 Oct 2020 08:32:41 -0300 Subject: [PATCH 120/198] Remove isEmitAllowed from roomTypes --- app/livechat/server/roomType.js | 4 ---- app/notifications/server/lib/Notifications.ts | 1 - app/utils/lib/RoomTypeConfig.js | 4 ---- 3 files changed, 9 deletions(-) diff --git a/app/livechat/server/roomType.js b/app/livechat/server/roomType.js index 9d8cb17af7b51..35f34b7740884 100644 --- a/app/livechat/server/roomType.js +++ b/app/livechat/server/roomType.js @@ -31,10 +31,6 @@ class LivechatRoomTypeServer extends LivechatRoomType { const { token } = message; return { token }; } - - isEmitAllowed() { - return true; - } } roomTypes.add(new LivechatRoomTypeServer()); diff --git a/app/notifications/server/lib/Notifications.ts b/app/notifications/server/lib/Notifications.ts index eea3a814c3364..7c929da5ccfe5 100644 --- a/app/notifications/server/lib/Notifications.ts +++ b/app/notifications/server/lib/Notifications.ts @@ -7,7 +7,6 @@ import { NotificationsModule } from '../../../../server/modules/notifications/no import { hasPermission, hasAtLeastOnePermission } from '../../../authorization/server'; import { Streamer, Publication, DDPSubscription, StreamerCentral } from '../../../../server/modules/streamer/streamer.module'; import { ISubscription } from '../../../../definition/ISubscription'; -import { roomTypes } from '../../../utils/server'; import { api } from '../../../../server/sdk/api'; import { Subscriptions as SubscriptionsRaw, diff --git a/app/utils/lib/RoomTypeConfig.js b/app/utils/lib/RoomTypeConfig.js index bc40944c6b3a2..2e67d8f9c0115 100644 --- a/app/utils/lib/RoomTypeConfig.js +++ b/app/utils/lib/RoomTypeConfig.js @@ -233,10 +233,6 @@ export class RoomTypeConfig { return false; } - isEmitAllowed() { - return false; - } - /** * Returns a text which can be used in generic UIs. * @param context The role of the text in the UI-Element From 3a1f5b98d164ad5a4ca0887725b136686ba92bef Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Fri, 2 Oct 2020 09:56:22 -0300 Subject: [PATCH 121/198] Disable react-hooks/exhaustive-deps warning --- .eslintrc | 1 - 1 file changed, 1 deletion(-) diff --git a/.eslintrc b/.eslintrc index ff4471548fc60..b88e66122940b 100644 --- a/.eslintrc +++ b/.eslintrc @@ -27,7 +27,6 @@ "syntax" ], "react-hooks/rules-of-hooks": "error", - "react-hooks/exhaustive-deps": "warn" }, "settings": { "import/resolver": { From 0bf521752c1e8406aa2801d96f7fefac230b8c73 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Fri, 2 Oct 2020 10:01:50 -0300 Subject: [PATCH 122/198] Implement stream stdout --- app/logger/server/streamer.js | 3 ++- app/notifications/server/lib/Notifications.ts | 4 ---- server/modules/notifications/notifications.module.ts | 7 ++++++- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/app/logger/server/streamer.js b/app/logger/server/streamer.js index ae0a930dbb99c..a54fa6cc3209e 100644 --- a/app/logger/server/streamer.js +++ b/app/logger/server/streamer.js @@ -54,7 +54,8 @@ export const StdOut = new class extends EventEmitter { Meteor.startup(() => { const handler = (string, item) => { - notifications.streamStdout.emitWithoutBroadcast('stdout', { + // TODO change to 'emit' from 'emitWithoutBroadcast' because ddp-streamer needs to receive this as well but we may need to think a way to broadcast only if needed + notifications.streamStdout.emit('stdout', { ...item, }); }; diff --git a/app/notifications/server/lib/Notifications.ts b/app/notifications/server/lib/Notifications.ts index 7c929da5ccfe5..87543224aa543 100644 --- a/app/notifications/server/lib/Notifications.ts +++ b/app/notifications/server/lib/Notifications.ts @@ -159,10 +159,6 @@ notifications.streamLivechatRoom.allowRead(async function(roomId, extraData) { return false; }); -notifications.streamStdout.allowRead(function() { - return this.userId ? hasPermission(this.userId, 'view-logs') : false; -}); - notifications.streamAll.allowRead('private-settings-changed', function() { if (this.userId == null) { return false; diff --git a/server/modules/notifications/notifications.module.ts b/server/modules/notifications/notifications.module.ts index 1fe98f78ad6cb..aef96b51f72ae 100644 --- a/server/modules/notifications/notifications.module.ts +++ b/server/modules/notifications/notifications.module.ts @@ -237,7 +237,12 @@ export class NotificationsModule { // this.streamLivechatQueueData.allowRead(function() { // Implemented outside this.streamStdout.allowWrite('none'); - // this.streamStdout.allowRead(function() { // Implemented outside + this.streamStdout.allowRead(async function() { + if (!this.userId) { + return false; + } + return Authorization.hasPermission(this.userId, 'view-logs'); + }); this.streamRoomData.allowWrite('none'); this.streamRoomData.allowRead(async function(rid) { From 87dd68e925a3c56b837f11458c1c143f7cda8a73 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Fri, 2 Oct 2020 12:56:07 -0300 Subject: [PATCH 123/198] Implement livechat:setUpConnection on DDPStreamer --- ee/server/services/DDPStreamer/Client.ts | 13 +++++++++++++ ee/server/services/DDPStreamer/configureServer.ts | 15 +++++++++++++++ server/sdk/types/IMeteor.ts | 2 ++ server/services/meteor/service.ts | 5 +++++ 4 files changed, 35 insertions(+) diff --git a/ee/server/services/DDPStreamer/Client.ts b/ee/server/services/DDPStreamer/Client.ts index 1cef8a54920d4..92c5b2e801acb 100644 --- a/ee/server/services/DDPStreamer/Client.ts +++ b/ee/server/services/DDPStreamer/Client.ts @@ -8,6 +8,11 @@ import { SERVER_ID } from './Server'; import { server } from './configureServer'; import { IPacket } from './types/IPacket'; +interface IConnection { + livechatToken?: string; + onClose(fn: (...args: any[]) => void): void; +} + export class Client extends EventEmitter { private chain = Promise.resolve(); @@ -17,6 +22,8 @@ export class Client extends EventEmitter { public subscriptions = new Map(); + public connection: IConnection; + public wait = false; public userId: string; @@ -28,6 +35,12 @@ export class Client extends EventEmitter { ) { super(); + this.connection = { + onClose: (fn): void => { + this.on('close', fn); + }, + }; + this.renewTimeout(TIMEOUT / 1000); this.ws.on('message', this.handler); this.ws.on('close', (...args) => { diff --git a/ee/server/services/DDPStreamer/configureServer.ts b/ee/server/services/DDPStreamer/configureServer.ts index 82afc5edd5658..508c9fb41d05b 100644 --- a/ee/server/services/DDPStreamer/configureServer.ts +++ b/ee/server/services/DDPStreamer/configureServer.ts @@ -119,6 +119,21 @@ server.methods({ const { userId } = this; return Presence.setStatus(userId, status, statusText); }, + // Copied from /app/livechat/server/methods/setUpConnection.js + 'livechat:setUpConnection'(data = {}) { + const { token } = data; + + if (typeof token !== 'string') { + return new Error('Token must be string'); + } + + if (!this.connection.livechatToken) { + this.connection.livechatToken = token; + this.connection.onClose(() => { + MeteorService.notifyGuestStatusChanged(token, 'offline'); + }); + } + }, }); server.on(DDP_EVENTS.LOGGED, ({ uid, session }) => { diff --git a/server/sdk/types/IMeteor.ts b/server/sdk/types/IMeteor.ts index 1f951107feaba..06b75caed53a6 100644 --- a/server/sdk/types/IMeteor.ts +++ b/server/sdk/types/IMeteor.ts @@ -16,4 +16,6 @@ export interface IMeteor extends IServiceClass { getLoginServiceConfiguration(): Promise; callMethodWithToken(userId: string, token: string, method: string, args: any[]): Promise; + + notifyGuestStatusChanged(token: string, status: string): Promise; } diff --git a/server/services/meteor/service.ts b/server/services/meteor/service.ts index e30ff4798fc79..5fe18623554e8 100644 --- a/server/services/meteor/service.ts +++ b/server/services/meteor/service.ts @@ -5,6 +5,7 @@ import { ServiceClass } from '../../sdk/types/ServiceClass'; import { IMeteor, AutoUpdateRecord } from '../../sdk/types/IMeteor'; import { api } from '../../sdk/api'; import { Users } from '../../../app/models/server/raw/index'; +import { Livechat } from '../../../app/livechat/server'; const autoUpdateRecords = new Map(); @@ -48,4 +49,8 @@ export class MeteorService extends ServiceClass implements IMeteor { result: Meteor.runAsUser(userId, () => Meteor.call(method, ...args)), }; } + + async notifyGuestStatusChanged(token: string, status: string): Promise { + return Livechat.notifyGuestStatusChanged(token, status); + } } From c3c1eecf43189a4751a6842cc3ea84de91b50d34 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Fri, 2 Oct 2020 13:36:09 -0300 Subject: [PATCH 124/198] Suport send message to both wesocket types again --- ee/server/services/DDPStreamer/Client.ts | 25 ++++++++++--------- ee/server/services/DDPStreamer/DDPStreamer.ts | 4 +-- ee/server/services/DDPStreamer/Streamer.ts | 10 +++++--- server/modules/streamer/streamer.module.ts | 1 + 4 files changed, 23 insertions(+), 17 deletions(-) diff --git a/ee/server/services/DDPStreamer/Client.ts b/ee/server/services/DDPStreamer/Client.ts index 92c5b2e801acb..50c6d711ade08 100644 --- a/ee/server/services/DDPStreamer/Client.ts +++ b/ee/server/services/DDPStreamer/Client.ts @@ -32,6 +32,7 @@ export class Client extends EventEmitter { constructor( public ws: WebSocket, + public meteorClient = false, ) { super(); @@ -72,6 +73,9 @@ export class Client extends EventEmitter { greeting(): void { // no greeting by default + if (this.meteorClient) { + return this.ws.send('o'); + } } async callMethod(packet: IPacket): Promise { @@ -157,20 +161,17 @@ export class Client extends EventEmitter { } }; - send(payload: string): void { - return this.ws.send(payload); - } -} - -export class MeteorClient extends Client { - // TODO implement meteor errors - // a["{\"msg\":\"result\",\"id\":\"12\",\"error\":{\"isClientSafe\":true,\"error\":403,\"reason\":\"User has no password set\",\"message\":\"User has no password set [403]\",\"errorType\":\"Meteor.Error\"}}"] - - greeting(): void { - return this.ws.send('o'); + encodePayload(payload: string): string { + if (this.meteorClient) { + return `a${ JSON.stringify([payload]) }`; + } + return payload; } send(payload: string): void { - return this.ws.send(`a${ JSON.stringify([payload]) }`); + return this.ws.send(this.encodePayload(payload)); } } + +// TODO implement meteor errors +// a["{\"msg\":\"result\",\"id\":\"12\",\"error\":{\"isClientSafe\":true,\"error\":403,\"reason\":\"User has no password set\",\"message\":\"User has no password set [403]\",\"errorType\":\"Meteor.Error\"}}"] diff --git a/ee/server/services/DDPStreamer/DDPStreamer.ts b/ee/server/services/DDPStreamer/DDPStreamer.ts index abf809becbeff..aeed7c198a50b 100644 --- a/ee/server/services/DDPStreamer/DDPStreamer.ts +++ b/ee/server/services/DDPStreamer/DDPStreamer.ts @@ -4,7 +4,7 @@ import url from 'url'; import WebSocket from 'ws'; // import PromService from 'moleculer-prometheus'; -import { Client, MeteorClient } from './Client'; +import { Client } from './Client'; // import { STREAMER_EVENTS, STREAM_NAMES } from './constants'; import { isEmpty } from './lib/utils'; import { ServiceClass } from '../../../../server/sdk/types/ServiceClass'; @@ -58,7 +58,7 @@ httpServer.listen(port); const wss = new WebSocket.Server({ server: httpServer }); -wss.on('connection', (ws, req) => (req.url === '/websocket' ? new Client(ws) : new MeteorClient(ws))); +wss.on('connection', (ws, req) => new Client(ws, req.url !== '/websocket')); // export default { // name: 'streamer', diff --git a/ee/server/services/DDPStreamer/Streamer.ts b/ee/server/services/DDPStreamer/Streamer.ts index 8f0dc998e7e47..aad9b6cfc2f05 100644 --- a/ee/server/services/DDPStreamer/Streamer.ts +++ b/ee/server/services/DDPStreamer/Streamer.ts @@ -34,13 +34,17 @@ export class Stream extends Streamer { async sendToManySubscriptions(subscriptions: Set, origin: Connection | undefined, eventName: string, args: any[], msg: string): Promise { // TODO: missing typing - const data = [Buffer.concat((WebSocket as any).Sender.frame(Buffer.from(`a${ JSON.stringify([msg]) }`), { + const options = { fin: true, // sending a single fragment message rsv1: false, // don"t set rsv1 bit (no compression) opcode: 1, // opcode for a text frame mask: false, // set false for client-side readOnly: false, // the data can be modified as needed - }))]; + }; + const data = { + meteor: [Buffer.concat((WebSocket as any).Sender.frame(Buffer.from(`a${ JSON.stringify([msg]) }`), options))], + normal: [Buffer.concat((WebSocket as any).Sender.frame(Buffer.from(msg), options))], + }; for await (const { subscription } of subscriptions) { if (this.retransmitToSelf === false && origin && origin === subscription.connection) { @@ -51,7 +55,7 @@ export class Stream extends Streamer { await new Promise((resolve) => { // TODO: missing typing (subscription.client.ws as any)._sender.sendFrame( - data, + data[subscription.client.meteorClient ? 'meteor' : 'normal'], resolve, ); }); diff --git a/server/modules/streamer/streamer.module.ts b/server/modules/streamer/streamer.module.ts index 71f791d03b7d8..c9a78d1fe40bb 100644 --- a/server/modules/streamer/streamer.module.ts +++ b/server/modules/streamer/streamer.module.ts @@ -11,6 +11,7 @@ class StreamerCentralClass extends EventEmitter { export const StreamerCentral = new StreamerCentralClass(); export type Client = { + meteorClient: boolean; ws: any; userId: string; send: Function; From c31d1a10932124d0f66e13be96bbc454a6d73278 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Fri, 2 Oct 2020 13:36:44 -0300 Subject: [PATCH 125/198] FIx parse of params for subscriptions on DDPStreamer --- ee/server/services/DDPStreamer/Server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ee/server/services/DDPStreamer/Server.ts b/ee/server/services/DDPStreamer/Server.ts index 4bbf3d7c2d835..269b41111795b 100644 --- a/ee/server/services/DDPStreamer/Server.ts +++ b/ee/server/services/DDPStreamer/Server.ts @@ -75,7 +75,7 @@ export class Server extends EventEmitter { } const publication = new Publication(client, packet, this); - const [eventName, ...options] = packet.params; + const [eventName, options] = packet.params; await fn.call(publication, eventName, options); } catch (error) { this.nosub(client, packet, error.toString()); From dc9a507a1c80aab8fe594525822212bb18a12830 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Fri, 2 Oct 2020 13:37:53 -0300 Subject: [PATCH 126/198] Move streamLivechatRoom to inside module --- app/notifications/server/lib/Notifications.ts | 15 +-------------- .../modules/notifications/notifications.module.ts | 14 +++++++++++++- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/app/notifications/server/lib/Notifications.ts b/app/notifications/server/lib/Notifications.ts index 7c929da5ccfe5..a80ff00a2f471 100644 --- a/app/notifications/server/lib/Notifications.ts +++ b/app/notifications/server/lib/Notifications.ts @@ -2,7 +2,7 @@ import { Meteor } from 'meteor/meteor'; import { Promise } from 'meteor/promise'; import { DDPCommon } from 'meteor/ddp-common'; -import { Subscriptions, Rooms, LivechatRooms } from '../../../models/server'; +import { Subscriptions, Rooms } from '../../../models/server'; import { NotificationsModule } from '../../../../server/modules/notifications/notifications.module'; import { hasPermission, hasAtLeastOnePermission } from '../../../authorization/server'; import { Streamer, Publication, DDPSubscription, StreamerCentral } from '../../../../server/modules/streamer/streamer.module'; @@ -145,19 +145,6 @@ notifications.streamLivechatQueueData.allowRead(function() { return this.userId ? hasPermission(this.userId, 'view-l-room') : false; }); -notifications.streamLivechatRoom.allowRead(async function(roomId, extraData) { - const room = LivechatRooms.findOneById(roomId); - - if (!room) { - console.warn(`Invalid eventName: "${ roomId }"`); - return false; - } - - if (room.t === 'l' && extraData && extraData.visitorToken && room.v.token === extraData.visitorToken) { - return true; - } - return false; -}); notifications.streamStdout.allowRead(function() { return this.userId ? hasPermission(this.userId, 'view-logs') : false; diff --git a/server/modules/notifications/notifications.module.ts b/server/modules/notifications/notifications.module.ts index 1fe98f78ad6cb..70034e8e880fa 100644 --- a/server/modules/notifications/notifications.module.ts +++ b/server/modules/notifications/notifications.module.ts @@ -231,7 +231,19 @@ export class NotificationsModule { ]); }); - // this.streamLivechatRoom.allowRead((roomId, extraData) => { // Implemented outside + this.streamLivechatRoom.allowRead(async function(roomId, extraData) { + const room = await Rooms.findOneById(roomId, { projection: { _id: 0, t: 1, v: 1 } }); + + if (!room) { + console.warn(`Invalid eventName: "${ roomId }"`); + return false; + } + + if (room.t === 'l' && extraData?.visitorToken && room.v.token === extraData.visitorToken) { + return true; + } + return false; + }); this.streamLivechatQueueData.allowWrite('none'); // this.streamLivechatQueueData.allowRead(function() { // Implemented outside From 5c63459a2429eb2de672e185eb4eee0b9134c188 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Fri, 2 Oct 2020 13:59:32 -0300 Subject: [PATCH 127/198] Move streamLivechatQueueData to inside module --- app/notifications/server/lib/Notifications.ts | 5 ----- ee/server/services/DDPStreamer/DDPStreamer.ts | 13 ++++++++++--- .../modules/notifications/notifications.module.ts | 4 +++- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/app/notifications/server/lib/Notifications.ts b/app/notifications/server/lib/Notifications.ts index a80ff00a2f471..11e8536743edf 100644 --- a/app/notifications/server/lib/Notifications.ts +++ b/app/notifications/server/lib/Notifications.ts @@ -141,11 +141,6 @@ notifications.configure({ export default notifications; -notifications.streamLivechatQueueData.allowRead(function() { - return this.userId ? hasPermission(this.userId, 'view-l-room') : false; -}); - - notifications.streamStdout.allowRead(function() { return this.userId ? hasPermission(this.userId, 'view-logs') : false; }); diff --git a/ee/server/services/DDPStreamer/DDPStreamer.ts b/ee/server/services/DDPStreamer/DDPStreamer.ts index aeed7c198a50b..05c4f5032210c 100644 --- a/ee/server/services/DDPStreamer/DDPStreamer.ts +++ b/ee/server/services/DDPStreamer/DDPStreamer.ts @@ -99,6 +99,13 @@ wss.on('connection', (ws, req) => new Client(ws, req.url !== '/websocket')); // }, // mixins: PROMETHEUS_PORT !== 'false' ? [PromService] : [], +const types: Record = { + inserted: 'added', + updated: 'changed', + removed: 'removed', +}; +const mountDataToEmit = (type: string, data: object): object => ({ type: types[type], ...data }); + export class DDPStreamer extends ServiceClass { protected name = 'streamer'; @@ -108,11 +115,11 @@ export class DDPStreamer extends ServiceClass { // [STREAM_NAMES.LIVECHAT_INQUIRY]({ action, inquiry }) { this.onEvent('livechat-inquiry-queue-observer', ({ action, inquiry }): void => { if (!inquiry.department) { - notifications.streamLivechatQueueData.emit('public', action, inquiry); + notifications.streamLivechatQueueData.emitWithoutBroadcast('public', mountDataToEmit(action, inquiry)); return; } - notifications.streamLivechatQueueData.emit(`department/${ inquiry.department }`, action, inquiry); - notifications.streamLivechatQueueData.emit(inquiry._id, action, inquiry); + notifications.streamLivechatQueueData.emitWithoutBroadcast(`department/${ inquiry.department }`, mountDataToEmit(action, inquiry)); + notifications.streamLivechatQueueData.emitWithoutBroadcast(inquiry._id, mountDataToEmit(action, inquiry)); }); // [STREAMER_EVENTS.STREAM]([streamer, eventName, payload]) { diff --git a/server/modules/notifications/notifications.module.ts b/server/modules/notifications/notifications.module.ts index 70034e8e880fa..5c0ca29e939f5 100644 --- a/server/modules/notifications/notifications.module.ts +++ b/server/modules/notifications/notifications.module.ts @@ -246,7 +246,9 @@ export class NotificationsModule { }); this.streamLivechatQueueData.allowWrite('none'); - // this.streamLivechatQueueData.allowRead(function() { // Implemented outside + this.streamLivechatQueueData.allowRead(async function() { + return this.userId ? Authorization.hasPermission(this.userId, 'view-l-room') : false; + }); this.streamStdout.allowWrite('none'); // this.streamStdout.allowRead(function() { // Implemented outside From 911f2667208f4942a7da446f35b384714a3d5101 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Fri, 2 Oct 2020 14:03:41 -0300 Subject: [PATCH 128/198] Move streamAll `private-settings-changed` to inside module --- app/notifications/server/lib/Notifications.ts | 7 ------- server/modules/notifications/notifications.module.ts | 6 ++++++ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/app/notifications/server/lib/Notifications.ts b/app/notifications/server/lib/Notifications.ts index 11e8536743edf..9a33a3d5cc593 100644 --- a/app/notifications/server/lib/Notifications.ts +++ b/app/notifications/server/lib/Notifications.ts @@ -144,10 +144,3 @@ export default notifications; notifications.streamStdout.allowRead(function() { return this.userId ? hasPermission(this.userId, 'view-logs') : false; }); - -notifications.streamAll.allowRead('private-settings-changed', function() { - if (this.userId == null) { - return false; - } - return hasAtLeastOnePermission(this.userId, ['view-privileged-setting', 'edit-privileged-setting', 'manage-selected-settings']); -}); diff --git a/server/modules/notifications/notifications.module.ts b/server/modules/notifications/notifications.module.ts index 5c0ca29e939f5..1c8a81ad683eb 100644 --- a/server/modules/notifications/notifications.module.ts +++ b/server/modules/notifications/notifications.module.ts @@ -124,6 +124,12 @@ export class NotificationsModule { this.streamAll.allowWrite('none'); this.streamAll.allowRead('all'); + this.streamAll.allowRead('private-settings-changed', async function() { + if (this.userId == null) { + return false; + } + return Authorization.hasAtLeastOnePermission(this.userId, ['view-privileged-setting', 'edit-privileged-setting', 'manage-selected-settings']); + }); this.streamLogged.allowWrite('none'); this.streamLogged.allowRead('logged'); From be832b8de640424e61645990dcdd92cb56edd90d Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Fri, 2 Oct 2020 15:31:30 -0300 Subject: [PATCH 129/198] Remove commented code --- .../services/DDPStreamer/streams/index.ts | 30 ------------------- 1 file changed, 30 deletions(-) diff --git a/ee/server/services/DDPStreamer/streams/index.ts b/ee/server/services/DDPStreamer/streams/index.ts index a0e2590aa1589..d8af89dab0e0b 100644 --- a/ee/server/services/DDPStreamer/streams/index.ts +++ b/ee/server/services/DDPStreamer/streams/index.ts @@ -118,33 +118,3 @@ getConnection() }); export default notifications; - -// TODO: Implementation not complete -// notifications.streamRoomMessage.allowRead(async function(rid) { -// return !!this.userId && Authorization.canAccessRoom({ _id: rid }, { _id: this.userId }); -// }); - -// notifications.streamLivechatQueueData.allowRead(async function() { -// return !!this.userId && Authorization.hasPermission(this.userId, 'view-l-room'); -// }); - -// TODO: Implement permission -// this.streamCannedResponses.allowRead(function() { // Implemented outside -// this.streamLivechatRoom.allowRead((roomId, extraData) => { // Implemented outside - - -// TODO: Implement permission -// const self = this; -// notifications.streamRoomUsers.allowWrite(function(eventName, ...args) { -// const [roomId, e] = eventName.split('/'); -// // const user = Meteor.users.findOne(this.userId, { -// // fields: { -// // username: 1 -// // } -// // }); -// if (Subscriptions.findOneByRoomIdAndUserId(roomId, this.userId) != null) { -// const subscriptions = Subscriptions.findByRoomIdAndNotUserId(roomId, this.userId).fetch(); -// subscriptions.forEach((subscription) => self.notifyUser(subscription.u._id, e, ...args)); -// } -// return false; -// }); From ddbfc85ca2cfc0bb8603a0ca7d4f1127cec5a2fa Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Fri, 2 Oct 2020 15:41:10 -0300 Subject: [PATCH 130/198] Make RoomStreamer from Notifications more close to the DDPStreamer one --- app/notifications/server/lib/Notifications.ts | 41 +++++++++++++------ .../services/DDPStreamer/streams/index.ts | 13 ++++-- 2 files changed, 38 insertions(+), 16 deletions(-) diff --git a/app/notifications/server/lib/Notifications.ts b/app/notifications/server/lib/Notifications.ts index 512a10fbd223d..07f7441ffe54a 100644 --- a/app/notifications/server/lib/Notifications.ts +++ b/app/notifications/server/lib/Notifications.ts @@ -48,25 +48,40 @@ export class Stream extends Streamer { class RoomStreamer extends Stream { async _publish(publication: Publication, eventName: string, options: boolean | {useCollection?: boolean; args?: any} = false): Promise { await super._publish(publication, eventName, options); - const uid = Meteor.userId(); - if (!uid) { + const userId = Meteor.userId(); + if (!userId) { return; } if (/rooms-changed/.test(eventName)) { - const roomEvent = (...args: any[]): void => publication._session.socket?.send(this.changedPayload(this.subscriptionName, 'id', { - eventName: `${ uid }/rooms-changed`, - args, - })); - const rooms: Pick[] = Subscriptions.find({ 'u._id': uid }, { fields: { rid: 1 } }).fetch(); - rooms.forEach(({ rid }) => { + const roomEvent = (...args: any[]): void => { + const payload = this.changedPayload(this.subscriptionName, 'id', { + eventName: `${ userId }/rooms-changed`, + args, + }); + + publication._session.socket?.send( + payload, + ); + }; + + const subscriptions: Pick[] = Subscriptions.find( + { 'u._id': userId }, + { fields: { rid: 1 } }, + ).fetch(); + + subscriptions.forEach(({ rid }) => { this.on(rid, roomEvent); }); - const userEvent = (clientAction: string, { rid }: {rid: string}): void => { + const userEvent = (clientAction: string, { rid }: Partial = {}): void => { + if (!rid) { + return; + } + switch (clientAction) { case 'inserted': - rooms.push({ rid }); + subscriptions.push({ rid }); this.on(rid, roomEvent); // after a subscription is added need to emit the room again @@ -78,11 +93,11 @@ class RoomStreamer extends Stream { break; } }; - this.on(uid, userEvent); + this.on(userId, userEvent); publication.onStop(() => { - this.removeListener(uid, userEvent); - rooms.forEach(({ rid }) => this.removeListener(rid, roomEvent)); + this.removeListener(userId, userEvent); + subscriptions.forEach(({ rid }) => this.removeListener(rid, roomEvent)); }); } } diff --git a/ee/server/services/DDPStreamer/streams/index.ts b/ee/server/services/DDPStreamer/streams/index.ts index d8af89dab0e0b..acd07a3c5a518 100644 --- a/ee/server/services/DDPStreamer/streams/index.ts +++ b/ee/server/services/DDPStreamer/streams/index.ts @@ -13,10 +13,13 @@ import { UsersRaw } from '../../../../../app/models/server/raw/Users'; import { SettingsRaw } from '../../../../../app/models/server/raw/Settings'; export class RoomStreamer extends Stream { - async _publish(publication: Publication, eventName = '', options: boolean | {useCollection?: boolean; args?: any} = false): Promise { - super._publish(publication, eventName, options); - // const uid = Meteor.userId(); + async _publish(publication: Publication, eventName: string, options: boolean | {useCollection?: boolean; args?: any} = false): Promise { + await super._publish(publication, eventName, options); const { userId } = publication.client; + if (!userId) { + return; + } + if (/rooms-changed/.test(eventName)) { // TODO: change this to serialize only once const roomEvent = (...args: any[]): void => { @@ -50,6 +53,10 @@ export class RoomStreamer extends Stream { case 'inserted': subscriptions.push({ rid }); this.on(rid, roomEvent); + + // From Original Notifications.ts + // after a subscription is added need to emit the room again + // roomEvent('inserted', Rooms.findOneById(rid)); break; case 'removed': From 11a98d01de1643562f8bff9c342fc8e2010213bf Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Fri, 2 Oct 2020 15:47:53 -0300 Subject: [PATCH 131/198] Make MessageStream from Notifications more close to DDPStreamer one --- app/notifications/server/lib/Notifications.ts | 11 ++- .../services/DDPStreamer/streams/index.ts | 76 ++++++++++--------- 2 files changed, 45 insertions(+), 42 deletions(-) diff --git a/app/notifications/server/lib/Notifications.ts b/app/notifications/server/lib/Notifications.ts index 07f7441ffe54a..4b35df7bfa53e 100644 --- a/app/notifications/server/lib/Notifications.ts +++ b/app/notifications/server/lib/Notifications.ts @@ -4,7 +4,6 @@ import { DDPCommon } from 'meteor/ddp-common'; import { Subscriptions, Rooms } from '../../../models/server'; import { NotificationsModule } from '../../../../server/modules/notifications/notifications.module'; -import { hasPermission, hasAtLeastOnePermission } from '../../../authorization/server'; import { Streamer, Publication, DDPSubscription, StreamerCentral } from '../../../../server/modules/streamer/streamer.module'; import { ISubscription } from '../../../../definition/ISubscription'; import { api } from '../../../../server/sdk/api'; @@ -110,21 +109,21 @@ class MessageStream extends Stream { async _publish(publication: Publication, eventName: string, options: boolean | {useCollection?: boolean; args?: any} = false): Promise { await super._publish(publication, eventName, options); - const uid = Meteor.userId(); - if (!uid) { + const userId = Meteor.userId(); + if (!userId) { return; } const userEvent = (clientAction: string, { rid }: {rid: string}): void => { switch (clientAction) { case 'removed': - this.removeListener(uid, userEvent); - const sub = this.getSubscriptionByUserIdAndRoomId(uid, rid); + this.removeListener(userId, userEvent); + const sub = this.getSubscriptionByUserIdAndRoomId(userId, rid); sub && this.removeSubscription(sub, eventName); break; } }; - this.on(uid, userEvent); + this.on(userId, userEvent); } mymessage(eventName: string, args: any[]): void { diff --git a/ee/server/services/DDPStreamer/streams/index.ts b/ee/server/services/DDPStreamer/streams/index.ts index acd07a3c5a518..6f48e21a79c6e 100644 --- a/ee/server/services/DDPStreamer/streams/index.ts +++ b/ee/server/services/DDPStreamer/streams/index.ts @@ -6,7 +6,7 @@ import { IUser } from '../../../../../definition/IUser'; import { ISetting } from '../../../../../definition/ISetting'; import { getCollection, Collections, getConnection } from '../../mongo'; // import { Authorization } from '../../../../../server/sdk'; -import { Publication } from '../../../../../server/modules/streamer/streamer.module'; +import { Publication, DDPSubscription } from '../../../../../server/modules/streamer/streamer.module'; import { RoomsRaw } from '../../../../../app/models/server/raw/Rooms'; import { SubscriptionsRaw } from '../../../../../app/models/server/raw/Subscriptions'; import { UsersRaw } from '../../../../../app/models/server/raw/Users'; @@ -75,41 +75,45 @@ export class RoomStreamer extends Stream { } class MessageStream extends Stream { - // TODO: implement the code bellow - // getSubscriptionByUserIdAndRoomId(userId, rid) { - // return this.subscriptions.find((sub) => sub.eventName === rid && sub.subscription.userId === userId); - // } - - // _publish(publication, eventName, options) { - // super._publish(publication, eventName, options); - // const uid = Meteor.userId(); - - // const userEvent = (clientAction, { rid }) => { - // switch (clientAction) { - // case 'removed': - // this.removeListener(uid, userEvent); - // this.removeSubscription(this.getSubscriptionByUserIdAndRoomId(uid, rid), eventName); - // break; - // } - // }; - // this.on(uid, userEvent); - // } - - // mymessage = (eventName, args) => { - // const subscriptions = this.subscriptionsByEventName[eventName]; - // if (!Array.isArray(subscriptions)) { - // return; - // } - // subscriptions.forEach(({ subscription }) => { - // const options = this.isEmitAllowed(subscription, eventName, args); - // if (options) { - // send(subscription._session, changedPayload(this.subscriptionName, 'id', { - // eventName, - // args: [args, options], - // })); - // } - // }); - // } + getSubscriptionByUserIdAndRoomId(userId: string, rid: string): DDPSubscription | undefined { + return [...this.subscriptions].find((sub) => sub.eventName === rid && sub.subscription.userId === userId); + } + + async _publish(publication: Publication, eventName: string, options: boolean | {useCollection?: boolean; args?: any} = false): Promise { + await super._publish(publication, eventName, options); + const { userId } = publication.client; + if (!userId) { + return; + } + + const userEvent = (clientAction: string, { rid }: {rid: string}): void => { + switch (clientAction) { + case 'removed': + this.removeListener(userId, userEvent); + const sub = this.getSubscriptionByUserIdAndRoomId(userId, rid); + sub && this.removeSubscription(sub, eventName); + break; + } + }; + this.on(userId, userEvent); + } + + mymessage(eventName: string, args: any[]): void { + const subscriptions = this.subscriptionsByEventName.get(eventName); + if (!Array.isArray(subscriptions)) { + return; + } + subscriptions.forEach(async ({ subscription }) => { + // TODO: bring back the options + const options = await this.isEmitAllowed(subscription, eventName, args); + if (options) { + subscription._session.socket?.send(this.changedPayload(this.subscriptionName, 'id', { + eventName, + args: [args, options], + })); + } + }); + } } const notifications = new NotificationsModule(Stream, RoomStreamer, MessageStream); From 710f3c111561b7dbe2a601166ae0d69e00569bba Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Fri, 2 Oct 2020 16:28:12 -0300 Subject: [PATCH 132/198] Use publication._session to get userId and unify Publication type def --- app/notifications/server/lib/Notifications.ts | 10 ++++----- ee/server/services/DDPStreamer/Publication.ts | 15 ++++++------- .../services/DDPStreamer/streams/index.ts | 10 ++++----- server/modules/streamer/streamer.module.ts | 21 ++++++++++--------- 4 files changed, 29 insertions(+), 27 deletions(-) diff --git a/app/notifications/server/lib/Notifications.ts b/app/notifications/server/lib/Notifications.ts index 4b35df7bfa53e..96b1f8d94572c 100644 --- a/app/notifications/server/lib/Notifications.ts +++ b/app/notifications/server/lib/Notifications.ts @@ -4,7 +4,7 @@ import { DDPCommon } from 'meteor/ddp-common'; import { Subscriptions, Rooms } from '../../../models/server'; import { NotificationsModule } from '../../../../server/modules/notifications/notifications.module'; -import { Streamer, Publication, DDPSubscription, StreamerCentral } from '../../../../server/modules/streamer/streamer.module'; +import { Streamer, IPublication, DDPSubscription, StreamerCentral } from '../../../../server/modules/streamer/streamer.module'; import { ISubscription } from '../../../../definition/ISubscription'; import { api } from '../../../../server/sdk/api'; import { @@ -45,9 +45,9 @@ export class Stream extends Streamer { } class RoomStreamer extends Stream { - async _publish(publication: Publication, eventName: string, options: boolean | {useCollection?: boolean; args?: any} = false): Promise { + async _publish(publication: IPublication, eventName: string, options: boolean | {useCollection?: boolean; args?: any} = false): Promise { await super._publish(publication, eventName, options); - const userId = Meteor.userId(); + const { userId } = publication._session; if (!userId) { return; } @@ -107,9 +107,9 @@ class MessageStream extends Stream { return [...this.subscriptions].find((sub) => sub.eventName === rid && sub.subscription.userId === userId); } - async _publish(publication: Publication, eventName: string, options: boolean | {useCollection?: boolean; args?: any} = false): Promise { + async _publish(publication: IPublication, eventName: string, options: boolean | {useCollection?: boolean; args?: any} = false): Promise { await super._publish(publication, eventName, options); - const userId = Meteor.userId(); + const { userId } = publication._session; if (!userId) { return; } diff --git a/ee/server/services/DDPStreamer/Publication.ts b/ee/server/services/DDPStreamer/Publication.ts index 82906acdb565a..e4136140dde63 100644 --- a/ee/server/services/DDPStreamer/Publication.ts +++ b/ee/server/services/DDPStreamer/Publication.ts @@ -3,15 +3,12 @@ import { EventEmitter } from 'events'; import { Server } from './Server'; import { Client } from './Client'; import { IPacket } from './types/IPacket'; +import { IPublication } from '../../../../server/modules/streamer/streamer.module'; -interface ISession { - socket?: { - send: Function; - }; -} +export class Publication extends EventEmitter implements IPublication { + _session: IPublication['_session']; -export class Publication extends EventEmitter { - _session: ISession; + connection: IPublication['connection']; constructor( public client: Client, @@ -25,8 +22,12 @@ export class Publication extends EventEmitter { this.once('stop', () => client.subscriptions.delete(packet.id)); this._session = { + sendAdded: this.added, socket: client, + userId: client.userId, }; + + this.connection = {}; } ready(): void { diff --git a/ee/server/services/DDPStreamer/streams/index.ts b/ee/server/services/DDPStreamer/streams/index.ts index 6f48e21a79c6e..46554827945e5 100644 --- a/ee/server/services/DDPStreamer/streams/index.ts +++ b/ee/server/services/DDPStreamer/streams/index.ts @@ -6,16 +6,16 @@ import { IUser } from '../../../../../definition/IUser'; import { ISetting } from '../../../../../definition/ISetting'; import { getCollection, Collections, getConnection } from '../../mongo'; // import { Authorization } from '../../../../../server/sdk'; -import { Publication, DDPSubscription } from '../../../../../server/modules/streamer/streamer.module'; +import { IPublication, DDPSubscription } from '../../../../../server/modules/streamer/streamer.module'; import { RoomsRaw } from '../../../../../app/models/server/raw/Rooms'; import { SubscriptionsRaw } from '../../../../../app/models/server/raw/Subscriptions'; import { UsersRaw } from '../../../../../app/models/server/raw/Users'; import { SettingsRaw } from '../../../../../app/models/server/raw/Settings'; export class RoomStreamer extends Stream { - async _publish(publication: Publication, eventName: string, options: boolean | {useCollection?: boolean; args?: any} = false): Promise { + async _publish(publication: IPublication, eventName: string, options: boolean | {useCollection?: boolean; args?: any} = false): Promise { await super._publish(publication, eventName, options); - const { userId } = publication.client; + const { userId } = publication._session; if (!userId) { return; } @@ -79,9 +79,9 @@ class MessageStream extends Stream { return [...this.subscriptions].find((sub) => sub.eventName === rid && sub.subscription.userId === userId); } - async _publish(publication: Publication, eventName: string, options: boolean | {useCollection?: boolean; args?: any} = false): Promise { + async _publish(publication: IPublication, eventName: string, options: boolean | {useCollection?: boolean; args?: any} = false): Promise { await super._publish(publication, eventName, options); - const { userId } = publication.client; + const { userId } = publication._session; if (!userId) { return; } diff --git a/server/modules/streamer/streamer.module.ts b/server/modules/streamer/streamer.module.ts index c9a78d1fe40bb..137d0030af123 100644 --- a/server/modules/streamer/streamer.module.ts +++ b/server/modules/streamer/streamer.module.ts @@ -17,12 +17,13 @@ export type Client = { send: Function; } -export type Publication = { +export interface IPublication { onStop: Function; stop: Function; connection: Connection; _session: { sendAdded(publicationName: string, id: string, fields: Record): void; + userId: string; socket?: { send: Function; }; @@ -32,7 +33,7 @@ export type Publication = { client: Client; } -type Rule = (this: Publication, eventName: string, ...args: any) => Promise; +type Rule = (this: IPublication, eventName: string, ...args: any) => Promise; interface IRules { [k: string]: Rule; @@ -42,7 +43,7 @@ export type Connection = any; export type DDPSubscription = { eventName: string; - subscription: Publication; + subscription: IPublication; } export interface IStreamer { @@ -167,7 +168,7 @@ export abstract class Streamer extends EventEmitter implements IStreamer { } private isAllowed(rules: IRules) { - return async (scope: Publication, eventName: string, args: any): Promise => { + return async (scope: IPublication, eventName: string, args: any): Promise => { if (rules[eventName]) { return rules[eventName].call(scope, eventName, ...args); } @@ -176,15 +177,15 @@ export abstract class Streamer extends EventEmitter implements IStreamer { }; } - async isReadAllowed(scope: Publication, eventName: string, args: any): Promise { + async isReadAllowed(scope: IPublication, eventName: string, args: any): Promise { return this.isAllowed(this._allowRead)(scope, eventName, args); } - async isEmitAllowed(scope: Publication, eventName: string, ...args: any[]): Promise { + async isEmitAllowed(scope: IPublication, eventName: string, ...args: any[]): Promise { return this.isAllowed(this._allowEmit)(scope, eventName, args); } - async isWriteAllowed(scope: Publication, eventName: string, args: any): Promise { + async isWriteAllowed(scope: IPublication, eventName: string, args: any): Promise { return this.isAllowed(this._allowWrite)(scope, eventName, args); } @@ -206,7 +207,7 @@ export abstract class Streamer extends EventEmitter implements IStreamer { } } - async _publish(publication: Publication, eventName: string, options: boolean | {useCollection?: boolean; args?: any} = false): Promise { + async _publish(publication: IPublication, eventName: string, options: boolean | {useCollection?: boolean; args?: any} = false): Promise { let useCollection; let args = []; @@ -258,7 +259,7 @@ export abstract class Streamer extends EventEmitter implements IStreamer { iniPublication(): void { const _publish = this._publish.bind(this); - this.registerPublication(this.subscriptionName, async function(this: Publication, eventName: string, options: boolean | {useCollection?: boolean; args?: any}) { + this.registerPublication(this.subscriptionName, async function(this: IPublication, eventName: string, options: boolean | {useCollection?: boolean; args?: any}) { return _publish(this, eventName, options); }); } @@ -272,7 +273,7 @@ export abstract class Streamer extends EventEmitter implements IStreamer { const { retransmit } = this; const method: Record any> = { - async [this.subscriptionName](this: Publication, eventName, ...args): Promise { + async [this.subscriptionName](this: IPublication, eventName, ...args): Promise { if (await isWriteAllowed(this, eventName, args) !== true) { return; } From ca60ba6e5c02df39b52efaf2216e1cf36db81e48 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Fri, 2 Oct 2020 16:33:24 -0300 Subject: [PATCH 133/198] Prevent infinite loop --- app/logger/server/streamer.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/logger/server/streamer.js b/app/logger/server/streamer.js index a54fa6cc3209e..24f7ed60c3a2d 100644 --- a/app/logger/server/streamer.js +++ b/app/logger/server/streamer.js @@ -59,5 +59,9 @@ Meteor.startup(() => { ...item, }); }; - StdOut.on('write', handler); + + // does not emit to StdOut if moleculer log level set to debug because it creates an infinite loop + if (String(process.env.MOLECULER_LOG_LEVEL).toLowerCase() !== 'debug') { + StdOut.on('write', handler); + } }); From d9d626202711a407c53b6a4efe20880130d9ddd0 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Fri, 2 Oct 2020 17:28:21 -0300 Subject: [PATCH 134/198] Add Meteor.bindEnvironment to stream broadcast --- server/stream/streamBroadcast.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/server/stream/streamBroadcast.js b/server/stream/streamBroadcast.js index 4048c0890e25d..cd66cd31fdf7a 100644 --- a/server/stream/streamBroadcast.js +++ b/server/stream/streamBroadcast.js @@ -310,9 +310,7 @@ function startStreamBroadcast() { return results; } - const onBroadcast = function(streamName, eventName, args) { - return broadcast(streamName, eventName, args); - }; + const onBroadcast = Meteor.bindEnvironment(broadcast); let TroubleshootDisableInstanceBroadcast; settings.get('Troubleshoot_Disable_Instance_Broadcast', (key, value) => { From ffe2919d830b2501ff3401b8cdc785f587038079 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Sat, 3 Oct 2020 10:52:01 -0300 Subject: [PATCH 135/198] Unify RoomStreamer and MessageStreamer --- app/notifications/server/lib/Notifications.ts | 106 +---------------- ee/server/services/DDPStreamer/DDPStreamer.ts | 6 +- ee/server/services/DDPStreamer/Streamer.ts | 13 ++- .../services/DDPStreamer/streams/index.ts | 110 +----------------- .../notifications/notifications.module.ts | 88 ++++++++++++-- server/modules/streamer/streamer.module.ts | 38 +++++- server/stream/messages/emitter.js | 7 +- 7 files changed, 137 insertions(+), 231 deletions(-) diff --git a/app/notifications/server/lib/Notifications.ts b/app/notifications/server/lib/Notifications.ts index 96b1f8d94572c..a84a8f2d4d393 100644 --- a/app/notifications/server/lib/Notifications.ts +++ b/app/notifications/server/lib/Notifications.ts @@ -2,10 +2,8 @@ import { Meteor } from 'meteor/meteor'; import { Promise } from 'meteor/promise'; import { DDPCommon } from 'meteor/ddp-common'; -import { Subscriptions, Rooms } from '../../../models/server'; import { NotificationsModule } from '../../../../server/modules/notifications/notifications.module'; -import { Streamer, IPublication, DDPSubscription, StreamerCentral } from '../../../../server/modules/streamer/streamer.module'; -import { ISubscription } from '../../../../definition/ISubscription'; +import { Streamer, StreamerCentral } from '../../../../server/modules/streamer/streamer.module'; import { api } from '../../../../server/sdk/api'; import { Subscriptions as SubscriptionsRaw, @@ -44,107 +42,7 @@ export class Stream extends Streamer { } } -class RoomStreamer extends Stream { - async _publish(publication: IPublication, eventName: string, options: boolean | {useCollection?: boolean; args?: any} = false): Promise { - await super._publish(publication, eventName, options); - const { userId } = publication._session; - if (!userId) { - return; - } - - if (/rooms-changed/.test(eventName)) { - const roomEvent = (...args: any[]): void => { - const payload = this.changedPayload(this.subscriptionName, 'id', { - eventName: `${ userId }/rooms-changed`, - args, - }); - - publication._session.socket?.send( - payload, - ); - }; - - const subscriptions: Pick[] = Subscriptions.find( - { 'u._id': userId }, - { fields: { rid: 1 } }, - ).fetch(); - - subscriptions.forEach(({ rid }) => { - this.on(rid, roomEvent); - }); - - const userEvent = (clientAction: string, { rid }: Partial = {}): void => { - if (!rid) { - return; - } - - switch (clientAction) { - case 'inserted': - subscriptions.push({ rid }); - this.on(rid, roomEvent); - - // after a subscription is added need to emit the room again - roomEvent('inserted', Rooms.findOneById(rid)); - break; - - case 'removed': - this.removeListener(rid, roomEvent); - break; - } - }; - this.on(userId, userEvent); - - publication.onStop(() => { - this.removeListener(userId, userEvent); - subscriptions.forEach(({ rid }) => this.removeListener(rid, roomEvent)); - }); - } - } -} - -class MessageStream extends Stream { - getSubscriptionByUserIdAndRoomId(userId: string, rid: string): DDPSubscription | undefined { - return [...this.subscriptions].find((sub) => sub.eventName === rid && sub.subscription.userId === userId); - } - - async _publish(publication: IPublication, eventName: string, options: boolean | {useCollection?: boolean; args?: any} = false): Promise { - await super._publish(publication, eventName, options); - const { userId } = publication._session; - if (!userId) { - return; - } - - const userEvent = (clientAction: string, { rid }: {rid: string}): void => { - switch (clientAction) { - case 'removed': - this.removeListener(userId, userEvent); - const sub = this.getSubscriptionByUserIdAndRoomId(userId, rid); - sub && this.removeSubscription(sub, eventName); - break; - } - }; - this.on(userId, userEvent); - } - - mymessage(eventName: string, args: any[]): void { - const subscriptions = this.subscriptionsByEventName.get(eventName); - if (!Array.isArray(subscriptions)) { - return; - } - subscriptions.forEach(async ({ subscription }) => { - // TODO: bring back the options - const options = await this.isEmitAllowed(subscription, eventName, args); - if (options) { - subscription._session.socket?.send(this.changedPayload(this.subscriptionName, 'id', { - eventName, - args: [args, options], - })); - } - }); - } -} - -const notifications = new NotificationsModule(Stream, RoomStreamer, MessageStream); +const notifications = new NotificationsModule(Stream); notifications.configure({ Rooms: RoomsRaw, diff --git a/ee/server/services/DDPStreamer/DDPStreamer.ts b/ee/server/services/DDPStreamer/DDPStreamer.ts index 05c4f5032210c..fd502d689e3b3 100644 --- a/ee/server/services/DDPStreamer/DDPStreamer.ts +++ b/ee/server/services/DDPStreamer/DDPStreamer.ts @@ -130,7 +130,11 @@ export class DDPStreamer extends ServiceClass { // message({ message }) { this.onEvent('message', ({ message }): void => { - // roomMessages.emitWithoutBroadcast('__my_messages__', record, {}); + notifications.streamRoomMessage._emit('__my_messages__', [message], undefined, false, (streamer, _sub, eventName, args, allowed) => streamer.changedPayload(streamer.subscriptionName, 'id', { + eventName, + args: [args, allowed], + })); + notifications.streamRoomMessage.emit(message.rid, message); }); diff --git a/ee/server/services/DDPStreamer/Streamer.ts b/ee/server/services/DDPStreamer/Streamer.ts index aad9b6cfc2f05..f9bd5cc192f4a 100644 --- a/ee/server/services/DDPStreamer/Streamer.ts +++ b/ee/server/services/DDPStreamer/Streamer.ts @@ -3,7 +3,7 @@ import WebSocket from 'ws'; import { server } from './configureServer'; import { DDP_EVENTS } from './constants'; import { isEmpty } from './lib/utils'; -import { Streamer, DDPSubscription, Connection, StreamerCentral } from '../../../../server/modules/streamer/streamer.module'; +import { Streamer, DDPSubscription, Connection, StreamerCentral, TransformMessage } from '../../../../server/modules/streamer/streamer.module'; import { api } from '../../../../server/sdk/api'; StreamerCentral.on('broadcast', (name, eventName, args) => { @@ -32,7 +32,11 @@ export class Stream extends Streamer { }); } - async sendToManySubscriptions(subscriptions: Set, origin: Connection | undefined, eventName: string, args: any[], msg: string): Promise { + async sendToManySubscriptions(subscriptions: Set, origin: Connection | undefined, eventName: string, args: any[], getMsg: string | TransformMessage): Promise { + if (typeof getMsg === 'function') { + return super.sendToManySubscriptions(subscriptions, origin, eventName, args, getMsg); + } + // TODO: missing typing const options = { fin: true, // sending a single fragment message @@ -41,9 +45,10 @@ export class Stream extends Streamer { mask: false, // set false for client-side readOnly: false, // the data can be modified as needed }; + const data = { - meteor: [Buffer.concat((WebSocket as any).Sender.frame(Buffer.from(`a${ JSON.stringify([msg]) }`), options))], - normal: [Buffer.concat((WebSocket as any).Sender.frame(Buffer.from(msg), options))], + meteor: [Buffer.concat((WebSocket as any).Sender.frame(Buffer.from(`a${ JSON.stringify([getMsg]) }`), options))], + normal: [Buffer.concat((WebSocket as any).Sender.frame(Buffer.from(getMsg), options))], }; for await (const { subscription } of subscriptions) { diff --git a/ee/server/services/DDPStreamer/streams/index.ts b/ee/server/services/DDPStreamer/streams/index.ts index 46554827945e5..a34fd2757eec7 100644 --- a/ee/server/services/DDPStreamer/streams/index.ts +++ b/ee/server/services/DDPStreamer/streams/index.ts @@ -4,119 +4,13 @@ import { ISubscription } from '../../../../../definition/ISubscription'; import { IRoom } from '../../../../../definition/IRoom'; import { IUser } from '../../../../../definition/IUser'; import { ISetting } from '../../../../../definition/ISetting'; -import { getCollection, Collections, getConnection } from '../../mongo'; -// import { Authorization } from '../../../../../server/sdk'; -import { IPublication, DDPSubscription } from '../../../../../server/modules/streamer/streamer.module'; +import { Collections, getConnection } from '../../mongo'; import { RoomsRaw } from '../../../../../app/models/server/raw/Rooms'; import { SubscriptionsRaw } from '../../../../../app/models/server/raw/Subscriptions'; import { UsersRaw } from '../../../../../app/models/server/raw/Users'; import { SettingsRaw } from '../../../../../app/models/server/raw/Settings'; -export class RoomStreamer extends Stream { - async _publish(publication: IPublication, eventName: string, options: boolean | {useCollection?: boolean; args?: any} = false): Promise { - await super._publish(publication, eventName, options); - const { userId } = publication._session; - if (!userId) { - return; - } - - if (/rooms-changed/.test(eventName)) { - // TODO: change this to serialize only once - const roomEvent = (...args: any[]): void => { - const payload = this.changedPayload(this.subscriptionName, 'id', { - eventName: `${ userId }/rooms-changed`, - args, - }); - - payload && publication.client?.send( - payload, - ); - }; - - const Subscription = await getCollection(Collections.Subscriptions); - - const subscriptions = await Subscription.find>( - { 'u._id': userId }, - { projection: { rid: 1 } }, - ).toArray(); - - subscriptions.forEach(({ rid }) => { - this.on(rid, roomEvent); - }); - - const userEvent = (clientAction: string, { rid }: Partial = {}): void => { - if (!rid) { - return; - } - - switch (clientAction) { - case 'inserted': - subscriptions.push({ rid }); - this.on(rid, roomEvent); - - // From Original Notifications.ts - // after a subscription is added need to emit the room again - // roomEvent('inserted', Rooms.findOneById(rid)); - break; - - case 'removed': - this.removeListener(rid, roomEvent); - break; - } - }; - this.on(userId, userEvent); - - publication.onStop(() => { - this.removeListener(userId, userEvent); - subscriptions.forEach(({ rid }) => this.removeListener(rid, roomEvent)); - }); - } - } -} - -class MessageStream extends Stream { - getSubscriptionByUserIdAndRoomId(userId: string, rid: string): DDPSubscription | undefined { - return [...this.subscriptions].find((sub) => sub.eventName === rid && sub.subscription.userId === userId); - } - - async _publish(publication: IPublication, eventName: string, options: boolean | {useCollection?: boolean; args?: any} = false): Promise { - await super._publish(publication, eventName, options); - const { userId } = publication._session; - if (!userId) { - return; - } - - const userEvent = (clientAction: string, { rid }: {rid: string}): void => { - switch (clientAction) { - case 'removed': - this.removeListener(userId, userEvent); - const sub = this.getSubscriptionByUserIdAndRoomId(userId, rid); - sub && this.removeSubscription(sub, eventName); - break; - } - }; - this.on(userId, userEvent); - } - - mymessage(eventName: string, args: any[]): void { - const subscriptions = this.subscriptionsByEventName.get(eventName); - if (!Array.isArray(subscriptions)) { - return; - } - subscriptions.forEach(async ({ subscription }) => { - // TODO: bring back the options - const options = await this.isEmitAllowed(subscription, eventName, args); - if (options) { - subscription._session.socket?.send(this.changedPayload(this.subscriptionName, 'id', { - eventName, - args: [args, options], - })); - } - }); - } -} - -const notifications = new NotificationsModule(Stream, RoomStreamer, MessageStream); +const notifications = new NotificationsModule(Stream); getConnection() .then((db) => { diff --git a/server/modules/notifications/notifications.module.ts b/server/modules/notifications/notifications.module.ts index 7947fa73c9aba..2619f5d54d914 100644 --- a/server/modules/notifications/notifications.module.ts +++ b/server/modules/notifications/notifications.module.ts @@ -1,4 +1,4 @@ -import { IStreamer, IStreamerConstructor } from '../streamer/streamer.module'; +import { IStreamer, IStreamerConstructor, IPublication } from '../streamer/streamer.module'; import { Authorization } from '../../sdk'; import { RoomsRaw } from '../../../app/models/server/raw/Rooms'; import { SubscriptionsRaw } from '../../../app/models/server/raw/Subscriptions'; @@ -50,17 +50,11 @@ export class NotificationsModule { constructor( private Streamer: IStreamerConstructor, - private RoomStreamer: IStreamerConstructor, - private MessageStreamer: IStreamerConstructor, ) { - // this.notifyUser = this.notifyUser.bind(this); - this.streamAll = new this.Streamer('notify-all'); this.streamLogged = new this.Streamer('notify-logged'); this.streamRoom = new this.Streamer('notify-room'); this.streamRoomUsers = new this.Streamer('notify-room-users'); - this.streamRoomMessage = new this.MessageStreamer('room-messages'); - this.streamUser = new this.RoomStreamer('notify-user'); this.streamImporters = new this.Streamer('importers', { retransmit: false }); this.streamRoles = new this.Streamer('roles'); this.streamApps = new this.Streamer('apps', { retransmit: false }); @@ -71,6 +65,29 @@ export class NotificationsModule { this.streamLivechatQueueData = new this.Streamer('livechat-inquiry-queue-observer'); this.streamStdout = new this.Streamer('stdout'); this.streamRoomData = new this.Streamer('room-data'); + + this.streamRoomMessage = new this.Streamer('room-messages'); + + this.streamRoomMessage.on('_afterPublish', async (streamer: IStreamer, publication: IPublication, eventName: string): Promise => { + const { userId } = publication._session; + if (!userId) { + return; + } + + const userEvent = (clientAction: string, { rid }: {rid: string}): void => { + switch (clientAction) { + case 'removed': + streamer.removeListener(userId, userEvent); + const sub = [...streamer.subscriptions].find((sub) => sub.eventName === rid && sub.subscription.userId === userId); + sub && streamer.removeSubscription(sub, eventName); + break; + } + }; + + streamer.on(userId, userEvent); + }); + + this.streamUser = new this.Streamer('notify-user'); } async configure({ Rooms, Subscriptions, Users, Settings }: IModelsParam): Promise { @@ -95,7 +112,6 @@ export class NotificationsModule { return true; }); - // TODO need to test this.streamRoomMessage.allowRead('__my_messages__', 'all'); this.streamRoomMessage.allowEmit('__my_messages__', async function(_eventName, { rid }) { try { @@ -285,6 +301,62 @@ export class NotificationsModule { this.streamRoles.allowWrite('none'); this.streamRoles.allowRead('logged'); + + this.streamUser.on('_afterPublish', async (streamer: IStreamer, publication: IPublication, eventName: string): Promise => { + const { userId } = publication._session; + if (!userId) { + return; + } + + if (/rooms-changed/.test(eventName)) { + // TODO: change this to serialize only once + const roomEvent = (...args: any[]): void => { + const payload = streamer.changedPayload(streamer.subscriptionName, 'id', { + eventName: `${ userId }/rooms-changed`, + args, + }); + + payload && publication._session.socket?.send( + payload, + ); + }; + + const subscriptions: Pick[] = await Subscriptions.find( + { 'u._id': userId }, + { projection: { rid: 1 } }, + ).toArray(); + + subscriptions.forEach(({ rid }) => { + streamer.on(rid, roomEvent); + }); + + const userEvent = async (clientAction: string, { rid }: Partial = {}): Promise => { + if (!rid) { + return; + } + + switch (clientAction) { + case 'inserted': + subscriptions.push({ rid }); + streamer.on(rid, roomEvent); + + // after a subscription is added need to emit the room again + roomEvent('inserted', await Rooms.findOneById(rid)); + break; + + case 'removed': + streamer.removeListener(rid, roomEvent); + break; + } + }; + streamer.on(userId, userEvent); + + publication.onStop(() => { + streamer.removeListener(userId, userEvent); + subscriptions.forEach(({ rid }) => streamer.removeListener(rid, roomEvent)); + }); + } + }); } notifyAll(eventName: string, ...args: any[]): void { diff --git a/server/modules/streamer/streamer.module.ts b/server/modules/streamer/streamer.module.ts index 137d0030af123..7c2538f5429c2 100644 --- a/server/modules/streamer/streamer.module.ts +++ b/server/modules/streamer/streamer.module.ts @@ -49,6 +49,10 @@ export type DDPSubscription = { export interface IStreamer { serverOnly: boolean; + subscriptions: Set; + + subscriptionName: string; + allowEmit(eventName: string | boolean | Rule, fn?: Rule | 'all' | 'none' | 'logged'): void; allowWrite(eventName: string | boolean | Rule, fn?: Rule | 'all' | 'none' | 'logged'): void; @@ -57,9 +61,19 @@ export interface IStreamer { emit(event: string, ...data: any[]): void; + on(event: string, fn: (...data: any[]) => void): void; + + removeSubscription(subscription: DDPSubscription, eventName: string): void; + + removeListener(event: string, fn: (...data: any[]) => void): void; + __emit(...data: any[]): void; + _emit(eventName: string, args: any[], origin: Connection | undefined, broadcast: boolean, transform?: TransformMessage): boolean; + emitWithoutBroadcast(event: string, ...data: any[]): void; + + changedPayload(collection: string, id: string, fields: Record): string | false; } export interface IStreamerConstructor { @@ -67,8 +81,10 @@ export interface IStreamerConstructor { new(name: string, options?: {retransmit?: boolean; retransmitToSelf?: boolean}): IStreamer; } +export type TransformMessage = (streamer: Streamer, subscription: DDPSubscription, eventName: string, args: any[], allowed: boolean | object) => string | false; + export abstract class Streamer extends EventEmitter implements IStreamer { - protected subscriptions = new Set(); + public subscriptions = new Set(); protected subscriptionsByEventName = new Map>(); @@ -253,6 +269,8 @@ export abstract class Streamer extends EventEmitter implements IStreamer { } publication.ready(); + + super.emit('_afterPublish', this, publication, eventName, options); } abstract registerPublication(name: string, fn: (eventName: string, options: boolean | {useCollection?: boolean; args?: any}) => Promise): void; @@ -295,7 +313,7 @@ export abstract class Streamer extends EventEmitter implements IStreamer { abstract changedPayload(collection: string, id: string, fields: Record): string | false; - _emit(eventName: string, args: any[], origin: Connection | undefined, broadcast: boolean): boolean { + _emit(eventName: string, args: any[], origin: Connection | undefined, broadcast: boolean, transform?: TransformMessage): boolean { if (broadcast === true) { StreamerCentral.emit('broadcast', this.name, eventName, args); } @@ -305,6 +323,12 @@ export abstract class Streamer extends EventEmitter implements IStreamer { return false; } + if (transform) { + this.sendToManySubscriptions(subscriptions, origin, eventName, args, transform); + + return true; + } + const msg = this.changedPayload(this.subscriptionName, 'id', { eventName, args, @@ -319,14 +343,18 @@ export abstract class Streamer extends EventEmitter implements IStreamer { return true; } - async sendToManySubscriptions(subscriptions: Set, origin: Connection | undefined, eventName: string, args: any[], msg: string): Promise { + async sendToManySubscriptions(subscriptions: Set, origin: Connection | undefined, eventName: string, args: any[], getMsg: string | TransformMessage): Promise { subscriptions.forEach(async (subscription) => { if (this.retransmitToSelf === false && origin && origin === subscription.subscription.connection) { return; } - if (await this.isEmitAllowed(subscription.subscription, eventName, ...args)) { - subscription.subscription._session.socket?.send(msg); + const allowed = await this.isEmitAllowed(subscription.subscription, eventName, ...args); + if (allowed) { + const msg = typeof getMsg === 'string' ? getMsg : getMsg(this, subscription, eventName, args, allowed); + if (msg) { + subscription.subscription._session.socket?.send(msg); + } } }); } diff --git a/server/stream/messages/emitter.js b/server/stream/messages/emitter.js index d90038eb388cf..84ac112b8db0a 100644 --- a/server/stream/messages/emitter.js +++ b/server/stream/messages/emitter.js @@ -20,7 +20,12 @@ Meteor.startup(function() { mention.name = user && user.name; }); } - msgStream.mymessage('__my_messages__', record); + + msgStream._emit('__my_messages__', [record], undefined, false, (streamer, sub, eventName, args, allowed) => streamer.changedPayload(streamer.subscriptionName, 'id', { + eventName, + args: [args, allowed], + })); + msgStream.emitWithoutBroadcast(record.rid, record); } } From 9b3809c2ba55fb22ff1ac4d41acc8314a8c9211e Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Sat, 3 Oct 2020 11:09:45 -0300 Subject: [PATCH 136/198] Fix userpresence --- .../services/DDPStreamer/configureServer.ts | 18 +++++++++--------- ee/server/services/DDPStreamer/constants.ts | 12 ++++++------ 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/ee/server/services/DDPStreamer/configureServer.ts b/ee/server/services/DDPStreamer/configureServer.ts index 508c9fb41d05b..73dbb13c1454a 100644 --- a/ee/server/services/DDPStreamer/configureServer.ts +++ b/ee/server/services/DDPStreamer/configureServer.ts @@ -12,11 +12,11 @@ export const events = new EventEmitter(); // TODO: remove, this was replaced by stream-notify-user/[user-id]/userData // server.subscribe('userData', async function(publication) { -// if (!publication.uid) { +// if (!publication.userId) { // throw new Error('user should be connected'); // } -// const key = `${ STREAMER_EVENTS.USER_CHANGED }/${ publication.uid }`; +// const key = `${ STREAMER_EVENTS.USER_CHANGED }/${ publication.userId }`; // await User.addSubscription(publication, key); // publication.once('stop', () => User.removeSubscription(publication, key)); // publication.ready(); @@ -136,15 +136,15 @@ server.methods({ }, }); -server.on(DDP_EVENTS.LOGGED, ({ uid, session }) => { - Presence.newConnection(uid, session); +server.on(DDP_EVENTS.LOGGED, ({ userId, session }) => { + Presence.newConnection(userId, session); }); -server.on(DDP_EVENTS.DISCONNECTED, ({ uid, session }) => { - if (!uid) { +server.on(DDP_EVENTS.DISCONNECTED, ({ userId, session }) => { + if (!userId) { return; } - Presence.removeConnection(uid, session); + Presence.removeConnection(userId, session); }); // TODO: resolve metrics @@ -168,7 +168,7 @@ server.on(DDP_EVENTS.DISCONNECTED, ({ uid, session }) => { // }); // }); -// server.on(DDP_EVENTS.DISCONNECTED, ({ uid }) => { +// server.on(DDP_EVENTS.DISCONNECTED, ({ userId }) => { // broker.emit('metrics.update', { // name: 'streamer_users_connected', // method: 'dec', @@ -176,7 +176,7 @@ server.on(DDP_EVENTS.DISCONNECTED, ({ uid, session }) => { // nodeID: broker.nodeID, // }, // }); -// if (uid) { +// if (userId) { // broker.emit('metrics.update', { // name: 'streamer_users_logged', // method: 'dec', diff --git a/ee/server/services/DDPStreamer/constants.ts b/ee/server/services/DDPStreamer/constants.ts index 54d0dcf093fd3..dfeebef91bf6a 100644 --- a/ee/server/services/DDPStreamer/constants.ts +++ b/ee/server/services/DDPStreamer/constants.ts @@ -46,11 +46,11 @@ export const WS_ERRORS_MESSAGES = { export const TIMEOUT = 1000 * 30; // 30 seconds -export const STREAM_NAMES = { - STREAMER_PREFIX: 'stream-', +// export const STREAM_NAMES = { +// STREAMER_PREFIX: 'stream-', - ROOMS_CHANGED: 'rooms-changed', - ROOM_DATA: 'room-data', // TODO both data are the same plx merge them +// ROOMS_CHANGED: 'rooms-changed', +// ROOM_DATA: 'room-data', // TODO both data are the same plx merge them - 'my-message': '__my_messages__', -}; +// 'my-message': '__my_messages__', +// }; From a99e811774b4299b3644e2965337186ea927eba0 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Sat, 3 Oct 2020 12:37:51 -0300 Subject: [PATCH 137/198] Resolve network broaker chain --- ee/server/broker.ts | 7 ++++ .../services/.config/services/service.env | 2 +- .../.config/traefik/servers/localhost.yml | 2 +- ee/server/services/.env | 1 + ee/server/services/.gitignore | 1 + ee/server/services/README.md | 8 ++++ ee/server/services/docker-compose.yml | 42 +++++++++---------- server/sdk/types/ServiceClass.ts | 1 + 8 files changed, 41 insertions(+), 23 deletions(-) create mode 100644 ee/server/services/.env diff --git a/ee/server/broker.ts b/ee/server/broker.ts index 91767da2255ae..c7421a839f545 100644 --- a/ee/server/broker.ts +++ b/ee/server/broker.ts @@ -52,6 +52,12 @@ class NetworkBroker implements IBroker { } await this.broker.waitForServices(method.split('.')[0]); + + const context = asyncLocalStorage.getStore(); + if (context?.ctx?.call) { + return context.ctx.call(method, data); + } + return this.broker.call(method, data); } @@ -113,6 +119,7 @@ class NetworkBroker implements IBroker { nodeID: ctx.nodeID, requestID: ctx.requestID, broker: this, + ctx, }, async (): Promise => { if (this.whitelist.actions.includes(`${ name }.${ method }`) || await this.allowed) { return i[method](...ctx.params); diff --git a/ee/server/services/.config/services/service.env b/ee/server/services/.config/services/service.env index a8e4d902b28b6..78aa22d519c64 100644 --- a/ee/server/services/.config/services/service.env +++ b/ee/server/services/.config/services/service.env @@ -1,5 +1,5 @@ MS_METRICS=true TRACING_ENABLED=true TRANSPORTER=nats://nats:4222 -MONGO_URL=mongodb://host.docker.internal:27017/rocketchat +MONGO_URL=mongodb://host.docker.internal:3001/meteor MOLECULER_LOG_LEVEL=info diff --git a/ee/server/services/.config/traefik/servers/localhost.yml b/ee/server/services/.config/traefik/servers/localhost.yml index 1a51c75859229..6ea37a941d6eb 100644 --- a/ee/server/services/.config/traefik/servers/localhost.yml +++ b/ee/server/services/.config/traefik/servers/localhost.yml @@ -2,7 +2,7 @@ http: routers: router2: rule: Host(`localhost`) && PathPrefix(`/sockjs/`, `/websocket/`) - service: ddp-streamer-service-services@docker + service: ddp-streamer-service@docker priority: 2 entryPoints: - web diff --git a/ee/server/services/.env b/ee/server/services/.env new file mode 100644 index 0000000000000..6dcc49f9bae60 --- /dev/null +++ b/ee/server/services/.env @@ -0,0 +1 @@ +HOST_NAME=localhost diff --git a/ee/server/services/.gitignore b/ee/server/services/.gitignore index cd5e298d48f12..a244e0ddd3a75 100644 --- a/ee/server/services/.gitignore +++ b/ee/server/services/.gitignore @@ -1,2 +1,3 @@ dist .config/data +!.env diff --git a/ee/server/services/README.md b/ee/server/services/README.md index 57f6fcea6106d..326dccdb3ebb8 100644 --- a/ee/server/services/README.md +++ b/ee/server/services/README.md @@ -41,6 +41,14 @@ meteor npm run pm2 -- logs ## Docker Compose +The `.env` file defines the HTTP address to be used, default to `localhost`. + +It requires meteor to be running, and the config at `services/.config/services/service.env` uses the default meteor mongodb. To run it: + +``` +TRANSPORTER=nats://localhost:4222 MOLECULER_LOG_LEVEL=debug meteor +``` + The `docker-compose.yml` file contais a setup of the micro-services plus some extra tools: ### Traefik diff --git a/ee/server/services/docker-compose.yml b/ee/server/services/docker-compose.yml index 69c9ac6fc9ae7..d89fd96000320 100644 --- a/ee/server/services/docker-compose.yml +++ b/ee/server/services/docker-compose.yml @@ -46,10 +46,9 @@ services: - nats - traefik labels: - - "traefik.enable=true" - - "traefik.backend=ddp-streamer-service" - - "traefik.port=80" - - "traefik.frontend.rule=Host:${HOST_NAME}" + traefik.enable: true + traefik.http.services.ddp-streamer-service.loadbalancer.server.port: 80 + traefik.http.routers.ddp-streamer-service.service: ddp-streamer-service stream-hub-service: container_name: stream-hub-service @@ -72,11 +71,11 @@ services: image: jaegertracing/all-in-one:latest container_name: jaeger ports: + - "16686:16686" - "5775:5775/udp" - "6831:6831/udp" - "6832:6832/udp" - "5778:5778" - - "16686:16686" - "14268:14268" - "9411:9411" depends_on: @@ -84,10 +83,11 @@ services: # environment: # - QUERY_BASE_PATH=/jaeger labels: - - "traefik.enable=true" - - "traefik.jaeger.frontend=jaeger" - - "traefik.jaeger.port=16686" - - 'traefik.jaeger.frontend.rule=Host:jaeger.${HOST_NAME}' + traefik.enable: true + traefik.http.routers.jaeger.rule: Host(`jaeger.${HOST_NAME}`) + traefik.http.routers.jaeger.entrypoints: web + traefik.http.services.jaeger.loadbalancer.server.port: 16686 + # traefik.http.routers.jaeger.service: jaeger grafana: image: grafana/grafana @@ -101,10 +101,10 @@ services: - prometheus - traefik labels: - - "traefik.enable=true" - - "traefik.port=3000" - - "traefik.frontend.rule=Host:grafana.${HOST_NAME}" - # - 'traefik.frontend.redirect.entryPoint=https' + traefik.enable: true + traefik.http.routers.grafana.rule: Host(`grafana.${HOST_NAME}`) + traefik.http.routers.grafana.entrypoints: web + traefik.http.services.grafana.loadbalancer.server.port: 3000 prometheus: image: quay.io/prometheus/prometheus @@ -122,10 +122,10 @@ services: ports: - 9090:9090 labels: - - "traefik.enable=true" - - "traefik.frontend.rule=Host:prometheus.${HOST_NAME}" - - "traefik.port=9090" - # - 'traefik.frontend.redirect.entryPoint=https' + traefik.enable: true + traefik.http.routers.prometheus.rule: Host(`prometheus.${HOST_NAME}`) + traefik.http.routers.prometheus.entrypoints: web + traefik.http.services.prometheus.loadbalancer.server.port: 9090 traefik: image: traefik @@ -145,11 +145,11 @@ services: - ./.config/traefik/servers/:/servers/ # - ./.config/traefik/acme.json:/acme.json labels: - - "traefik.enable=true" + traefik.enable: true # Dashboard - - "traefik.http.routers.traefik.rule=Host(`traefik.${HOST_NAME}`)" - - "traefik.http.routers.traefik.service=api@internal" - - "traefik.http.routers.traefik.entrypoints=web" + traefik.http.routers.traefik.rule: Host(`traefik.${HOST_NAME}`) + traefik.http.routers.traefik.service: api@internal + traefik.http.routers.traefik.entrypoints: web cadvisor: diff --git a/server/sdk/types/ServiceClass.ts b/server/sdk/types/ServiceClass.ts index c2248a904af58..7a45e29531847 100644 --- a/server/sdk/types/ServiceClass.ts +++ b/server/sdk/types/ServiceClass.ts @@ -19,6 +19,7 @@ export interface IServiceContext { // locals: any; // Local data. // level: Number; // Request level (in nested-calls). The first level is 1. // span: Span; // Current active span. + ctx?: any; } export interface IServiceClass { From 6752e5bc1c6714224dfa2b8f12b549c85bf1c589 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Sun, 4 Oct 2020 20:11:40 -0300 Subject: [PATCH 138/198] Init wathers.module --- .../server/lib/restrictLoginAttempts.ts | 4 +- app/models/server/raw/BaseRaw.js | 52 -------- app/models/server/raw/BaseRaw.ts | 85 ++++++++++++ .../server/raw/LivechatBusinessHours.ts | 4 +- app/models/server/raw/NotificationQueue.ts | 2 +- app/models/server/raw/ServerEvents.ts | 2 +- app/models/server/raw/Subscriptions.js | 68 ---------- app/models/server/raw/Subscriptions.ts | 71 ++++++++++ app/models/server/raw/index.ts | 78 +++++++---- definition/IMessage.ts | 10 ++ definition/ISubscription.ts | 4 + .../server/business-hour/Custom.ts | 6 +- .../server/business-hour/Helper.ts | 9 +- .../server/business-hour/Multiple.ts | 9 ++ ee/server/services/DDPStreamer/DDPStreamer.ts | 39 ------ ee/server/services/DDPStreamer/service.ts | 3 + ee/server/services/StreamHub/StreamHub.ts | 87 ++++++++++--- ee/server/services/StreamHub/watchMessages.ts | 22 ---- .../services/StreamHub/watchSubscriptions.ts | 28 ---- server/lib/markRoomAsRead.ts | 2 +- server/main.js | 1 - server/modules/watchers/watchers.module.ts | 123 ++++++++++++++++++ server/publications/subscription/emitter.js | 37 ------ server/publications/subscription/index.js | 44 +------ server/sdk/lib/Events.ts | 2 + .../services/authorization/canAccessRoom.ts | 9 +- server/services/listeners/notification.ts | 40 +++++- server/services/startup.ts | 3 +- server/stream/messages/emitter.js | 42 ------ server/stream/messages/index.js | 1 - 30 files changed, 497 insertions(+), 390 deletions(-) delete mode 100644 app/models/server/raw/BaseRaw.js create mode 100644 app/models/server/raw/BaseRaw.ts delete mode 100644 app/models/server/raw/Subscriptions.js create mode 100644 app/models/server/raw/Subscriptions.ts delete mode 100644 ee/server/services/StreamHub/watchMessages.ts delete mode 100644 ee/server/services/StreamHub/watchSubscriptions.ts create mode 100644 server/modules/watchers/watchers.module.ts delete mode 100644 server/publications/subscription/emitter.js delete mode 100644 server/stream/messages/emitter.js delete mode 100644 server/stream/messages/index.js diff --git a/app/authentication/server/lib/restrictLoginAttempts.ts b/app/authentication/server/lib/restrictLoginAttempts.ts index 0e1fa7ac237a9..ef3b9368816a0 100644 --- a/app/authentication/server/lib/restrictLoginAttempts.ts +++ b/app/authentication/server/lib/restrictLoginAttempts.ts @@ -18,10 +18,10 @@ export const isValidLoginAttemptByIp = async (ip: string): Promise => { return true; } - const lastLogin = await Sessions.findLastLoginByIp(ip); + const lastLogin = await Sessions.findLastLoginByIp(ip) as {loginAt?: Date} | undefined; let failedAttemptsSinceLastLogin; - if (!lastLogin) { + if (!lastLogin || !lastLogin.loginAt) { failedAttemptsSinceLastLogin = await ServerEvents.countFailedAttemptsByIp(ip); } else { failedAttemptsSinceLastLogin = await ServerEvents.countFailedAttemptsByIpSince(ip, new Date(lastLogin.loginAt)); diff --git a/app/models/server/raw/BaseRaw.js b/app/models/server/raw/BaseRaw.js deleted file mode 100644 index 312ff9df2444b..0000000000000 --- a/app/models/server/raw/BaseRaw.js +++ /dev/null @@ -1,52 +0,0 @@ -export class BaseRaw { - constructor(col) { - this.col = col; - } - - _ensureDefaultFields(options) { - if (!this.defaultFields) { - return options; - } - - if (!options) { - return { projection: this.defaultFields }; - } - - // TODO: change all places using "fields" for raw models and remove the additional condition here - if ((options.projection != null && Object.keys(options.projection).length > 0) - || (options.fields != null && Object.keys(options.fields).length > 0)) { - return options; - } - - return { - ...options, - projection: this.defaultFields, - }; - } - - findOneById(_id, options = {}) { - return this.findOne({ _id }, options); - } - - findOne(query = {}, options = {}) { - const optionsDef = this._ensureDefaultFields(options); - return this.col.findOne(query, optionsDef); - } - - findUsersInRoles() { - throw new Error('overwrite-function', 'You must overwrite this function in the extended classes'); - } - - find(query = {}, options = {}) { - const optionsDef = this._ensureDefaultFields(options); - return this.col.find(query, optionsDef); - } - - update(...args) { - return this.col.update(...args); - } - - removeById(_id) { - return this.col.deleteOne({ _id }); - } -} diff --git a/app/models/server/raw/BaseRaw.ts b/app/models/server/raw/BaseRaw.ts new file mode 100644 index 0000000000000..e225b71b5f995 --- /dev/null +++ b/app/models/server/raw/BaseRaw.ts @@ -0,0 +1,85 @@ +import { Collection, FindOneOptions, Cursor, WriteOpResult, DeleteWriteOpResultObject, FilterQuery, UpdateQuery, UpdateOneOptions } from 'mongodb'; + +interface ITrash { + __collection__: string; +} + +export interface IBaseRaw { + col: Collection; +} + +export class BaseRaw implements IBaseRaw { + public defaultFields?: Record; + + constructor( + public readonly col: Collection, + public readonly trash?: Collection, + ) { + // + } + + _ensureDefaultFields(options: FindOneOptions): FindOneOptions { + if (!this.defaultFields) { + return options; + } + + if (!options) { + return { projection: this.defaultFields }; + } + + // TODO: change all places using "fields" for raw models and remove the additional condition here + if ((options.projection != null && Object.keys(options.projection).length > 0) + || (options.fields != null && Object.keys(options.fields).length > 0)) { + return options; + } + + return { + ...options, + projection: this.defaultFields, + }; + } + + async findOneById(_id: string, options: FindOneOptions = {}): Promise { + return this.findOne({ _id }, options); + } + + async findOne(query = {}, options: FindOneOptions = {}): Promise { + const optionsDef = this._ensureDefaultFields(options); + return await this.col.findOne(query, optionsDef) ?? undefined; + } + + findUsersInRoles(): void { + throw new Error('[overwrite-function] You must overwrite this function in the extended classes'); + } + + find(query = {}, options: FindOneOptions = {}): Cursor { + const optionsDef = this._ensureDefaultFields(options); + return this.col.find(query, optionsDef); + } + + update(filter: FilterQuery, update: UpdateQuery | Partial, options?: UpdateOneOptions & { multi?: boolean }): Promise { + return this.col.update(filter, update, options); + } + + removeById(_id: string): Promise { + const query: object = { _id }; + return this.col.deleteOne(query); + } + + // Trash + trashFind(query: FilterQuery, options: FindOneOptions): Cursor | undefined { + return this.trash?.find({ + __collection__: this.col.collectionName, + ...query, + }, options); + } + + async trashFindOneById(_id: string, options: FindOneOptions): Promise { + const query: object = { + _id, + __collection__: this.col.collectionName, + }; + + return await this.trash?.findOne(query, options) ?? undefined; + } +} diff --git a/app/models/server/raw/LivechatBusinessHours.ts b/app/models/server/raw/LivechatBusinessHours.ts index 1c16328cf16ca..09993b1697f5f 100644 --- a/app/models/server/raw/LivechatBusinessHours.ts +++ b/app/models/server/raw/LivechatBusinessHours.ts @@ -17,10 +17,10 @@ export interface IWorkHoursCronJobsWrapper { finish: IWorkHoursCronJobsItem[]; } -export class LivechatBusinessHoursRaw extends BaseRaw { +export class LivechatBusinessHoursRaw extends BaseRaw { public readonly col!: Collection; - findOneDefaultBusinessHour(options?: any): Promise { + findOneDefaultBusinessHour(options?: any): Promise { return this.findOne({ type: LivechatBusinessHourTypes.DEFAULT }, options); } diff --git a/app/models/server/raw/NotificationQueue.ts b/app/models/server/raw/NotificationQueue.ts index 44a34a21be7f5..9aedb96809028 100644 --- a/app/models/server/raw/NotificationQueue.ts +++ b/app/models/server/raw/NotificationQueue.ts @@ -7,7 +7,7 @@ import { import { BaseRaw } from './BaseRaw'; import { INotification } from '../../../../definition/INotification'; -export class NotificationQueueRaw extends BaseRaw { +export class NotificationQueueRaw extends BaseRaw { public readonly col!: Collection; unsetSendingById(_id: string) { diff --git a/app/models/server/raw/ServerEvents.ts b/app/models/server/raw/ServerEvents.ts index d906f41edce26..f36b44983e193 100644 --- a/app/models/server/raw/ServerEvents.ts +++ b/app/models/server/raw/ServerEvents.ts @@ -4,7 +4,7 @@ import { BaseRaw } from './BaseRaw'; import { IServerEvent, IServerEventType } from '../../../../definition/IServerEvent'; import { IUser } from '../../../../definition/IUser'; -export class ServerEventsRaw extends BaseRaw { +export class ServerEventsRaw extends BaseRaw { public readonly col!: Collection; async insertOne(data: Omit): Promise { diff --git a/app/models/server/raw/Subscriptions.js b/app/models/server/raw/Subscriptions.js deleted file mode 100644 index d1b6469b429c2..0000000000000 --- a/app/models/server/raw/Subscriptions.js +++ /dev/null @@ -1,68 +0,0 @@ -import { BaseRaw } from './BaseRaw'; - -export class SubscriptionsRaw extends BaseRaw { - findOneByRoomIdAndUserId(rid, uid, options = {}) { - const query = { - rid, - 'u._id': uid, - }; - - return this.col.findOne(query, options); - } - - findByRoomIdAndNotUserId(roomId, userId, options = {}) { - const query = { - rid: roomId, - 'u._id': { - $ne: userId, - }, - }; - - return this.col.find(query, options); - } - - countByRoomIdAndUserId(rid, uid) { - const query = { - rid, - 'u._id': uid, - }; - - const cursor = this.col.find(query, { projection: { _id: 0 } }); - - return cursor.count(); - } - - isUserInRole(uid, roleName, rid) { - if (rid == null) { - return; - } - - const query = { - 'u._id': uid, - rid, - roles: roleName, - }; - - return this.findOne(query, { fields: { roles: 1 } }); - } - - setAsReadByRoomIdAndUserId(rid, uid, alert = false) { - const query = { - rid, - 'u._id': uid, - }; - - const update = { - $set: { - open: true, - alert, - unread: 0, - userMentions: 0, - groupMentions: 0, - ls: new Date(), - }, - }; - - return this.col.update(query, update); - } -} diff --git a/app/models/server/raw/Subscriptions.ts b/app/models/server/raw/Subscriptions.ts new file mode 100644 index 0000000000000..25107d51c2328 --- /dev/null +++ b/app/models/server/raw/Subscriptions.ts @@ -0,0 +1,71 @@ +import { FindOneOptions, Cursor, UpdateQuery, FilterQuery } from 'mongodb'; + +import { BaseRaw } from './BaseRaw'; +import { ISubscription } from '../../../../definition/ISubscription'; + +export class SubscriptionsRaw extends BaseRaw { + findOneByRoomIdAndUserId(rid: string, uid: string, options: FindOneOptions = {}): Promise { + const query = { + rid, + 'u._id': uid, + }; + + return this.findOne(query, options); + } + + findByRoomIdAndNotUserId(roomId: string, userId: string, options: FindOneOptions = {}): Cursor { + const query = { + rid: roomId, + 'u._id': { + $ne: userId, + }, + }; + + return this.find(query, options); + } + + countByRoomIdAndUserId(rid: string, uid: string): Promise { + const query = { + rid, + 'u._id': uid, + }; + + const cursor = this.find(query, { projection: { _id: 0 } }); + + return cursor.count(); + } + + async isUserInRole(uid: string, roleName: string, rid: string): Promise { + if (rid == null) { + return; + } + + const query = { + 'u._id': uid, + rid, + roles: roleName, + }; + + return this.findOne(query, { projection: { roles: 1 } }); + } + + setAsReadByRoomIdAndUserId(rid: string, uid: string, alert = false, options: FindOneOptions = {}): ReturnType['update']> { + const query: FilterQuery = { + rid, + 'u._id': uid, + }; + + const update: UpdateQuery = { + $set: { + open: true, + alert, + unread: 0, + userMentions: 0, + groupMentions: 0, + ls: new Date(), + }, + }; + + return this.update(query, update, options); + } +} diff --git a/app/models/server/raw/index.ts b/app/models/server/raw/index.ts index 21b023ab58f11..e67377b9b7b59 100644 --- a/app/models/server/raw/index.ts +++ b/app/models/server/raw/index.ts @@ -50,30 +50,56 @@ import LivechatBusinessHoursModel from '../models/LivechatBusinessHours'; import { LivechatBusinessHoursRaw } from './LivechatBusinessHours'; import ServerEventModel from '../models/ServerEvents'; import { ServerEventsRaw } from './ServerEvents'; +import { trash } from '../models/_BaseDb'; +import { initWatchers } from '../../../../server/modules/watchers/watchers.module'; -export const Permissions = new PermissionsRaw(PermissionsModel.model.rawCollection()); -export const Roles = new RolesRaw(RolesModel.model.rawCollection()); -export const Subscriptions = new SubscriptionsRaw(SubscriptionsModel.model.rawCollection()); -export const Settings = new SettingsRaw(SettingsModel.model.rawCollection()); -export const Users = new UsersRaw(UsersModel.model.rawCollection()); -export const Rooms = new RoomsRaw(RoomsModel.model.rawCollection()); -export const LivechatCustomField = new LivechatCustomFieldRaw(LivechatCustomFieldModel.model.rawCollection()); -export const LivechatTrigger = new LivechatTriggerRaw(LivechatTriggerModel.model.rawCollection()); -export const LivechatDepartment = new LivechatDepartmentRaw(LivechatDepartmentModel.model.rawCollection()); -export const LivechatDepartmentAgents = new LivechatDepartmentAgentsRaw(LivechatDepartmentAgentsModel.model.rawCollection()); -export const LivechatRooms = new LivechatRoomsRaw(LivechatRoomsModel.model.rawCollection()); -export const Messages = new MessagesRaw(MessagesModel.model.rawCollection()); -export const LivechatExternalMessage = new LivechatExternalMessageRaw(LivechatExternalMessagesModel.model.rawCollection()); -export const LivechatVisitors = new LivechatVisitorsRaw(LivechatVisitorsModel.model.rawCollection()); -export const LivechatInquiry = new LivechatInquiryRaw(LivechatInquiryModel.model.rawCollection()); -export const Integrations = new IntegrationsRaw(IntegrationsModel.model.rawCollection()); -export const EmojiCustom = new EmojiCustomRaw(EmojiCustomModel.model.rawCollection()); -export const WebdavAccounts = new WebdavAccountsRaw(WebdavAccountsModel.model.rawCollection()); -export const OAuthApps = new OAuthAppsRaw(OAuthAppsModel.model.rawCollection()); -export const CustomSounds = new CustomSoundsRaw(CustomSoundsModel.model.rawCollection()); -export const CustomUserStatus = new CustomUserStatusRaw(CustomUserStatusModel.model.rawCollection()); -export const LivechatAgentActivity = new LivechatAgentActivityRaw(LivechatAgentActivityModel.model.rawCollection()); -export const Statistics = new StatisticsRaw(StatisticsModel.model.rawCollection()); -export const NotificationQueue = new NotificationQueueRaw(NotificationQueueModel.model.rawCollection()); -export const LivechatBusinessHours = new LivechatBusinessHoursRaw(LivechatBusinessHoursModel.model.rawCollection()); -export const ServerEvents = new ServerEventsRaw(ServerEventModel.model.rawCollection()); +const trashCollection = trash.rawCollection(); + +export const Permissions = new PermissionsRaw(PermissionsModel.model.rawCollection(), trashCollection); +export const Roles = new RolesRaw(RolesModel.model.rawCollection(), trashCollection); +export const Subscriptions = new SubscriptionsRaw(SubscriptionsModel.model.rawCollection(), trashCollection); +export const Settings = new SettingsRaw(SettingsModel.model.rawCollection(), trashCollection); +export const Users = new UsersRaw(UsersModel.model.rawCollection(), trashCollection); +export const Rooms = new RoomsRaw(RoomsModel.model.rawCollection(), trashCollection); +export const LivechatCustomField = new LivechatCustomFieldRaw(LivechatCustomFieldModel.model.rawCollection(), trashCollection); +export const LivechatTrigger = new LivechatTriggerRaw(LivechatTriggerModel.model.rawCollection(), trashCollection); +export const LivechatDepartment = new LivechatDepartmentRaw(LivechatDepartmentModel.model.rawCollection(), trashCollection); +export const LivechatDepartmentAgents = new LivechatDepartmentAgentsRaw(LivechatDepartmentAgentsModel.model.rawCollection(), trashCollection); +export const LivechatRooms = new LivechatRoomsRaw(LivechatRoomsModel.model.rawCollection(), trashCollection); +export const Messages = new MessagesRaw(MessagesModel.model.rawCollection(), trashCollection); +export const LivechatExternalMessage = new LivechatExternalMessageRaw(LivechatExternalMessagesModel.model.rawCollection(), trashCollection); +export const LivechatVisitors = new LivechatVisitorsRaw(LivechatVisitorsModel.model.rawCollection(), trashCollection); +export const LivechatInquiry = new LivechatInquiryRaw(LivechatInquiryModel.model.rawCollection(), trashCollection); +export const Integrations = new IntegrationsRaw(IntegrationsModel.model.rawCollection(), trashCollection); +export const EmojiCustom = new EmojiCustomRaw(EmojiCustomModel.model.rawCollection(), trashCollection); +export const WebdavAccounts = new WebdavAccountsRaw(WebdavAccountsModel.model.rawCollection(), trashCollection); +export const OAuthApps = new OAuthAppsRaw(OAuthAppsModel.model.rawCollection(), trashCollection); +export const CustomSounds = new CustomSoundsRaw(CustomSoundsModel.model.rawCollection(), trashCollection); +export const CustomUserStatus = new CustomUserStatusRaw(CustomUserStatusModel.model.rawCollection(), trashCollection); +export const LivechatAgentActivity = new LivechatAgentActivityRaw(LivechatAgentActivityModel.model.rawCollection(), trashCollection); +export const Statistics = new StatisticsRaw(StatisticsModel.model.rawCollection(), trashCollection); +export const NotificationQueue = new NotificationQueueRaw(NotificationQueueModel.model.rawCollection(), trashCollection); +export const LivechatBusinessHours = new LivechatBusinessHoursRaw(LivechatBusinessHoursModel.model.rawCollection(), trashCollection); +export const ServerEvents = new ServerEventsRaw(ServerEventModel.model.rawCollection(), trashCollection); + +const map = { + [Messages.col.collectionName]: MessagesModel, + [Users.col.collectionName]: UsersModel, + [Subscriptions.col.collectionName]: SubscriptionsModel, + [Settings.col.collectionName]: SettingsModel, +}; + +initWatchers({ + Messages, + Users, + Subscriptions, + Settings, +}, (model, fn) => { + const meteorModel = map[model.col.collectionName]; + + if (!meteorModel) { + return; + } + + meteorModel.on('change', fn); +}); diff --git a/definition/IMessage.ts b/definition/IMessage.ts index c01bce32c5ee9..acad0aba9c50c 100644 --- a/definition/IMessage.ts +++ b/definition/IMessage.ts @@ -2,4 +2,14 @@ export interface IMessage { _id: string; rid: string; _updatedAt?: Date; + _hidden?: boolean; + imported?: boolean; + mentions?: { + _id: string; + name?: string; + }[]; + u: { + _id: string; + name?: string; + }; } diff --git a/definition/ISubscription.ts b/definition/ISubscription.ts index fab1f113d7c19..c640202d8bdb6 100644 --- a/definition/ISubscription.ts +++ b/definition/ISubscription.ts @@ -5,4 +5,8 @@ export interface ISubscription { u: { _id: string; }; + roles?: string[]; + ls?: Date; + alert?: boolean; + tunread?: string[]; } diff --git a/ee/app/livechat-enterprise/server/business-hour/Custom.ts b/ee/app/livechat-enterprise/server/business-hour/Custom.ts index 9dd3fe08a6859..f652dee9d2068 100644 --- a/ee/app/livechat-enterprise/server/business-hour/Custom.ts +++ b/ee/app/livechat-enterprise/server/business-hour/Custom.ts @@ -25,7 +25,7 @@ class CustomBusinessHour extends AbstractBusinessHourType implements IBusinessHo return; } - const businessHour: ILivechatBusinessHour = await this.BusinessHourRepository.findOneById(id); + const businessHour = await this.BusinessHourRepository.findOneById(id); if (!businessHour) { return; } @@ -62,13 +62,13 @@ class CustomBusinessHour extends AbstractBusinessHourType implements IBusinessHo await this.BusinessHourRepository.removeById(businessHourId); await this.removeBusinessHourFromAgents(businessHourId); await this.DepartmentsRepository.removeBusinessHourFromDepartmentsByBusinessHourId(businessHourId); - return this.UsersRepository.updateLivechatStatusBasedOnBusinessHours(); + this.UsersRepository.updateLivechatStatusBasedOnBusinessHours(); } private async removeBusinessHourFromAgents(businessHourId: string): Promise { const departmentIds = (await this.DepartmentsRepository.findByBusinessHourId(businessHourId, { fields: { _id: 1 } }).toArray()).map((dept: any) => dept._id); const agentIds = (await this.DepartmentsAgentsRepository.findByDepartmentIds(departmentIds, { fields: { agentId: 1 } }).toArray()).map((dept: any) => dept.agentId); - return this.UsersRepository.removeBusinessHourByAgentIds(agentIds, businessHourId); + this.UsersRepository.removeBusinessHourByAgentIds(agentIds, businessHourId); } private async removeBusinessHourFromDepartmentsIfNeeded(businessHourId: string, departmentsToRemove: string[]): Promise { diff --git a/ee/app/livechat-enterprise/server/business-hour/Helper.ts b/ee/app/livechat-enterprise/server/business-hour/Helper.ts index 3fe0b4e76034b..47dc2a5be8088 100644 --- a/ee/app/livechat-enterprise/server/business-hour/Helper.ts +++ b/ee/app/livechat-enterprise/server/business-hour/Helper.ts @@ -28,13 +28,13 @@ const getAgentIdsToHandle = async (businessHour: Record): Promise): Promise => { const agentIds: string[] = await getAgentIdsToHandle(businessHour); await Users.addBusinessHourByAgentIds(agentIds, businessHour._id); - return Users.updateLivechatStatusBasedOnBusinessHours(); + Users.updateLivechatStatusBasedOnBusinessHours(); }; export const closeBusinessHour = async (businessHour: Record): Promise => { const agentIds: string[] = await getAgentIdsToHandle(businessHour); await Users.removeBusinessHourByAgentIds(agentIds, businessHour._id); - return Users.updateLivechatStatusBasedOnBusinessHours(); + Users.updateLivechatStatusBasedOnBusinessHours(); }; export const removeBusinessHourByAgentIds = async (agentIds: string[], businessHourId: string): Promise => { @@ -42,7 +42,7 @@ export const removeBusinessHourByAgentIds = async (agentIds: string[], businessH return; } await Users.removeBusinessHourByAgentIds(agentIds, businessHourId); - return Users.updateLivechatStatusBasedOnBusinessHours(); + Users.updateLivechatStatusBasedOnBusinessHours(); }; export const resetDefaultBusinessHourIfNeeded = async (): Promise => { @@ -54,6 +54,9 @@ export const resetDefaultBusinessHourIfNeeded = async (): Promise => { return; } const defaultBusinessHour = await LivechatBusinessHours.findOneDefaultBusinessHour({ fields: { _id: 1 } }); + if (!defaultBusinessHour) { + return; + } LivechatBusinessHours.update({ _id: defaultBusinessHour._id }, { $set: { timezone: { diff --git a/ee/app/livechat-enterprise/server/business-hour/Multiple.ts b/ee/app/livechat-enterprise/server/business-hour/Multiple.ts index 1e030e1b04373..d4eb9940aa839 100644 --- a/ee/app/livechat-enterprise/server/business-hour/Multiple.ts +++ b/ee/app/livechat-enterprise/server/business-hour/Multiple.ts @@ -78,6 +78,9 @@ export class MultipleBusinessHoursBehavior extends AbstractBusinessHourBehavior const toRemove = [...(currentDepartments || []).filter((dept: Record) => !departments.includes(dept._id))]; await this.removeBusinessHourFromRemovedDepartmentsUsersIfNeeded(businessHourData._id, toRemove); const businessHour = await this.BusinessHourRepository.findOneById(businessHourData._id); + if (!businessHour) { + return; + } const businessHourIdToOpen = (await filterBusinessHoursThatMustBeOpened([businessHour])).map((businessHour) => businessHour._id); if (!businessHourIdToOpen.length) { return closeBusinessHour(businessHour); @@ -92,6 +95,9 @@ export class MultipleBusinessHoursBehavior extends AbstractBusinessHourBehavior return options; } const defaultBusinessHour = await this.BusinessHourRepository.findOneDefaultBusinessHour(); + if (!defaultBusinessHour) { + return options; + } await removeBusinessHourByAgentIds(agentsId, defaultBusinessHour._id); if (!department.businessHourId) { return options; @@ -144,6 +150,9 @@ export class MultipleBusinessHoursBehavior extends AbstractBusinessHourBehavior return options; } const defaultBusinessHour = await this.BusinessHourRepository.findOneDefaultBusinessHour(); + if (!defaultBusinessHour) { + return options; + } const businessHourToOpen = await filterBusinessHoursThatMustBeOpened([defaultBusinessHour]); if (!businessHourToOpen.length) { return options; diff --git a/ee/server/services/DDPStreamer/DDPStreamer.ts b/ee/server/services/DDPStreamer/DDPStreamer.ts index fd502d689e3b3..5d149457b565d 100644 --- a/ee/server/services/DDPStreamer/DDPStreamer.ts +++ b/ee/server/services/DDPStreamer/DDPStreamer.ts @@ -128,16 +128,6 @@ export class DDPStreamer extends ServiceClass { return stream && stream.emitWithoutBroadcast(eventName, ...args); }); - // message({ message }) { - this.onEvent('message', ({ message }): void => { - notifications.streamRoomMessage._emit('__my_messages__', [message], undefined, false, (streamer, _sub, eventName, args, allowed) => streamer.changedPayload(streamer.subscriptionName, 'id', { - eventName, - args: [args, allowed], - })); - - notifications.streamRoomMessage.emit(message.rid, message); - }); - // userpresence(payload) { this.onEvent('userpresence', (payload): void => { const STATUS_MAP: {[k: string]: number} = { @@ -208,35 +198,6 @@ export class DDPStreamer extends ServiceClass { notifications.notifyLogged('Users:NameChanged', { _id, name, username }); }); - // 'setting'() { }, - // subscription({ action, subscription }) { - this.onEvent('subscription', ({ action, subscription }): void => { - if (!subscription.u?._id) { - return; - } - - notifications.notifyUser( - subscription.u._id, - 'subscriptions-changed', - action, - subscription, - ); - - notifications.streamUser.__emit(subscription.u._id, action, subscription); - - // RocketChat.Notifications.notifyUserInThisInstance( - // subscription.u._id, - // 'subscriptions-changed', - // action, - // subscription - // ); - // notifyUser.emit('subscriptions-changed', action, subscription); TODO REMOVE ID - - // notifyUser.emit(subscription.u._id, 'subscriptions-changed', action, subscription); - // RocketChat.Notifications.streamUser.__emit(subscription.u._id, action, subscription); - // RocketChat.Notifications.notifyUserInThisInstance(subscription.u._id, 'subscriptions-changed', action, subscription); - }); - // room({ room, action }) { this.onEvent('room', ({ room, action }): void => { // RocketChat.Notifications.streamUser.__emit(id, clientAction, data); diff --git a/ee/server/services/DDPStreamer/service.ts b/ee/server/services/DDPStreamer/service.ts index 2dcaaadea5eec..d69a56b616e4c 100755 --- a/ee/server/services/DDPStreamer/service.ts +++ b/ee/server/services/DDPStreamer/service.ts @@ -2,5 +2,8 @@ import '../../broker'; import { api } from '../../../../server/sdk/api'; import { DDPStreamer } from './DDPStreamer'; +import { NotificationService } from '../../../../server/services/listeners/notification'; +import notifications from './streams/index'; api.registerService(new DDPStreamer()); +api.registerService(new NotificationService(notifications)); diff --git a/ee/server/services/StreamHub/StreamHub.ts b/ee/server/services/StreamHub/StreamHub.ts index 5d3d3cb1a918a..6ac16ab3998b3 100755 --- a/ee/server/services/StreamHub/StreamHub.ts +++ b/ee/server/services/StreamHub/StreamHub.ts @@ -1,13 +1,16 @@ import { watchUsers } from './watchUsers'; -import { watchMessages } from './watchMessages'; import { watchSettings } from './watchSettings'; import { watchRooms } from './watchRooms'; -import { watchSubscriptions } from './watchSubscriptions'; import { watchRoles } from './watchRoles'; import { watchInquiries } from './watchInquiries'; import { getConnection } from '../mongo'; import { ServiceClass, IServiceClass } from '../../../../server/sdk/types/ServiceClass'; import { watchLoginServiceConfiguration } from './watchLoginServiceConfiguration'; +import { initWatchers } from '../../../../server/modules/watchers/watchers.module'; +import { MessagesRaw } from '../../../../app/models/server/raw/Messages'; +import { UsersRaw } from '../../../../app/models/server/raw/Users'; +import { SubscriptionsRaw } from '../../../../app/models/server/raw/Subscriptions'; +import { SettingsRaw } from '../../../../app/models/server/raw/Settings'; export class StreamHub extends ServiceClass implements IServiceClass { protected name = 'hub'; @@ -25,21 +28,75 @@ export class StreamHub extends ServiceClass implements IServiceClass { const Inquiry = db.collection('rocketchat_livechat_inquiry'); const loginServiceConfiguration = db.collection('meteor_accounts_loginServiceConfiguration'); - Users.watch([], { fullDocument: 'updateLookup' }).on('change', watchUsers); + initWatchers({ + Messages: new MessagesRaw(Messages, Trash), + Users: new UsersRaw(Users, Trash), + Subscriptions: new SubscriptionsRaw(Subscriptions, Trash), + Settings: new SettingsRaw(Settings, Trash), + }, (model, fn) => { + model.col.watch<{_id: string}>([]).on('change', (event) => { + switch (event.operationType) { + case 'insert': + fn({ + action: 'insert', + clientAction: 'inserted', + id: event.documentKey._id, + data: event.fullDocument, + }); - Messages.watch([{ - $addFields: { - tmpfields: { - $objectToArray: '$updateDescription.updatedFields', - }, - } }, { - $match: { - 'tmpfields.k': { - $nin: ['u.username'], // avoid flood the streamer with messages changes (by username change) - }, - } }], { fullDocument: 'updateLookup' }).on('change', watchMessages); + break; + case 'update': + const diff: Record = {}; + + if (event.updateDescription.updatedFields) { + for (const key in event.updateDescription.updatedFields) { + if (event.updateDescription.updatedFields.hasOwnProperty(key)) { + diff[key] = event.updateDescription.updatedFields[key]; + } + } + } + + if (event.updateDescription.removedFields) { + for (const key in event.updateDescription.removedFields) { + if (event.updateDescription.removedFields.hasOwnProperty(key)) { + diff[key] = undefined; + } + } + } + + fn({ + action: 'update', + clientAction: 'updated', + id: event.documentKey._id, + diff, + }); + + break; + case 'delete': + fn({ + action: 'remove', + clientAction: 'removed', + id: event.documentKey._id, + }); + break; + } + }); + }); + + Users.watch([], { fullDocument: 'updateLookup' }).on('change', watchUsers); - Subscriptions.watch([], { fullDocument: 'updateLookup' }).on('change', watchSubscriptions(Trash)); + // TODO: check the improvement mentioned below that was ignored on code unification + // Messages.watch([{ + // $addFields: { + // tmpfields: { + // $objectToArray: '$updateDescription.updatedFields', + // }, + // } }, { + // $match: { + // 'tmpfields.k': { + // $nin: ['u.username'], // avoid flood the streamer with messages changes (by username change) + // }, + // } }], { fullDocument: 'updateLookup' }).on('change', watchMessages); Inquiry.watch([], { fullDocument: 'updateLookup' }).on('change', watchInquiries); diff --git a/ee/server/services/StreamHub/watchMessages.ts b/ee/server/services/StreamHub/watchMessages.ts deleted file mode 100644 index 216155b822995..0000000000000 --- a/ee/server/services/StreamHub/watchMessages.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { ChangeEvent } from 'mongodb'; - -import { normalize } from './utils'; -import { IMessage } from '../../../../definition/IMessage'; -import { api } from '../../../../server/sdk/api'; - -export async function watchMessages(event: ChangeEvent): Promise { - switch (event.operationType) { - case 'insert': - case 'update': - // const message = await Messages.findOne(documentKey); - const message = event.fullDocument; - // Streamer.emitWithoutBroadcast('__my_messages__', message, {}); - if (!message) { - return; - } - api.broadcast('message', { action: normalize[event.operationType], message }); - // RocketChat.Logger.info('Message record', fullDocument); - // return Streamer[method]({ stream: STREA M_NAMES['room-messages'], eventName: message.rid, args: message }); - // publishMessage(operationType, message); - } -} diff --git a/ee/server/services/StreamHub/watchSubscriptions.ts b/ee/server/services/StreamHub/watchSubscriptions.ts deleted file mode 100644 index 83c6f95140fe8..0000000000000 --- a/ee/server/services/StreamHub/watchSubscriptions.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { ChangeEvent, Collection } from 'mongodb'; - -import { normalize } from './utils'; -import { api } from '../../../../server/sdk/api'; -import { ISubscription } from '../../../../definition/ISubscription'; - -export function watchSubscriptions(Trash: Collection) { - return async (event: ChangeEvent): Promise => { - let subscription; - switch (event.operationType) { - case 'insert': - case 'update': - // subscription = await Subscriptions.findOne(documentKey/* , { fields }*/); - subscription = event.fullDocument; - break; - case 'delete': - subscription = await Trash.findOne>(event.documentKey, { fields: { u: 1, rid: 1 } }); - break; - default: - return; - } - if (!subscription) { - return; - } - api.broadcast('subscription', { action: normalize[event.operationType], subscription }); - // RocketChat.Logger.info('Subscription record', fullDocument); - }; -} diff --git a/server/lib/markRoomAsRead.ts b/server/lib/markRoomAsRead.ts index 99c89c82eaca7..f419631cbae20 100644 --- a/server/lib/markRoomAsRead.ts +++ b/server/lib/markRoomAsRead.ts @@ -11,7 +11,7 @@ export async function markRoomAsRead(rid: string, uid: string): Promise { } // do not mark room as read if there are still unread threads - const alert = sub.alert && sub.tunread?.length > 0; + const alert = sub.alert && sub.tunread && sub.tunread.length > 0; await Subscriptions.setAsReadByRoomIdAndUserId(rid, uid, alert); diff --git a/server/main.js b/server/main.js index d7c048ba99fc8..86e6f4215d69d 100644 --- a/server/main.js +++ b/server/main.js @@ -75,5 +75,4 @@ import './publications/settings'; import './publications/spotlight'; import './publications/subscription'; import './routes/avatar'; -import './stream/messages'; import './stream/streamBroadcast'; diff --git a/server/modules/watchers/watchers.module.ts b/server/modules/watchers/watchers.module.ts new file mode 100644 index 0000000000000..d9fad99847572 --- /dev/null +++ b/server/modules/watchers/watchers.module.ts @@ -0,0 +1,123 @@ +// import { Authorization } from '../../sdk'; +// import { RoomsRaw } from '../../../app/models/server/raw/Rooms'; +import { SubscriptionsRaw } from '../../../app/models/server/raw/Subscriptions'; +import { UsersRaw } from '../../../app/models/server/raw/Users'; +import { SettingsRaw } from '../../../app/models/server/raw/Settings'; +import { MessagesRaw } from '../../../app/models/server/raw/Messages'; +import { IMessage } from '../../../definition/IMessage'; +import { ISubscription } from '../../../definition/ISubscription'; +import { IBaseRaw } from '../../../app/models/server/raw/BaseRaw'; +import { api } from '../../sdk/api'; + +interface IModelsParam { + // Rooms: RoomsRaw; + Subscriptions: SubscriptionsRaw; + Users: UsersRaw; + Settings: SettingsRaw; + Messages: MessagesRaw; +} + +interface IChange { + action: 'insert' | 'update' | 'remove'; + clientAction: 'inserted' | 'updated' | 'removed'; + id: string; + data?: T; + diff?: Partial; +} + +type Watcher = (model: IBaseRaw, fn: (event: IChange<{_id: string}>) => void) => void; + +// TODO: find a better place +export const subscriptionFields = { + t: 1, + ts: 1, + ls: 1, + lr: 1, + name: 1, + fname: 1, + rid: 1, + code: 1, + f: 1, + u: 1, + open: 1, + alert: 1, + roles: 1, + unread: 1, + prid: 1, + userMentions: 1, + groupMentions: 1, + archived: 1, + audioNotifications: 1, + audioNotificationValue: 1, + desktopNotifications: 1, + mobilePushNotifications: 1, + emailNotifications: 1, + unreadAlert: 1, + _updatedAt: 1, + blocked: 1, + blocker: 1, + autoTranslate: 1, + autoTranslateLanguage: 1, + disableNotifications: 1, + hideUnreadStatus: 1, + muteGroupMentions: 1, + ignored: 1, + E2EKey: 1, + tunread: 1, + tunreadGroup: 1, + tunreadUser: 1, +}; + +export function initWatchers({ Messages, Users, Settings, Subscriptions }: IModelsParam, watch: Watcher): void { + watch(Messages, async ({ clientAction, id, data }) => { + switch (clientAction) { + case 'inserted': + case 'updated': + const message: IMessage | undefined = data ?? await Messages.findOne({ _id: id }); + if (!message) { + return; + } + + if (message._hidden !== true && message.imported == null) { + const UseRealName = await Settings.getValueById('UI_Use_Real_Name') === true; + + if (UseRealName) { + if (message.u?._id) { + const user = await Users.findOneById(message.u._id); + message.u.name = user?.name; + } + + if (message.mentions?.length) { + for await (const mention of message.mentions) { + const user = await Users.findOneById(mention._id); + mention.name = user?.name; + } + } + } + + api.broadcast('watch.messages', { clientAction, message }); + } + break; + } + }); + + watch(Subscriptions, async ({ clientAction, id, data }) => { + switch (clientAction) { + case 'inserted': + case 'updated': + // Override data cuz we do not publish all fields + data = await Subscriptions.findOneById(id, { projection: subscriptionFields }); + break; + + case 'removed': + data = await Subscriptions.trashFindOneById(id, { projection: { u: 1, rid: 1 } }); + break; + } + + if (!data) { + return; + } + + api.broadcast('watch.subscriptions', { clientAction, subscription: data }); + }); +} diff --git a/server/publications/subscription/emitter.js b/server/publications/subscription/emitter.js deleted file mode 100644 index ba93668f488bd..0000000000000 --- a/server/publications/subscription/emitter.js +++ /dev/null @@ -1,37 +0,0 @@ -import { Notifications } from '../../../app/notifications'; -import { Subscriptions } from '../../../app/models'; -import { msgStream } from '../../../app/lib/server/lib/msgStream'; - -import { fields } from '.'; - -Subscriptions.on('change', ({ clientAction, id, data }) => { - switch (clientAction) { - case 'inserted': - case 'updated': - // Override data cuz we do not publish all fields - data = Subscriptions.findOneById(id, { fields }); - break; - - case 'removed': - data = Subscriptions.trashFindOneById(id, { fields: { u: 1, rid: 1 } }); - if (!data) { - return; - } - // emit a removed event on msg stream to remove the user's stream-room-messages subscription when the user is removed from room - msgStream.__emit(data.u._id, clientAction, data); - break; - } - - if (!data) { - return; - } - - Notifications.streamUser.__emit(data.u._id, clientAction, data); - - Notifications.notifyUserInThisInstance( - data.u._id, - 'subscriptions-changed', - clientAction, - data, - ); -}); diff --git a/server/publications/subscription/index.js b/server/publications/subscription/index.js index e01534f56049b..4142d990e9f79 100644 --- a/server/publications/subscription/index.js +++ b/server/publications/subscription/index.js @@ -1,47 +1,7 @@ import { Meteor } from 'meteor/meteor'; import { Subscriptions } from '../../../app/models'; -import './emitter'; - -export const fields = { - t: 1, - ts: 1, - ls: 1, - lr: 1, - name: 1, - fname: 1, - rid: 1, - code: 1, - f: 1, - u: 1, - open: 1, - alert: 1, - roles: 1, - unread: 1, - prid: 1, - userMentions: 1, - groupMentions: 1, - archived: 1, - audioNotifications: 1, - audioNotificationValue: 1, - desktopNotifications: 1, - mobilePushNotifications: 1, - emailNotifications: 1, - unreadAlert: 1, - _updatedAt: 1, - blocked: 1, - blocker: 1, - autoTranslate: 1, - autoTranslateLanguage: 1, - disableNotifications: 1, - hideUnreadStatus: 1, - muteGroupMentions: 1, - ignored: 1, - E2EKey: 1, - tunread: 1, - tunreadGroup: 1, - tunreadUser: 1, -}; +import { subscriptionFields } from '../../modules/watchers/watchers.module'; Meteor.methods({ 'subscriptions/get'(updatedAt) { @@ -49,7 +9,7 @@ Meteor.methods({ return []; } - const options = { fields }; + const options = { fields: subscriptionFields }; const records = Subscriptions.findByUserId(Meteor.userId(), options).fetch(); diff --git a/server/sdk/lib/Events.ts b/server/sdk/lib/Events.ts index fc8f156378865..34d56151e81c6 100644 --- a/server/sdk/lib/Events.ts +++ b/server/sdk/lib/Events.ts @@ -40,4 +40,6 @@ export type EventSignatures = { 'user.roleUpdate'(update: Record): void; 'user.updateCustomStatus'(userStatus: IUserStatus): void; 'userpresence'(data: { action: string; user: Partial }): void; + 'watch.messages'(data: { clientAction: string; message: Partial }): void; + 'watch.subscriptions'(data: { clientAction: string; subscription: Partial }): void; } diff --git a/server/services/authorization/canAccessRoom.ts b/server/services/authorization/canAccessRoom.ts index bcf5ba078fe62..b8428e70d991b 100644 --- a/server/services/authorization/canAccessRoom.ts +++ b/server/services/authorization/canAccessRoom.ts @@ -21,6 +21,9 @@ const roomAccessValidators: RoomAccessValidator[] = [ }, async function(room, user): Promise { + if (!room._id) { + return false; + } if (await Subscriptions.countByRoomIdAndUserId(room._id, user._id)) { return true; } @@ -31,7 +34,11 @@ const roomAccessValidators: RoomAccessValidator[] = [ if (!room.prid) { return false; } - return Authorization.canAccessRoom(Rooms.findOne(room.prid), user); + room = await Rooms.findOne(room.prid); + if (!room) { + return false; + } + return Authorization.canAccessRoom(room, user); }, canAccessRoomLivechat, canAccessRoomTokenpass, diff --git a/server/services/listeners/notification.ts b/server/services/listeners/notification.ts index d320f5de5b659..22f3c85e807b9 100644 --- a/server/services/listeners/notification.ts +++ b/server/services/listeners/notification.ts @@ -1,5 +1,5 @@ import { ServiceClass } from '../../sdk/types/ServiceClass'; -import notifications from '../../../app/notifications/server/lib/Notifications'; +import { NotificationsModule } from '../../modules/notifications/notifications.module'; const STATUS_MAP: {[k: string]: number} = { offline: 0, @@ -8,10 +8,13 @@ const STATUS_MAP: {[k: string]: number} = { busy: 3, }; +// TODO: Convert to module and implement/import on monolith and on DDPStreamer export class NotificationService extends ServiceClass { protected name = 'notification'; - constructor() { + constructor( + notifications: NotificationsModule, + ) { super(); this.onEvent('emoji.deleteCustom', (emoji) => { @@ -94,5 +97,38 @@ export class NotificationService extends ServiceClass { userStatusData: userStatus, }); }); + + this.onEvent('watch.messages', ({ message }) => { + if (!message.rid) { + return; + } + + notifications.streamRoomMessage._emit('__my_messages__', [message], undefined, false, (streamer, _sub, eventName, args, allowed) => streamer.changedPayload(streamer.subscriptionName, 'id', { + eventName, + args: [args, allowed], + })); + + notifications.streamRoomMessage.emitWithoutBroadcast(message.rid, message); + }); + + this.onEvent('watch.subscriptions', ({ clientAction, subscription }) => { + if (!subscription.u?._id) { + return; + } + + // emit a removed event on msg stream to remove the user's stream-room-messages subscription when the user is removed from room + if (clientAction === 'removed') { + notifications.streamRoomMessage.__emit(subscription.u._id, clientAction, subscription); + } + + notifications.streamUser.__emit(subscription.u._id, clientAction, subscription); + + notifications.notifyUserInThisInstance( + subscription.u._id, + 'subscriptions-changed', + clientAction, + subscription, + ); + }); } } diff --git a/server/services/startup.ts b/server/services/startup.ts index 1d2ec402a75de..9c8a3162b7724 100644 --- a/server/services/startup.ts +++ b/server/services/startup.ts @@ -4,7 +4,8 @@ import { api } from '../sdk/api'; import { Authorization } from './authorization/service'; import { MeteorService } from './meteor/service'; import { NotificationService } from './listeners/notification'; +import notifications from '../../app/notifications/server/lib/Notifications'; api.registerService(new Authorization(MongoInternals.defaultRemoteCollectionDriver().mongo.db)); api.registerService(new MeteorService()); -api.registerService(new NotificationService()); +api.registerService(new NotificationService(notifications)); diff --git a/server/stream/messages/emitter.js b/server/stream/messages/emitter.js deleted file mode 100644 index 84ac112b8db0a..0000000000000 --- a/server/stream/messages/emitter.js +++ /dev/null @@ -1,42 +0,0 @@ -import { Meteor } from 'meteor/meteor'; - -import { settings } from '../../../app/settings'; -import { Users, Messages } from '../../../app/models'; -import { msgStream } from '../../../app/lib/server'; - -Meteor.startup(function() { - function publishMessage(type, record) { - if (record._hidden !== true && (record.imported == null)) { - const UI_Use_Real_Name = settings.get('UI_Use_Real_Name') === true; - - if (record.u && record.u._id && UI_Use_Real_Name) { - const user = Users.findOneById(record.u._id); - record.u.name = user && user.name; - } - - if (record.mentions && record.mentions.length && UI_Use_Real_Name) { - record.mentions.forEach((mention) => { - const user = Users.findOneById(mention._id); - mention.name = user && user.name; - }); - } - - msgStream._emit('__my_messages__', [record], undefined, false, (streamer, sub, eventName, args, allowed) => streamer.changedPayload(streamer.subscriptionName, 'id', { - eventName, - args: [args, allowed], - })); - - msgStream.emitWithoutBroadcast(record.rid, record); - } - } - - return Messages.on('change', function({ clientAction, id, data/* , oplog*/ }) { - switch (clientAction) { - case 'inserted': - case 'updated': - const message = data ?? Messages.findOne({ _id: id }); - publishMessage(clientAction, message); - break; - } - }); -}); diff --git a/server/stream/messages/index.js b/server/stream/messages/index.js deleted file mode 100644 index 625877ba64089..0000000000000 --- a/server/stream/messages/index.js +++ /dev/null @@ -1 +0,0 @@ -import './emitter'; From 34f763058da36b04417a7f4711846e9ffa57a577 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Mon, 5 Oct 2020 14:47:33 -0300 Subject: [PATCH 139/198] Type improvements --- app/models/server/raw/Subscriptions.ts | 7 ++++--- definition/IBaseData.ts | 3 +++ ee/server/services/StreamHub/StreamHub.ts | 2 +- server/modules/watchers/watchers.module.ts | 5 +++-- 4 files changed, 11 insertions(+), 6 deletions(-) create mode 100644 definition/IBaseData.ts diff --git a/app/models/server/raw/Subscriptions.ts b/app/models/server/raw/Subscriptions.ts index 25107d51c2328..1cd61692b69cf 100644 --- a/app/models/server/raw/Subscriptions.ts +++ b/app/models/server/raw/Subscriptions.ts @@ -3,7 +3,8 @@ import { FindOneOptions, Cursor, UpdateQuery, FilterQuery } from 'mongodb'; import { BaseRaw } from './BaseRaw'; import { ISubscription } from '../../../../definition/ISubscription'; -export class SubscriptionsRaw extends BaseRaw { +type T = ISubscription; +export class SubscriptionsRaw extends BaseRaw { findOneByRoomIdAndUserId(rid: string, uid: string, options: FindOneOptions = {}): Promise { const query = { rid, @@ -50,12 +51,12 @@ export class SubscriptionsRaw extends BaseRaw { } setAsReadByRoomIdAndUserId(rid: string, uid: string, alert = false, options: FindOneOptions = {}): ReturnType['update']> { - const query: FilterQuery = { + const query: FilterQuery = { rid, 'u._id': uid, }; - const update: UpdateQuery = { + const update: UpdateQuery = { $set: { open: true, alert, diff --git a/definition/IBaseData.ts b/definition/IBaseData.ts new file mode 100644 index 0000000000000..16227f92a1e36 --- /dev/null +++ b/definition/IBaseData.ts @@ -0,0 +1,3 @@ +export interface IBaseData { + _id: string; +} diff --git a/ee/server/services/StreamHub/StreamHub.ts b/ee/server/services/StreamHub/StreamHub.ts index 6ac16ab3998b3..3c81855b3eb50 100755 --- a/ee/server/services/StreamHub/StreamHub.ts +++ b/ee/server/services/StreamHub/StreamHub.ts @@ -34,7 +34,7 @@ export class StreamHub extends ServiceClass implements IServiceClass { Subscriptions: new SubscriptionsRaw(Subscriptions, Trash), Settings: new SettingsRaw(Settings, Trash), }, (model, fn) => { - model.col.watch<{_id: string}>([]).on('change', (event) => { + model.col.watch([]).on('change', (event) => { switch (event.operationType) { case 'insert': fn({ diff --git a/server/modules/watchers/watchers.module.ts b/server/modules/watchers/watchers.module.ts index d9fad99847572..09ef732b915ff 100644 --- a/server/modules/watchers/watchers.module.ts +++ b/server/modules/watchers/watchers.module.ts @@ -8,6 +8,7 @@ import { IMessage } from '../../../definition/IMessage'; import { ISubscription } from '../../../definition/ISubscription'; import { IBaseRaw } from '../../../app/models/server/raw/BaseRaw'; import { api } from '../../sdk/api'; +import { IBaseData } from '../../../definition/IBaseData'; interface IModelsParam { // Rooms: RoomsRaw; @@ -22,10 +23,10 @@ interface IChange { clientAction: 'inserted' | 'updated' | 'removed'; id: string; data?: T; - diff?: Partial; + diff?: Record; } -type Watcher = (model: IBaseRaw, fn: (event: IChange<{_id: string}>) => void) => void; +type Watcher = (model: IBaseRaw, fn: (event: IChange) => void) => void; // TODO: find a better place export const subscriptionFields = { From a84ed8f61fed9b8f4a86a83ba608739ba42ed99c Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Mon, 5 Oct 2020 19:08:26 -0300 Subject: [PATCH 140/198] Implement Roles watcher --- .../server/methods/deleteRole.js | 10 +- app/authorization/server/methods/saveRole.js | 5 - app/models/server/raw/Roles.js | 10 +- app/models/server/raw/index.ts | 4 +- ee/server/services/DDPStreamer/DDPStreamer.ts | 3 - ee/server/services/StreamHub/StreamHub.ts | 37 ++-- ee/server/services/StreamHub/watchRoles.ts | 27 --- ee/server/services/package-lock.json | 187 +++++++++++++++++- server/modules/watchers/watchers.module.ts | 20 +- server/sdk/lib/Events.ts | 2 +- server/services/listeners/notification.ts | 8 + 11 files changed, 243 insertions(+), 70 deletions(-) delete mode 100644 ee/server/services/StreamHub/watchRoles.ts diff --git a/app/authorization/server/methods/deleteRole.js b/app/authorization/server/methods/deleteRole.js index 89af2485e575a..8613e1761b0a5 100644 --- a/app/authorization/server/methods/deleteRole.js +++ b/app/authorization/server/methods/deleteRole.js @@ -2,7 +2,6 @@ import { Meteor } from 'meteor/meteor'; import * as Models from '../../../models/server'; import { hasPermission } from '../functions/hasPermission'; -import notifications from '../../../notifications/server/lib/Notifications'; Meteor.methods({ 'authorization:deleteRole'(roleName) { @@ -36,13 +35,6 @@ Meteor.methods({ }); } - const removed = Models.Roles.remove(role.name); - if (removed) { - notifications.streamRoles.emit('roles', { - type: 'removed', - name: roleName, - }); - } - return removed; + return Models.Roles.remove(role.name); }, }); diff --git a/app/authorization/server/methods/saveRole.js b/app/authorization/server/methods/saveRole.js index d0f0acbb45300..5e09f211240d7 100644 --- a/app/authorization/server/methods/saveRole.js +++ b/app/authorization/server/methods/saveRole.js @@ -4,7 +4,6 @@ import { Roles } from '../../../models/server'; import { settings } from '../../../settings/server'; import { hasPermission } from '../functions/hasPermission'; import { api } from '../../../../server/sdk/api'; -import notifications from '../../../notifications/server/lib/Notifications'; Meteor.methods({ 'authorization:saveRole'(roleData) { @@ -32,10 +31,6 @@ Meteor.methods({ _id: roleData.name, }); } - notifications.streamRoles.emit('roles', { - type: 'changed', - ...roleData, - }); return update; }, }); diff --git a/app/models/server/raw/Roles.js b/app/models/server/raw/Roles.js index bda23eea6b557..523aa968057d1 100644 --- a/app/models/server/raw/Roles.js +++ b/app/models/server/raw/Roles.js @@ -1,8 +1,12 @@ import { BaseRaw } from './BaseRaw'; -import * as Models from './index'; - export class RolesRaw extends BaseRaw { + constructor(col, trash, models) { + super(col, trash); + + this.models = models; + } + async isUserInRoles(userId, roles, scope) { roles = [].concat(roles); @@ -12,7 +16,7 @@ export class RolesRaw extends BaseRaw { // eslint-disable-next-line no-await-in-loop const role = await this.findOne({ _id: roleName }); const roleScope = (role && role.scope) || 'Users'; - const model = Models[roleScope]; + const model = this.models[roleScope]; // eslint-disable-next-line no-await-in-loop const permitted = await (model && model.isUserInRole && model.isUserInRole(userId, roleName, scope)); diff --git a/app/models/server/raw/index.ts b/app/models/server/raw/index.ts index e67377b9b7b59..c267bba27d60e 100644 --- a/app/models/server/raw/index.ts +++ b/app/models/server/raw/index.ts @@ -56,7 +56,6 @@ import { initWatchers } from '../../../../server/modules/watchers/watchers.modul const trashCollection = trash.rawCollection(); export const Permissions = new PermissionsRaw(PermissionsModel.model.rawCollection(), trashCollection); -export const Roles = new RolesRaw(RolesModel.model.rawCollection(), trashCollection); export const Subscriptions = new SubscriptionsRaw(SubscriptionsModel.model.rawCollection(), trashCollection); export const Settings = new SettingsRaw(SettingsModel.model.rawCollection(), trashCollection); export const Users = new UsersRaw(UsersModel.model.rawCollection(), trashCollection); @@ -81,12 +80,14 @@ export const Statistics = new StatisticsRaw(StatisticsModel.model.rawCollection( export const NotificationQueue = new NotificationQueueRaw(NotificationQueueModel.model.rawCollection(), trashCollection); export const LivechatBusinessHours = new LivechatBusinessHoursRaw(LivechatBusinessHoursModel.model.rawCollection(), trashCollection); export const ServerEvents = new ServerEventsRaw(ServerEventModel.model.rawCollection(), trashCollection); +export const Roles = new RolesRaw(RolesModel.model.rawCollection(), trashCollection, { Users, Subscriptions }); const map = { [Messages.col.collectionName]: MessagesModel, [Users.col.collectionName]: UsersModel, [Subscriptions.col.collectionName]: SubscriptionsModel, [Settings.col.collectionName]: SettingsModel, + [Roles.col.collectionName]: RolesModel, }; initWatchers({ @@ -94,6 +95,7 @@ initWatchers({ Users, Subscriptions, Settings, + Roles, }, (model, fn) => { const meteorModel = map[model.col.collectionName]; diff --git a/ee/server/services/DDPStreamer/DDPStreamer.ts b/ee/server/services/DDPStreamer/DDPStreamer.ts index 5d149457b565d..3ea3afe7d8f9d 100644 --- a/ee/server/services/DDPStreamer/DDPStreamer.ts +++ b/ee/server/services/DDPStreamer/DDPStreamer.ts @@ -215,9 +215,6 @@ export class DDPStreamer extends ServiceClass { // }, // }, // role(payload) { - this.onEvent('role', (payload): void => { - notifications.streamRoles.emit('roles', payload); - }); this.onEvent('meteor.loginServiceConfiguration', ({ action, record }): void => { events.emit('meteor.loginServiceConfiguration', action, record); diff --git a/ee/server/services/StreamHub/StreamHub.ts b/ee/server/services/StreamHub/StreamHub.ts index 3c81855b3eb50..09a2284838d0b 100755 --- a/ee/server/services/StreamHub/StreamHub.ts +++ b/ee/server/services/StreamHub/StreamHub.ts @@ -1,7 +1,6 @@ import { watchUsers } from './watchUsers'; import { watchSettings } from './watchSettings'; import { watchRooms } from './watchRooms'; -import { watchRoles } from './watchRoles'; import { watchInquiries } from './watchInquiries'; import { getConnection } from '../mongo'; import { ServiceClass, IServiceClass } from '../../../../server/sdk/types/ServiceClass'; @@ -11,6 +10,7 @@ import { MessagesRaw } from '../../../../app/models/server/raw/Messages'; import { UsersRaw } from '../../../../app/models/server/raw/Users'; import { SubscriptionsRaw } from '../../../../app/models/server/raw/Subscriptions'; import { SettingsRaw } from '../../../../app/models/server/raw/Settings'; +import { RolesRaw } from '../../../../app/models/server/raw/Roles'; export class StreamHub extends ServiceClass implements IServiceClass { protected name = 'hub'; @@ -19,21 +19,28 @@ export class StreamHub extends ServiceClass implements IServiceClass { const db = await getConnection(); const Trash = db.collection('rocketchat_trash'); - const Users = db.collection('users'); - const Roles = db.collection('rocketchat_roles'); - const Messages = db.collection('rocketchat_message'); - const Subscriptions = db.collection('rocketchat_subscription'); const Rooms = db.collection('rocketchat_room'); - const Settings = db.collection('rocketchat_settings'); const Inquiry = db.collection('rocketchat_livechat_inquiry'); const loginServiceConfiguration = db.collection('meteor_accounts_loginServiceConfiguration'); - initWatchers({ - Messages: new MessagesRaw(Messages, Trash), - Users: new UsersRaw(Users, Trash), - Subscriptions: new SubscriptionsRaw(Subscriptions, Trash), - Settings: new SettingsRaw(Settings, Trash), - }, (model, fn) => { + const UsersCol = db.collection('users'); + const SettingsCol = db.collection('rocketchat_settings'); + + const Settings = new SettingsRaw(SettingsCol, Trash); + const Users = new UsersRaw(UsersCol, Trash); + const Subscriptions = new SubscriptionsRaw(db.collection('rocketchat_subscription'), Trash); + const Messages = new MessagesRaw(db.collection('rocketchat_message'), Trash); + const Roles = new RolesRaw(db.collection('rocketchat_roles'), Trash, { Users, Subscriptions }); + + const models = { + Messages, + Users, + Subscriptions, + Settings, + Roles, + }; + + initWatchers(models, (model, fn) => { model.col.watch([]).on('change', (event) => { switch (event.operationType) { case 'insert': @@ -83,7 +90,7 @@ export class StreamHub extends ServiceClass implements IServiceClass { }); }); - Users.watch([], { fullDocument: 'updateLookup' }).on('change', watchUsers); + UsersCol.watch([], { fullDocument: 'updateLookup' }).on('change', watchUsers); // TODO: check the improvement mentioned below that was ignored on code unification // Messages.watch([{ @@ -102,9 +109,7 @@ export class StreamHub extends ServiceClass implements IServiceClass { Rooms.watch([], { fullDocument: 'updateLookup' }).on('change', watchRooms); - Roles.watch([], { fullDocument: 'updateLookup' }).on('change', watchRoles); - - Settings.watch([{ + SettingsCol.watch([{ $addFields: { tmpfields: { $objectToArray: '$updateDescription.updatedFields', diff --git a/ee/server/services/StreamHub/watchRoles.ts b/ee/server/services/StreamHub/watchRoles.ts deleted file mode 100644 index 23d355827a319..0000000000000 --- a/ee/server/services/StreamHub/watchRoles.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { ChangeEvent } from 'mongodb'; - -import { api } from '../../../../server/sdk/api'; -import { IRole } from '../../../../definition/IRole'; - -export async function watchRoles(event: ChangeEvent): Promise { - // RocketChat.Logger.info('Role record', documentKey); - switch (event.operationType) { - case 'insert': - case 'update': - if (!event.fullDocument) { - return; - } - - api.broadcast('role', { - type: 'changed', - ...event.fullDocument, - }); - break; - case 'delete': - api.broadcast('role', { - type: 'removed', - name: event.documentKey._id, - }); - break; - } -} diff --git a/ee/server/services/package-lock.json b/ee/server/services/package-lock.json index d838d0b891914..98ea7907784b2 100644 --- a/ee/server/services/package-lock.json +++ b/ee/server/services/package-lock.json @@ -359,6 +359,63 @@ } } }, + "args": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/args/-/args-5.0.1.tgz", + "integrity": "sha512-1kqmFCFsPffavQFGt8OxJdIcETti99kySRUPMpOhaGjL6mRJn8HFU1OxKY5bMqfZKUwTQc1mZkAjmGYaVOHFtQ==", + "requires": { + "camelcase": "5.0.0", + "chalk": "2.4.2", + "leven": "2.1.0", + "mri": "1.1.4" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, "ast-types": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.14.1.tgz", @@ -480,6 +537,11 @@ "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", "dev": true }, + "camelcase": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz", + "integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==" + }, "chalk": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", @@ -694,6 +756,11 @@ "xtend": "~4.0.0" } }, + "es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==" + }, "es6-promise": { "version": "4.2.8", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", @@ -715,6 +782,11 @@ "integrity": "sha1-9EvaEtRbvfnLf4Yu5+SCez3TIlQ=", "dev": true }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, "escodegen": { "version": "1.14.3", "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", @@ -772,6 +844,11 @@ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, + "fastest-validator": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/fastest-validator/-/fastest-validator-1.7.0.tgz", + "integrity": "sha512-g8G9gNYHVH2EdsIOAnQ60xTeRtf+yitBudR1/5ZpGCIUyXpYv4H9oVT4mwocUOAi/JH9X3kC/R3P6tiukItbvg==" + }, "fclone": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/fclone/-/fclone-1.0.11.tgz", @@ -793,6 +870,11 @@ "to-regex-range": "^5.0.1" } }, + "fn-args": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/fn-args/-/fn-args-5.0.0.tgz", + "integrity": "sha512-CtbfI3oFFc3nbdIoHycrfbrxiGgxXBXXuyOl49h47JawM1mYrqpiRqnH5CB2mBatdXvHHOUO6a+RiAuuvKt0lw==" + }, "follow-redirects": { "version": "1.5.10", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", @@ -1067,6 +1149,11 @@ "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", "dev": true }, + "ipaddr.js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.0.tgz", + "integrity": "sha512-S54H9mIj0rbxRIyrDMEuuER86LdlgUg9FSeZ8duQb6CUG2iRrA36MYVQBSprTF/ZeAwvyQ5mDGuNvIPM0BIl3w==" + }, "is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -1129,12 +1216,22 @@ } } }, + "kleur": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.3.tgz", + "integrity": "sha512-H1tr8QP2PxFTNwAFM74Mui2b6ovcY9FoxJefgrwxY+OCJcq01k5nvhf4M/KnizzrJvLRap5STUy7dgDV35iUBw==" + }, "lazy": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/lazy/-/lazy-1.0.11.tgz", "integrity": "sha1-2qBoIGKCVCwIgojpdcKXwa53tpA=", "dev": true }, + "leven": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz", + "integrity": "sha1-wuep93IJTe6dNCAq6KzORoeHVYA=" + }, "levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", @@ -1148,8 +1245,7 @@ "lodash": { "version": "4.17.20", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", - "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", - "dev": true + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" }, "log-driver": { "version": "1.2.7", @@ -1225,6 +1321,44 @@ "integrity": "sha1-EUyUlnPiqKNenTV4hSeqN7Z52is=", "dev": true }, + "moleculer": { + "version": "0.14.11", + "resolved": "https://registry.npmjs.org/moleculer/-/moleculer-0.14.11.tgz", + "integrity": "sha512-u8uO5PTMgyW1ZD1rKkIOCwthOfSyR9z1FfcpflGO20IKI9zqJxy5VRQ0BTGfHcrTD/+o/ux5eAFV1i2CwidpxQ==", + "requires": { + "args": "^5.0.1", + "es6-error": "^4.1.1", + "eventemitter2": "^6.4.3", + "fastest-validator": "^1.7.0", + "fn-args": "^5.0.0", + "glob": "^7.1.6", + "ipaddr.js": "^2.0.0", + "kleur": "^4.1.2", + "lodash": "^4.17.20", + "lru-cache": "^6.0.0", + "node-fetch": "^2.6.1" + }, + "dependencies": { + "eventemitter2": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.3.tgz", + "integrity": "sha512-t0A2msp6BzOf+QAcI6z9XMktLj52OjGQg+8SJH6v5+3uxNpWYRR3wQmfA+6xtMU9kOC59qk9licus5dYcrYkMQ==" + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } + }, "moment": { "version": "2.27.0", "resolved": "https://registry.npmjs.org/moment/-/moment-2.27.0.tgz", @@ -1253,6 +1387,11 @@ "saslprep": "^1.0.0" } }, + "mri": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.1.4.tgz", + "integrity": "sha512-6y7IjGPm8AzlvoUrwAaw1tLnUBudaS3752vcd8JtrpGGQn+rXIe63LFVHm/YMwtqAuh+LJPCFdlLYPWM1nYn6w==" + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -1275,6 +1414,15 @@ "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", "dev": true }, + "nats": { + "version": "1.4.12", + "resolved": "https://registry.npmjs.org/nats/-/nats-1.4.12.tgz", + "integrity": "sha512-Jf4qesEF0Ay0D4AMw3OZnKMRTQm+6oZ5q8/m4gpy5bTmiDiK6wCXbZpzEslmezGpE93LV3RojNEG6dpK/mysLQ==", + "requires": { + "nuid": "^1.1.4", + "ts-nkeys": "^1.0.16" + } + }, "needle": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/needle/-/needle-2.4.0.tgz", @@ -1306,6 +1454,11 @@ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz", "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==" }, + "node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" + }, "node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -1405,6 +1558,11 @@ } } }, + "nuid": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/nuid/-/nuid-1.1.4.tgz", + "integrity": "sha512-PXiYyHhGfrq8H4g5HyC8enO1lz6SBe5z6x1yx/JG4tmADzDGJVQy3l1sRf3VtEvPsN8dGn9hRFRwDKWL62x0BA==" + }, "number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", @@ -1904,8 +2062,7 @@ "sprintf-js": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", - "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==", - "dev": true + "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==" }, "statuses": { "version": "1.5.0", @@ -2027,6 +2184,14 @@ "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", "dev": true }, + "ts-nkeys": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/ts-nkeys/-/ts-nkeys-1.0.16.tgz", + "integrity": "sha512-1qrhAlavbm36wtW+7NtKOgxpzl+70NTF8xlz9mEhiA5zHMlMxjj3sEVKWm3pGZhHXE0Q3ykjrj+OSRVaYw+Dqg==", + "requires": { + "tweetnacl": "^1.0.3" + } + }, "ts-node": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.0.0.tgz", @@ -2064,6 +2229,11 @@ "integrity": "sha1-0CDIRvrdUMhVq7JeuuzGj8EPeWM=", "dev": true }, + "tweetnacl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" + }, "type-check": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", @@ -2079,6 +2249,15 @@ "integrity": "sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==", "dev": true }, + "underscore.string": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-3.3.5.tgz", + "integrity": "sha512-g+dpmgn+XBneLmXXo+sGlW5xQEt4ErkS3mgeN2GFbremYeMBSJKr9Wf2KJplQVaiPY/f7FN6atosWYNm9ovrYg==", + "requires": { + "sprintf-js": "^1.0.3", + "util-deprecate": "^1.0.2" + } + }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", diff --git a/server/modules/watchers/watchers.module.ts b/server/modules/watchers/watchers.module.ts index 09ef732b915ff..133eb64d3dad4 100644 --- a/server/modules/watchers/watchers.module.ts +++ b/server/modules/watchers/watchers.module.ts @@ -4,8 +4,10 @@ import { SubscriptionsRaw } from '../../../app/models/server/raw/Subscriptions'; import { UsersRaw } from '../../../app/models/server/raw/Users'; import { SettingsRaw } from '../../../app/models/server/raw/Settings'; import { MessagesRaw } from '../../../app/models/server/raw/Messages'; +import { RolesRaw } from '../../../app/models/server/raw/Roles'; import { IMessage } from '../../../definition/IMessage'; import { ISubscription } from '../../../definition/ISubscription'; +import { IRole } from '../../../definition/IRole'; import { IBaseRaw } from '../../../app/models/server/raw/BaseRaw'; import { api } from '../../sdk/api'; import { IBaseData } from '../../../definition/IBaseData'; @@ -16,6 +18,7 @@ interface IModelsParam { Users: UsersRaw; Settings: SettingsRaw; Messages: MessagesRaw; + Roles: RolesRaw; } interface IChange { @@ -69,7 +72,7 @@ export const subscriptionFields = { tunreadUser: 1, }; -export function initWatchers({ Messages, Users, Settings, Subscriptions }: IModelsParam, watch: Watcher): void { +export function initWatchers({ Messages, Users, Settings, Subscriptions, Roles }: IModelsParam, watch: Watcher): void { watch(Messages, async ({ clientAction, id, data }) => { switch (clientAction) { case 'inserted': @@ -121,4 +124,19 @@ export function initWatchers({ Messages, Users, Settings, Subscriptions }: IMode api.broadcast('watch.subscriptions', { clientAction, subscription: data }); }); + + watch(Roles, async ({ clientAction, id, data }) => { + const role = clientAction === 'removed' + ? { _id: id, name: id } + : data || await Roles.findOneById(id); + + if (!role) { + return; + } + + api.broadcast('watch.roles', { + clientAction: clientAction !== 'removed' ? 'changed' : clientAction, + role, + }); + }); } diff --git a/server/sdk/lib/Events.ts b/server/sdk/lib/Events.ts index 34d56151e81c6..29f0ff6724649 100644 --- a/server/sdk/lib/Events.ts +++ b/server/sdk/lib/Events.ts @@ -23,7 +23,6 @@ export type EventSignatures = { 'meteor.loginServiceConfiguration'(data: { action: string; record: any }): void; 'notify.ephemeralMessage'(uid: string, rid: string, message: Partial): void; 'permission.changed'(data: { clientAction: string; data: any }): void; - 'role'(data: {type: 'changed' | 'removed' } & Partial): void; 'room'(data: { action: string; room: Partial }): void; 'room.avatarUpdate'(room: Partial): void; 'setting'(data: { action: string; setting: Partial }): void; @@ -41,5 +40,6 @@ export type EventSignatures = { 'user.updateCustomStatus'(userStatus: IUserStatus): void; 'userpresence'(data: { action: string; user: Partial }): void; 'watch.messages'(data: { clientAction: string; message: Partial }): void; + 'watch.roles'(data: { clientAction: string; role: Partial }): void; 'watch.subscriptions'(data: { clientAction: string; subscription: Partial }): void; } diff --git a/server/services/listeners/notification.ts b/server/services/listeners/notification.ts index 22f3c85e807b9..55e8217a4950b 100644 --- a/server/services/listeners/notification.ts +++ b/server/services/listeners/notification.ts @@ -130,5 +130,13 @@ export class NotificationService extends ServiceClass { subscription, ); }); + + this.onEvent('watch.roles', ({ clientAction, role }): void => { + const payload = { + type: clientAction, + ...role, + }; + notifications.streamRoles.emit('roles', payload); + }); } } From 48aedfaff691faa51394735e89cabea321f9e3dc Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Mon, 5 Oct 2020 20:26:52 -0300 Subject: [PATCH 141/198] Move Permissions change watcher to module --- .../server/streamer/permissions/emitter.js | 37 ------------- .../server/streamer/permissions/index.js | 1 - app/models/server/raw/Permissions.js | 4 -- app/models/server/raw/Permissions.ts | 5 ++ app/models/server/raw/Settings.js | 37 ------------- app/models/server/raw/Settings.ts | 53 +++++++++++++++++++ app/models/server/raw/index.ts | 2 + definition/IPermission.ts | 12 +++++ ee/server/services/StreamHub/StreamHub.ts | 2 + ee/server/services/package-lock.json | 27 ++++++++++ ee/server/services/package.json | 1 + server/modules/watchers/watchers.module.ts | 46 +++++++++++++++- server/sdk/lib/Events.ts | 2 +- server/services/authorization/service.ts | 18 +++++-- 14 files changed, 162 insertions(+), 85 deletions(-) delete mode 100644 app/authorization/server/streamer/permissions/emitter.js delete mode 100644 app/models/server/raw/Permissions.js create mode 100644 app/models/server/raw/Permissions.ts delete mode 100644 app/models/server/raw/Settings.js create mode 100644 app/models/server/raw/Settings.ts create mode 100644 definition/IPermission.ts diff --git a/app/authorization/server/streamer/permissions/emitter.js b/app/authorization/server/streamer/permissions/emitter.js deleted file mode 100644 index 468e94cbfb818..0000000000000 --- a/app/authorization/server/streamer/permissions/emitter.js +++ /dev/null @@ -1,37 +0,0 @@ -import Settings from '../../../../models/server/models/Settings'; -import { CONSTANTS } from '../../../lib'; -import Permissions from '../../../../models/server/models/Permissions'; -import { clearCache } from '../../functions/hasPermission'; -import { api } from '../../../../../server/sdk/api'; - -Permissions.on('change', ({ clientAction, id, data, diff }) => { - if (diff && Object.keys(diff).length === 1 && diff._updatedAt) { - // avoid useless changes - return; - } - switch (clientAction) { - case 'updated': - case 'inserted': - data = data ?? Permissions.findOneById(id); - break; - - case 'removed': - data = { _id: id }; - break; - } - - clearCache(); - - api.broadcast('permission.changed', { clientAction, data }); - - if (data.level && data.level === CONSTANTS.SETTINGS_LEVEL) { - // if the permission changes, the effect on the visible settings depends on the role affected. - // The selected-settings-based consumers have to react accordingly and either add or remove the - // setting from the user's collection - const setting = Settings.findOneNotHiddenById(data.settingId); - if (!setting) { - return; - } - api.broadcast('setting.privateChanged', { clientAction: 'updated', setting }); - } -}); diff --git a/app/authorization/server/streamer/permissions/index.js b/app/authorization/server/streamer/permissions/index.js index 09e7ccffc6c7a..edffbdfe3e734 100644 --- a/app/authorization/server/streamer/permissions/index.js +++ b/app/authorization/server/streamer/permissions/index.js @@ -1,7 +1,6 @@ import { Meteor } from 'meteor/meteor'; import Permissions from '../../../../models/server/models/Permissions'; -import './emitter'; Meteor.methods({ 'permissions/get'(updatedAt) { diff --git a/app/models/server/raw/Permissions.js b/app/models/server/raw/Permissions.js deleted file mode 100644 index 3c5c1aaf6e673..0000000000000 --- a/app/models/server/raw/Permissions.js +++ /dev/null @@ -1,4 +0,0 @@ -import { BaseRaw } from './BaseRaw'; - -export class PermissionsRaw extends BaseRaw { -} diff --git a/app/models/server/raw/Permissions.ts b/app/models/server/raw/Permissions.ts new file mode 100644 index 0000000000000..d5321c82c80b0 --- /dev/null +++ b/app/models/server/raw/Permissions.ts @@ -0,0 +1,5 @@ +import { BaseRaw } from './BaseRaw'; +import { IPermission } from '../../../../definition/IPermission'; + +export class PermissionsRaw extends BaseRaw { +} diff --git a/app/models/server/raw/Settings.js b/app/models/server/raw/Settings.js deleted file mode 100644 index 7bfeb5be325b7..0000000000000 --- a/app/models/server/raw/Settings.js +++ /dev/null @@ -1,37 +0,0 @@ -import { BaseRaw } from './BaseRaw'; - -export class SettingsRaw extends BaseRaw { - async getValueById(_id) { - const setting = await this.col.findOne({ _id }, { projection: { value: 1 } }); - - return setting.value; - } - - findByIds(_id = []) { - _id = [].concat(_id); - - const query = { - _id: { - $in: _id, - }, - }; - - return this.find(query); - } - - updateValueById(_id, value) { - const query = { - blocked: { $ne: true }, - value: { $ne: value }, - _id, - }; - - const update = { - $set: { - value, - }, - }; - - return this.col.update(query, update); - } -} diff --git a/app/models/server/raw/Settings.ts b/app/models/server/raw/Settings.ts new file mode 100644 index 0000000000000..7f25b049284c0 --- /dev/null +++ b/app/models/server/raw/Settings.ts @@ -0,0 +1,53 @@ +import { Cursor, WriteOpResult } from 'mongodb'; + +import { BaseRaw } from './BaseRaw'; +import { ISetting } from '../../../../definition/ISetting'; + +type T = ISetting; + +export class SettingsRaw extends BaseRaw { + async getValueById(_id: string): Promise { + const setting = await this.findOne({ _id }, { projection: { value: 1 } }); + + return setting?.value; + } + + findOneNotHiddenById(_id: string): Promise { + const query = { + _id, + hidden: { $ne: true }, + }; + + return this.findOne(query); + } + + findByIds(_id: string[] | string = []): Cursor { + if (typeof _id === 'string') { + _id = [_id]; + } + + const query = { + _id: { + $in: _id, + }, + }; + + return this.find(query); + } + + updateValueById(_id: string, value: any): Promise { + const query = { + blocked: { $ne: true }, + value: { $ne: value }, + _id, + }; + + const update = { + $set: { + value, + }, + }; + + return this.update(query, update); + } +} diff --git a/app/models/server/raw/index.ts b/app/models/server/raw/index.ts index c267bba27d60e..b104fbf237953 100644 --- a/app/models/server/raw/index.ts +++ b/app/models/server/raw/index.ts @@ -88,6 +88,7 @@ const map = { [Subscriptions.col.collectionName]: SubscriptionsModel, [Settings.col.collectionName]: SettingsModel, [Roles.col.collectionName]: RolesModel, + [Permissions.col.collectionName]: PermissionsModel, }; initWatchers({ @@ -95,6 +96,7 @@ initWatchers({ Users, Subscriptions, Settings, + Permissions, Roles, }, (model, fn) => { const meteorModel = map[model.col.collectionName]; diff --git a/definition/IPermission.ts b/definition/IPermission.ts new file mode 100644 index 0000000000000..be119d7bacee8 --- /dev/null +++ b/definition/IPermission.ts @@ -0,0 +1,12 @@ +export interface IPermission { + _id: string; + _updatedAt?: Date; + roles: string[]; + group?: string; + groupPermissionId?: string; + level?: 'settings'; + section?: string; + sectionPermissionId?: string; + settingId?: string; + sorter?: number; +} diff --git a/ee/server/services/StreamHub/StreamHub.ts b/ee/server/services/StreamHub/StreamHub.ts index 09a2284838d0b..85801f7132dd4 100755 --- a/ee/server/services/StreamHub/StreamHub.ts +++ b/ee/server/services/StreamHub/StreamHub.ts @@ -30,12 +30,14 @@ export class StreamHub extends ServiceClass implements IServiceClass { const Users = new UsersRaw(UsersCol, Trash); const Subscriptions = new SubscriptionsRaw(db.collection('rocketchat_subscription'), Trash); const Messages = new MessagesRaw(db.collection('rocketchat_message'), Trash); + const Permissions = new MessagesRaw(db.collection('rocketchat_permissions'), Trash); const Roles = new RolesRaw(db.collection('rocketchat_roles'), Trash, { Users, Subscriptions }); const models = { Messages, Users, Subscriptions, + Permissions, Settings, Roles, }; diff --git a/ee/server/services/package-lock.json b/ee/server/services/package-lock.json index 98ea7907784b2..ece759bfa1dd1 100644 --- a/ee/server/services/package-lock.json +++ b/ee/server/services/package-lock.json @@ -1273,12 +1273,34 @@ "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", "dev": true }, + "map-age-cleaner": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", + "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", + "requires": { + "p-defer": "^1.0.0" + } + }, + "mem": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/mem/-/mem-6.1.1.tgz", + "integrity": "sha512-Ci6bIfq/UgcxPTYa8dQQ5FY3BzKkT894bwXWXxC/zqs0XgMO2cT20CGkOqda7gZNkmK5VP4x89IGZ6K7hfbn3Q==", + "requires": { + "map-age-cleaner": "^0.1.3", + "mimic-fn": "^3.0.0" + } + }, "memory-pager": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", "optional": true }, + "mimic-fn": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-3.1.0.tgz", + "integrity": "sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==" + }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -1619,6 +1641,11 @@ "os-tmpdir": "^1.0.0" } }, + "p-defer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", + "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=" + }, "pac-proxy-agent": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-3.0.1.tgz", diff --git a/ee/server/services/package.json b/ee/server/services/package.json index 5395d85f035b2..1f1bb72fdfa6c 100644 --- a/ee/server/services/package.json +++ b/ee/server/services/package.json @@ -21,6 +21,7 @@ "bcrypt": "^4.0.1", "ejson": "^2.2.0", "jaeger-client": "^3.18.1", + "mem": "^6.1.0", "moleculer": "^0.14.10", "mongodb": "^3.6.1", "msgpack5": "^4.2.1", diff --git a/server/modules/watchers/watchers.module.ts b/server/modules/watchers/watchers.module.ts index 133eb64d3dad4..d892fd14c786a 100644 --- a/server/modules/watchers/watchers.module.ts +++ b/server/modules/watchers/watchers.module.ts @@ -3,6 +3,7 @@ import { SubscriptionsRaw } from '../../../app/models/server/raw/Subscriptions'; import { UsersRaw } from '../../../app/models/server/raw/Users'; import { SettingsRaw } from '../../../app/models/server/raw/Settings'; +import { PermissionsRaw } from '../../../app/models/server/raw/Permissions'; import { MessagesRaw } from '../../../app/models/server/raw/Messages'; import { RolesRaw } from '../../../app/models/server/raw/Roles'; import { IMessage } from '../../../definition/IMessage'; @@ -11,10 +12,12 @@ import { IRole } from '../../../definition/IRole'; import { IBaseRaw } from '../../../app/models/server/raw/BaseRaw'; import { api } from '../../sdk/api'; import { IBaseData } from '../../../definition/IBaseData'; +import { IPermission } from '../../../definition/IPermission'; interface IModelsParam { // Rooms: RoomsRaw; Subscriptions: SubscriptionsRaw; + Permissions: PermissionsRaw; Users: UsersRaw; Settings: SettingsRaw; Messages: MessagesRaw; @@ -72,7 +75,14 @@ export const subscriptionFields = { tunreadUser: 1, }; -export function initWatchers({ Messages, Users, Settings, Subscriptions, Roles }: IModelsParam, watch: Watcher): void { +export function initWatchers({ + Messages, + Users, + Settings, + Subscriptions, + Roles, + Permissions, +}: IModelsParam, watch: Watcher): void { watch(Messages, async ({ clientAction, id, data }) => { switch (clientAction) { case 'inserted': @@ -139,4 +149,38 @@ export function initWatchers({ Messages, Users, Settings, Subscriptions, Roles } role, }); }); + + watch(Permissions, async ({ clientAction, id, data, diff }) => { + if (diff && Object.keys(diff).length === 1 && diff._updatedAt) { + // avoid useless changes + return; + } + switch (clientAction) { + case 'updated': + case 'inserted': + data = data ?? await Permissions.findOneById(id); + break; + + case 'removed': + data = { _id: id, roles: [] }; + break; + } + + if (!data) { + return; + } + + api.broadcast('permission.changed', { clientAction, data }); + + if (data.level === 'settings' && data.settingId) { + // if the permission changes, the effect on the visible settings depends on the role affected. + // The selected-settings-based consumers have to react accordingly and either add or remove the + // setting from the user's collection + const setting = await Settings.findOneNotHiddenById(data.settingId); + if (!setting) { + return; + } + api.broadcast('setting.privateChanged', { clientAction: 'updated', setting }); + } + }); } diff --git a/server/sdk/lib/Events.ts b/server/sdk/lib/Events.ts index 29f0ff6724649..d1ba1e897c915 100644 --- a/server/sdk/lib/Events.ts +++ b/server/sdk/lib/Events.ts @@ -26,7 +26,7 @@ export type EventSignatures = { 'room'(data: { action: string; room: Partial }): void; 'room.avatarUpdate'(room: Partial): void; 'setting'(data: { action: string; setting: Partial }): void; - 'setting.privateChanged'(data: { clientAction: string; setting: any }): void; + 'setting.privateChanged'(data: { clientAction: string; setting: ISetting }): void; 'stream'([streamer, eventName, payload]: [string, string, string]): void; 'stream.ephemeralMessage'(uid: string, rid: string, message: Partial): void; 'subscription'(data: { action: string; subscription: Partial }): void; diff --git a/server/services/authorization/service.ts b/server/services/authorization/service.ts index 9b8bee3c67785..2ee36f892081f 100644 --- a/server/services/authorization/service.ts +++ b/server/services/authorization/service.ts @@ -1,5 +1,6 @@ // import mem from 'mem'; import { Db, Collection } from 'mongodb'; +import mem from 'mem'; import { IAuthorization, RoomAccessValidator } from '../../sdk/types/IAuthorization'; import { ServiceClass } from '../../sdk/types/ServiceClass'; @@ -25,6 +26,10 @@ export class Authorization extends ServiceClass implements IAuthorization { private Users: Collection; + private getRolesCached = mem(this.getRoles.bind(this), { maxAge: 1000, cacheKey: JSON.stringify }) + + private rolesHasPermissionCached = mem(this.rolesHasPermission.bind(this), { cacheKey: JSON.stringify, ...process.env.TEST_MODE === 'true' && { maxAge: 1 } }) + constructor(db: Db) { super(); @@ -34,6 +39,11 @@ export class Authorization extends ServiceClass implements IAuthorization { Subscriptions = new SubscriptionsRaw(db.collection('rocketchat_subscription')); Settings = new SettingsRaw(db.collection('rocketchat_settings')); Rooms = new RoomsRaw(db.collection('rocketchat_room')); + + this.onEvent('permission.changed', () => { + mem.clear(this.getRolesCached); + mem.clear(this.rolesHasPermissionCached); + }); } async hasAllPermission(userId: string, permissions: string[], scope?: string): Promise { @@ -88,9 +98,9 @@ export class Authorization extends ServiceClass implements IAuthorization { // } private async atLeastOne(uid: string, permissions: string[] = [], scope?: string): Promise { - const sortedRoles = await this.getRoles(uid, scope); + const sortedRoles = await this.getRolesCached(uid, scope); for (const permission of permissions) { - if (await this.rolesHasPermission(permission, sortedRoles)) { // eslint-disable-line + if (await this.rolesHasPermissionCached(permission, sortedRoles)) { // eslint-disable-line return true; } } @@ -99,9 +109,9 @@ export class Authorization extends ServiceClass implements IAuthorization { } private async all(uid: string, permissions: string[] = [], scope?: string): Promise { - const sortedRoles = await this.getRoles(uid, scope); + const sortedRoles = await this.getRolesCached(uid, scope); for (const permission of permissions) { - if (!await this.rolesHasPermission(permission, sortedRoles)) { // eslint-disable-line + if (!await this.rolesHasPermissionCached(permission, sortedRoles)) { // eslint-disable-line return false; } } From bdd30a907a3de25f15978403bfa2661a23e7cee3 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Tue, 6 Oct 2020 14:30:39 -0300 Subject: [PATCH 142/198] Move settings to module --- app/models/server/raw/BaseRaw.ts | 2 +- app/settings/client/lib/settings.ts | 3 +- app/settings/lib/settings.ts | 5 +- app/settings/server/functions/settings.ts | 65 ++----------------- client/admin/sidebar/AdminSidebar.js | 5 +- client/contexts/SettingsContext.ts | 2 +- .../omnichannel/appearance/AppearancePage.tsx | 2 +- definition/ISetting.ts | 51 ++++++++------- ee/app/settings/server/index.js | 1 + .../server/settings.internalService.ts | 15 +++++ ee/app/settings/server/settings.ts | 27 ++------ ee/server/broker.ts | 19 ++++++ ee/server/services/StreamHub/StreamHub.ts | 27 ++++---- ee/server/services/StreamHub/watchSettings.ts | 32 --------- .../notifications/notifications.module.ts | 2 +- server/modules/watchers/watchers.module.ts | 29 ++++++++- server/publications/settings/emitter.js | 46 ------------- server/publications/settings/index.js | 1 - server/sdk/index.ts | 17 +++-- server/sdk/lib/Api.ts | 5 ++ server/sdk/lib/Events.ts | 2 +- server/sdk/lib/LocalBroker.ts | 4 ++ server/sdk/lib/proxify.ts | 16 +++-- server/sdk/types/IBroker.ts | 1 + server/sdk/types/IEnterpriseSettings.ts | 6 ++ .../authorization/canAccessRoomLivechat.ts | 4 +- .../authorization/canAccessRoomTokenpass.ts | 4 +- server/services/listeners/notification.ts | 34 ++++++++-- server/services/meteor/service.ts | 17 +++++ 29 files changed, 219 insertions(+), 225 deletions(-) create mode 100644 ee/app/settings/server/settings.internalService.ts delete mode 100644 ee/server/services/StreamHub/watchSettings.ts delete mode 100644 server/publications/settings/emitter.js create mode 100644 server/sdk/types/IEnterpriseSettings.ts diff --git a/app/models/server/raw/BaseRaw.ts b/app/models/server/raw/BaseRaw.ts index e225b71b5f995..47c5dcca1f7ce 100644 --- a/app/models/server/raw/BaseRaw.ts +++ b/app/models/server/raw/BaseRaw.ts @@ -74,7 +74,7 @@ export class BaseRaw implements IBaseRaw { }, options); } - async trashFindOneById(_id: string, options: FindOneOptions): Promise { + async trashFindOneById(_id: string, options: FindOneOptions = {}): Promise { const query: object = { _id, __collection__: this.col.collectionName, diff --git a/app/settings/client/lib/settings.ts b/app/settings/client/lib/settings.ts index c94664ae4d93e..ae5610677f0ed 100644 --- a/app/settings/client/lib/settings.ts +++ b/app/settings/client/lib/settings.ts @@ -2,7 +2,8 @@ import { Meteor } from 'meteor/meteor'; import { ReactiveDict } from 'meteor/reactive-dict'; import { PublicSettingsCachedCollection } from '../../../../client/lib/settings/PublicSettingsCachedCollection'; -import { SettingsBase, SettingValue } from '../../lib/settings'; +import { SettingsBase } from '../../lib/settings'; +import { SettingValue } from '../../../../definition/ISetting'; class Settings extends SettingsBase { cachedCollection = PublicSettingsCachedCollection.get() diff --git a/app/settings/lib/settings.ts b/app/settings/lib/settings.ts index 4bc180c994156..ae4bb6a7b2f78 100644 --- a/app/settings/lib/settings.ts +++ b/app/settings/lib/settings.ts @@ -1,9 +1,8 @@ import { Meteor } from 'meteor/meteor'; import _ from 'underscore'; -export type SettingValueMultiSelect = Array<{key: string; i18nLabel: string}> -export type SettingValueRoomPick = Array<{_id: string; name: string}> | string -export type SettingValue = string | boolean | number | SettingValueMultiSelect | undefined; +import { SettingValue } from '../../../definition/ISetting'; + export type SettingComposedValue = {key: string; value: SettingValue}; export type SettingCallback = (key: string, value: SettingValue, initialLoad?: boolean) => void; diff --git a/app/settings/server/functions/settings.ts b/app/settings/server/functions/settings.ts index 3bff85f1f1b84..3c79ea20bec41 100644 --- a/app/settings/server/functions/settings.ts +++ b/app/settings/server/functions/settings.ts @@ -3,9 +3,10 @@ import { EventEmitter } from 'events'; import { Meteor } from 'meteor/meteor'; import _ from 'underscore'; -import { SettingsBase, SettingValue } from '../../lib/settings'; +import { SettingsBase } from '../../lib/settings'; import SettingsModel from '../../../models/server/models/Settings'; -import { setValue, updateValue } from '../raw'; +import { updateValue } from '../raw'; +import { ISetting, SettingValue } from '../../../../definition/ISetting'; const blockedSettings = new Set(); const hiddenSettings = new Set(); @@ -64,44 +65,8 @@ const overrideSetting = (_id: string, value: SettingValue, options: ISettingAddO return value; }; -export interface ISettingAddOptions { - _id?: string; - type?: 'group' | 'boolean' | 'int' | 'string' | 'asset' | 'code' | 'select' | 'password' | 'action' | 'relativeUrl' | 'language' | 'date' | 'color' | 'font' | 'roomPick' | 'multiSelect'; - editor?: string; - packageEditor?: string; - packageValue?: SettingValue; - valueSource?: string; - hidden?: boolean; - blocked?: boolean; - requiredOnWizard?: boolean; - secret?: boolean; - sorter?: number; - i18nLabel?: string; - i18nDescription?: string; - autocomplete?: boolean; +export interface ISettingAddOptions extends Partial { force?: boolean; - group?: string; - section?: string; - enableQuery?: any; - processEnvValue?: SettingValue; - meteorSettingsValue?: SettingValue; - value?: SettingValue; - ts?: Date; - multiline?: boolean; - values?: Array; - public?: boolean; - enterprise?: boolean; - modules?: Array; - invalidValue?: SettingValue; -} -export interface ISettingSelectOption { - key: string; - i18nLabel: string; -} -export interface ISettingRecord extends ISettingAddOptions { - _id: string; - env: boolean; - value: SettingValue; } export interface ISettingAddGroupOptions { @@ -362,7 +327,7 @@ class Settings extends SettingsBase { /* * Change a setting value on the Meteor.settings object */ - storeSettingValue(record: ISettingRecord, initialLoad: boolean): void { + storeSettingValue(record: ISetting, initialLoad: boolean): void { const newData = { value: record.value, }; @@ -380,7 +345,7 @@ class Settings extends SettingsBase { /* * Remove a setting value on the Meteor.settings object */ - removeSettingValue(record: ISettingRecord, initialLoad: boolean): void { + removeSettingValue(record: ISetting, initialLoad: boolean): void { SettingsEvents.emit('remove-setting-value', record); delete Meteor.settings[record._id]; @@ -396,28 +361,12 @@ class Settings extends SettingsBase { */ init(): void { this.initialLoad = true; - SettingsModel.find().fetch().forEach((record: ISettingRecord) => { + SettingsModel.find().fetch().forEach((record: ISetting) => { this.storeSettingValue(record, this.initialLoad); updateValue(record._id, { value: record.value }); }); this.initialLoad = false; this.afterInitialLoad.forEach((fn) => fn(Meteor.settings)); - - SettingsModel.on('change', ({ clientAction, id, data }) => { - switch (clientAction) { - case 'inserted': - case 'updated': - data = data ?? SettingsModel.findOneById(id); - this.storeSettingValue(data, this.initialLoad); - updateValue(id, { value: data.value }); - break; - case 'removed': - data = SettingsModel.trashFindOneById(id); - this.removeSettingValue(data, this.initialLoad); - setValue(id, undefined); - break; - } - }); } onAfterInitialLoad(fn: (settings: Meteor.Settings) => void): void { diff --git a/client/admin/sidebar/AdminSidebar.js b/client/admin/sidebar/AdminSidebar.js index 2ea9ff38571da..0cb396dffa46a 100644 --- a/client/admin/sidebar/AdminSidebar.js +++ b/client/admin/sidebar/AdminSidebar.js @@ -4,7 +4,6 @@ import React, { useCallback, useState, useMemo, useEffect } from 'react'; import { useSubscription } from 'use-subscription'; import { menu, SideNav, Layout } from '../../../app/ui-utils/client'; -import { SettingType } from '../../../definition/ISetting'; import { useSettings } from '../../contexts/SettingsContext'; import { useTranslation } from '../../contexts/TranslationContext'; import { useRoutePath, useCurrentRoute } from '../../contexts/RouterContext'; @@ -53,7 +52,7 @@ const useSettingsGroups = (filter) => { settings .filter(filterPredicate) .map((setting) => { - if (setting.type === SettingType.GROUP) { + if (setting.type === 'group') { return setting._id; } @@ -62,7 +61,7 @@ const useSettingsGroups = (filter) => { )); return settings - .filter(({ type, group, _id }) => type === SettingType.GROUP && groupIds.includes(group || _id)) + .filter(({ type, group, _id }) => type === 'group' && groupIds.includes(group || _id)) .sort((a, b) => t(a.i18nLabel || a._id).localeCompare(t(b.i18nLabel || b._id))); }, [settings, filterPredicate, t]); }; diff --git a/client/contexts/SettingsContext.ts b/client/contexts/SettingsContext.ts index 2e4e817dca82d..0f105034215f4 100644 --- a/client/contexts/SettingsContext.ts +++ b/client/contexts/SettingsContext.ts @@ -60,7 +60,7 @@ export const useSettings = (query?: SettingsContextQuery): ISetting[] => { export const useSettingsDispatch = (): ((changes: Partial[]) => Promise) => useContext(SettingsContext).dispatch; -export const useSettingSetValue = (_id: SettingId): ((value: T) => Promise) => { +export const useSettingSetValue = (_id: SettingId): ((value: T) => Promise) => { const dispatch = useSettingsDispatch(); return useCallback((value: T) => dispatch([{ _id, value }]), [dispatch, _id]); }; diff --git a/client/omnichannel/appearance/AppearancePage.tsx b/client/omnichannel/appearance/AppearancePage.tsx index 7ca7e3b8896e6..0e4cfe0bca8c8 100644 --- a/client/omnichannel/appearance/AppearancePage.tsx +++ b/client/omnichannel/appearance/AppearancePage.tsx @@ -86,7 +86,7 @@ const AppearancePage: FC = ({ settings }) => { const t = useTranslation(); const dispatchToastMessage = useToastMessageDispatch(); - const save: (settings: Pick[]) => Promise = useMethod('livechat:saveAppearance'); + const save: (settings: Pick[] & {value: unknown}[]) => Promise = useMethod('livechat:saveAppearance'); const { values, handlers, commit, reset, hasUnsavedChanges } = useForm(reduceAppearance(settings)); diff --git a/definition/ISetting.ts b/definition/ISetting.ts index eb29e6b8cf307..16cf30f1b3f23 100644 --- a/definition/ISetting.ts +++ b/definition/ISetting.ts @@ -4,41 +4,48 @@ export type SettingId = string; export type GroupId = SettingId; export type SectionName = string; -export enum SettingType { - BOOLEAN = 'boolean', - STRING = 'string', - RELATIVE_URL = 'relativeUrl', - PASSWORD = 'password', - INT = 'int', - SELECT = 'select', - MULTI_SELECT = 'multiSelect', - LANGUAGE = 'language', - COLOR = 'color', - FONT = 'font', - CODE = 'code', - ACTION = 'action', - ASSET = 'asset', - ROOM_PICK = 'roomPick', - GROUP = 'group', -} - export enum SettingEditor { COLOR = 'color', EXPRESSION = 'expression' } +export type SettingValueMultiSelect = Array<{key: string; i18nLabel: string}> +export type SettingValueRoomPick = Array<{_id: string; name: string}> | string +export type SettingValue = string | boolean | number | SettingValueMultiSelect | undefined; + +export interface ISettingSelectOption { + key: string; + i18nLabel: string; +} + export interface ISetting { _id: SettingId; - type: SettingType; + type: 'boolean' | 'string' | 'relativeUrl' | 'password' | 'int' | 'select' | 'multiSelect' | 'language' | 'color' | 'font' | 'code' | 'action' | 'asset' | 'roomPick' | 'group' | 'date'; public: boolean; + env: boolean; group?: GroupId; section?: SectionName; i18nLabel: string; - value: unknown; - packageValue: unknown; + value: SettingValue; + packageValue: SettingValue; editor?: SettingEditor; packageEditor?: SettingEditor; blocked: boolean; - enableQuery?: string | FilterQuery; + enableQuery?: string | FilterQuery | FilterQuery[]; sorter?: number; + properties?: unknown; + enterprise?: boolean; + requiredOnWizard?: boolean; + hidden?: boolean; + modules?: Array; + invalidValue?: SettingValue; + valueSource?: string; + secret?: boolean; + i18nDescription?: string; + autocomplete?: boolean; + processEnvValue?: SettingValue; + meteorSettingsValue?: SettingValue; + ts?: Date; + multiline?: boolean; + values?: Array; } diff --git a/ee/app/settings/server/index.js b/ee/app/settings/server/index.js index 97097791afdc4..d3510e7884f6e 100644 --- a/ee/app/settings/server/index.js +++ b/ee/app/settings/server/index.js @@ -1 +1,2 @@ import './settings'; +import './settings.internalService'; diff --git a/ee/app/settings/server/settings.internalService.ts b/ee/app/settings/server/settings.internalService.ts new file mode 100644 index 0000000000000..8dbe604a13aba --- /dev/null +++ b/ee/app/settings/server/settings.internalService.ts @@ -0,0 +1,15 @@ +import { ServiceClass } from '../../../../server/sdk/types/ServiceClass'; +import { api } from '../../../../server/sdk/api'; +import { IEnterpriseSettings } from '../../../../server/sdk/types/IEnterpriseSettings'; +import { changeSettingValue } from './settings'; +import { ISetting } from '../../../../definition/ISetting'; + +class EnterpriseSettings extends ServiceClass implements IEnterpriseSettings { + protected name = 'ee-settings'; + + changeSettingValue(record: ISetting): undefined | { value: ISetting['value'] } { + return changeSettingValue(record); + } +} + +api.registerService(new EnterpriseSettings()); diff --git a/ee/app/settings/server/settings.ts b/ee/app/settings/server/settings.ts index 560d057c5f3ec..0f1a63ddb9445 100644 --- a/ee/app/settings/server/settings.ts +++ b/ee/app/settings/server/settings.ts @@ -1,11 +1,11 @@ import { Meteor } from 'meteor/meteor'; -import { SettingsEvents, settings, ISettingRecord } from '../../../../app/settings/server/functions/settings'; -import { SettingValue } from '../../../../app/settings/lib/settings'; +import { SettingsEvents, settings } from '../../../../app/settings/server/functions/settings'; import { isEnterprise, hasLicense, onValidateLicenses } from '../../license/server/license'; import SettingsModel from '../../../../app/models/server/models/Settings'; +import { ISetting, SettingValue } from '../../../../definition/ISetting'; -function changeSettingValue(record: ISettingRecord): undefined | { value: SettingValue } { +export function changeSettingValue(record: ISetting): undefined | { value: SettingValue } { if (!record.enterprise) { return; } @@ -25,14 +25,14 @@ function changeSettingValue(record: ISettingRecord): undefined | { value: Settin } } -SettingsEvents.on('store-setting-value', (record: ISettingRecord, newRecord: { value: SettingValue }) => { +SettingsEvents.on('store-setting-value', (record: ISetting, newRecord: { value: SettingValue }) => { const changedValue = changeSettingValue(record); if (changedValue) { newRecord.value = changedValue.value; } }); -SettingsEvents.on('fetch-settings', (settings: Array): void => { +SettingsEvents.on('fetch-settings', (settings: Array): void => { for (const setting of settings) { const changedValue = changeSettingValue(setting); if (changedValue) { @@ -41,25 +41,10 @@ SettingsEvents.on('fetch-settings', (settings: Array): void => { } }); -type ISettingNotificationValue = { - _id: string; - value: SettingValue; - editor: string; - properties: string; - enterprise: boolean; -}; - -SettingsEvents.on('change-setting', (record: ISettingRecord, value: ISettingNotificationValue): void => { - const changedValue = changeSettingValue(record); - if (changedValue) { - value.value = changedValue.value; - } -}); - function updateSettings(): void { const enterpriseSettings = SettingsModel.findEnterpriseSettings(); - enterpriseSettings.forEach((record: ISettingRecord) => settings.storeSettingValue(record, false)); + enterpriseSettings.forEach((record: ISetting) => settings.storeSettingValue(record, false)); } diff --git a/ee/server/broker.ts b/ee/server/broker.ts index c7421a839f545..b79605a6dafac 100644 --- a/ee/server/broker.ts +++ b/ee/server/broker.ts @@ -51,6 +51,25 @@ class NetworkBroker implements IBroker { return this.localBroker.call(method, data); } + const context = asyncLocalStorage.getStore(); + if (context?.ctx?.call) { + return context.ctx.call(method, data); + } + + const services: {name: string}[] = await this.broker.call('$node.services', { onlyAvailable: true }); + if (!services.find((service) => service.name === method.split('.')[0])) { + return new Error('method-not-available'); + } + return this.broker.call(method, data); + } + + async waitAndCall(method: string, data: any): Promise { + await this.started; + + if (!(this.whitelist.actions.includes(method) || await this.allowed)) { + return this.localBroker.call(method, data); + } + await this.broker.waitForServices(method.split('.')[0]); const context = asyncLocalStorage.getStore(); diff --git a/ee/server/services/StreamHub/StreamHub.ts b/ee/server/services/StreamHub/StreamHub.ts index 85801f7132dd4..a21408bf68693 100755 --- a/ee/server/services/StreamHub/StreamHub.ts +++ b/ee/server/services/StreamHub/StreamHub.ts @@ -1,5 +1,4 @@ import { watchUsers } from './watchUsers'; -import { watchSettings } from './watchSettings'; import { watchRooms } from './watchRooms'; import { watchInquiries } from './watchInquiries'; import { getConnection } from '../mongo'; @@ -111,19 +110,19 @@ export class StreamHub extends ServiceClass implements IServiceClass { Rooms.watch([], { fullDocument: 'updateLookup' }).on('change', watchRooms); - SettingsCol.watch([{ - $addFields: { - tmpfields: { - $objectToArray: '$updateDescription.updatedFields', - }, - }, - }, { - $match: { - 'tmpfields.k': { - $in: ['value'], // avoid flood the streamer with messages changes (by username change) - }, - }, - }], { fullDocument: 'updateLookup' }).on('change', watchSettings); + // SettingsCol.watch([{ + // $addFields: { + // tmpfields: { + // $objectToArray: '$updateDescription.updatedFields', + // }, + // }, + // }, { + // $match: { + // 'tmpfields.k': { + // $in: ['value'], // avoid flood the streamer with messages changes (by username change) + // }, + // }, + // }], { fullDocument: 'updateLookup' }).on('change', watchSettings); loginServiceConfiguration.watch([{ $project: { 'fullDocument.secret': 0 } }], { fullDocument: 'updateLookup' }).on('change', watchLoginServiceConfiguration); } diff --git a/ee/server/services/StreamHub/watchSettings.ts b/ee/server/services/StreamHub/watchSettings.ts deleted file mode 100644 index 1643209f6d491..0000000000000 --- a/ee/server/services/StreamHub/watchSettings.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { ChangeEvent } from 'mongodb'; - -import { normalize } from './utils'; -import { api } from '../../../../server/sdk/api'; -import { ISetting } from '../../../../definition/ISetting'; - -export async function watchSettings(event: ChangeEvent): Promise { - if ('updateDescription' in event && event.updateDescription.updatedFields._updatedAt) { - return; - } - let setting; - switch (event.operationType) { - case 'insert': - case 'update': - // setting = Settings.findOne(documentKey/* , { fields }*/); - setting = event.fullDocument; - break; - case 'delete': - setting = event.documentKey; - break; - default: - return; - } - - if (!setting) { - return; - } - - api.broadcast('setting', { action: normalize[event.operationType], setting }); - // RocketChat.Notifications.streamUser.__emit(data._id, operationType, data); - // RocketChat.Logger.info('Settings record', setting); -} diff --git a/server/modules/notifications/notifications.module.ts b/server/modules/notifications/notifications.module.ts index 2619f5d54d914..19a783a128988 100644 --- a/server/modules/notifications/notifications.module.ts +++ b/server/modules/notifications/notifications.module.ts @@ -239,7 +239,7 @@ export class NotificationsModule { this.streamCannedResponses.allowWrite('none'); this.streamCannedResponses.allowRead(async function() { - return this.userId && await Settings.getValueById('Canned_Responses_Enable') && Authorization.hasPermission(this.userId, 'view-canned-responses'); + return !!this.userId && !!await Settings.getValueById('Canned_Responses_Enable') && Authorization.hasPermission(this.userId, 'view-canned-responses'); }); this.streamIntegrationHistory.allowWrite('none'); diff --git a/server/modules/watchers/watchers.module.ts b/server/modules/watchers/watchers.module.ts index d892fd14c786a..e24a990f1683e 100644 --- a/server/modules/watchers/watchers.module.ts +++ b/server/modules/watchers/watchers.module.ts @@ -13,6 +13,7 @@ import { IBaseRaw } from '../../../app/models/server/raw/BaseRaw'; import { api } from '../../sdk/api'; import { IBaseData } from '../../../definition/IBaseData'; import { IPermission } from '../../../definition/IPermission'; +import { ISetting } from '../../../definition/ISetting'; interface IModelsParam { // Rooms: RoomsRaw; @@ -180,7 +181,33 @@ export function initWatchers({ if (!setting) { return; } - api.broadcast('setting.privateChanged', { clientAction: 'updated', setting }); + api.broadcast('watch.settings', { clientAction: 'updated', setting }); } }); + + watch(Settings, async ({ clientAction, id, data, diff }) => { + if (diff && Object.keys(diff).length === 1 && diff._updatedAt) { // avoid useless changes + return; + } + + let setting; + switch (clientAction) { + case 'updated': + case 'inserted': { + setting = data ?? await Settings.findOneById(id); + break; + } + + case 'removed': { + setting = data ?? await Settings.trashFindOneById(id); + break; + } + } + + if (!setting) { + return; + } + + api.broadcast('watch.settings', { clientAction, setting }); + }); } diff --git a/server/publications/settings/emitter.js b/server/publications/settings/emitter.js deleted file mode 100644 index a6e2d0eb20718..0000000000000 --- a/server/publications/settings/emitter.js +++ /dev/null @@ -1,46 +0,0 @@ -import { Settings } from '../../../app/models/server'; -import { Notifications } from '../../../app/notifications/server'; -import { SettingsEvents } from '../../../app/settings/server/functions/settings'; -import { api } from '../../sdk/api'; - -Settings.on('change', ({ clientAction, id, data, diff }) => { - if (diff && Object.keys(diff).length === 1 && diff._updatedAt) { // avoid useless changes - return; - } - switch (clientAction) { - case 'updated': - case 'inserted': { - const setting = data ?? Settings.findOneById(id); - const value = { - _id: setting._id, - value: setting.value, - editor: setting.editor, - properties: setting.properties, - enterprise: setting.enterprise, - requiredOnWizard: setting.requiredOnWizard, - }; - - SettingsEvents.emit('change-setting', setting, value); - - if (setting.hidden) { - return; - } - - if (setting.public === true) { - Notifications.notifyAllInThisInstance('public-settings-changed', clientAction, value); - } - api.broadcast('setting.privateChanged', { clientAction, setting }); - break; - } - - case 'removed': { - const setting = data || Settings.findOneById(id, { fields: { public: 1 } }); - - if (setting && setting.public === true) { - Notifications.notifyAllInThisInstance('public-settings-changed', clientAction, { _id: id }); - } - api.broadcast('setting.privateChanged', { clientAction, setting: { _id: id } }); - break; - } - } -}); diff --git a/server/publications/settings/index.js b/server/publications/settings/index.js index d0afdc93a98b8..e14f2079ca305 100644 --- a/server/publications/settings/index.js +++ b/server/publications/settings/index.js @@ -4,7 +4,6 @@ import { Settings } from '../../../app/models/server'; import { hasPermission, hasAtLeastOnePermission } from '../../../app/authorization/server'; import { getSettingPermissionId } from '../../../app/authorization/lib'; import { SettingsEvents } from '../../../app/settings/server/functions/settings'; -import './emitter'; Meteor.methods({ 'public-settings/get'(updatedAt) { diff --git a/server/sdk/index.ts b/server/sdk/index.ts index d672c82949fea..42a24ab3a13e9 100644 --- a/server/sdk/index.ts +++ b/server/sdk/index.ts @@ -1,18 +1,23 @@ import { AsyncLocalStorage } from 'async_hooks'; -import { proxify } from './lib/proxify'; +import { proxify, proxifyWithWait } from './lib/proxify'; import { IAuthorization } from './types/IAuthorization'; import { IServiceContext } from './types/ServiceClass'; import { IPresence } from './types/IPresence'; import { IAccount } from './types/IAccount'; import { ILicense } from './types/ILicense'; import { IMeteor } from './types/IMeteor'; +import { IEnterpriseSettings } from './types/IEnterpriseSettings'; // TODO think in a way to not have to pass the service name to proxify here as well -export const Authorization = proxify('authorization'); -export const Presence = proxify('presence'); -export const Account = proxify('accounts'); -export const License = proxify('license'); -export const MeteorService = proxify('meteor'); +export const Authorization = proxifyWithWait('authorization'); +export const Presence = proxifyWithWait('presence'); +export const Account = proxifyWithWait('accounts'); +export const License = proxifyWithWait('license'); +export const MeteorService = proxifyWithWait('meteor'); + +// Calls without wait. Means that the service is optional and the result may be ane error +// of service/method not available +export const EnterpriseSettings = proxify('ee-settings'); export const asyncLocalStorage = new AsyncLocalStorage(); diff --git a/server/sdk/lib/Api.ts b/server/sdk/lib/Api.ts index 11832963664e1..1b869a92bd9dd 100644 --- a/server/sdk/lib/Api.ts +++ b/server/sdk/lib/Api.ts @@ -28,6 +28,11 @@ export class Api { return this.broker.call(method, data); } + async waitAndCall(method: string, data: any): Promise { + // console.log('api call', method, this.broker.constructor.name); + return this.broker.waitAndCall(method, data); + } + async broadcast(event: T, ...args: Parameters): Promise { return this.broker.broadcast(event, ...args); } diff --git a/server/sdk/lib/Events.ts b/server/sdk/lib/Events.ts index d1ba1e897c915..b4ea35355f725 100644 --- a/server/sdk/lib/Events.ts +++ b/server/sdk/lib/Events.ts @@ -26,7 +26,6 @@ export type EventSignatures = { 'room'(data: { action: string; room: Partial }): void; 'room.avatarUpdate'(room: Partial): void; 'setting'(data: { action: string; setting: Partial }): void; - 'setting.privateChanged'(data: { clientAction: string; setting: ISetting }): void; 'stream'([streamer, eventName, payload]: [string, string, string]): void; 'stream.ephemeralMessage'(uid: string, rid: string, message: Partial): void; 'subscription'(data: { action: string; subscription: Partial }): void; @@ -42,4 +41,5 @@ export type EventSignatures = { 'watch.messages'(data: { clientAction: string; message: Partial }): void; 'watch.roles'(data: { clientAction: string; role: Partial }): void; 'watch.subscriptions'(data: { clientAction: string; subscription: Partial }): void; + 'watch.settings'(data: { clientAction: string; setting: ISetting }): void; } diff --git a/server/sdk/lib/LocalBroker.ts b/server/sdk/lib/LocalBroker.ts index ce9be56f34f94..e6294f1534de5 100644 --- a/server/sdk/lib/LocalBroker.ts +++ b/server/sdk/lib/LocalBroker.ts @@ -19,6 +19,10 @@ export class LocalBroker implements IBroker { return result; } + async waitAndCall(method: string, data: any): Promise { + return this.call(method, data); + } + createService(instance: ServiceClass): void { const namespace = instance.getName(); diff --git a/server/sdk/lib/proxify.ts b/server/sdk/lib/proxify.ts index f9fa306a720fe..99b30750a17e8 100644 --- a/server/sdk/lib/proxify.ts +++ b/server/sdk/lib/proxify.ts @@ -8,12 +8,20 @@ type Prom = { [K in FunctionPropertyNames]: ReturnType extends Promise ? T[K] : (...params: Parameters) => Promise>; } -function handler(namespace: string): ProxyHandler { +type PromOrError = { + [K in FunctionPropertyNames]: ReturnType extends Promise ? (...params: Parameters) => ReturnType | Promise : (...params: Parameters) => Promise | Error>; +} + +function handler(namespace: string, waitService: boolean): ProxyHandler { return { - get: (_target: T, prop: string): any => (...params: any): Promise => api.call(`${ namespace }.${ prop }`, params), + get: (_target: T, prop: string): any => (...params: any): Promise => api[waitService ? 'waitAndCall' : 'call'](`${ namespace }.${ prop }`, params), }; } -export function proxify(namespace: string): Prom { - return new Proxy({}, handler(namespace)) as unknown as Prom; +export function proxifyWithWait(namespace: string): Prom { + return new Proxy({}, handler(namespace, true)) as unknown as Prom; +} + +export function proxify(namespace: string): PromOrError { + return new Proxy({}, handler(namespace, false)) as unknown as Prom; } diff --git a/server/sdk/types/IBroker.ts b/server/sdk/types/IBroker.ts index 196f7ef99a466..383edb0432238 100644 --- a/server/sdk/types/IBroker.ts +++ b/server/sdk/types/IBroker.ts @@ -23,6 +23,7 @@ export interface IBrokerNode { export interface IBroker { createService(service: ServiceClass): void; call(method: string, data: any): Promise; + waitAndCall(method: string, data: any): Promise; broadcast(event: T, ...args: Parameters): Promise; nodeList(): Promise; } diff --git a/server/sdk/types/IEnterpriseSettings.ts b/server/sdk/types/IEnterpriseSettings.ts new file mode 100644 index 0000000000000..5fa2144c2d96e --- /dev/null +++ b/server/sdk/types/IEnterpriseSettings.ts @@ -0,0 +1,6 @@ +import { IServiceClass } from './ServiceClass'; +import { ISetting } from '../../../definition/ISetting'; + +export interface IEnterpriseSettings extends IServiceClass { + changeSettingValue(record: ISetting): undefined | { value: ISetting['value'] }; +} diff --git a/server/services/authorization/canAccessRoomLivechat.ts b/server/services/authorization/canAccessRoomLivechat.ts index bf5d02ac2455a..ab3e38686b9d1 100644 --- a/server/services/authorization/canAccessRoomLivechat.ts +++ b/server/services/authorization/canAccessRoomLivechat.ts @@ -1,8 +1,8 @@ import { IAuthorizationLivechat } from '../../sdk/types/IAuthorizationLivechat'; -import { proxify } from '../../sdk/lib/proxify'; +import { proxifyWithWait } from '../../sdk/lib/proxify'; import { RoomAccessValidator } from '../../sdk/types/IAuthorization'; -export const AuthorizationLivechat = proxify('authorization-livechat'); +export const AuthorizationLivechat = proxifyWithWait('authorization-livechat'); export const canAccessRoomLivechat: RoomAccessValidator = async (room, user): Promise => { if (room.t !== 'l') { diff --git a/server/services/authorization/canAccessRoomTokenpass.ts b/server/services/authorization/canAccessRoomTokenpass.ts index d5fe6728f20f1..df701ed7ec5cb 100644 --- a/server/services/authorization/canAccessRoomTokenpass.ts +++ b/server/services/authorization/canAccessRoomTokenpass.ts @@ -1,8 +1,8 @@ import { IAuthorizationTokenpass } from '../../sdk/types/IAuthorizationTokenpass'; -import { proxify } from '../../sdk/lib/proxify'; +import { proxifyWithWait } from '../../sdk/lib/proxify'; import { RoomAccessValidator } from '../../sdk/types/IAuthorization'; -export const AuthorizationTokenpass = proxify('authorization-tokenpass'); +export const AuthorizationTokenpass = proxifyWithWait('authorization-tokenpass'); export const canAccessRoomTokenpass: RoomAccessValidator = async (room, user): Promise => { if (!room.tokenpass) { diff --git a/server/services/listeners/notification.ts b/server/services/listeners/notification.ts index 55e8217a4950b..0ff8098501bb7 100644 --- a/server/services/listeners/notification.ts +++ b/server/services/listeners/notification.ts @@ -1,5 +1,6 @@ import { ServiceClass } from '../../sdk/types/ServiceClass'; import { NotificationsModule } from '../../modules/notifications/notifications.module'; +import { EnterpriseSettings } from '../../sdk/index'; const STATUS_MAP: {[k: string]: number} = { offline: 0, @@ -50,10 +51,6 @@ export class NotificationService extends ServiceClass { }); }); - this.onEvent('setting.privateChanged', ({ clientAction, setting }) => { - notifications.notifyLogged('private-settings-changed', clientAction, setting); - }); - this.onEvent('user.avatarUpdate', ({ username, avatarETag: etag }) => { notifications.notifyLogged('updateAvatar', { username, @@ -138,5 +135,34 @@ export class NotificationService extends ServiceClass { }; notifications.streamRoles.emit('roles', payload); }); + + this.onEvent('watch.settings', async ({ clientAction, setting }): Promise => { + if (clientAction !== 'removed') { + const result = await EnterpriseSettings.changeSettingValue(setting); + if (!(result instanceof Error)) { + setting.value = result?.value; + } + } + + if (setting.hidden) { + return; + } + + + const value = { + _id: setting._id, + value: setting.value, + editor: setting.editor, + properties: setting.properties, + enterprise: setting.enterprise, + requiredOnWizard: setting.requiredOnWizard, + }; + + if (setting.public === true) { + notifications.notifyAllInThisInstance('public-settings-changed', clientAction, value); + } + + notifications.notifyLogged('private-settings-changed', clientAction, value); + }); } } diff --git a/server/services/meteor/service.ts b/server/services/meteor/service.ts index 5fe18623554e8..ef8245c5d473c 100644 --- a/server/services/meteor/service.ts +++ b/server/services/meteor/service.ts @@ -6,6 +6,8 @@ import { IMeteor, AutoUpdateRecord } from '../../sdk/types/IMeteor'; import { api } from '../../sdk/api'; import { Users } from '../../../app/models/server/raw/index'; import { Livechat } from '../../../app/livechat/server'; +import { settings } from '../../../app/settings/server/functions/settings'; +import { setValue, updateValue } from '../../../app/settings/server/raw'; const autoUpdateRecords = new Map(); @@ -29,6 +31,21 @@ Meteor.server.publish_handlers.meteor_autoupdate_clientVersions.call({ export class MeteorService extends ServiceClass implements IMeteor { protected name = 'meteor'; + constructor() { + super(); + + this.onEvent('watch.settings', async ({ clientAction, setting }): Promise => { + if (clientAction !== 'removed') { + settings.storeSettingValue(setting, false); + updateValue(setting._id, { value: setting.value }); + return; + } + + settings.removeSettingValue(setting, false); + setValue(setting._id, undefined); + }); + } + async getLastAutoUpdateClientVersions(): Promise { return [...autoUpdateRecords.values()]; } From 789bfb93b92755dc9ef01b8190257e8c0393b9be Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Tue, 6 Oct 2020 18:09:06 -0300 Subject: [PATCH 143/198] Move LivechatInquiry to module --- app/livechat/server/index.js | 1 - .../server/lib/stream/queueManager.js | 43 ----------------- app/models/server/raw/index.ts | 2 + definition/IRoutingManagerConfig.ts | 9 ++++ ee/server/services/DDPStreamer/DDPStreamer.ts | 17 ------- ee/server/services/StreamHub/StreamHub.ts | 7 ++- .../services/StreamHub/watchInquiries.ts | 18 ------- server/modules/watchers/watchers.module.ts | 27 +++++++++++ server/sdk/lib/Events.ts | 1 + server/sdk/types/IMeteor.ts | 3 ++ server/services/listeners/notification.ts | 47 ++++++++++++++++++- server/services/meteor/service.ts | 6 +++ 12 files changed, 97 insertions(+), 84 deletions(-) delete mode 100644 app/livechat/server/lib/stream/queueManager.js create mode 100644 definition/IRoutingManagerConfig.ts delete mode 100644 ee/server/services/StreamHub/watchInquiries.ts diff --git a/app/livechat/server/index.js b/app/livechat/server/index.js index dea202128cf2c..ea21b43c8bcd7 100644 --- a/app/livechat/server/index.js +++ b/app/livechat/server/index.js @@ -81,7 +81,6 @@ import './lib/routing/ManualSelection'; import './lib/routing/AutoSelection'; import './lib/stream/agentStatus'; import './lib/stream/departmentAgents'; -import './lib/stream/queueManager'; import './sendMessageBySMS'; import './api'; import './api/rest'; diff --git a/app/livechat/server/lib/stream/queueManager.js b/app/livechat/server/lib/stream/queueManager.js deleted file mode 100644 index 18a759312788e..0000000000000 --- a/app/livechat/server/lib/stream/queueManager.js +++ /dev/null @@ -1,43 +0,0 @@ -import { LivechatInquiry } from '../../../../models/server'; -import { RoutingManager } from '../RoutingManager'; -import notifications from '../../../../notifications/server/lib/Notifications'; - -const emitQueueDataEvent = (event, data) => notifications.streamLivechatQueueData.emitWithoutBroadcast(event, data); -const mountDataToEmit = (type, data) => ({ type, ...data }); - -LivechatInquiry.on('change', ({ clientAction, id: _id, data: record }) => { - if (RoutingManager.getConfig().autoAssignAgent) { - return; - } - - switch (clientAction) { - case 'inserted': - emitQueueDataEvent(_id, { ...record, clientAction }); - if (record && record.department) { - return emitQueueDataEvent(`department/${ record.department }`, mountDataToEmit('added', record)); - } - emitQueueDataEvent('public', mountDataToEmit('added', record)); - break; - case 'updated': - const isUpdatingDepartment = record && record.department; - const updatedRecord = LivechatInquiry.findOneById(_id); - emitQueueDataEvent(_id, { ...updatedRecord, clientAction }); - if (updatedRecord && !updatedRecord.department) { - return emitQueueDataEvent('public', mountDataToEmit('changed', updatedRecord)); - } - if (isUpdatingDepartment) { - emitQueueDataEvent('public', mountDataToEmit('changed', updatedRecord)); - } - emitQueueDataEvent(`department/${ updatedRecord.department }`, mountDataToEmit('changed', updatedRecord)); - break; - - case 'removed': - const removedRecord = LivechatInquiry.trashFindOneById(_id); - emitQueueDataEvent(_id, { _id, clientAction }); - if (removedRecord && removedRecord.department) { - return emitQueueDataEvent(`department/${ removedRecord.department }`, mountDataToEmit('removed', { _id })); - } - emitQueueDataEvent('public', mountDataToEmit('removed', { _id })); - break; - } -}); diff --git a/app/models/server/raw/index.ts b/app/models/server/raw/index.ts index b104fbf237953..e4a120667f9f7 100644 --- a/app/models/server/raw/index.ts +++ b/app/models/server/raw/index.ts @@ -89,6 +89,7 @@ const map = { [Settings.col.collectionName]: SettingsModel, [Roles.col.collectionName]: RolesModel, [Permissions.col.collectionName]: PermissionsModel, + [LivechatInquiry.col.collectionName]: LivechatInquiryModel, }; initWatchers({ @@ -96,6 +97,7 @@ initWatchers({ Users, Subscriptions, Settings, + LivechatInquiry, Permissions, Roles, }, (model, fn) => { diff --git a/definition/IRoutingManagerConfig.ts b/definition/IRoutingManagerConfig.ts new file mode 100644 index 0000000000000..70c76c499a86b --- /dev/null +++ b/definition/IRoutingManagerConfig.ts @@ -0,0 +1,9 @@ +export interface IRoutingManagerConfig { + previewRoom: boolean; + showConnecting: boolean; + showQueue: boolean; + showQueueLink: boolean; + returnQueue: boolean; + enableTriggerAction: boolean; + autoAssignAgent: boolean; +} diff --git a/ee/server/services/DDPStreamer/DDPStreamer.ts b/ee/server/services/DDPStreamer/DDPStreamer.ts index 3ea3afe7d8f9d..81b3ee90e8a0a 100644 --- a/ee/server/services/DDPStreamer/DDPStreamer.ts +++ b/ee/server/services/DDPStreamer/DDPStreamer.ts @@ -99,29 +99,12 @@ wss.on('connection', (ws, req) => new Client(ws, req.url !== '/websocket')); // }, // mixins: PROMETHEUS_PORT !== 'false' ? [PromService] : [], -const types: Record = { - inserted: 'added', - updated: 'changed', - removed: 'removed', -}; -const mountDataToEmit = (type: string, data: object): object => ({ type: types[type], ...data }); - export class DDPStreamer extends ServiceClass { protected name = 'streamer'; constructor() { super(); - // [STREAM_NAMES.LIVECHAT_INQUIRY]({ action, inquiry }) { - this.onEvent('livechat-inquiry-queue-observer', ({ action, inquiry }): void => { - if (!inquiry.department) { - notifications.streamLivechatQueueData.emitWithoutBroadcast('public', mountDataToEmit(action, inquiry)); - return; - } - notifications.streamLivechatQueueData.emitWithoutBroadcast(`department/${ inquiry.department }`, mountDataToEmit(action, inquiry)); - notifications.streamLivechatQueueData.emitWithoutBroadcast(inquiry._id, mountDataToEmit(action, inquiry)); - }); - // [STREAMER_EVENTS.STREAM]([streamer, eventName, payload]) { this.onEvent('stream', ([streamer, eventName, args]): void => { const stream = StreamerCentral.instances[streamer]; diff --git a/ee/server/services/StreamHub/StreamHub.ts b/ee/server/services/StreamHub/StreamHub.ts index a21408bf68693..fc4f78e9f37b9 100755 --- a/ee/server/services/StreamHub/StreamHub.ts +++ b/ee/server/services/StreamHub/StreamHub.ts @@ -1,6 +1,5 @@ import { watchUsers } from './watchUsers'; import { watchRooms } from './watchRooms'; -import { watchInquiries } from './watchInquiries'; import { getConnection } from '../mongo'; import { ServiceClass, IServiceClass } from '../../../../server/sdk/types/ServiceClass'; import { watchLoginServiceConfiguration } from './watchLoginServiceConfiguration'; @@ -10,6 +9,7 @@ import { UsersRaw } from '../../../../app/models/server/raw/Users'; import { SubscriptionsRaw } from '../../../../app/models/server/raw/Subscriptions'; import { SettingsRaw } from '../../../../app/models/server/raw/Settings'; import { RolesRaw } from '../../../../app/models/server/raw/Roles'; +import { LivechatInquiryRaw } from '../../../../app/models/server/raw/LivechatInquiry'; export class StreamHub extends ServiceClass implements IServiceClass { protected name = 'hub'; @@ -19,7 +19,6 @@ export class StreamHub extends ServiceClass implements IServiceClass { const Trash = db.collection('rocketchat_trash'); const Rooms = db.collection('rocketchat_room'); - const Inquiry = db.collection('rocketchat_livechat_inquiry'); const loginServiceConfiguration = db.collection('meteor_accounts_loginServiceConfiguration'); const UsersCol = db.collection('users'); @@ -28,6 +27,7 @@ export class StreamHub extends ServiceClass implements IServiceClass { const Settings = new SettingsRaw(SettingsCol, Trash); const Users = new UsersRaw(UsersCol, Trash); const Subscriptions = new SubscriptionsRaw(db.collection('rocketchat_subscription'), Trash); + const LivechatInquiry = new LivechatInquiryRaw(db.collection('rocketchat_livechat_inquiry'), Trash); const Messages = new MessagesRaw(db.collection('rocketchat_message'), Trash); const Permissions = new MessagesRaw(db.collection('rocketchat_permissions'), Trash); const Roles = new RolesRaw(db.collection('rocketchat_roles'), Trash, { Users, Subscriptions }); @@ -37,6 +37,7 @@ export class StreamHub extends ServiceClass implements IServiceClass { Users, Subscriptions, Permissions, + LivechatInquiry, Settings, Roles, }; @@ -106,8 +107,6 @@ export class StreamHub extends ServiceClass implements IServiceClass { // }, // } }], { fullDocument: 'updateLookup' }).on('change', watchMessages); - Inquiry.watch([], { fullDocument: 'updateLookup' }).on('change', watchInquiries); - Rooms.watch([], { fullDocument: 'updateLookup' }).on('change', watchRooms); // SettingsCol.watch([{ diff --git a/ee/server/services/StreamHub/watchInquiries.ts b/ee/server/services/StreamHub/watchInquiries.ts deleted file mode 100644 index 4d51717c3d0b5..0000000000000 --- a/ee/server/services/StreamHub/watchInquiries.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { ChangeEvent } from 'mongodb'; - -import { normalize } from './utils'; -import { IInquiry } from '../../../../definition/IInquiry'; -import { api } from '../../../../server/sdk/api'; - -export async function watchInquiries(event: ChangeEvent): Promise { - switch (event.operationType) { - case 'insert': - case 'update': - const record = event.fullDocument; - if (!record) { - return; - } - return api.broadcast('livechat-inquiry-queue-observer', { action: normalize[event.operationType], inquiry: record }); - default: - } -} diff --git a/server/modules/watchers/watchers.module.ts b/server/modules/watchers/watchers.module.ts index e24a990f1683e..e6ddf4f8cab06 100644 --- a/server/modules/watchers/watchers.module.ts +++ b/server/modules/watchers/watchers.module.ts @@ -10,10 +10,12 @@ import { IMessage } from '../../../definition/IMessage'; import { ISubscription } from '../../../definition/ISubscription'; import { IRole } from '../../../definition/IRole'; import { IBaseRaw } from '../../../app/models/server/raw/BaseRaw'; +import { LivechatInquiryRaw } from '../../../app/models/server/raw/LivechatInquiry'; import { api } from '../../sdk/api'; import { IBaseData } from '../../../definition/IBaseData'; import { IPermission } from '../../../definition/IPermission'; import { ISetting } from '../../../definition/ISetting'; +import { IInquiry } from '../../../definition/IInquiry'; interface IModelsParam { // Rooms: RoomsRaw; @@ -22,6 +24,7 @@ interface IModelsParam { Users: UsersRaw; Settings: SettingsRaw; Messages: MessagesRaw; + LivechatInquiry: LivechatInquiryRaw; Roles: RolesRaw; } @@ -83,6 +86,7 @@ export function initWatchers({ Subscriptions, Roles, Permissions, + LivechatInquiry, }: IModelsParam, watch: Watcher): void { watch(Messages, async ({ clientAction, id, data }) => { switch (clientAction) { @@ -151,6 +155,29 @@ export function initWatchers({ }); }); + watch(LivechatInquiry, async ({ clientAction, id, data, diff }) => { + // if (RoutingManager.getConfig().autoAssignAgent) { + // return; + // } + + switch (clientAction) { + case 'inserted': + case 'updated': + data = data ?? await LivechatInquiry.findOneById(id); + break; + + case 'removed': + data = await LivechatInquiry.trashFindOneById(id); + break; + } + + if (!data) { + return; + } + + api.broadcast('watch.inquiries', { clientAction, inquiry: data, diff }); + }); + watch(Permissions, async ({ clientAction, id, data, diff }) => { if (diff && Object.keys(diff).length === 1 && diff._updatedAt) { // avoid useless changes diff --git a/server/sdk/lib/Events.ts b/server/sdk/lib/Events.ts index b4ea35355f725..d2390bf907ae2 100644 --- a/server/sdk/lib/Events.ts +++ b/server/sdk/lib/Events.ts @@ -41,5 +41,6 @@ export type EventSignatures = { 'watch.messages'(data: { clientAction: string; message: Partial }): void; 'watch.roles'(data: { clientAction: string; role: Partial }): void; 'watch.subscriptions'(data: { clientAction: string; subscription: Partial }): void; + 'watch.inquiries'(data: { clientAction: string; inquiry: IInquiry; diff?: Record }): void; 'watch.settings'(data: { clientAction: string; setting: ISetting }): void; } diff --git a/server/sdk/types/IMeteor.ts b/server/sdk/types/IMeteor.ts index 06b75caed53a6..176081546f9ed 100644 --- a/server/sdk/types/IMeteor.ts +++ b/server/sdk/types/IMeteor.ts @@ -1,4 +1,5 @@ import { IServiceClass } from './ServiceClass'; +import { IRoutingManagerConfig } from '../../../definition/IRoutingManagerConfig'; export type AutoUpdateRecord = { _id: string; @@ -18,4 +19,6 @@ export interface IMeteor extends IServiceClass { callMethodWithToken(userId: string, token: string, method: string, args: any[]): Promise; notifyGuestStatusChanged(token: string, status: string): Promise; + + getRoutingManagerConfig(): IRoutingManagerConfig; } diff --git a/server/services/listeners/notification.ts b/server/services/listeners/notification.ts index 0ff8098501bb7..7c37aab5df375 100644 --- a/server/services/listeners/notification.ts +++ b/server/services/listeners/notification.ts @@ -1,6 +1,7 @@ import { ServiceClass } from '../../sdk/types/ServiceClass'; import { NotificationsModule } from '../../modules/notifications/notifications.module'; -import { EnterpriseSettings } from '../../sdk/index'; +import { EnterpriseSettings, MeteorService } from '../../sdk/index'; +import { IRoutingManagerConfig } from '../../../definition/IRoutingManagerConfig'; const STATUS_MAP: {[k: string]: number} = { offline: 0, @@ -136,7 +137,51 @@ export class NotificationService extends ServiceClass { notifications.streamRoles.emit('roles', payload); }); + let autoAssignAgent: IRoutingManagerConfig | undefined; + async function getRoutingManagerConfig(): Promise { + if (!autoAssignAgent) { + autoAssignAgent = await MeteorService.getRoutingManagerConfig(); + } + + return autoAssignAgent; + } + + const inquiryTypeMap: Record = { inserted: 'added', updated: 'changed', removed: 'removed' }; + this.onEvent('watch.inquiries', async ({ clientAction, inquiry, diff }): Promise => { + const config = await getRoutingManagerConfig(); + if (config.autoAssignAgent) { + return; + } + + const type = inquiryTypeMap[clientAction]; + if (clientAction === 'removed') { + notifications.streamLivechatQueueData.emitWithoutBroadcast(inquiry._id, { _id: inquiry._id, clientAction }); + + if (inquiry.department) { + return notifications.streamLivechatQueueData.emitWithoutBroadcast(`department/${ inquiry.department }`, { type, ...inquiry }); + } + + return notifications.streamLivechatQueueData.emitWithoutBroadcast('public', { type, ...inquiry }); + } + + notifications.streamLivechatQueueData.emitWithoutBroadcast(inquiry._id, { ...inquiry, clientAction }); + + if (!inquiry.department) { + return notifications.streamLivechatQueueData.emitWithoutBroadcast('public', { type, ...inquiry }); + } + + notifications.streamLivechatQueueData.emitWithoutBroadcast(`department/${ inquiry.department }`, { type, ...inquiry }); + + if (clientAction === 'updated' && !diff?.department) { + notifications.streamLivechatQueueData.emitWithoutBroadcast('public', { type, ...inquiry }); + } + }); + this.onEvent('watch.settings', async ({ clientAction, setting }): Promise => { + if (setting._id === 'Livechat_Routing_Method') { + autoAssignAgent = undefined; + } + if (clientAction !== 'removed') { const result = await EnterpriseSettings.changeSettingValue(setting); if (!(result instanceof Error)) { diff --git a/server/services/meteor/service.ts b/server/services/meteor/service.ts index ef8245c5d473c..0b10862151e1c 100644 --- a/server/services/meteor/service.ts +++ b/server/services/meteor/service.ts @@ -8,6 +8,8 @@ import { Users } from '../../../app/models/server/raw/index'; import { Livechat } from '../../../app/livechat/server'; import { settings } from '../../../app/settings/server/functions/settings'; import { setValue, updateValue } from '../../../app/settings/server/raw'; +import { IRoutingManagerConfig } from '../../../definition/IRoutingManagerConfig'; +import { RoutingManager } from '../../../app/livechat/server/lib/RoutingManager'; const autoUpdateRecords = new Map(); @@ -70,4 +72,8 @@ export class MeteorService extends ServiceClass implements IMeteor { async notifyGuestStatusChanged(token: string, status: string): Promise { return Livechat.notifyGuestStatusChanged(token, status); } + + getRoutingManagerConfig(): IRoutingManagerConfig { + return RoutingManager.getConfig(); + } } From 81d088b5ad4146ea7626cb6bf511c80a00c74f80 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Tue, 6 Oct 2020 18:55:39 -0300 Subject: [PATCH 144/198] Move UserPrensenceMonitor to module --- app/models/server/raw/UsersSessions.ts | 4 ++++ app/models/server/raw/index.ts | 5 +++++ ee/server/services/StreamHub/StreamHub.ts | 3 +++ server/modules/watchers/watchers.module.ts | 25 ++++++++++++++++++---- server/sdk/lib/Events.ts | 2 ++ server/services/listeners/notification.ts | 5 +++-- server/services/meteor/service.ts | 16 ++++++++++++++ server/startup/presence.js | 24 +-------------------- typings.d.ts | 6 ++++++ 9 files changed, 61 insertions(+), 29 deletions(-) create mode 100644 app/models/server/raw/UsersSessions.ts diff --git a/app/models/server/raw/UsersSessions.ts b/app/models/server/raw/UsersSessions.ts new file mode 100644 index 0000000000000..b89feaa9deb3f --- /dev/null +++ b/app/models/server/raw/UsersSessions.ts @@ -0,0 +1,4 @@ +import { BaseRaw } from './BaseRaw'; +import { IUserSession } from '../../../../definition/IUserSession'; + +export class UsersSessionsRaw extends BaseRaw {} diff --git a/app/models/server/raw/index.ts b/app/models/server/raw/index.ts index e4a120667f9f7..d4a197e9851bb 100644 --- a/app/models/server/raw/index.ts +++ b/app/models/server/raw/index.ts @@ -49,6 +49,8 @@ import { NotificationQueueRaw } from './NotificationQueue'; import LivechatBusinessHoursModel from '../models/LivechatBusinessHours'; import { LivechatBusinessHoursRaw } from './LivechatBusinessHours'; import ServerEventModel from '../models/ServerEvents'; +import { UsersSessionsRaw } from './UsersSessions'; +import UsersSessionsModel from '../models/UsersSessions'; import { ServerEventsRaw } from './ServerEvents'; import { trash } from '../models/_BaseDb'; import { initWatchers } from '../../../../server/modules/watchers/watchers.module'; @@ -81,6 +83,7 @@ export const NotificationQueue = new NotificationQueueRaw(NotificationQueueModel export const LivechatBusinessHours = new LivechatBusinessHoursRaw(LivechatBusinessHoursModel.model.rawCollection(), trashCollection); export const ServerEvents = new ServerEventsRaw(ServerEventModel.model.rawCollection(), trashCollection); export const Roles = new RolesRaw(RolesModel.model.rawCollection(), trashCollection, { Users, Subscriptions }); +export const UsersSessions = new UsersSessionsRaw(UsersSessionsModel.model.rawCollection(), trashCollection); const map = { [Messages.col.collectionName]: MessagesModel, @@ -90,6 +93,7 @@ const map = { [Roles.col.collectionName]: RolesModel, [Permissions.col.collectionName]: PermissionsModel, [LivechatInquiry.col.collectionName]: LivechatInquiryModel, + [UsersSessions.col.collectionName]: UsersSessionsModel, }; initWatchers({ @@ -98,6 +102,7 @@ initWatchers({ Subscriptions, Settings, LivechatInquiry, + UsersSessions, Permissions, Roles, }, (model, fn) => { diff --git a/ee/server/services/StreamHub/StreamHub.ts b/ee/server/services/StreamHub/StreamHub.ts index fc4f78e9f37b9..d781ae60f6110 100755 --- a/ee/server/services/StreamHub/StreamHub.ts +++ b/ee/server/services/StreamHub/StreamHub.ts @@ -10,6 +10,7 @@ import { SubscriptionsRaw } from '../../../../app/models/server/raw/Subscription import { SettingsRaw } from '../../../../app/models/server/raw/Settings'; import { RolesRaw } from '../../../../app/models/server/raw/Roles'; import { LivechatInquiryRaw } from '../../../../app/models/server/raw/LivechatInquiry'; +import { UsersSessionsRaw } from '../../../../app/models/server/raw/UsersSessions'; export class StreamHub extends ServiceClass implements IServiceClass { protected name = 'hub'; @@ -26,6 +27,7 @@ export class StreamHub extends ServiceClass implements IServiceClass { const Settings = new SettingsRaw(SettingsCol, Trash); const Users = new UsersRaw(UsersCol, Trash); + const UsersSessions = new UsersSessionsRaw(db.collection('usersSessions'), Trash); const Subscriptions = new SubscriptionsRaw(db.collection('rocketchat_subscription'), Trash); const LivechatInquiry = new LivechatInquiryRaw(db.collection('rocketchat_livechat_inquiry'), Trash); const Messages = new MessagesRaw(db.collection('rocketchat_message'), Trash); @@ -35,6 +37,7 @@ export class StreamHub extends ServiceClass implements IServiceClass { const models = { Messages, Users, + UsersSessions, Subscriptions, Permissions, LivechatInquiry, diff --git a/server/modules/watchers/watchers.module.ts b/server/modules/watchers/watchers.module.ts index e6ddf4f8cab06..68628931f658a 100644 --- a/server/modules/watchers/watchers.module.ts +++ b/server/modules/watchers/watchers.module.ts @@ -16,6 +16,8 @@ import { IBaseData } from '../../../definition/IBaseData'; import { IPermission } from '../../../definition/IPermission'; import { ISetting } from '../../../definition/ISetting'; import { IInquiry } from '../../../definition/IInquiry'; +import { UsersSessionsRaw } from '../../../app/models/server/raw/UsersSessions'; +import { IUserSession } from '../../../definition/IUserSession'; interface IModelsParam { // Rooms: RoomsRaw; @@ -25,6 +27,7 @@ interface IModelsParam { Settings: SettingsRaw; Messages: MessagesRaw; LivechatInquiry: LivechatInquiryRaw; + UsersSessions: UsersSessionsRaw; Roles: RolesRaw; } @@ -84,6 +87,7 @@ export function initWatchers({ Users, Settings, Subscriptions, + UsersSessions, Roles, Permissions, LivechatInquiry, @@ -155,11 +159,24 @@ export function initWatchers({ }); }); - watch(LivechatInquiry, async ({ clientAction, id, data, diff }) => { - // if (RoutingManager.getConfig().autoAssignAgent) { - // return; - // } + watch(UsersSessions, async ({ clientAction, id, data }) => { + switch (clientAction) { + case 'inserted': + case 'updated': + data = data ?? await UsersSessions.findOneById(id); + if (!data) { + return; + } + api.broadcast('watch.userSessions', { clientAction, userSession: data }); + break; + case 'removed': + api.broadcast('watch.userSessions', { clientAction, userSession: { _id: id } }); + break; + } + }); + + watch(LivechatInquiry, async ({ clientAction, id, data, diff }) => { switch (clientAction) { case 'inserted': case 'updated': diff --git a/server/sdk/lib/Events.ts b/server/sdk/lib/Events.ts index d2390bf907ae2..eb0fa6a8169d5 100644 --- a/server/sdk/lib/Events.ts +++ b/server/sdk/lib/Events.ts @@ -10,6 +10,7 @@ import { IUser } from '../../../definition/IUser'; import { AutoUpdateRecord } from '../types/IMeteor'; import { IEmoji } from '../../../definition/IEmoji'; import { IUserStatus } from '../../../definition/IUserStatus'; +import { IUserSession } from '../../../definition/IUserSession'; export type BufferList = ReturnType; @@ -41,6 +42,7 @@ export type EventSignatures = { 'watch.messages'(data: { clientAction: string; message: Partial }): void; 'watch.roles'(data: { clientAction: string; role: Partial }): void; 'watch.subscriptions'(data: { clientAction: string; subscription: Partial }): void; + 'watch.userSessions'(data: { clientAction: string; userSession: Partial }): void; 'watch.inquiries'(data: { clientAction: string; inquiry: IInquiry; diff?: Record }): void; 'watch.settings'(data: { clientAction: string; setting: ISetting }): void; } diff --git a/server/services/listeners/notification.ts b/server/services/listeners/notification.ts index 7c37aab5df375..e06c7d0bdffd7 100644 --- a/server/services/listeners/notification.ts +++ b/server/services/listeners/notification.ts @@ -10,6 +10,8 @@ const STATUS_MAP: {[k: string]: number} = { busy: 3, }; +export const minimongoChangeMap: Record = { inserted: 'added', updated: 'changed', removed: 'removed' }; + // TODO: Convert to module and implement/import on monolith and on DDPStreamer export class NotificationService extends ServiceClass { protected name = 'notification'; @@ -146,14 +148,13 @@ export class NotificationService extends ServiceClass { return autoAssignAgent; } - const inquiryTypeMap: Record = { inserted: 'added', updated: 'changed', removed: 'removed' }; this.onEvent('watch.inquiries', async ({ clientAction, inquiry, diff }): Promise => { const config = await getRoutingManagerConfig(); if (config.autoAssignAgent) { return; } - const type = inquiryTypeMap[clientAction]; + const type = minimongoChangeMap[clientAction]; if (clientAction === 'removed') { notifications.streamLivechatQueueData.emitWithoutBroadcast(inquiry._id, { _id: inquiry._id, clientAction }); diff --git a/server/services/meteor/service.ts b/server/services/meteor/service.ts index 0b10862151e1c..fdb9bb1161afd 100644 --- a/server/services/meteor/service.ts +++ b/server/services/meteor/service.ts @@ -1,5 +1,6 @@ import { Meteor } from 'meteor/meteor'; import { ServiceConfiguration } from 'meteor/service-configuration'; +import { UserPresenceMonitor } from 'meteor/konecty:user-presence'; import { ServiceClass } from '../../sdk/types/ServiceClass'; import { IMeteor, AutoUpdateRecord } from '../../sdk/types/IMeteor'; @@ -10,6 +11,7 @@ import { settings } from '../../../app/settings/server/functions/settings'; import { setValue, updateValue } from '../../../app/settings/server/raw'; import { IRoutingManagerConfig } from '../../../definition/IRoutingManagerConfig'; import { RoutingManager } from '../../../app/livechat/server/lib/RoutingManager'; +import { minimongoChangeMap } from '../listeners/notification'; const autoUpdateRecords = new Map(); @@ -46,6 +48,20 @@ export class MeteorService extends ServiceClass implements IMeteor { settings.removeSettingValue(setting, false); setValue(setting._id, undefined); }); + + // TODO: May need to merge with https://github.com/RocketChat/Rocket.Chat/blob/0ddc2831baf8340cbbbc432f88fc2cb97be70e9b/ee/server/services/Presence/Presence.ts#L28 + this.onEvent('watch.userSessions', async ({ clientAction, userSession }): Promise => { + if (clientAction === 'removed') { + UserPresenceMonitor.processUserSession({ + _id: userSession._id, + connections: [{ + fake: true, + }], + }, 'removed'); + } + + UserPresenceMonitor.processUserSession(userSession, minimongoChangeMap[clientAction]); + }); } async getLastAutoUpdateClientVersions(): Promise { diff --git a/server/startup/presence.js b/server/startup/presence.js index d28c69906aec5..6d2d5e475ace8 100644 --- a/server/startup/presence.js +++ b/server/startup/presence.js @@ -1,6 +1,6 @@ import { Meteor } from 'meteor/meteor'; import { InstanceStatus } from 'meteor/konecty:multiple-instances-status'; -import { UserPresence, UserPresenceMonitor } from 'meteor/konecty:user-presence'; +import { UserPresence } from 'meteor/konecty:user-presence'; import InstanceStatusModel from '../../app/models/server/models/InstanceStatus'; import UsersSessionsModel from '../../app/models/server/models/UsersSessions'; @@ -45,27 +45,5 @@ Meteor.startup(function() { break; } }); - - UsersSessionsModel.on('change', ({ clientAction, id, data }) => { - switch (clientAction) { - case 'inserted': - UserPresenceMonitor.processUserSession(data, 'added'); - break; - case 'updated': - data = data ?? UsersSessionsModel.findOneById(id); - if (data) { - UserPresenceMonitor.processUserSession(data, 'changed'); - } - break; - case 'removed': - UserPresenceMonitor.processUserSession({ - _id: id, - connections: [{ - fake: true, - }], - }, 'removed'); - break; - } - }); } }); diff --git a/typings.d.ts b/typings.d.ts index 1362f56164200..d868b0a87e870 100644 --- a/typings.d.ts +++ b/typings.d.ts @@ -5,3 +5,9 @@ declare module 'meteor/ddp-common'; declare module 'meteor/routepolicy'; declare module 'xml-encryption'; declare module 'webdav'; + +declare module 'meteor/konecty:user-presence' { + namespace UserPresenceMonitor { + function processUserSession(userSession: any, event: string): void; + } +} From 7592159430f80970ef91ce03bc37e9702cfabe19 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Wed, 7 Oct 2020 02:08:25 -0300 Subject: [PATCH 145/198] Add room watcher --- app/models/server/raw/index.ts | 2 + ee/server/services/DDPStreamer/DDPStreamer.ts | 9 -- ee/server/services/StreamHub/StreamHub.ts | 7 +- ee/server/services/StreamHub/watchRooms.ts | 28 ------ .../notifications/notifications.module.ts | 1 + server/modules/watchers/publishFields.ts | 94 +++++++++++++++++++ server/modules/watchers/watchers.module.ts | 60 ++++-------- server/publications/room/emitter.js | 43 --------- server/publications/room/index.js | 61 +----------- server/sdk/lib/Events.ts | 1 + server/services/listeners/notification.ts | 8 ++ 11 files changed, 131 insertions(+), 183 deletions(-) delete mode 100644 ee/server/services/StreamHub/watchRooms.ts create mode 100644 server/modules/watchers/publishFields.ts delete mode 100644 server/publications/room/emitter.js diff --git a/app/models/server/raw/index.ts b/app/models/server/raw/index.ts index d4a197e9851bb..293d69ce0e100 100644 --- a/app/models/server/raw/index.ts +++ b/app/models/server/raw/index.ts @@ -94,6 +94,7 @@ const map = { [Permissions.col.collectionName]: PermissionsModel, [LivechatInquiry.col.collectionName]: LivechatInquiryModel, [UsersSessions.col.collectionName]: UsersSessionsModel, + [Rooms.col.collectionName]: RoomsModel, }; initWatchers({ @@ -105,6 +106,7 @@ initWatchers({ UsersSessions, Permissions, Roles, + Rooms, }, (model, fn) => { const meteorModel = map[model.col.collectionName]; diff --git a/ee/server/services/DDPStreamer/DDPStreamer.ts b/ee/server/services/DDPStreamer/DDPStreamer.ts index 81b3ee90e8a0a..e6fcd4ed8e5ed 100644 --- a/ee/server/services/DDPStreamer/DDPStreamer.ts +++ b/ee/server/services/DDPStreamer/DDPStreamer.ts @@ -181,15 +181,6 @@ export class DDPStreamer extends ServiceClass { notifications.notifyLogged('Users:NameChanged', { _id, name, username }); }); - // room({ room, action }) { - this.onEvent('room', ({ room, action }): void => { - // RocketChat.Notifications.streamUser.__emit(id, clientAction, data); - if (!room._id) { - return; - } - notifications.streamUser.__emit(room._id, action, room); - notifications.streamRoomData.emit(room._id, action, room); // TODO REMOVE - }); // stream: { // group: 'streamer', // handler(payload) { diff --git a/ee/server/services/StreamHub/StreamHub.ts b/ee/server/services/StreamHub/StreamHub.ts index d781ae60f6110..9273c8e3eb943 100755 --- a/ee/server/services/StreamHub/StreamHub.ts +++ b/ee/server/services/StreamHub/StreamHub.ts @@ -1,5 +1,4 @@ import { watchUsers } from './watchUsers'; -import { watchRooms } from './watchRooms'; import { getConnection } from '../mongo'; import { ServiceClass, IServiceClass } from '../../../../server/sdk/types/ServiceClass'; import { watchLoginServiceConfiguration } from './watchLoginServiceConfiguration'; @@ -11,6 +10,7 @@ import { SettingsRaw } from '../../../../app/models/server/raw/Settings'; import { RolesRaw } from '../../../../app/models/server/raw/Roles'; import { LivechatInquiryRaw } from '../../../../app/models/server/raw/LivechatInquiry'; import { UsersSessionsRaw } from '../../../../app/models/server/raw/UsersSessions'; +import { RoomsRaw } from '../../../../app/models/server/raw/Rooms'; export class StreamHub extends ServiceClass implements IServiceClass { protected name = 'hub'; @@ -19,12 +19,12 @@ export class StreamHub extends ServiceClass implements IServiceClass { const db = await getConnection(); const Trash = db.collection('rocketchat_trash'); - const Rooms = db.collection('rocketchat_room'); const loginServiceConfiguration = db.collection('meteor_accounts_loginServiceConfiguration'); const UsersCol = db.collection('users'); const SettingsCol = db.collection('rocketchat_settings'); + const Rooms = new RoomsRaw(db.collection('rocketchat_room'), Trash); const Settings = new SettingsRaw(SettingsCol, Trash); const Users = new UsersRaw(UsersCol, Trash); const UsersSessions = new UsersSessionsRaw(db.collection('usersSessions'), Trash); @@ -43,6 +43,7 @@ export class StreamHub extends ServiceClass implements IServiceClass { LivechatInquiry, Settings, Roles, + Rooms, }; initWatchers(models, (model, fn) => { @@ -110,8 +111,6 @@ export class StreamHub extends ServiceClass implements IServiceClass { // }, // } }], { fullDocument: 'updateLookup' }).on('change', watchMessages); - Rooms.watch([], { fullDocument: 'updateLookup' }).on('change', watchRooms); - // SettingsCol.watch([{ // $addFields: { // tmpfields: { diff --git a/ee/server/services/StreamHub/watchRooms.ts b/ee/server/services/StreamHub/watchRooms.ts deleted file mode 100644 index 5fa7ca8a2f4fd..0000000000000 --- a/ee/server/services/StreamHub/watchRooms.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { ChangeEvent } from 'mongodb'; - -import { normalize } from './utils'; -import { api } from '../../../../server/sdk/api'; -import { IRoom } from '../../../../definition/IRoom'; - -export async function watchRooms(event: ChangeEvent): Promise { - let room; - switch (event.operationType) { - case 'insert': - case 'update': - // room = await Rooms.findOne(documentKey/* , { fields }*/); - room = event.fullDocument; - break; - case 'delete': - room = event.documentKey; - break; - default: - return; - } - // console.log(room, documentKey); - if (!room) { - return; - } - api.broadcast('room', { action: normalize[event.operationType], room }); - // RocketChat.Notifications.streamUser.__emit(data._id, operationType, data); - // RocketChat.Logger.info('Rooms record', room); -} diff --git a/server/modules/notifications/notifications.module.ts b/server/modules/notifications/notifications.module.ts index 19a783a128988..06d35a5dcc1b1 100644 --- a/server/modules/notifications/notifications.module.ts +++ b/server/modules/notifications/notifications.module.ts @@ -311,6 +311,7 @@ export class NotificationsModule { if (/rooms-changed/.test(eventName)) { // TODO: change this to serialize only once const roomEvent = (...args: any[]): void => { + // TODO if receive a removed event could do => streamer.removeListener(rid, roomEvent); const payload = streamer.changedPayload(streamer.subscriptionName, 'id', { eventName: `${ userId }/rooms-changed`, args, diff --git a/server/modules/watchers/publishFields.ts b/server/modules/watchers/publishFields.ts new file mode 100644 index 0000000000000..1f7f0f90991a4 --- /dev/null +++ b/server/modules/watchers/publishFields.ts @@ -0,0 +1,94 @@ +export const subscriptionFields = { + t: 1, + ts: 1, + ls: 1, + lr: 1, + name: 1, + fname: 1, + rid: 1, + code: 1, + f: 1, + u: 1, + open: 1, + alert: 1, + roles: 1, + unread: 1, + prid: 1, + userMentions: 1, + groupMentions: 1, + archived: 1, + audioNotifications: 1, + audioNotificationValue: 1, + desktopNotifications: 1, + mobilePushNotifications: 1, + emailNotifications: 1, + unreadAlert: 1, + _updatedAt: 1, + blocked: 1, + blocker: 1, + autoTranslate: 1, + autoTranslateLanguage: 1, + disableNotifications: 1, + hideUnreadStatus: 1, + muteGroupMentions: 1, + ignored: 1, + E2EKey: 1, + tunread: 1, + tunreadGroup: 1, + tunreadUser: 1, +}; + +export const roomFields = { + _id: 1, + name: 1, + fname: 1, + t: 1, + cl: 1, + u: 1, + lm: 1, + // usernames: 1, + topic: 1, + announcement: 1, + announcementDetails: 1, + muted: 1, + unmuted: 1, + _updatedAt: 1, + archived: 1, + jitsiTimeout: 1, + description: 1, + default: 1, + customFields: 1, + lastMessage: 1, + retention: 1, + prid: 1, + avatarETag: 1, + usersCount: 1, + + // @TODO create an API to register this fields based on room type + livechatData: 1, + tags: 1, + sms: 1, + facebook: 1, + code: 1, + joinCodeRequired: 1, + open: 1, + v: 1, + label: 1, + ro: 1, + reactWhenReadOnly: 1, + sysMes: 1, + sentiment: 1, + tokenpass: 1, + streamingOptions: 1, + broadcast: 1, + encrypted: 1, + e2eKeyId: 1, + departmentId: 1, + servedBy: 1, + priorityId: 1, + transcriptRequest: 1, + + // fields used by DMs + usernames: 1, + uids: 1, +}; diff --git a/server/modules/watchers/watchers.module.ts b/server/modules/watchers/watchers.module.ts index 68628931f658a..0ea0691228f92 100644 --- a/server/modules/watchers/watchers.module.ts +++ b/server/modules/watchers/watchers.module.ts @@ -6,9 +6,11 @@ import { SettingsRaw } from '../../../app/models/server/raw/Settings'; import { PermissionsRaw } from '../../../app/models/server/raw/Permissions'; import { MessagesRaw } from '../../../app/models/server/raw/Messages'; import { RolesRaw } from '../../../app/models/server/raw/Roles'; +import { RoomsRaw } from '../../../app/models/server/raw/Rooms'; import { IMessage } from '../../../definition/IMessage'; import { ISubscription } from '../../../definition/ISubscription'; import { IRole } from '../../../definition/IRole'; +import { IRoom } from '../../../definition/IRoom'; import { IBaseRaw } from '../../../app/models/server/raw/BaseRaw'; import { LivechatInquiryRaw } from '../../../app/models/server/raw/LivechatInquiry'; import { api } from '../../sdk/api'; @@ -18,6 +20,7 @@ import { ISetting } from '../../../definition/ISetting'; import { IInquiry } from '../../../definition/IInquiry'; import { UsersSessionsRaw } from '../../../app/models/server/raw/UsersSessions'; import { IUserSession } from '../../../definition/IUserSession'; +import { subscriptionFields, roomFields } from './publishFields'; interface IModelsParam { // Rooms: RoomsRaw; @@ -29,6 +32,7 @@ interface IModelsParam { LivechatInquiry: LivechatInquiryRaw; UsersSessions: UsersSessionsRaw; Roles: RolesRaw; + Rooms: RoomsRaw; } interface IChange { @@ -41,47 +45,6 @@ interface IChange { type Watcher = (model: IBaseRaw, fn: (event: IChange) => void) => void; -// TODO: find a better place -export const subscriptionFields = { - t: 1, - ts: 1, - ls: 1, - lr: 1, - name: 1, - fname: 1, - rid: 1, - code: 1, - f: 1, - u: 1, - open: 1, - alert: 1, - roles: 1, - unread: 1, - prid: 1, - userMentions: 1, - groupMentions: 1, - archived: 1, - audioNotifications: 1, - audioNotificationValue: 1, - desktopNotifications: 1, - mobilePushNotifications: 1, - emailNotifications: 1, - unreadAlert: 1, - _updatedAt: 1, - blocked: 1, - blocker: 1, - autoTranslate: 1, - autoTranslateLanguage: 1, - disableNotifications: 1, - hideUnreadStatus: 1, - muteGroupMentions: 1, - ignored: 1, - E2EKey: 1, - tunread: 1, - tunreadGroup: 1, - tunreadUser: 1, -}; - export function initWatchers({ Messages, Users, @@ -91,6 +54,7 @@ export function initWatchers({ Roles, Permissions, LivechatInquiry, + Rooms, }: IModelsParam, watch: Watcher): void { watch(Messages, async ({ clientAction, id, data }) => { switch (clientAction) { @@ -254,4 +218,18 @@ export function initWatchers({ api.broadcast('watch.settings', { clientAction, setting }); }); + + watch(Rooms, async ({ clientAction, id, data }) => { + if (clientAction === 'removed') { + api.broadcast('watch.rooms', { clientAction, room: { _id: id } }); + return; + } + + const room = data ?? await Rooms.findOneById(id, { projection: roomFields }); + if (!room) { + return; + } + + api.broadcast('watch.rooms', { clientAction, room }); + }); } diff --git a/server/publications/room/emitter.js b/server/publications/room/emitter.js deleted file mode 100644 index 0c47dad371696..0000000000000 --- a/server/publications/room/emitter.js +++ /dev/null @@ -1,43 +0,0 @@ -import { Rooms, Subscriptions } from '../../../app/models/server'; -import { Notifications } from '../../../app/notifications/server'; -import notifications from '../../../app/notifications/server/lib/Notifications'; - -import { fields } from '.'; - -const getSubscriptions = (id) => { - const fields = { 'u._id': 1 }; - return Subscriptions.trashFind({ rid: id }, { fields }); -}; - -Rooms.on('change', ({ clientAction, id, data }) => { - switch (clientAction) { - case 'updated': - case 'inserted': - // Override data cuz we do not publish all fields - data = Rooms.findOneById(id, { fields }); - break; - - case 'removed': - data = { _id: id }; - break; - } - - if (!data) { - return; - } - if (clientAction === 'removed') { - getSubscriptions(id).forEach(({ u }) => { - Notifications.notifyUserInThisInstance( - u._id, - 'rooms-changed', - clientAction, - data, - ); - }); - } - - Notifications.streamUser.__emit(id, clientAction, data); - - // TODO validate emitWithoutBroadcast - notifications.streamRoomData.emitWithoutBroadcast(id, clientAction, data); -}); diff --git a/server/publications/room/index.js b/server/publications/room/index.js index 8063bd89888a6..ee7451885d923 100644 --- a/server/publications/room/index.js +++ b/server/publications/room/index.js @@ -5,73 +5,18 @@ import { roomTypes } from '../../../app/utils'; import { hasPermission } from '../../../app/authorization'; import { Rooms } from '../../../app/models'; import { settings } from '../../../app/settings'; -import './emitter'; - -export const fields = { - _id: 1, - name: 1, - fname: 1, - t: 1, - cl: 1, - u: 1, - lm: 1, - // usernames: 1, - topic: 1, - announcement: 1, - announcementDetails: 1, - muted: 1, - unmuted: 1, - _updatedAt: 1, - archived: 1, - jitsiTimeout: 1, - description: 1, - default: 1, - customFields: 1, - lastMessage: 1, - retention: 1, - prid: 1, - avatarETag: 1, - usersCount: 1, - - // @TODO create an API to register this fields based on room type - livechatData: 1, - tags: 1, - sms: 1, - facebook: 1, - code: 1, - joinCodeRequired: 1, - open: 1, - v: 1, - label: 1, - ro: 1, - reactWhenReadOnly: 1, - sysMes: 1, - sentiment: 1, - tokenpass: 1, - streamingOptions: 1, - broadcast: 1, - encrypted: 1, - e2eKeyId: 1, - departmentId: 1, - servedBy: 1, - priorityId: 1, - transcriptRequest: 1, - - // fields used by DMs - usernames: 1, - uids: 1, -}; +import { roomFields } from '../../modules/watchers/publishFields'; const roomMap = (record) => { if (record) { - return _.pick(record, ...Object.keys(fields)); + return _.pick(record, ...Object.keys(roomFields)); } return {}; }; Meteor.methods({ 'rooms/get'(updatedAt) { - const options = { fields }; + const options = { fields: roomFields }; if (!Meteor.userId()) { if (settings.get('Accounts_AllowAnonymousRead') === true) { diff --git a/server/sdk/lib/Events.ts b/server/sdk/lib/Events.ts index eb0fa6a8169d5..dc9f5cfa7f620 100644 --- a/server/sdk/lib/Events.ts +++ b/server/sdk/lib/Events.ts @@ -41,6 +41,7 @@ export type EventSignatures = { 'userpresence'(data: { action: string; user: Partial }): void; 'watch.messages'(data: { clientAction: string; message: Partial }): void; 'watch.roles'(data: { clientAction: string; role: Partial }): void; + 'watch.rooms'(data: { clientAction: string; room: Pick & Partial }): void; 'watch.subscriptions'(data: { clientAction: string; subscription: Partial }): void; 'watch.userSessions'(data: { clientAction: string; userSession: Partial }): void; 'watch.inquiries'(data: { clientAction: string; inquiry: IInquiry; diff?: Record }): void; diff --git a/server/services/listeners/notification.ts b/server/services/listeners/notification.ts index e06c7d0bdffd7..ed494fc849855 100644 --- a/server/services/listeners/notification.ts +++ b/server/services/listeners/notification.ts @@ -210,5 +210,13 @@ export class NotificationService extends ServiceClass { notifications.notifyLogged('private-settings-changed', clientAction, value); }); + + this.onEvent('watch.rooms', ({ clientAction, room }): void => { + // this emit will cause the user to receive a 'rooms-changed' event + notifications.streamUser.__emit(room._id, clientAction, room); + + // TODO validate emitWithoutBroadcast + notifications.streamRoomData.emitWithoutBroadcast(room._id, clientAction, room); + }); } } From 40b832569f8b1e83745781fe32ae8dced030a103 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Wed, 7 Oct 2020 12:05:08 -0300 Subject: [PATCH 146/198] Clear cache on roles change --- .../server/functions/hasPermission.js | 4 ---- app/authorization/server/startup.js | 9 --------- server/modules/watchers/watchers.module.ts | 7 ++++++- server/services/authorization/service.ts | 17 +++++------------ 4 files changed, 11 insertions(+), 26 deletions(-) diff --git a/app/authorization/server/functions/hasPermission.js b/app/authorization/server/functions/hasPermission.js index 0694f00aac6c5..e14bf7f869a49 100644 --- a/app/authorization/server/functions/hasPermission.js +++ b/app/authorization/server/functions/hasPermission.js @@ -1,9 +1,5 @@ import { Authorization } from '../../../../server/sdk'; -export const clearCache = () => { - // TODO need to implement a way to tell authorization service to clear its cache or not use cache at all -}; - export const hasAllPermissionAsync = async (userId, permissions, scope) => Authorization.hasAllPermission(userId, permissions, scope); export const hasPermissionAsync = async (userId, permissionId, scope) => Authorization.hasPermission(userId, permissionId, scope); export const hasAtLeastOnePermissionAsync = async (userId, permissions, scope) => Authorization.hasAtLeastOnePermission(userId, permissions, scope); diff --git a/app/authorization/server/startup.js b/app/authorization/server/startup.js index 9bc311170a69d..40b2608e737c3 100644 --- a/app/authorization/server/startup.js +++ b/app/authorization/server/startup.js @@ -4,7 +4,6 @@ import { Meteor } from 'meteor/meteor'; import { Roles, Permissions, Settings } from '../../models/server'; import { settings } from '../../settings/server'; import { getSettingPermissionId, CONSTANTS } from '../lib'; -import { clearCache } from './functions/hasPermission'; Meteor.startup(function() { // Note: @@ -223,12 +222,4 @@ Meteor.startup(function() { }; settings.onload('*', createPermissionForAddedSetting); - - Roles.on('change', ({ diff }) => { - if (diff && Object.keys(diff).length === 1 && diff._updatedAt) { - // avoid useless changes - return; - } - clearCache(); - }); }); diff --git a/server/modules/watchers/watchers.module.ts b/server/modules/watchers/watchers.module.ts index 0ea0691228f92..c14b6b31d4992 100644 --- a/server/modules/watchers/watchers.module.ts +++ b/server/modules/watchers/watchers.module.ts @@ -108,7 +108,12 @@ export function initWatchers({ api.broadcast('watch.subscriptions', { clientAction, subscription: data }); }); - watch(Roles, async ({ clientAction, id, data }) => { + watch(Roles, async ({ clientAction, id, data, diff }) => { + if (diff && Object.keys(diff).length === 1 && diff._updatedAt) { + // avoid useless changes + return; + } + const role = clientAction === 'removed' ? { _id: id, name: id } : data || await Roles.findOneById(id); diff --git a/server/services/authorization/service.ts b/server/services/authorization/service.ts index 2ee36f892081f..1165d68d1455a 100644 --- a/server/services/authorization/service.ts +++ b/server/services/authorization/service.ts @@ -40,10 +40,13 @@ export class Authorization extends ServiceClass implements IAuthorization { Settings = new SettingsRaw(db.collection('rocketchat_settings')); Rooms = new RoomsRaw(db.collection('rocketchat_room')); - this.onEvent('permission.changed', () => { + const clearCache = (): void => { mem.clear(this.getRolesCached); mem.clear(this.rolesHasPermissionCached); - }); + }; + + this.onEvent('watch.roles', clearCache); + this.onEvent('permission.changed', clearCache); } async hasAllPermission(userId: string, permissions: string[], scope?: string): Promise { @@ -80,22 +83,12 @@ export class Authorization extends ServiceClass implements IAuthorization { const result = await this.Permissions.findOne({ _id: permission, roles: { $in: roles } }, { projection: { _id: 1 } }); return !!result; } - // , { - // cacheKey: JSON.stringify, - // ...process.env.TEST_MODE === 'true' && { maxAge: 1 }, - // }); private async getRoles(uid: string, scope?: string): Promise { const { roles: userRoles = [] } = await this.Users.findOne({ _id: uid }, { projection: { roles: 1 } }) || {}; const { roles: subscriptionsRoles = [] } = (scope && await Subscriptions.findOne({ rid: scope, 'u._id': uid }, { projection: { roles: 1 } })) || {}; return [...userRoles, ...subscriptionsRoles].sort((a, b) => a.localeCompare(b)); } - // , { maxAge: 1000, cacheKey: JSON.stringify }); - - // private clearCache = (): void => { - // mem.clear(getRoles); - // mem.clear(rolesHasPermission); - // } private async atLeastOne(uid: string, permissions: string[] = [], scope?: string): Promise { const sortedRoles = await this.getRolesCached(uid, scope); From 943e68e2cf1ddfb29ed940c182b213b91ec62e86 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Mon, 12 Oct 2020 10:16:23 -0300 Subject: [PATCH 147/198] Move User watch to module --- app/lib/server/index.js | 1 - app/lib/server/startup/userDataStream.js | 104 -------------- app/livechat/server/lib/stream/agentStatus.ts | 38 +---- app/models/server/raw/Users.js | 9 ++ server/main.d.ts | 7 +- server/modules/watchers/watchers.module.ts | 5 + server/sdk/lib/Events.ts | 1 + server/services/listeners/notification.ts | 14 ++ server/services/meteor/service.ts | 135 ++++++++++++++++++ typings.d.ts | 4 + 10 files changed, 176 insertions(+), 142 deletions(-) delete mode 100644 app/lib/server/startup/userDataStream.js diff --git a/app/lib/server/index.js b/app/lib/server/index.js index 56ffb23f199f9..aa0f7c468bfc6 100644 --- a/app/lib/server/index.js +++ b/app/lib/server/index.js @@ -6,7 +6,6 @@ import './startup/settings'; import './startup/settingsOnLoadCdnPrefix'; import './startup/settingsOnLoadDirectReply'; import './startup/settingsOnLoadSMTP'; -import './startup/userDataStream'; import '../lib/MessageTypes'; import '../startup'; import '../startup/defaultRoomTypes'; diff --git a/app/lib/server/startup/userDataStream.js b/app/lib/server/startup/userDataStream.js deleted file mode 100644 index b7c796d992ede..0000000000000 --- a/app/lib/server/startup/userDataStream.js +++ /dev/null @@ -1,104 +0,0 @@ -import { MongoInternals } from 'meteor/mongo'; - -import { Users } from '../../../models/server'; -import { Notifications } from '../../../notifications/server'; -import loginServiceConfiguration from '../../../models/server/models/LoginServiceConfiguration'; - -let processOnChange; -// eslint-disable-next-line no-undef -const disableOplog = Package['disable-oplog']; - -if (disableOplog) { - // Stores the callbacks for the disconnection reactivity bellow - const userCallbacks = new Map(); - const serviceConfigCallbacks = new Set(); - - // Overrides the native observe changes to prevent database polling and stores the callbacks - // for the users' tokens to re-implement the reactivity based on our database listeners - const { mongo } = MongoInternals.defaultRemoteCollectionDriver(); - MongoInternals.Connection.prototype._observeChanges = function({ collectionName, selector, options = {} }, _ordered, callbacks) { - // console.error('Connection.Collection.prototype._observeChanges', collectionName, selector, options); - let cbs; - if (callbacks?.added) { - const records = Promise.await(mongo.rawCollection(collectionName).find(selector, { projection: options.fields }).toArray()); - for (const { _id, ...fields } of records) { - callbacks.added(_id, fields); - } - - if (collectionName === 'users' && selector['services.resume.loginTokens.hashedToken']) { - cbs = userCallbacks.get(selector._id) || new Set(); - cbs.add({ - hashedToken: selector['services.resume.loginTokens.hashedToken'], - callbacks, - }); - userCallbacks.set(selector._id, cbs); - } - } - - if (collectionName === 'meteor_accounts_loginServiceConfiguration') { - serviceConfigCallbacks.add(callbacks); - } - - return { - stop() { - if (cbs) { - cbs.delete(callbacks); - } - serviceConfigCallbacks.delete(callbacks); - }, - }; - }; - - // Re-implement meteor's reactivity that uses observe to disconnect sessions when the token - // associated was removed - processOnChange = (diff, id) => { - const loginTokens = diff['services.resume.loginTokens']; - if (loginTokens) { - const tokens = loginTokens.map(({ hashedToken }) => hashedToken); - - const cbs = userCallbacks.get(id); - if (cbs) { - [...cbs].filter(({ hashedToken }) => !tokens.includes(hashedToken)).forEach((item) => { - item.callbacks.removed(id); - cbs.delete(item); - }); - } - } - }; - - loginServiceConfiguration.on('change', ({ clientAction, id, data, diff }) => { - switch (clientAction) { - case 'inserted': - case 'updated': - const record = { ...data || diff }; - delete record.secret; - serviceConfigCallbacks.forEach((callbacks) => { - callbacks[clientAction === 'inserted' ? 'added' : 'changed']?.(id, record); - }); - break; - case 'removed': - serviceConfigCallbacks.forEach((callbacks) => { - callbacks.removed?.(id); - }); - } - }); -} - -Users.on('change', ({ clientAction, id, data, diff }) => { - switch (clientAction) { - case 'updated': - Notifications.notifyUserInThisInstance(id, 'userData', { diff, type: clientAction }); - - if (disableOplog) { - processOnChange(diff, id); - } - - break; - case 'inserted': - Notifications.notifyUserInThisInstance(id, 'userData', { data, type: clientAction }); - break; - case 'removed': - Notifications.notifyUserInThisInstance(id, 'userData', { id, type: clientAction }); - break; - } -}); diff --git a/app/livechat/server/lib/stream/agentStatus.ts b/app/livechat/server/lib/stream/agentStatus.ts index 4d875e95ebcc0..c04cd48d5e803 100644 --- a/app/livechat/server/lib/stream/agentStatus.ts +++ b/app/livechat/server/lib/stream/agentStatus.ts @@ -2,9 +2,8 @@ import { Meteor } from 'meteor/meteor'; import { Livechat } from '../Livechat'; import { settings } from '../../../../settings/server'; -import { Users } from '../../../../models/server'; -let monitorAgents = false; +export let monitorAgents = false; let actionTimeout = 60000; let action = 'none'; let comment = ''; @@ -28,7 +27,7 @@ settings.get('Livechat_agent_leave_comment', (_key, value) => { comment = value; }); -const onlineAgents = { +export const onlineAgents = { users: new Set(), queue: new Map(), @@ -74,36 +73,3 @@ const onlineAgents = { } }), }; - -Users.on('change', ({ clientAction, id, diff }) => { - if (!monitorAgents) { - return; - } - - if (clientAction !== 'removed' && diff && !diff.status && !diff.statusLivechat) { - return; - } - - switch (clientAction) { - case 'updated': - case 'inserted': - const agent = Users.findOneAgentById(id, { - fields: { - status: 1, - statusLivechat: 1, - }, - }); - const serviceOnline = agent && agent.status !== 'offline' && agent.statusLivechat === 'available'; - - if (serviceOnline) { - return onlineAgents.add(id); - } - - onlineAgents.remove(id); - - break; - case 'removed': - onlineAgents.remove(id); - break; - } -}); diff --git a/app/models/server/raw/Users.js b/app/models/server/raw/Users.js index 527fee492f24d..8bfee9eb6fe41 100644 --- a/app/models/server/raw/Users.js +++ b/app/models/server/raw/Users.js @@ -27,6 +27,15 @@ export class UsersRaw extends BaseRaw { return this.findOne(query, options); } + findOneAgentById(_id, options) { + const query = { + _id, + roles: 'livechat-agent', + }; + + return this.findOne(query, options); + } + findUsersInRolesWithQuery(roles, query, options) { roles = [].concat(roles); diff --git a/server/main.d.ts b/server/main.d.ts index 833263a549335..5e2c3fb103d71 100644 --- a/server/main.d.ts +++ b/server/main.d.ts @@ -1,5 +1,5 @@ import { EJSON } from 'meteor/ejson'; -import { Db } from 'mongodb'; +import { Db, Collection } from 'mongodb'; import { IStreamerConstructor } from './modules/streamer/streamer.module'; @@ -103,10 +103,15 @@ declare module 'meteor/mongo' { interface MongoConnection { db: Db; _oplogHandle: OplogHandle; + rawCollection(name: string): Collection; } namespace MongoInternals { function defaultRemoteCollectionDriver(): RemoteCollectionDriver; + + class ConnectionClass {} + + function Connection(): ConnectionClass; } } diff --git a/server/modules/watchers/watchers.module.ts b/server/modules/watchers/watchers.module.ts index c14b6b31d4992..a7522acfa4ffe 100644 --- a/server/modules/watchers/watchers.module.ts +++ b/server/modules/watchers/watchers.module.ts @@ -21,6 +21,7 @@ import { IInquiry } from '../../../definition/IInquiry'; import { UsersSessionsRaw } from '../../../app/models/server/raw/UsersSessions'; import { IUserSession } from '../../../definition/IUserSession'; import { subscriptionFields, roomFields } from './publishFields'; +import { IUser } from '../../../definition/IUser'; interface IModelsParam { // Rooms: RoomsRaw; @@ -237,4 +238,8 @@ export function initWatchers({ api.broadcast('watch.rooms', { clientAction, room }); }); + + watch(Users, ({ clientAction, id, data, diff }) => { + api.broadcast('watch.users', { clientAction, data, diff, id }); + }); } diff --git a/server/sdk/lib/Events.ts b/server/sdk/lib/Events.ts index dc9f5cfa7f620..49866261e34be 100644 --- a/server/sdk/lib/Events.ts +++ b/server/sdk/lib/Events.ts @@ -46,4 +46,5 @@ export type EventSignatures = { 'watch.userSessions'(data: { clientAction: string; userSession: Partial }): void; 'watch.inquiries'(data: { clientAction: string; inquiry: IInquiry; diff?: Record }): void; 'watch.settings'(data: { clientAction: string; setting: ISetting }): void; + 'watch.users'(data: { clientAction: string; data?: Partial; diff?: Record; id: string }): void; } diff --git a/server/services/listeners/notification.ts b/server/services/listeners/notification.ts index ed494fc849855..4f0ff84e0cd0d 100644 --- a/server/services/listeners/notification.ts +++ b/server/services/listeners/notification.ts @@ -218,5 +218,19 @@ export class NotificationService extends ServiceClass { // TODO validate emitWithoutBroadcast notifications.streamRoomData.emitWithoutBroadcast(room._id, clientAction, room); }); + + this.onEvent('watch.users', ({ clientAction, data, diff, id }): void => { + switch (clientAction) { + case 'updated': + notifications.notifyUserInThisInstance(id, 'userData', { diff, type: clientAction }); + break; + case 'inserted': + notifications.notifyUserInThisInstance(id, 'userData', { data, type: clientAction }); + break; + case 'removed': + notifications.notifyUserInThisInstance(id, 'userData', { id, type: clientAction }); + break; + } + }); } } diff --git a/server/services/meteor/service.ts b/server/services/meteor/service.ts index fdb9bb1161afd..cef7ecfa05f8b 100644 --- a/server/services/meteor/service.ts +++ b/server/services/meteor/service.ts @@ -1,6 +1,8 @@ import { Meteor } from 'meteor/meteor'; +import { Promise } from 'meteor/promise'; import { ServiceConfiguration } from 'meteor/service-configuration'; import { UserPresenceMonitor } from 'meteor/konecty:user-presence'; +import { MongoInternals } from 'meteor/mongo'; import { ServiceClass } from '../../sdk/types/ServiceClass'; import { IMeteor, AutoUpdateRecord } from '../../sdk/types/IMeteor'; @@ -12,6 +14,9 @@ import { setValue, updateValue } from '../../../app/settings/server/raw'; import { IRoutingManagerConfig } from '../../../definition/IRoutingManagerConfig'; import { RoutingManager } from '../../../app/livechat/server/lib/RoutingManager'; import { minimongoChangeMap } from '../listeners/notification'; +import loginServiceConfiguration from '../../../app/models/server/models/LoginServiceConfiguration'; +import { onlineAgents, monitorAgents } from '../../../app/livechat/server/lib/stream/agentStatus'; +import { IUser } from '../../../definition/IUser'; const autoUpdateRecords = new Map(); @@ -32,6 +37,95 @@ Meteor.server.publish_handlers.meteor_autoupdate_clientVersions.call({ }, }); +type Callbacks = { + added(id: string, record: object): void; + changed(id: string, record: object): void; + removed(id: string): void; +} + +let processOnChange: (diff: Record, id: string) => void; +// eslint-disable-next-line no-undef +const disableOplog = Package['disable-oplog']; + +if (disableOplog) { + // Stores the callbacks for the disconnection reactivity bellow + const userCallbacks = new Map(); + const serviceConfigCallbacks = new Set(); + + // Overrides the native observe changes to prevent database polling and stores the callbacks + // for the users' tokens to re-implement the reactivity based on our database listeners + const { mongo } = MongoInternals.defaultRemoteCollectionDriver(); + MongoInternals.Connection.prototype._observeChanges = function({ collectionName, selector, options = {} }: {collectionName: string; selector: Record; options?: {fields?: Record}}, _ordered: boolean, callbacks: Callbacks): any { + // console.error('Connection.Collection.prototype._observeChanges', collectionName, selector, options); + let cbs: Set<{hashedToken: string; callbacks: Callbacks}>; + let data: {hashedToken: string; callbacks: Callbacks}; + if (callbacks?.added) { + const records = Promise.await(mongo.rawCollection(collectionName).find(selector, { projection: options.fields }).toArray()); + for (const { _id, ...fields } of records) { + callbacks.added(_id, fields); + } + + if (collectionName === 'users' && selector['services.resume.loginTokens.hashedToken']) { + cbs = userCallbacks.get(selector._id) || new Set(); + data = { + hashedToken: selector['services.resume.loginTokens.hashedToken'], + callbacks, + }; + + cbs.add(data); + userCallbacks.set(selector._id, cbs); + } + } + + if (collectionName === 'meteor_accounts_loginServiceConfiguration') { + serviceConfigCallbacks.add(callbacks); + } + + return { + stop(): void { + if (cbs) { + cbs.delete(data); + } + serviceConfigCallbacks.delete(callbacks); + }, + }; + }; + + // Re-implement meteor's reactivity that uses observe to disconnect sessions when the token + // associated was removed + processOnChange = (diff: Record, id: string): void => { + const loginTokens: undefined | {hashedToken: string}[] = diff['services.resume.loginTokens']; + if (loginTokens) { + const tokens = loginTokens.map(({ hashedToken }) => hashedToken); + + const cbs = userCallbacks.get(id); + if (cbs) { + [...cbs].filter(({ hashedToken }) => !tokens.includes(hashedToken)).forEach((item) => { + item.callbacks.removed(id); + cbs.delete(item); + }); + } + } + }; + + loginServiceConfiguration.on('change', ({ clientAction, id, data, diff }) => { + switch (clientAction) { + case 'inserted': + case 'updated': + const record = { ...data || diff }; + delete record.secret; + serviceConfigCallbacks.forEach((callbacks) => { + callbacks[clientAction === 'inserted' ? 'added' : 'changed']?.(id, record); + }); + break; + case 'removed': + serviceConfigCallbacks.forEach((callbacks) => { + callbacks.removed?.(id); + }); + } + }); +} + export class MeteorService extends ServiceClass implements IMeteor { protected name = 'meteor'; @@ -62,6 +156,47 @@ export class MeteorService extends ServiceClass implements IMeteor { UserPresenceMonitor.processUserSession(userSession, minimongoChangeMap[clientAction]); }); + + if (disableOplog) { + this.onEvent('watch.users', ({ clientAction, id, diff }) => { + if (clientAction === 'updated' && diff) { + processOnChange(diff, id); + } + }); + } + + this.onEvent('watch.users', async ({ clientAction, id, diff }) => { + if (!monitorAgents) { + return; + } + + if (clientAction !== 'removed' && diff && !diff.status && !diff.statusLivechat) { + return; + } + + switch (clientAction) { + case 'updated': + case 'inserted': + const agent: IUser | undefined = await Users.findOneAgentById(id, { + projection: { + status: 1, + statusLivechat: 1, + }, + }); + const serviceOnline = agent && agent.status !== 'offline' && agent.statusLivechat === 'available'; + + if (serviceOnline) { + return onlineAgents.add(id); + } + + onlineAgents.remove(id); + + break; + case 'removed': + onlineAgents.remove(id); + break; + } + }); } async getLastAutoUpdateClientVersions(): Promise { diff --git a/typings.d.ts b/typings.d.ts index d868b0a87e870..a1d2a34ae8668 100644 --- a/typings.d.ts +++ b/typings.d.ts @@ -11,3 +11,7 @@ declare module 'meteor/konecty:user-presence' { function processUserSession(userSession: any, event: string): void; } } + +declare const Package: { + 'disable-oplog': object; +}; From 78bd9d2ec0158ddac90a2c54cfbb655b8c95df68 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Mon, 12 Oct 2020 11:02:17 -0300 Subject: [PATCH 148/198] Move search watchers to a module --- app/models/server/models/Users.js | 2 +- app/search/server/events/events.js | 41 ++----------------- app/search/server/index.js | 1 + app/search/server/search.internalService.ts | 45 +++++++++++++++++++++ ee/server/broker.ts | 6 +++ server/sdk/lib/Api.ts | 12 +++++- server/sdk/lib/Events.ts | 4 -- server/sdk/lib/LocalBroker.ts | 20 +++++++++ server/sdk/types/IBroker.ts | 1 + 9 files changed, 87 insertions(+), 45 deletions(-) create mode 100644 app/search/server/search.internalService.ts diff --git a/app/models/server/models/Users.js b/app/models/server/models/Users.js index 3507585943b85..85f189884d962 100644 --- a/app/models/server/models/Users.js +++ b/app/models/server/models/Users.js @@ -607,7 +607,7 @@ export class Users extends Base { return this.findOne(query, options); } - findOneById(userId, options) { + findOneById(userId, options = {}) { const query = { _id: userId }; return this.findOne(query, options); diff --git a/app/search/server/events/events.js b/app/search/server/events/events.js index 41a1e217b45c6..cbfc53a0d4c5d 100644 --- a/app/search/server/events/events.js +++ b/app/search/server/events/events.js @@ -2,7 +2,6 @@ import _ from 'underscore'; import { settings } from '../../../settings/server'; import { callbacks } from '../../../callbacks/server'; -import { Users, Rooms } from '../../../models/server'; import { searchProviderService } from '../service/providerService'; import SearchLogger from '../logger/logger'; @@ -19,61 +18,27 @@ class EventService { } } -const eventService = new EventService(); +export const searchEventService = new EventService(); /** * Listen to message changes via Hooks */ function afterSaveMessage(m) { - eventService.promoteEvent('message.save', m._id, m); + searchEventService.promoteEvent('message.save', m._id, m); return m; } function afterDeleteMessage(m) { - eventService.promoteEvent('message.delete', m._id); + searchEventService.promoteEvent('message.delete', m._id); return m; } -/** - * Listen to user and room changes via cursor - */ -function onUsersChange({ clientAction, id, data }) { - switch (clientAction) { - case 'updated': - case 'inserted': - const user = data ?? Users.findOneById(id); - eventService.promoteEvent('user.save', id, user); - break; - - case 'removed': - eventService.promoteEvent('user.delete', id); - break; - } -} - -function onRoomsChange({ clientAction, id, data }) { - switch (clientAction) { - case 'updated': - case 'inserted': - const room = data ?? Rooms.findOneById(id); - eventService.promoteEvent('room.save', id, room); - break; - - case 'removed': - eventService.promoteEvent('room.delete', id); - break; - } -} settings.get('Search.Provider', _.debounce(() => { if (searchProviderService.activeProvider?.on) { - Users.on('change', onUsersChange); - Rooms.on('change', onRoomsChange); callbacks.add('afterSaveMessage', afterSaveMessage, callbacks.priority.MEDIUM, 'search-events'); callbacks.add('afterDeleteMessage', afterDeleteMessage, callbacks.priority.MEDIUM, 'search-events-delete'); } else { - Users.removeListener('change', onUsersChange); - Rooms.removeListener('change', onRoomsChange); callbacks.remove('afterSaveMessage', 'search-events'); callbacks.remove('afterDeleteMessage', 'search-events-delete'); } diff --git a/app/search/server/index.js b/app/search/server/index.js index e0fd6273a1832..ae4aa30fa6127 100644 --- a/app/search/server/index.js +++ b/app/search/server/index.js @@ -3,6 +3,7 @@ import { searchProviderService } from './service/providerService.js'; import './service/validationService.js'; import './events/events.js'; import './provider/defaultProvider.js'; +import './search.internalService'; export { diff --git a/app/search/server/search.internalService.ts b/app/search/server/search.internalService.ts new file mode 100644 index 0000000000000..bc247103dc9c2 --- /dev/null +++ b/app/search/server/search.internalService.ts @@ -0,0 +1,45 @@ +import _ from 'underscore'; + +import { Users } from '../../models/server'; +import { settings } from '../../settings/server'; +import { searchProviderService } from './service/providerService'; +import { ServiceClass } from '../../../server/sdk/types/ServiceClass'; +import { api } from '../../../server/sdk/api'; +import { searchEventService } from './events/events'; + +class Search extends ServiceClass { + protected name = 'search'; + + constructor() { + super(); + + this.onEvent('watch.users', async ({ clientAction, data, id }) => { + if (clientAction === 'removed') { + searchEventService.promoteEvent('user.delete', id, undefined); + return; + } + + const user = data ?? Users.findOneById(id); + searchEventService.promoteEvent('user.save', id, user); + }); + + this.onEvent('watch.rooms', async ({ clientAction, room }) => { + if (clientAction === 'removed') { + searchEventService.promoteEvent('room.delete', room._id, undefined); + return; + } + + searchEventService.promoteEvent('room.save', room._id, room); + }); + } +} + +const service = new Search(); + +settings.get('Search.Provider', _.debounce(() => { + if (searchProviderService.activeProvider?.on) { + api.registerService(service); + } else { + api.destroyService(service); + } +}, 1000)); diff --git a/ee/server/broker.ts b/ee/server/broker.ts index b79605a6dafac..5202d6653d3ff 100644 --- a/ee/server/broker.ts +++ b/ee/server/broker.ts @@ -80,6 +80,12 @@ class NetworkBroker implements IBroker { return this.broker.call(method, data); } + destroyService(instance: ServiceClass): void { + this.localBroker.destroyService(instance); + + this.broker.destroyService(instance.getName()); + } + createService(instance: ServiceClass): void { this.localBroker.createService(instance); diff --git a/server/sdk/lib/Api.ts b/server/sdk/lib/Api.ts index 1b869a92bd9dd..da5b92d2f8150 100644 --- a/server/sdk/lib/Api.ts +++ b/server/sdk/lib/Api.ts @@ -4,7 +4,7 @@ import { ServiceClass } from '../types/ServiceClass'; import { EventSignatures } from './Events'; export class Api { - private services: ServiceClass[] = []; + private services = new Set(); private broker: IBroker; @@ -15,8 +15,16 @@ export class Api { this.services.forEach((service) => this.broker.createService(service)); } + destroyService(instance: ServiceClass): void { + this.services.delete(instance); + + if (this.broker) { + this.broker.destroyService(instance); + } + } + registerService(instance: ServiceClass): void { - this.services.push(instance); + this.services.add(instance); if (this.broker) { this.broker.createService(instance); diff --git a/server/sdk/lib/Events.ts b/server/sdk/lib/Events.ts index 49866261e34be..2118b0212e1e4 100644 --- a/server/sdk/lib/Events.ts +++ b/server/sdk/lib/Events.ts @@ -1,5 +1,3 @@ -import { MessagePack } from 'msgpack5'; - import { IInquiry } from '../../../definition/IInquiry'; import { IMessage } from '../../../definition/IMessage'; import { IRole } from '../../../definition/IRole'; @@ -12,8 +10,6 @@ import { IEmoji } from '../../../definition/IEmoji'; import { IUserStatus } from '../../../definition/IUserStatus'; import { IUserSession } from '../../../definition/IUserSession'; -export type BufferList = ReturnType; - export type EventSignatures = { 'emoji.deleteCustom'(emoji: IEmoji): void; 'emoji.updateCustom'(emoji: IEmoji): void; diff --git a/server/sdk/lib/LocalBroker.ts b/server/sdk/lib/LocalBroker.ts index e6294f1534de5..c70a57c9808af 100644 --- a/server/sdk/lib/LocalBroker.ts +++ b/server/sdk/lib/LocalBroker.ts @@ -23,6 +23,26 @@ export class LocalBroker implements IBroker { return this.call(method, data); } + destroyService(instance: ServiceClass): void { + const namespace = instance.getName(); + + for (const [event, fn] of Object.entries(instance.getEvents())) { + const fns = this.events.get(event); + if (fns) { + fns.delete(fn); + } + } + + const methods = instance.constructor?.name === 'Object' ? Object.getOwnPropertyNames(instance) : Object.getOwnPropertyNames(Object.getPrototypeOf(instance)); + for (const method of methods) { + if (method === 'constructor') { + continue; + } + + this.methods.delete(`${ namespace }.${ method }`); + } + } + createService(instance: ServiceClass): void { const namespace = instance.getName(); diff --git a/server/sdk/types/IBroker.ts b/server/sdk/types/IBroker.ts index 383edb0432238..4f4d21f7c724c 100644 --- a/server/sdk/types/IBroker.ts +++ b/server/sdk/types/IBroker.ts @@ -21,6 +21,7 @@ export interface IBrokerNode { } export interface IBroker { + destroyService(service: ServiceClass): void; createService(service: ServiceClass): void; call(method: string, data: any): Promise; waitAndCall(method: string, data: any): Promise; From 90ed53fafdcc1504e8450231a6db30f8da4cfd9b Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Mon, 12 Oct 2020 11:31:41 -0300 Subject: [PATCH 149/198] Convert login service configuration to module --- .../server/raw/LoginServiceConfiguration.ts | 4 +++ app/models/server/raw/index.ts | 5 +++ definition/ILoginServiceConfiguration.ts | 6 ++++ ee/server/services/DDPStreamer/DDPStreamer.ts | 15 +++++++-- ee/server/services/StreamHub/StreamHub.ts | 7 ++-- .../watchLoginServiceConfiguration.ts | 28 ---------------- server/modules/watchers/watchers.module.ts | 17 ++++++++-- server/sdk/lib/Events.ts | 3 +- server/services/meteor/service.ts | 33 ++++++++----------- 9 files changed, 61 insertions(+), 57 deletions(-) create mode 100644 app/models/server/raw/LoginServiceConfiguration.ts create mode 100644 definition/ILoginServiceConfiguration.ts delete mode 100644 ee/server/services/StreamHub/watchLoginServiceConfiguration.ts diff --git a/app/models/server/raw/LoginServiceConfiguration.ts b/app/models/server/raw/LoginServiceConfiguration.ts new file mode 100644 index 0000000000000..1653b8c2b5c96 --- /dev/null +++ b/app/models/server/raw/LoginServiceConfiguration.ts @@ -0,0 +1,4 @@ +import { BaseRaw } from './BaseRaw'; +import { ILoginServiceConfiguration } from '../../../../definition/ILoginServiceConfiguration'; + +export class LoginServiceConfigurationRaw extends BaseRaw {} diff --git a/app/models/server/raw/index.ts b/app/models/server/raw/index.ts index 293d69ce0e100..0d098ee3ffc93 100644 --- a/app/models/server/raw/index.ts +++ b/app/models/server/raw/index.ts @@ -54,6 +54,8 @@ import UsersSessionsModel from '../models/UsersSessions'; import { ServerEventsRaw } from './ServerEvents'; import { trash } from '../models/_BaseDb'; import { initWatchers } from '../../../../server/modules/watchers/watchers.module'; +import LoginServiceConfigurationModel from '../models/LoginServiceConfiguration'; +import { LoginServiceConfigurationRaw } from './LoginServiceConfiguration'; const trashCollection = trash.rawCollection(); @@ -84,6 +86,7 @@ export const LivechatBusinessHours = new LivechatBusinessHoursRaw(LivechatBusine export const ServerEvents = new ServerEventsRaw(ServerEventModel.model.rawCollection(), trashCollection); export const Roles = new RolesRaw(RolesModel.model.rawCollection(), trashCollection, { Users, Subscriptions }); export const UsersSessions = new UsersSessionsRaw(UsersSessionsModel.model.rawCollection(), trashCollection); +export const LoginServiceConfiguration = new LoginServiceConfigurationRaw(LoginServiceConfigurationModel.model.rawCollection(), trashCollection); const map = { [Messages.col.collectionName]: MessagesModel, @@ -95,6 +98,7 @@ const map = { [LivechatInquiry.col.collectionName]: LivechatInquiryModel, [UsersSessions.col.collectionName]: UsersSessionsModel, [Rooms.col.collectionName]: RoomsModel, + [LoginServiceConfiguration.col.collectionName]: LoginServiceConfigurationModel, }; initWatchers({ @@ -107,6 +111,7 @@ initWatchers({ Permissions, Roles, Rooms, + LoginServiceConfiguration, }, (model, fn) => { const meteorModel = map[model.col.collectionName]; diff --git a/definition/ILoginServiceConfiguration.ts b/definition/ILoginServiceConfiguration.ts new file mode 100644 index 0000000000000..0d082b43a324e --- /dev/null +++ b/definition/ILoginServiceConfiguration.ts @@ -0,0 +1,6 @@ +export interface ILoginServiceConfiguration { + _id: string; + service: string; + clientId: string; + secret: string; +} diff --git a/ee/server/services/DDPStreamer/DDPStreamer.ts b/ee/server/services/DDPStreamer/DDPStreamer.ts index e6fcd4ed8e5ed..2c481249d19c5 100644 --- a/ee/server/services/DDPStreamer/DDPStreamer.ts +++ b/ee/server/services/DDPStreamer/DDPStreamer.ts @@ -190,8 +190,19 @@ export class DDPStreamer extends ServiceClass { // }, // role(payload) { - this.onEvent('meteor.loginServiceConfiguration', ({ action, record }): void => { - events.emit('meteor.loginServiceConfiguration', action, record); + this.onEvent('watch.loginServiceConfiguration', ({ clientAction, id, data }) => { + if (clientAction === 'removed') { + events.emit('meteor.loginServiceConfiguration', 'removed', { + _id: id, + }); + return; + } + + events.emit( + 'meteor.loginServiceConfiguration', + clientAction === 'inserted' ? 'added' : 'changed', + data, + ); }); this.onEvent('meteor.autoUpdateClientVersionChanged', ({ record }): void => { diff --git a/ee/server/services/StreamHub/StreamHub.ts b/ee/server/services/StreamHub/StreamHub.ts index 9273c8e3eb943..3b7ce6c82f883 100755 --- a/ee/server/services/StreamHub/StreamHub.ts +++ b/ee/server/services/StreamHub/StreamHub.ts @@ -1,7 +1,6 @@ import { watchUsers } from './watchUsers'; import { getConnection } from '../mongo'; import { ServiceClass, IServiceClass } from '../../../../server/sdk/types/ServiceClass'; -import { watchLoginServiceConfiguration } from './watchLoginServiceConfiguration'; import { initWatchers } from '../../../../server/modules/watchers/watchers.module'; import { MessagesRaw } from '../../../../app/models/server/raw/Messages'; import { UsersRaw } from '../../../../app/models/server/raw/Users'; @@ -11,6 +10,7 @@ import { RolesRaw } from '../../../../app/models/server/raw/Roles'; import { LivechatInquiryRaw } from '../../../../app/models/server/raw/LivechatInquiry'; import { UsersSessionsRaw } from '../../../../app/models/server/raw/UsersSessions'; import { RoomsRaw } from '../../../../app/models/server/raw/Rooms'; +import { LoginServiceConfigurationRaw } from '../../../../app/models/server/raw/LoginServiceConfiguration'; export class StreamHub extends ServiceClass implements IServiceClass { protected name = 'hub'; @@ -19,7 +19,6 @@ export class StreamHub extends ServiceClass implements IServiceClass { const db = await getConnection(); const Trash = db.collection('rocketchat_trash'); - const loginServiceConfiguration = db.collection('meteor_accounts_loginServiceConfiguration'); const UsersCol = db.collection('users'); const SettingsCol = db.collection('rocketchat_settings'); @@ -33,6 +32,7 @@ export class StreamHub extends ServiceClass implements IServiceClass { const Messages = new MessagesRaw(db.collection('rocketchat_message'), Trash); const Permissions = new MessagesRaw(db.collection('rocketchat_permissions'), Trash); const Roles = new RolesRaw(db.collection('rocketchat_roles'), Trash, { Users, Subscriptions }); + const LoginServiceConfiguration = new LoginServiceConfigurationRaw(db.collection('meteor_accounts_loginServiceConfiguration'), Trash); const models = { Messages, @@ -44,6 +44,7 @@ export class StreamHub extends ServiceClass implements IServiceClass { Settings, Roles, Rooms, + LoginServiceConfiguration, }; initWatchers(models, (model, fn) => { @@ -124,7 +125,5 @@ export class StreamHub extends ServiceClass implements IServiceClass { // }, // }, // }], { fullDocument: 'updateLookup' }).on('change', watchSettings); - - loginServiceConfiguration.watch([{ $project: { 'fullDocument.secret': 0 } }], { fullDocument: 'updateLookup' }).on('change', watchLoginServiceConfiguration); } } diff --git a/ee/server/services/StreamHub/watchLoginServiceConfiguration.ts b/ee/server/services/StreamHub/watchLoginServiceConfiguration.ts deleted file mode 100644 index eddca306c30df..0000000000000 --- a/ee/server/services/StreamHub/watchLoginServiceConfiguration.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { ChangeEvent } from 'mongodb'; - -import { api } from '../../../../server/sdk/api'; -import { IRole } from '../../../../definition/IRole'; - -export async function watchLoginServiceConfiguration(event: ChangeEvent): Promise { - switch (event.operationType) { - case 'insert': - case 'update': - if (!event.fullDocument) { - return; - } - - api.broadcast('meteor.loginServiceConfiguration', { - action: event.operationType === 'insert' ? 'added' : 'changed', - record: event.fullDocument, - }); - break; - case 'delete': - api.broadcast('meteor.loginServiceConfiguration', { - action: 'removed', - record: { - _id: event.documentKey._id, - }, - }); - break; - } -} diff --git a/server/modules/watchers/watchers.module.ts b/server/modules/watchers/watchers.module.ts index a7522acfa4ffe..bb28f8a747400 100644 --- a/server/modules/watchers/watchers.module.ts +++ b/server/modules/watchers/watchers.module.ts @@ -1,5 +1,3 @@ -// import { Authorization } from '../../sdk'; -// import { RoomsRaw } from '../../../app/models/server/raw/Rooms'; import { SubscriptionsRaw } from '../../../app/models/server/raw/Subscriptions'; import { UsersRaw } from '../../../app/models/server/raw/Users'; import { SettingsRaw } from '../../../app/models/server/raw/Settings'; @@ -22,9 +20,10 @@ import { UsersSessionsRaw } from '../../../app/models/server/raw/UsersSessions'; import { IUserSession } from '../../../definition/IUserSession'; import { subscriptionFields, roomFields } from './publishFields'; import { IUser } from '../../../definition/IUser'; +import { LoginServiceConfigurationRaw } from '../../../app/models/server/raw/LoginServiceConfiguration'; +import { ILoginServiceConfiguration } from '../../../definition/ILoginServiceConfiguration'; interface IModelsParam { - // Rooms: RoomsRaw; Subscriptions: SubscriptionsRaw; Permissions: PermissionsRaw; Users: UsersRaw; @@ -34,6 +33,7 @@ interface IModelsParam { UsersSessions: UsersSessionsRaw; Roles: RolesRaw; Rooms: RoomsRaw; + LoginServiceConfiguration: LoginServiceConfigurationRaw; } interface IChange { @@ -56,6 +56,7 @@ export function initWatchers({ Permissions, LivechatInquiry, Rooms, + LoginServiceConfiguration, }: IModelsParam, watch: Watcher): void { watch(Messages, async ({ clientAction, id, data }) => { switch (clientAction) { @@ -242,4 +243,14 @@ export function initWatchers({ watch(Users, ({ clientAction, id, data, diff }) => { api.broadcast('watch.users', { clientAction, data, diff, id }); }); + + watch(LoginServiceConfiguration, async ({ clientAction, id }) => { + const data = await InstanceStatus.findOneById(id, { projection: { secret: 0 } }); + if (!data) { + return; + } + + api.broadcast('watch.loginServiceConfiguration', { clientAction, data, id }); + }); + }); } diff --git a/server/sdk/lib/Events.ts b/server/sdk/lib/Events.ts index 2118b0212e1e4..54eb71e964f72 100644 --- a/server/sdk/lib/Events.ts +++ b/server/sdk/lib/Events.ts @@ -9,6 +9,7 @@ import { AutoUpdateRecord } from '../types/IMeteor'; import { IEmoji } from '../../../definition/IEmoji'; import { IUserStatus } from '../../../definition/IUserStatus'; import { IUserSession } from '../../../definition/IUserSession'; +import { ILoginServiceConfiguration } from '../../../definition/ILoginServiceConfiguration'; export type EventSignatures = { 'emoji.deleteCustom'(emoji: IEmoji): void; @@ -17,7 +18,6 @@ export type EventSignatures = { 'livechat-inquiry-queue-observer'(data: { action: string; inquiry: IInquiry }): void; 'message'(data: { action: string; message: IMessage }): void; 'meteor.autoUpdateClientVersionChanged'(data: {record: AutoUpdateRecord }): void; - 'meteor.loginServiceConfiguration'(data: { action: string; record: any }): void; 'notify.ephemeralMessage'(uid: string, rid: string, message: Partial): void; 'permission.changed'(data: { clientAction: string; data: any }): void; 'room'(data: { action: string; room: Partial }): void; @@ -43,4 +43,5 @@ export type EventSignatures = { 'watch.inquiries'(data: { clientAction: string; inquiry: IInquiry; diff?: Record }): void; 'watch.settings'(data: { clientAction: string; setting: ISetting }): void; 'watch.users'(data: { clientAction: string; data?: Partial; diff?: Record; id: string }): void; + 'watch.loginServiceConfiguration'(data: { clientAction: string; data: Partial; id: string }): void; } diff --git a/server/services/meteor/service.ts b/server/services/meteor/service.ts index cef7ecfa05f8b..5cbb1b5081162 100644 --- a/server/services/meteor/service.ts +++ b/server/services/meteor/service.ts @@ -14,7 +14,6 @@ import { setValue, updateValue } from '../../../app/settings/server/raw'; import { IRoutingManagerConfig } from '../../../definition/IRoutingManagerConfig'; import { RoutingManager } from '../../../app/livechat/server/lib/RoutingManager'; import { minimongoChangeMap } from '../listeners/notification'; -import loginServiceConfiguration from '../../../app/models/server/models/LoginServiceConfiguration'; import { onlineAgents, monitorAgents } from '../../../app/livechat/server/lib/stream/agentStatus'; import { IUser } from '../../../definition/IUser'; @@ -46,11 +45,11 @@ type Callbacks = { let processOnChange: (diff: Record, id: string) => void; // eslint-disable-next-line no-undef const disableOplog = Package['disable-oplog']; +const serviceConfigCallbacks = new Set(); if (disableOplog) { // Stores the callbacks for the disconnection reactivity bellow const userCallbacks = new Map(); - const serviceConfigCallbacks = new Set(); // Overrides the native observe changes to prevent database polling and stores the callbacks // for the users' tokens to re-implement the reactivity based on our database listeners @@ -107,23 +106,6 @@ if (disableOplog) { } } }; - - loginServiceConfiguration.on('change', ({ clientAction, id, data, diff }) => { - switch (clientAction) { - case 'inserted': - case 'updated': - const record = { ...data || diff }; - delete record.secret; - serviceConfigCallbacks.forEach((callbacks) => { - callbacks[clientAction === 'inserted' ? 'added' : 'changed']?.(id, record); - }); - break; - case 'removed': - serviceConfigCallbacks.forEach((callbacks) => { - callbacks.removed?.(id); - }); - } - }); } export class MeteorService extends ServiceClass implements IMeteor { @@ -163,6 +145,19 @@ export class MeteorService extends ServiceClass implements IMeteor { processOnChange(diff, id); } }); + + this.onEvent('watch.loginServiceConfiguration', ({ clientAction, id, data }) => { + if (clientAction === 'removed') { + serviceConfigCallbacks.forEach((callbacks) => { + callbacks.removed?.(id); + }); + return; + } + + serviceConfigCallbacks.forEach((callbacks) => { + callbacks[clientAction === 'inserted' ? 'added' : 'changed']?.(id, data); + }); + }); } this.onEvent('watch.users', async ({ clientAction, id, diff }) => { From eae19224f4389e8089384837453a132200134b55 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Mon, 12 Oct 2020 13:35:34 -0300 Subject: [PATCH 150/198] Move Instance Status to module --- app/models/server/raw/InstanceStatus.ts | 4 ++++ app/models/server/raw/index.ts | 5 +++++ definition/IInstanceStatus.ts | 6 ++++++ ee/server/services/StreamHub/StreamHub.ts | 3 +++ server/modules/watchers/watchers.module.ts | 7 +++++++ server/sdk/lib/Events.ts | 2 ++ server/services/meteor/service.ts | 17 ++++++++++++++++- server/startup/presence.js | 8 -------- server/stream/streamBroadcast.js | 17 +++-------------- typings.d.ts | 4 ++++ 10 files changed, 50 insertions(+), 23 deletions(-) create mode 100644 app/models/server/raw/InstanceStatus.ts create mode 100644 definition/IInstanceStatus.ts diff --git a/app/models/server/raw/InstanceStatus.ts b/app/models/server/raw/InstanceStatus.ts new file mode 100644 index 0000000000000..775c17becbb15 --- /dev/null +++ b/app/models/server/raw/InstanceStatus.ts @@ -0,0 +1,4 @@ +import { BaseRaw } from './BaseRaw'; +import { IInstanceStatus } from '../../../../definition/IInstanceStatus'; + +export class InstanceStatusRaw extends BaseRaw {} diff --git a/app/models/server/raw/index.ts b/app/models/server/raw/index.ts index 0d098ee3ffc93..3a8bd087ef1f2 100644 --- a/app/models/server/raw/index.ts +++ b/app/models/server/raw/index.ts @@ -56,6 +56,8 @@ import { trash } from '../models/_BaseDb'; import { initWatchers } from '../../../../server/modules/watchers/watchers.module'; import LoginServiceConfigurationModel from '../models/LoginServiceConfiguration'; import { LoginServiceConfigurationRaw } from './LoginServiceConfiguration'; +import { InstanceStatusRaw } from './InstanceStatus'; +import InstanceStatusModel from '../models/InstanceStatus'; const trashCollection = trash.rawCollection(); @@ -87,6 +89,7 @@ export const ServerEvents = new ServerEventsRaw(ServerEventModel.model.rawCollec export const Roles = new RolesRaw(RolesModel.model.rawCollection(), trashCollection, { Users, Subscriptions }); export const UsersSessions = new UsersSessionsRaw(UsersSessionsModel.model.rawCollection(), trashCollection); export const LoginServiceConfiguration = new LoginServiceConfigurationRaw(LoginServiceConfigurationModel.model.rawCollection(), trashCollection); +export const InstanceStatus = new InstanceStatusRaw(InstanceStatusModel.model.rawCollection(), trashCollection); const map = { [Messages.col.collectionName]: MessagesModel, @@ -99,6 +102,7 @@ const map = { [UsersSessions.col.collectionName]: UsersSessionsModel, [Rooms.col.collectionName]: RoomsModel, [LoginServiceConfiguration.col.collectionName]: LoginServiceConfigurationModel, + [InstanceStatus.col.collectionName]: InstanceStatusModel, }; initWatchers({ @@ -112,6 +116,7 @@ initWatchers({ Roles, Rooms, LoginServiceConfiguration, + InstanceStatus, }, (model, fn) => { const meteorModel = map[model.col.collectionName]; diff --git a/definition/IInstanceStatus.ts b/definition/IInstanceStatus.ts new file mode 100644 index 0000000000000..c4d6cd2f4713a --- /dev/null +++ b/definition/IInstanceStatus.ts @@ -0,0 +1,6 @@ +export interface IInstanceStatus { + _id: string; + extraInformation?: { + port?: number; + }; +} diff --git a/ee/server/services/StreamHub/StreamHub.ts b/ee/server/services/StreamHub/StreamHub.ts index 3b7ce6c82f883..ba8eec66c39b1 100755 --- a/ee/server/services/StreamHub/StreamHub.ts +++ b/ee/server/services/StreamHub/StreamHub.ts @@ -11,6 +11,7 @@ import { LivechatInquiryRaw } from '../../../../app/models/server/raw/LivechatIn import { UsersSessionsRaw } from '../../../../app/models/server/raw/UsersSessions'; import { RoomsRaw } from '../../../../app/models/server/raw/Rooms'; import { LoginServiceConfigurationRaw } from '../../../../app/models/server/raw/LoginServiceConfiguration'; +import { InstanceStatusRaw } from '../../../../app/models/server/raw/InstanceStatus'; export class StreamHub extends ServiceClass implements IServiceClass { protected name = 'hub'; @@ -33,6 +34,7 @@ export class StreamHub extends ServiceClass implements IServiceClass { const Permissions = new MessagesRaw(db.collection('rocketchat_permissions'), Trash); const Roles = new RolesRaw(db.collection('rocketchat_roles'), Trash, { Users, Subscriptions }); const LoginServiceConfiguration = new LoginServiceConfigurationRaw(db.collection('meteor_accounts_loginServiceConfiguration'), Trash); + const InstanceStatus = new InstanceStatusRaw(db.collection('instances'), Trash); const models = { Messages, @@ -45,6 +47,7 @@ export class StreamHub extends ServiceClass implements IServiceClass { Roles, Rooms, LoginServiceConfiguration, + InstanceStatus, }; initWatchers(models, (model, fn) => { diff --git a/server/modules/watchers/watchers.module.ts b/server/modules/watchers/watchers.module.ts index bb28f8a747400..35347056d99da 100644 --- a/server/modules/watchers/watchers.module.ts +++ b/server/modules/watchers/watchers.module.ts @@ -22,6 +22,8 @@ import { subscriptionFields, roomFields } from './publishFields'; import { IUser } from '../../../definition/IUser'; import { LoginServiceConfigurationRaw } from '../../../app/models/server/raw/LoginServiceConfiguration'; import { ILoginServiceConfiguration } from '../../../definition/ILoginServiceConfiguration'; +import { IInstanceStatus } from '../../../definition/IInstanceStatus'; +import { InstanceStatusRaw } from '../../../app/models/server/raw/InstanceStatus'; interface IModelsParam { Subscriptions: SubscriptionsRaw; @@ -34,6 +36,7 @@ interface IModelsParam { Roles: RolesRaw; Rooms: RoomsRaw; LoginServiceConfiguration: LoginServiceConfigurationRaw; + InstanceStatus: InstanceStatusRaw; } interface IChange { @@ -57,6 +60,7 @@ export function initWatchers({ LivechatInquiry, Rooms, LoginServiceConfiguration, + InstanceStatus, }: IModelsParam, watch: Watcher): void { watch(Messages, async ({ clientAction, id, data }) => { switch (clientAction) { @@ -252,5 +256,8 @@ export function initWatchers({ api.broadcast('watch.loginServiceConfiguration', { clientAction, data, id }); }); + + watch(InstanceStatus, ({ clientAction, id, data, diff }) => { + api.broadcast('watch.instanceStatus', { clientAction, data, diff, id }); }); } diff --git a/server/sdk/lib/Events.ts b/server/sdk/lib/Events.ts index 54eb71e964f72..a39e420ce2f02 100644 --- a/server/sdk/lib/Events.ts +++ b/server/sdk/lib/Events.ts @@ -10,6 +10,7 @@ import { IEmoji } from '../../../definition/IEmoji'; import { IUserStatus } from '../../../definition/IUserStatus'; import { IUserSession } from '../../../definition/IUserSession'; import { ILoginServiceConfiguration } from '../../../definition/ILoginServiceConfiguration'; +import { IInstanceStatus } from '../../../definition/IInstanceStatus'; export type EventSignatures = { 'emoji.deleteCustom'(emoji: IEmoji): void; @@ -44,4 +45,5 @@ export type EventSignatures = { 'watch.settings'(data: { clientAction: string; setting: ISetting }): void; 'watch.users'(data: { clientAction: string; data?: Partial; diff?: Record; id: string }): void; 'watch.loginServiceConfiguration'(data: { clientAction: string; data: Partial; id: string }): void; + 'watch.instanceStatus'(data: { clientAction: string; data?: Partial; diff?: Record; id: string }): void; } diff --git a/server/services/meteor/service.ts b/server/services/meteor/service.ts index 5cbb1b5081162..b3a42e96ee4ef 100644 --- a/server/services/meteor/service.ts +++ b/server/services/meteor/service.ts @@ -1,7 +1,7 @@ import { Meteor } from 'meteor/meteor'; import { Promise } from 'meteor/promise'; import { ServiceConfiguration } from 'meteor/service-configuration'; -import { UserPresenceMonitor } from 'meteor/konecty:user-presence'; +import { UserPresenceMonitor, UserPresence } from 'meteor/konecty:user-presence'; import { MongoInternals } from 'meteor/mongo'; import { ServiceClass } from '../../sdk/types/ServiceClass'; @@ -16,6 +16,7 @@ import { RoutingManager } from '../../../app/livechat/server/lib/RoutingManager' import { minimongoChangeMap } from '../listeners/notification'; import { onlineAgents, monitorAgents } from '../../../app/livechat/server/lib/stream/agentStatus'; import { IUser } from '../../../definition/IUser'; +import { matrixBroadCastActions } from '../../stream/streamBroadcast'; const autoUpdateRecords = new Map(); @@ -139,6 +140,20 @@ export class MeteorService extends ServiceClass implements IMeteor { UserPresenceMonitor.processUserSession(userSession, minimongoChangeMap[clientAction]); }); + this.onEvent('watch.instanceStatus', async ({ clientAction, id, data }): Promise => { + if (clientAction === 'removed') { + UserPresence.removeConnectionsByInstanceId(id); + matrixBroadCastActions?.removed?.(id); + return; + } + + if (clientAction === 'inserted') { + if (data?.extraInformation?.port) { + matrixBroadCastActions?.added?.(data); + } + } + }); + if (disableOplog) { this.onEvent('watch.users', ({ clientAction, id, diff }) => { if (clientAction === 'updated' && diff) { diff --git a/server/startup/presence.js b/server/startup/presence.js index 6d2d5e475ace8..193dfc3bb1176 100644 --- a/server/startup/presence.js +++ b/server/startup/presence.js @@ -37,13 +37,5 @@ Meteor.startup(function() { }, }; UsersSessionsModel.update({}, update, { multi: true }); - - InstanceStatusModel.on('change', ({ clientAction, id }) => { - switch (clientAction) { - case 'removed': - UserPresence.removeConnectionsByInstanceId(id); - break; - } - }); } }); diff --git a/server/stream/streamBroadcast.js b/server/stream/streamBroadcast.js index cd66cd31fdf7a..990a907d4e38a 100644 --- a/server/stream/streamBroadcast.js +++ b/server/stream/streamBroadcast.js @@ -60,12 +60,13 @@ function authorizeConnection(instance) { const cache = new Map(); const originalSetDefaultStatus = UserPresence.setDefaultStatus; +export let matrixBroadCastActions; function startMatrixBroadcast() { if (!startMonitor) { UserPresence.setDefaultStatus = originalSetDefaultStatus; } - const actions = { + matrixBroadCastActions = { added(record) { cache.set(record._id, record); @@ -143,19 +144,7 @@ function startMatrixBroadcast() { }, }; - InstanceStatusModel.find(query, options).fetch().forEach(actions.added); - return InstanceStatusModel.on('change', ({ clientAction, id, data }) => { - switch (clientAction) { - case 'inserted': - if (data.extraInformation?.port) { - actions.added(data); - } - break; - case 'removed': - actions.removed(id); - break; - } - }); + InstanceStatusModel.find(query, options).fetch().forEach(matrixBroadCastActions.added); } Meteor.methods({ diff --git a/typings.d.ts b/typings.d.ts index a1d2a34ae8668..312f900307918 100644 --- a/typings.d.ts +++ b/typings.d.ts @@ -10,6 +10,10 @@ declare module 'meteor/konecty:user-presence' { namespace UserPresenceMonitor { function processUserSession(userSession: any, event: string): void; } + + namespace UserPresence { + function removeConnectionsByInstanceId(id: string): void; + } } declare const Package: { From 5821f32809fb4ca976201767bcdc399ff191dab7 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Mon, 12 Oct 2020 14:26:06 -0300 Subject: [PATCH 151/198] Move Integration History to module --- app/integrations/server/index.js | 1 - app/integrations/server/streamer.js | 19 ---------------- app/models/server/raw/IntegrationHistory.ts | 4 ++++ app/models/server/raw/index.ts | 5 +++++ definition/IIntegrationHistory.ts | 14 ++++++++++++ ee/server/services/StreamHub/StreamHub.ts | 6 +++-- server/modules/watchers/watchers.module.ts | 25 +++++++++++++++++++++ server/sdk/lib/Events.ts | 2 ++ server/services/listeners/notification.ts | 16 +++++++++++++ 9 files changed, 70 insertions(+), 22 deletions(-) delete mode 100644 app/integrations/server/streamer.js create mode 100644 app/models/server/raw/IntegrationHistory.ts create mode 100644 definition/IIntegrationHistory.ts diff --git a/app/integrations/server/index.js b/app/integrations/server/index.js index b022e3034fea1..4e826bf4fbd1d 100644 --- a/app/integrations/server/index.js +++ b/app/integrations/server/index.js @@ -11,7 +11,6 @@ import './methods/outgoing/deleteOutgoingIntegration'; import './methods/clearIntegrationHistory'; import './api/api'; import './lib/triggerHandler'; -import './streamer'; import './triggers'; export { mountIntegrationQueryBasedOnPermissions, mountIntegrationHistoryQueryBasedOnPermissions } from './lib/mountQueriesBasedOnPermission'; diff --git a/app/integrations/server/streamer.js b/app/integrations/server/streamer.js deleted file mode 100644 index cb72c9a0bebc4..0000000000000 --- a/app/integrations/server/streamer.js +++ /dev/null @@ -1,19 +0,0 @@ -import { IntegrationHistory } from '../../models/server'; -import notifications from '../../notifications/server/lib/Notifications'; - -IntegrationHistory.on('change', ({ clientAction, id, data, diff }) => { - switch (clientAction) { - case 'updated': { - const history = IntegrationHistory.findOneById(id, { fields: { 'integration._id': 1 } }); - if (!history && !history.integration) { - return; - } - notifications.streamIntegrationHistory.emit(history.integration._id, { id, diff, type: clientAction }); - break; - } - case 'inserted': { - notifications.streamIntegrationHistory.emit(data.integration._id, { data, type: clientAction }); - break; - } - } -}); diff --git a/app/models/server/raw/IntegrationHistory.ts b/app/models/server/raw/IntegrationHistory.ts new file mode 100644 index 0000000000000..31cd7fe9877ae --- /dev/null +++ b/app/models/server/raw/IntegrationHistory.ts @@ -0,0 +1,4 @@ +import { BaseRaw } from './BaseRaw'; +import { IIntegrationHistory } from '../../../../definition/iIntegrationHistory'; + +export class IntegrationHistoryRaw extends BaseRaw {} diff --git a/app/models/server/raw/index.ts b/app/models/server/raw/index.ts index 3a8bd087ef1f2..67eda082696f9 100644 --- a/app/models/server/raw/index.ts +++ b/app/models/server/raw/index.ts @@ -58,6 +58,8 @@ import LoginServiceConfigurationModel from '../models/LoginServiceConfiguration' import { LoginServiceConfigurationRaw } from './LoginServiceConfiguration'; import { InstanceStatusRaw } from './InstanceStatus'; import InstanceStatusModel from '../models/InstanceStatus'; +import { IntegrationHistoryRaw } from './IntegrationHistory'; +import IntegrationHistoryModel from '../models/IntegrationHistory'; const trashCollection = trash.rawCollection(); @@ -90,6 +92,7 @@ export const Roles = new RolesRaw(RolesModel.model.rawCollection(), trashCollect export const UsersSessions = new UsersSessionsRaw(UsersSessionsModel.model.rawCollection(), trashCollection); export const LoginServiceConfiguration = new LoginServiceConfigurationRaw(LoginServiceConfigurationModel.model.rawCollection(), trashCollection); export const InstanceStatus = new InstanceStatusRaw(InstanceStatusModel.model.rawCollection(), trashCollection); +export const IntegrationHistory = new IntegrationHistoryRaw(IntegrationHistoryModel.model.rawCollection(), trashCollection); const map = { [Messages.col.collectionName]: MessagesModel, @@ -103,6 +106,7 @@ const map = { [Rooms.col.collectionName]: RoomsModel, [LoginServiceConfiguration.col.collectionName]: LoginServiceConfigurationModel, [InstanceStatus.col.collectionName]: InstanceStatusModel, + [IntegrationHistory.col.collectionName]: IntegrationHistoryModel, }; initWatchers({ @@ -117,6 +121,7 @@ initWatchers({ Rooms, LoginServiceConfiguration, InstanceStatus, + IntegrationHistory, }, (model, fn) => { const meteorModel = map[model.col.collectionName]; diff --git a/definition/IIntegrationHistory.ts b/definition/IIntegrationHistory.ts new file mode 100644 index 0000000000000..2149aff521bea --- /dev/null +++ b/definition/IIntegrationHistory.ts @@ -0,0 +1,14 @@ +export interface IIntegrationHistory { + _id: string; + type: string; + step: string; + integration: { + _id: string; + }; + event: string; + _createdAt: Date; + _updatedAt: Date; + // "data" : + ranPrepareScript: boolean; + finished: boolean; +} diff --git a/ee/server/services/StreamHub/StreamHub.ts b/ee/server/services/StreamHub/StreamHub.ts index ba8eec66c39b1..ab7f50ac932a4 100755 --- a/ee/server/services/StreamHub/StreamHub.ts +++ b/ee/server/services/StreamHub/StreamHub.ts @@ -12,6 +12,7 @@ import { UsersSessionsRaw } from '../../../../app/models/server/raw/UsersSession import { RoomsRaw } from '../../../../app/models/server/raw/Rooms'; import { LoginServiceConfigurationRaw } from '../../../../app/models/server/raw/LoginServiceConfiguration'; import { InstanceStatusRaw } from '../../../../app/models/server/raw/InstanceStatus'; +import { IntegrationHistoryRaw } from '../../../../app/models/server/raw/IntegrationHistory'; export class StreamHub extends ServiceClass implements IServiceClass { protected name = 'hub'; @@ -22,10 +23,9 @@ export class StreamHub extends ServiceClass implements IServiceClass { const Trash = db.collection('rocketchat_trash'); const UsersCol = db.collection('users'); - const SettingsCol = db.collection('rocketchat_settings'); const Rooms = new RoomsRaw(db.collection('rocketchat_room'), Trash); - const Settings = new SettingsRaw(SettingsCol, Trash); + const Settings = new SettingsRaw(db.collection('rocketchat_settings'), Trash); const Users = new UsersRaw(UsersCol, Trash); const UsersSessions = new UsersSessionsRaw(db.collection('usersSessions'), Trash); const Subscriptions = new SubscriptionsRaw(db.collection('rocketchat_subscription'), Trash); @@ -35,6 +35,7 @@ export class StreamHub extends ServiceClass implements IServiceClass { const Roles = new RolesRaw(db.collection('rocketchat_roles'), Trash, { Users, Subscriptions }); const LoginServiceConfiguration = new LoginServiceConfigurationRaw(db.collection('meteor_accounts_loginServiceConfiguration'), Trash); const InstanceStatus = new InstanceStatusRaw(db.collection('instances'), Trash); + const IntegrationHistory = new IntegrationHistoryRaw(db.collection('rocketchat_integration_history'), Trash); const models = { Messages, @@ -48,6 +49,7 @@ export class StreamHub extends ServiceClass implements IServiceClass { Rooms, LoginServiceConfiguration, InstanceStatus, + IntegrationHistory, }; initWatchers(models, (model, fn) => { diff --git a/server/modules/watchers/watchers.module.ts b/server/modules/watchers/watchers.module.ts index 35347056d99da..1a524b630d19c 100644 --- a/server/modules/watchers/watchers.module.ts +++ b/server/modules/watchers/watchers.module.ts @@ -24,6 +24,8 @@ import { LoginServiceConfigurationRaw } from '../../../app/models/server/raw/Log import { ILoginServiceConfiguration } from '../../../definition/ILoginServiceConfiguration'; import { IInstanceStatus } from '../../../definition/IInstanceStatus'; import { InstanceStatusRaw } from '../../../app/models/server/raw/InstanceStatus'; +import { IntegrationHistoryRaw } from '../../../app/models/server/raw/IntegrationHistory'; +import { IIntegrationHistory } from '../../../definition/IIntegrationHistory'; interface IModelsParam { Subscriptions: SubscriptionsRaw; @@ -37,6 +39,7 @@ interface IModelsParam { Rooms: RoomsRaw; LoginServiceConfiguration: LoginServiceConfigurationRaw; InstanceStatus: InstanceStatusRaw; + IntegrationHistory: IntegrationHistoryRaw; } interface IChange { @@ -61,6 +64,7 @@ export function initWatchers({ Rooms, LoginServiceConfiguration, InstanceStatus, + IntegrationHistory, }: IModelsParam, watch: Watcher): void { watch(Messages, async ({ clientAction, id, data }) => { switch (clientAction) { @@ -260,4 +264,25 @@ export function initWatchers({ watch(InstanceStatus, ({ clientAction, id, data, diff }) => { api.broadcast('watch.instanceStatus', { clientAction, data, diff, id }); }); + + watch(IntegrationHistory, async ({ clientAction, id, data, diff }) => { + switch (clientAction) { + case 'updated': { + const history = await IntegrationHistory.findOneById(id, { projection: { 'integration._id': 1 } }); + if (!history || !history.integration) { + return; + } + data = history; + api.broadcast('watch.integrationHistory', { clientAction, data, diff, id }); + break; + } + case 'inserted': { + if (!data) { + return; + } + api.broadcast('watch.integrationHistory', { clientAction, data, diff, id }); + break; + } + } + }); } diff --git a/server/sdk/lib/Events.ts b/server/sdk/lib/Events.ts index a39e420ce2f02..6c160ac850ef5 100644 --- a/server/sdk/lib/Events.ts +++ b/server/sdk/lib/Events.ts @@ -11,6 +11,7 @@ import { IUserStatus } from '../../../definition/IUserStatus'; import { IUserSession } from '../../../definition/IUserSession'; import { ILoginServiceConfiguration } from '../../../definition/ILoginServiceConfiguration'; import { IInstanceStatus } from '../../../definition/IInstanceStatus'; +import { IIntegrationHistory } from '../../../definition/IIntegrationHistory'; export type EventSignatures = { 'emoji.deleteCustom'(emoji: IEmoji): void; @@ -46,4 +47,5 @@ export type EventSignatures = { 'watch.users'(data: { clientAction: string; data?: Partial; diff?: Record; id: string }): void; 'watch.loginServiceConfiguration'(data: { clientAction: string; data: Partial; id: string }): void; 'watch.instanceStatus'(data: { clientAction: string; data?: Partial; diff?: Record; id: string }): void; + 'watch.integrationHistory'(data: { clientAction: string; data: Partial; diff?: Record; id: string }): void; } diff --git a/server/services/listeners/notification.ts b/server/services/listeners/notification.ts index 4f0ff84e0cd0d..adbf5936b32fd 100644 --- a/server/services/listeners/notification.ts +++ b/server/services/listeners/notification.ts @@ -232,5 +232,21 @@ export class NotificationService extends ServiceClass { break; } }); + + this.onEvent('watch.integrationHistory', ({ clientAction, data, diff, id }): void => { + if (!data?.integration?._id) { + return; + } + switch (clientAction) { + case 'updated': { + notifications.streamIntegrationHistory.emit(data.integration._id, { id, diff, type: clientAction }); + break; + } + case 'inserted': { + notifications.streamIntegrationHistory.emit(data.integration._id, { data, type: clientAction }); + break; + } + } + }); } } From f7d1fce8697efe63f729d054675d6dcb0877dced Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Mon, 12 Oct 2020 14:54:35 -0300 Subject: [PATCH 152/198] Move LivechatDepartmentAgents to module --- app/livechat/server/index.js | 1 - .../server/lib/stream/departmentAgents.js | 29 ------------------- app/models/server/raw/index.ts | 2 ++ definition/ILivechatDepartmentAgents.ts | 7 +++++ ee/server/services/StreamHub/StreamHub.ts | 3 ++ server/modules/watchers/watchers.module.ts | 22 ++++++++++++++ server/sdk/lib/Events.ts | 2 ++ server/services/listeners/notification.ts | 12 ++++++++ 8 files changed, 48 insertions(+), 30 deletions(-) delete mode 100644 app/livechat/server/lib/stream/departmentAgents.js create mode 100644 definition/ILivechatDepartmentAgents.ts diff --git a/app/livechat/server/index.js b/app/livechat/server/index.js index ea21b43c8bcd7..ad8f2ca1c2d7a 100644 --- a/app/livechat/server/index.js +++ b/app/livechat/server/index.js @@ -80,7 +80,6 @@ import './lib/routing/External'; import './lib/routing/ManualSelection'; import './lib/routing/AutoSelection'; import './lib/stream/agentStatus'; -import './lib/stream/departmentAgents'; import './sendMessageBySMS'; import './api'; import './api/rest'; diff --git a/app/livechat/server/lib/stream/departmentAgents.js b/app/livechat/server/lib/stream/departmentAgents.js deleted file mode 100644 index a6c6fab751229..0000000000000 --- a/app/livechat/server/lib/stream/departmentAgents.js +++ /dev/null @@ -1,29 +0,0 @@ -import { LivechatDepartmentAgents } from '../../../../models/server'; -import { Notifications } from '../../../../notifications'; - -const fields = { agentId: 1, departmentId: 1 }; - -const emitNotification = (action, payload = {}) => { - const { agentId = null } = payload; - if (!agentId) { - return; - } - - Notifications.notifyUserInThisInstance(agentId, 'departmentAgentData', { - action, - ...payload, - }); -}; - -LivechatDepartmentAgents.on('change', ({ clientAction, id }) => { - switch (clientAction) { - case 'inserted': - case 'updated': - emitNotification(clientAction, LivechatDepartmentAgents.findOneById(id, { fields })); - break; - - case 'removed': - emitNotification(clientAction, LivechatDepartmentAgents.trashFindOneById(id, { fields })); - break; - } -}); diff --git a/app/models/server/raw/index.ts b/app/models/server/raw/index.ts index 67eda082696f9..bf515dba42256 100644 --- a/app/models/server/raw/index.ts +++ b/app/models/server/raw/index.ts @@ -102,6 +102,7 @@ const map = { [Roles.col.collectionName]: RolesModel, [Permissions.col.collectionName]: PermissionsModel, [LivechatInquiry.col.collectionName]: LivechatInquiryModel, + [LivechatDepartmentAgents.col.collectionName]: LivechatDepartmentAgentsModel, [UsersSessions.col.collectionName]: UsersSessionsModel, [Rooms.col.collectionName]: RoomsModel, [LoginServiceConfiguration.col.collectionName]: LoginServiceConfigurationModel, @@ -115,6 +116,7 @@ initWatchers({ Subscriptions, Settings, LivechatInquiry, + LivechatDepartmentAgents, UsersSessions, Permissions, Roles, diff --git a/definition/ILivechatDepartmentAgents.ts b/definition/ILivechatDepartmentAgents.ts new file mode 100644 index 0000000000000..3c221e1325f89 --- /dev/null +++ b/definition/ILivechatDepartmentAgents.ts @@ -0,0 +1,7 @@ +export interface ILivechatDepartmentAgents { + _id: string; + departmentId: string; + departmentEnabled: boolean; + agentId: string; + username: string; +} diff --git a/ee/server/services/StreamHub/StreamHub.ts b/ee/server/services/StreamHub/StreamHub.ts index ab7f50ac932a4..54311c3da5a7f 100755 --- a/ee/server/services/StreamHub/StreamHub.ts +++ b/ee/server/services/StreamHub/StreamHub.ts @@ -13,6 +13,7 @@ import { RoomsRaw } from '../../../../app/models/server/raw/Rooms'; import { LoginServiceConfigurationRaw } from '../../../../app/models/server/raw/LoginServiceConfiguration'; import { InstanceStatusRaw } from '../../../../app/models/server/raw/InstanceStatus'; import { IntegrationHistoryRaw } from '../../../../app/models/server/raw/IntegrationHistory'; +import { LivechatDepartmentAgentsRaw } from '../../../app/models/server/raw/LivechatDepartmentAgents'; export class StreamHub extends ServiceClass implements IServiceClass { protected name = 'hub'; @@ -30,6 +31,7 @@ export class StreamHub extends ServiceClass implements IServiceClass { const UsersSessions = new UsersSessionsRaw(db.collection('usersSessions'), Trash); const Subscriptions = new SubscriptionsRaw(db.collection('rocketchat_subscription'), Trash); const LivechatInquiry = new LivechatInquiryRaw(db.collection('rocketchat_livechat_inquiry'), Trash); + const LivechatDepartmentAgents = new LivechatDepartmentAgentsRaw(db.collection('rocketchat_livechat_department_agents'), Trash); const Messages = new MessagesRaw(db.collection('rocketchat_message'), Trash); const Permissions = new MessagesRaw(db.collection('rocketchat_permissions'), Trash); const Roles = new RolesRaw(db.collection('rocketchat_roles'), Trash, { Users, Subscriptions }); @@ -44,6 +46,7 @@ export class StreamHub extends ServiceClass implements IServiceClass { Subscriptions, Permissions, LivechatInquiry, + LivechatDepartmentAgents, Settings, Roles, Rooms, diff --git a/server/modules/watchers/watchers.module.ts b/server/modules/watchers/watchers.module.ts index 1a524b630d19c..7c8043b9298e7 100644 --- a/server/modules/watchers/watchers.module.ts +++ b/server/modules/watchers/watchers.module.ts @@ -26,6 +26,8 @@ import { IInstanceStatus } from '../../../definition/IInstanceStatus'; import { InstanceStatusRaw } from '../../../app/models/server/raw/InstanceStatus'; import { IntegrationHistoryRaw } from '../../../app/models/server/raw/IntegrationHistory'; import { IIntegrationHistory } from '../../../definition/IIntegrationHistory'; +import { LivechatDepartmentAgentsRaw } from '../../../app/models/server/raw/LivechatDepartmentAgents'; +import { ILivechatDepartmentAgents } from '../../../definition/ILivechatDepartmentAgents'; interface IModelsParam { Subscriptions: SubscriptionsRaw; @@ -34,6 +36,7 @@ interface IModelsParam { Settings: SettingsRaw; Messages: MessagesRaw; LivechatInquiry: LivechatInquiryRaw; + LivechatDepartmentAgents: LivechatDepartmentAgentsRaw; UsersSessions: UsersSessionsRaw; Roles: RolesRaw; Rooms: RoomsRaw; @@ -61,6 +64,7 @@ export function initWatchers({ Roles, Permissions, LivechatInquiry, + LivechatDepartmentAgents, Rooms, LoginServiceConfiguration, InstanceStatus, @@ -174,6 +178,24 @@ export function initWatchers({ api.broadcast('watch.inquiries', { clientAction, inquiry: data, diff }); }); + watch(LivechatDepartmentAgents, async ({ clientAction, id, data, diff }) => { + if (clientAction === 'removed') { + data = await LivechatDepartmentAgents.trashFindOneById(id, { projection: { agentId: 1, departmentId: 1 } }); + if (!data) { + return; + } + api.broadcast('watch.livechatDepartmentAgents', { clientAction, id, data, diff }); + return; + } + + data = await LivechatDepartmentAgents.findOneById(id, { projection: { agentId: 1, departmentId: 1 } }); + if (!data) { + return; + } + api.broadcast('watch.livechatDepartmentAgents', { clientAction, id, data, diff }); + }); + + watch(Permissions, async ({ clientAction, id, data, diff }) => { if (diff && Object.keys(diff).length === 1 && diff._updatedAt) { // avoid useless changes diff --git a/server/sdk/lib/Events.ts b/server/sdk/lib/Events.ts index 6c160ac850ef5..1b1a71cca120e 100644 --- a/server/sdk/lib/Events.ts +++ b/server/sdk/lib/Events.ts @@ -12,6 +12,7 @@ import { IUserSession } from '../../../definition/IUserSession'; import { ILoginServiceConfiguration } from '../../../definition/ILoginServiceConfiguration'; import { IInstanceStatus } from '../../../definition/IInstanceStatus'; import { IIntegrationHistory } from '../../../definition/IIntegrationHistory'; +import { ILivechatDepartmentAgents } from '../../../definition/ILivechatDepartmentAgents'; export type EventSignatures = { 'emoji.deleteCustom'(emoji: IEmoji): void; @@ -48,4 +49,5 @@ export type EventSignatures = { 'watch.loginServiceConfiguration'(data: { clientAction: string; data: Partial; id: string }): void; 'watch.instanceStatus'(data: { clientAction: string; data?: Partial; diff?: Record; id: string }): void; 'watch.integrationHistory'(data: { clientAction: string; data: Partial; diff?: Record; id: string }): void; + 'watch.livechatDepartmentAgents'(data: { clientAction: string; data: Partial; diff?: Record; id: string }): void; } diff --git a/server/services/listeners/notification.ts b/server/services/listeners/notification.ts index adbf5936b32fd..0b177e5db58d1 100644 --- a/server/services/listeners/notification.ts +++ b/server/services/listeners/notification.ts @@ -248,5 +248,17 @@ export class NotificationService extends ServiceClass { } } }); + + this.onEvent('watch.livechatDepartmentAgents', ({ clientAction, data }): void => { + const { agentId } = data; + if (!agentId) { + return; + } + + notifications.notifyUserInThisInstance(agentId, 'departmentAgentData', { + action: clientAction, + ...data, + }); + }); } } From 8e98241267ace920a21a43b00e55f2baa4c93eea Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Mon, 12 Oct 2020 15:25:40 -0300 Subject: [PATCH 153/198] Move Integrations watch to module --- app/integrations/server/lib/triggerHandler.js | 22 ++----------------- app/models/server/raw/IntegrationHistory.ts | 2 +- app/models/server/raw/index.ts | 2 ++ definition/IIntegration.ts | 5 +++++ ee/server/services/StreamHub/StreamHub.ts | 3 +++ server/modules/watchers/watchers.module.ts | 18 +++++++++++++++ server/sdk/lib/Events.ts | 2 ++ server/services/meteor/service.ts | 20 +++++++++++++++++ 8 files changed, 53 insertions(+), 21 deletions(-) create mode 100644 definition/IIntegration.ts diff --git a/app/integrations/server/lib/triggerHandler.js b/app/integrations/server/lib/triggerHandler.js index 0541855cbf640..3763178e12973 100644 --- a/app/integrations/server/lib/triggerHandler.js +++ b/app/integrations/server/lib/triggerHandler.js @@ -23,26 +23,6 @@ integrations.triggerHandler = new class RocketChatIntegrationHandler { this.triggers = {}; Models.Integrations.find({ type: 'webhook-outgoing' }).fetch().forEach((data) => this.addIntegration(data)); - - Models.Integrations.on('change', ({ clientAction, id, data }) => { - switch (clientAction) { - case 'inserted': - if (data.type === 'webhook-outgoing') { - this.addIntegration(data); - } - break; - case 'updated': - data = data ?? Models.Integrations.findOneById(id); - if (data.type === 'webhook-outgoing') { - this.removeIntegration(data); - this.addIntegration(data); - } - break; - case 'removed': - this.removeIntegration({ _id: id }); - break; - } - }); } addIntegration(record) { @@ -826,3 +806,5 @@ integrations.triggerHandler = new class RocketChatIntegrationHandler { this.executeTriggerUrl(history.url, integration, { event, message, room, owner, user }); } }(); + +export { integrations }; diff --git a/app/models/server/raw/IntegrationHistory.ts b/app/models/server/raw/IntegrationHistory.ts index 31cd7fe9877ae..53f7167db7962 100644 --- a/app/models/server/raw/IntegrationHistory.ts +++ b/app/models/server/raw/IntegrationHistory.ts @@ -1,4 +1,4 @@ import { BaseRaw } from './BaseRaw'; -import { IIntegrationHistory } from '../../../../definition/iIntegrationHistory'; +import { IIntegrationHistory } from '../../../../definition/IIntegrationHistory'; export class IntegrationHistoryRaw extends BaseRaw {} diff --git a/app/models/server/raw/index.ts b/app/models/server/raw/index.ts index bf515dba42256..53678b1e7a54a 100644 --- a/app/models/server/raw/index.ts +++ b/app/models/server/raw/index.ts @@ -108,6 +108,7 @@ const map = { [LoginServiceConfiguration.col.collectionName]: LoginServiceConfigurationModel, [InstanceStatus.col.collectionName]: InstanceStatusModel, [IntegrationHistory.col.collectionName]: IntegrationHistoryModel, + [Integrations.col.collectionName]: IntegrationsModel, }; initWatchers({ @@ -124,6 +125,7 @@ initWatchers({ LoginServiceConfiguration, InstanceStatus, IntegrationHistory, + Integrations, }, (model, fn) => { const meteorModel = map[model.col.collectionName]; diff --git a/definition/IIntegration.ts b/definition/IIntegration.ts new file mode 100644 index 0000000000000..5ad55faab937a --- /dev/null +++ b/definition/IIntegration.ts @@ -0,0 +1,5 @@ +export interface IIntegration { + _id: string; + type: string; + enabled: boolean; +} diff --git a/ee/server/services/StreamHub/StreamHub.ts b/ee/server/services/StreamHub/StreamHub.ts index 54311c3da5a7f..f8f0cb0eec931 100755 --- a/ee/server/services/StreamHub/StreamHub.ts +++ b/ee/server/services/StreamHub/StreamHub.ts @@ -14,6 +14,7 @@ import { LoginServiceConfigurationRaw } from '../../../../app/models/server/raw/ import { InstanceStatusRaw } from '../../../../app/models/server/raw/InstanceStatus'; import { IntegrationHistoryRaw } from '../../../../app/models/server/raw/IntegrationHistory'; import { LivechatDepartmentAgentsRaw } from '../../../app/models/server/raw/LivechatDepartmentAgents'; +import { IntegrationsRaw } from '../../../../app/models/server/raw/Integrations'; export class StreamHub extends ServiceClass implements IServiceClass { protected name = 'hub'; @@ -38,6 +39,7 @@ export class StreamHub extends ServiceClass implements IServiceClass { const LoginServiceConfiguration = new LoginServiceConfigurationRaw(db.collection('meteor_accounts_loginServiceConfiguration'), Trash); const InstanceStatus = new InstanceStatusRaw(db.collection('instances'), Trash); const IntegrationHistory = new IntegrationHistoryRaw(db.collection('rocketchat_integration_history'), Trash); + const Integrations = new IntegrationsRaw(db.collection('rocketchat_integrations'), Trash); const models = { Messages, @@ -53,6 +55,7 @@ export class StreamHub extends ServiceClass implements IServiceClass { LoginServiceConfiguration, InstanceStatus, IntegrationHistory, + Integrations, }; initWatchers(models, (model, fn) => { diff --git a/server/modules/watchers/watchers.module.ts b/server/modules/watchers/watchers.module.ts index 7c8043b9298e7..798b2a650d7b5 100644 --- a/server/modules/watchers/watchers.module.ts +++ b/server/modules/watchers/watchers.module.ts @@ -28,6 +28,8 @@ import { IntegrationHistoryRaw } from '../../../app/models/server/raw/Integratio import { IIntegrationHistory } from '../../../definition/IIntegrationHistory'; import { LivechatDepartmentAgentsRaw } from '../../../app/models/server/raw/LivechatDepartmentAgents'; import { ILivechatDepartmentAgents } from '../../../definition/ILivechatDepartmentAgents'; +import { IIntegration } from '../../../definition/IIntegration'; +import { IntegrationsRaw } from '../../../app/models/server/raw/Integrations'; interface IModelsParam { Subscriptions: SubscriptionsRaw; @@ -43,6 +45,7 @@ interface IModelsParam { LoginServiceConfiguration: LoginServiceConfigurationRaw; InstanceStatus: InstanceStatusRaw; IntegrationHistory: IntegrationHistoryRaw; + Integrations: IntegrationsRaw; } interface IChange { @@ -69,6 +72,7 @@ export function initWatchers({ LoginServiceConfiguration, InstanceStatus, IntegrationHistory, + Integrations, }: IModelsParam, watch: Watcher): void { watch(Messages, async ({ clientAction, id, data }) => { switch (clientAction) { @@ -307,4 +311,18 @@ export function initWatchers({ } } }); + + watch(Integrations, async ({ clientAction, id, data }) => { + if (clientAction === 'removed') { + api.broadcast('watch.integrations', { clientAction, id, data: { _id: id } }); + return; + } + + data = data ?? await Integrations.findOneById(id); + if (!data) { + return; + } + + api.broadcast('watch.integrations', { clientAction, data, id }); + }); } diff --git a/server/sdk/lib/Events.ts b/server/sdk/lib/Events.ts index 1b1a71cca120e..bf920a95966f7 100644 --- a/server/sdk/lib/Events.ts +++ b/server/sdk/lib/Events.ts @@ -13,6 +13,7 @@ import { ILoginServiceConfiguration } from '../../../definition/ILoginServiceCon import { IInstanceStatus } from '../../../definition/IInstanceStatus'; import { IIntegrationHistory } from '../../../definition/IIntegrationHistory'; import { ILivechatDepartmentAgents } from '../../../definition/ILivechatDepartmentAgents'; +import { IIntegration } from '../../../definition/IIntegration'; export type EventSignatures = { 'emoji.deleteCustom'(emoji: IEmoji): void; @@ -49,5 +50,6 @@ export type EventSignatures = { 'watch.loginServiceConfiguration'(data: { clientAction: string; data: Partial; id: string }): void; 'watch.instanceStatus'(data: { clientAction: string; data?: Partial; diff?: Record; id: string }): void; 'watch.integrationHistory'(data: { clientAction: string; data: Partial; diff?: Record; id: string }): void; + 'watch.integrations'(data: { clientAction: string; data: Partial; id: string }): void; 'watch.livechatDepartmentAgents'(data: { clientAction: string; data: Partial; diff?: Record; id: string }): void; } diff --git a/server/services/meteor/service.ts b/server/services/meteor/service.ts index b3a42e96ee4ef..6b8ebe9b98842 100644 --- a/server/services/meteor/service.ts +++ b/server/services/meteor/service.ts @@ -17,6 +17,7 @@ import { minimongoChangeMap } from '../listeners/notification'; import { onlineAgents, monitorAgents } from '../../../app/livechat/server/lib/stream/agentStatus'; import { IUser } from '../../../definition/IUser'; import { matrixBroadCastActions } from '../../stream/streamBroadcast'; +import { integrations } from '../../../app/integrations/server/lib/triggerHandler'; const autoUpdateRecords = new Map(); @@ -207,6 +208,25 @@ export class MeteorService extends ServiceClass implements IMeteor { break; } }); + + this.onEvent('watch.integrations', async ({ clientAction, id, data }) => { + switch (clientAction) { + case 'inserted': + if (data.type === 'webhook-outgoing') { + integrations.triggerHandler.addIntegration(data); + } + break; + case 'updated': + if (data.type === 'webhook-outgoing') { + integrations.triggerHandler.removeIntegration(data); + integrations.triggerHandler.addIntegration(data); + } + break; + case 'removed': + integrations.triggerHandler.removeIntegration({ _id: id }); + break; + } + }); } async getLastAutoUpdateClientVersions(): Promise { From 611c9587f3f9768bb85556e8cd4aad5347b11869 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Mon, 12 Oct 2020 16:41:57 -0300 Subject: [PATCH 154/198] Remove unecessary events --- ee/server/services/DDPStreamer/DDPStreamer.ts | 80 ------------------- ee/server/services/StreamHub/watchUsers.ts | 48 +---------- server/sdk/lib/Events.ts | 2 - 3 files changed, 1 insertion(+), 129 deletions(-) diff --git a/ee/server/services/DDPStreamer/DDPStreamer.ts b/ee/server/services/DDPStreamer/DDPStreamer.ts index 2c481249d19c5..41e1e695dec92 100644 --- a/ee/server/services/DDPStreamer/DDPStreamer.ts +++ b/ee/server/services/DDPStreamer/DDPStreamer.ts @@ -6,7 +6,6 @@ import WebSocket from 'ws'; import { Client } from './Client'; // import { STREAMER_EVENTS, STREAM_NAMES } from './constants'; -import { isEmpty } from './lib/utils'; import { ServiceClass } from '../../../../server/sdk/types/ServiceClass'; import { events } from './configureServer'; import notifications from './streams/index'; @@ -111,85 +110,6 @@ export class DDPStreamer extends ServiceClass { return stream && stream.emitWithoutBroadcast(eventName, ...args); }); - // userpresence(payload) { - this.onEvent('userpresence', (payload): void => { - const STATUS_MAP: {[k: string]: number} = { - offline: 0, - online: 1, - away: 2, - busy: 3, - }; - - const { - user: { _id, username, status, statusText }, - } = payload; - // Streamer.userpresence.emit(_id, status); - if (status) { - notifications.notifyLogged('user-status', [_id, username, STATUS_MAP[status], statusText]); - } - // User.emit(`${ STREAMER_EVENTS.USER_CHANGED }/${ _id }`, _id, user); // use this method - }); - - // user(payload) { - this.onEvent('user', (payload): void => { - const { - action, - user: { _id, ...user }, - } = payload; - // User.emit(`${ STREAMER_EVENTS.USER_CHANGED }/${ _id }`, _id, user); - - if (isEmpty(user)) { - return; - } - - const data: { - type: string; - diff?: object; - data?: object; - id?: string; - } = { - type: action, - }; - - switch (action) { - case 'updated': - data.diff = user; - break; - case 'inserted': - data.data = user; - break; - case 'removed': - data.id = _id; - break; - } - - _id && notifications.notifyUser( - _id, - 'userData', - data, - ); - - // Notifications.notifyUserInThisInstance(id, 'userData', { diff, type: clientAction }); - }); - - // 'user.name'(payload) { - this.onEvent('user.name', (payload): void => { - const { - user: { _id, name, username }, - } = payload; - // User.emit(`${ STREAMER_EVENTS.USER_CHANGED }/${ _id }`, _id, user); - notifications.notifyLogged('Users:NameChanged', { _id, name, username }); - }); - - // stream: { - // group: 'streamer', - // handler(payload) { - // const [stream, ev, data] = msgpack.decode(payload); - // Streamer.central.emit(stream, ev, data); - // }, - // }, - // role(payload) { - this.onEvent('watch.loginServiceConfiguration', ({ clientAction, id, data }) => { if (clientAction === 'removed') { events.emit('meteor.loginServiceConfiguration', 'removed', { diff --git a/ee/server/services/StreamHub/watchUsers.ts b/ee/server/services/StreamHub/watchUsers.ts index 794841be7054b..75ed84695265e 100644 --- a/ee/server/services/StreamHub/watchUsers.ts +++ b/ee/server/services/StreamHub/watchUsers.ts @@ -4,30 +4,6 @@ import { normalize } from './utils'; import { IUser } from '../../../../definition/IUser'; import { api } from '../../../../server/sdk/api'; -function nestStringProperties(obj: object): object { - if (!obj) { - return {}; - } - - const isPlainObject = (obj: object): boolean => !!obj && obj.constructor === {}.constructor; - - const getNestedObject = (obj: object): object => - Object.entries(obj).reduce<{[k: string]: any}>((result, [prop, val]) => { - prop.split('.').reduce((nestedResult, prop, propIndex, propArray) => { - const lastProp = propIndex === propArray.length - 1; - if (lastProp) { - nestedResult[prop] = isPlainObject(val) ? getNestedObject(val) : val; - } else { - nestedResult[prop] = nestedResult[prop] || {}; - } - return nestedResult[prop]; - }, result); - return result; - }, {}); - - return getNestedObject(obj); -} - export async function watchUsers(event: ChangeEvent): Promise { switch (event.operationType) { case 'insert': @@ -47,30 +23,8 @@ export async function watchUsers(event: ChangeEvent): Promise { api.broadcast('userpresence', { action: normalize[event.operationType], user: { status, _id, username, statusText } }); // remove username // RocketChat.Logger.info('User: userpresence', { status, _id, username, statusText }); } - - if (updatedFields.username || updatedFields.name) { - const { name, username } = updatedFields; - const { _id } = event.documentKey; - const nameChange = { - _id, - name: name || user.name, - username: username || user.username, - }; - - api.broadcast('user.name', { - action: normalize[event.operationType], - user: nameChange, - }); - // RocketChat.Logger.info('User: user.name', nameChange); - } } - api.broadcast('user', { - action: normalize[event.operationType], - user: { - ...event.documentKey, - ...updatedFields ? nestStringProperties(updatedFields) : {}, - }, - }); + // RocketChat.Logger.info('User record', user); // return Streamer[method]({ stream: STREAM_NAMES['room-messages'], eventName: message.rid, args: message }); // publishMessage(operationType, message); diff --git a/server/sdk/lib/Events.ts b/server/sdk/lib/Events.ts index bf920a95966f7..4c8f9584fe4c9 100644 --- a/server/sdk/lib/Events.ts +++ b/server/sdk/lib/Events.ts @@ -30,12 +30,10 @@ export type EventSignatures = { 'stream'([streamer, eventName, payload]: [string, string, string]): void; 'stream.ephemeralMessage'(uid: string, rid: string, message: Partial): void; 'subscription'(data: { action: string; subscription: Partial }): void; - 'user'(data: { action: string; user: Partial }): void; 'user.avatarUpdate'(user: Partial): void; 'user.deleted'(user: Partial): void; 'user.deleteCustomStatus'(userStatus: IUserStatus): void; 'user.nameChanged'(user: Partial): void; - 'user.name'(data: { action: string; user: Partial }): void; 'user.roleUpdate'(update: Record): void; 'user.updateCustomStatus'(userStatus: IUserStatus): void; 'userpresence'(data: { action: string; user: Partial }): void; From 8adb53df403613a74dd14c326ab2ee898641f036 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Mon, 12 Oct 2020 18:54:10 -0300 Subject: [PATCH 155/198] Fix errors --- server/sdk/lib/Api.ts | 6 +++++- server/sdk/types/ServiceClass.ts | 2 +- server/services/meteor/service.ts | 12 ++++++------ 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/server/sdk/lib/Api.ts b/server/sdk/lib/Api.ts index da5b92d2f8150..f99a370b5c4f3 100644 --- a/server/sdk/lib/Api.ts +++ b/server/sdk/lib/Api.ts @@ -16,11 +16,15 @@ export class Api { } destroyService(instance: ServiceClass): void { - this.services.delete(instance); + if (!this.services.has(instance)) { + return; + } if (this.broker) { this.broker.destroyService(instance); } + + this.services.delete(instance); } registerService(instance: ServiceClass): void { diff --git a/server/sdk/types/ServiceClass.ts b/server/sdk/types/ServiceClass.ts index 7a45e29531847..70c9b16fa5d1c 100644 --- a/server/sdk/types/ServiceClass.ts +++ b/server/sdk/types/ServiceClass.ts @@ -52,7 +52,7 @@ export abstract class ServiceClass implements IServiceClass { public onEvent(event: T, handler: EventSignatures[T]): void { if (this.events.has(event)) { - throw new Error('event already registered'); + throw new Error(`event "${ event }" already registered`); } this.events.set(event, handler); diff --git a/server/services/meteor/service.ts b/server/services/meteor/service.ts index 6b8ebe9b98842..361a6682a0e29 100644 --- a/server/services/meteor/service.ts +++ b/server/services/meteor/service.ts @@ -156,12 +156,6 @@ export class MeteorService extends ServiceClass implements IMeteor { }); if (disableOplog) { - this.onEvent('watch.users', ({ clientAction, id, diff }) => { - if (clientAction === 'updated' && diff) { - processOnChange(diff, id); - } - }); - this.onEvent('watch.loginServiceConfiguration', ({ clientAction, id, data }) => { if (clientAction === 'removed') { serviceConfigCallbacks.forEach((callbacks) => { @@ -177,6 +171,12 @@ export class MeteorService extends ServiceClass implements IMeteor { } this.onEvent('watch.users', async ({ clientAction, id, diff }) => { + if (disableOplog) { + if (clientAction === 'updated' && diff) { + processOnChange(diff, id); + } + } + if (!monitorAgents) { return; } From 7ba70c5f91bfb24fc287e18d66d391d367f131da Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Mon, 12 Oct 2020 20:53:07 -0300 Subject: [PATCH 156/198] Prevent process to be stucked on exit --- ee/server/broker.ts | 2 ++ server/sdk/lib/Api.ts | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/ee/server/broker.ts b/ee/server/broker.ts index 5202d6653d3ff..d3f2078b1aa39 100644 --- a/ee/server/broker.ts +++ b/ee/server/broker.ts @@ -204,6 +204,8 @@ const { } = process.env; const network = new ServiceBroker({ + // TODO: Reevaluate, without this setting it was preventing the process to stop + skipProcessEventRegistration: true, transporter: TRANSPORTER, metrics: { enabled: MS_METRICS === 'true', diff --git a/server/sdk/lib/Api.ts b/server/sdk/lib/Api.ts index f99a370b5c4f3..0af4c76a5dd31 100644 --- a/server/sdk/lib/Api.ts +++ b/server/sdk/lib/Api.ts @@ -2,11 +2,12 @@ import { IBroker } from '../types/IBroker'; import { ServiceClass } from '../types/ServiceClass'; import { EventSignatures } from './Events'; +import { LocalBroker } from './LocalBroker'; export class Api { private services = new Set(); - private broker: IBroker; + private broker: IBroker = new LocalBroker(); // set a broker for the API and registers all services in the broker setBroker(broker: IBroker): void { From 8953a8e2861d03045730fdc1bc0638482d960d23 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Tue, 13 Oct 2020 08:26:39 -0300 Subject: [PATCH 157/198] Fix error Settings.findOneById is not a function --- app/settings/server/raw.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/settings/server/raw.js b/app/settings/server/raw.js index 436643cbae44f..d458f7a5561b8 100644 --- a/app/settings/server/raw.js +++ b/app/settings/server/raw.js @@ -1,4 +1,4 @@ -import { Settings } from '../../models/server/models/Settings'; +import Settings from '../../models/server/models/Settings'; const cache = new Map(); From 8bb777c8b167a32db57736c6f276946b4c4dfa16 Mon Sep 17 00:00:00 2001 From: Alan Sikora Date: Wed, 14 Oct 2020 15:15:08 -0300 Subject: [PATCH 158/198] removing meteor dependencies from StreamHub, using SubscriptionsRaw to instantiate Subscriptions --- ee/app/models/server/raw/LivechatDepartmentAgents.ts | 3 --- ee/server/services/StreamHub/StreamHub.ts | 3 ++- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/ee/app/models/server/raw/LivechatDepartmentAgents.ts b/ee/app/models/server/raw/LivechatDepartmentAgents.ts index 51f4c343056b6..c64046c4f4b6e 100644 --- a/ee/app/models/server/raw/LivechatDepartmentAgents.ts +++ b/ee/app/models/server/raw/LivechatDepartmentAgents.ts @@ -1,5 +1,4 @@ import { LivechatDepartmentAgentsRaw as Raw } from '../../../../../app/models/server/raw/LivechatDepartmentAgents'; -import { LivechatDepartmentAgents } from '../../../../../app/models/server'; export class LivechatDepartmentAgentsRaw extends Raw { findAgentsByAgentIdAndBusinessHourId(agentId: string, businessHourId: string): Promise> { @@ -25,5 +24,3 @@ export class LivechatDepartmentAgentsRaw extends Raw { return this.col.aggregate([match, lookup, unwind, withBusinessHourId, project]).toArray(); } } - -export default new LivechatDepartmentAgentsRaw(LivechatDepartmentAgents.model.rawCollection()); diff --git a/ee/server/services/StreamHub/StreamHub.ts b/ee/server/services/StreamHub/StreamHub.ts index f8f0cb0eec931..99eb37ebd1690 100755 --- a/ee/server/services/StreamHub/StreamHub.ts +++ b/ee/server/services/StreamHub/StreamHub.ts @@ -15,6 +15,7 @@ import { InstanceStatusRaw } from '../../../../app/models/server/raw/InstanceSta import { IntegrationHistoryRaw } from '../../../../app/models/server/raw/IntegrationHistory'; import { LivechatDepartmentAgentsRaw } from '../../../app/models/server/raw/LivechatDepartmentAgents'; import { IntegrationsRaw } from '../../../../app/models/server/raw/Integrations'; +import { PermissionsRaw } from '../../../../app/models/server/raw/Permissions'; export class StreamHub extends ServiceClass implements IServiceClass { protected name = 'hub'; @@ -34,7 +35,7 @@ export class StreamHub extends ServiceClass implements IServiceClass { const LivechatInquiry = new LivechatInquiryRaw(db.collection('rocketchat_livechat_inquiry'), Trash); const LivechatDepartmentAgents = new LivechatDepartmentAgentsRaw(db.collection('rocketchat_livechat_department_agents'), Trash); const Messages = new MessagesRaw(db.collection('rocketchat_message'), Trash); - const Permissions = new MessagesRaw(db.collection('rocketchat_permissions'), Trash); + const Permissions = new PermissionsRaw(db.collection('rocketchat_permissions'), Trash); const Roles = new RolesRaw(db.collection('rocketchat_roles'), Trash, { Users, Subscriptions }); const LoginServiceConfiguration = new LoginServiceConfigurationRaw(db.collection('meteor_accounts_loginServiceConfiguration'), Trash); const InstanceStatus = new InstanceStatusRaw(db.collection('instances'), Trash); From 342a4ad7e708f8ed81ad3f4d9ea8b09577be211e Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Wed, 14 Oct 2020 15:46:02 -0300 Subject: [PATCH 159/198] Fix test error --- .scripts/start.js | 13 ++++++++++--- app/api/server/v1/settings.js | 2 ++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/.scripts/start.js b/.scripts/start.js index 1159290bbbb9a..8caf131893a6b 100644 --- a/.scripts/start.js +++ b/.scripts/start.js @@ -19,13 +19,17 @@ const isPortTaken = (port) => new Promise((resolve, reject) => { .listen(port); }); -const waitPortRelease = (port) => new Promise((resolve, reject) => { +const waitPortRelease = (port, count = 0) => new Promise((resolve, reject) => { isPortTaken(port).then((taken) => { if (!taken) { return resolve(); } + if (count > 60) { + return reject(); + } + console.log('Port', port, 'not release, waiting 1s...'); setTimeout(() => { - waitPortRelease(port).then(resolve).catch(reject); + waitPortRelease(port, ++count).then(resolve).catch(reject); }, 1000); }); }); @@ -77,7 +81,10 @@ function startProcess(opts, callback) { processes.splice(processes.indexOf(proc), 1); - processes.forEach((p) => p.kill()); + processes.forEach((p) => { + console.log('Killing process', p.pid); + p.kill(); + }); if (processes.length === 0) { waitPortRelease(appOptions.env.PORT).then(() => { diff --git a/app/api/server/v1/settings.js b/app/api/server/v1/settings.js index cf926f44d4785..c7a7ca33c97f8 100644 --- a/app/api/server/v1/settings.js +++ b/app/api/server/v1/settings.js @@ -7,6 +7,7 @@ import { Settings } from '../../../models/server'; import { hasPermission } from '../../../authorization'; import { API } from '../api'; import { SettingsEvents, settings } from '../../../settings/server'; +import { setValue } from '../../../settings/server/raw'; const fetchSettings = (query, sort, offset, count, fields) => { const settings = Settings.find(query, { @@ -150,6 +151,7 @@ API.v1.addRoute('settings/:_id', { authRequired: true }, { _id: this.urlParams._id, value: this.bodyParams.value, }); + setValue(this.urlParams._id, this.bodyParams.value); return API.v1.success(); } From 2956986ff6542d8f9818fe8324af5bc7aa9dd444 Mon Sep 17 00:00:00 2001 From: Alan Sikora Date: Wed, 14 Oct 2020 16:36:07 -0300 Subject: [PATCH 160/198] fixed Livechat files: Custom.ts and Multiple.ts, the latter still complaints about the inheritance, we should take a look at it --- ee/app/livechat-enterprise/server/business-hour/Custom.ts | 4 ++-- .../livechat-enterprise/server/business-hour/Multiple.ts | 7 +++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/ee/app/livechat-enterprise/server/business-hour/Custom.ts b/ee/app/livechat-enterprise/server/business-hour/Custom.ts index f652dee9d2068..1bea445a56617 100644 --- a/ee/app/livechat-enterprise/server/business-hour/Custom.ts +++ b/ee/app/livechat-enterprise/server/business-hour/Custom.ts @@ -4,9 +4,9 @@ import { } from '../../../../../app/livechat/server/business-hour/AbstractBusinessHour'; import { ILivechatBusinessHour, LivechatBusinessHourTypes } from '../../../../../definition/ILivechatBusinessHour'; import { LivechatDepartmentRaw } from '../../../../../app/models/server/raw/LivechatDepartment'; -import { LivechatDepartment } from '../../../../../app/models/server/raw'; +import { LivechatDepartmentAgentsRaw } from '../../../../../app/models/server/raw/LivechatDepartmentAgents'; +import { LivechatDepartment, LivechatDepartmentAgents } from '../../../../../app/models/server/raw'; import { businessHourManager } from '../../../../../app/livechat/server/business-hour'; -import LivechatDepartmentAgents, { LivechatDepartmentAgentsRaw } from '../../../models/server/raw/LivechatDepartmentAgents'; export interface IBusinessHoursExtraProperties extends ILivechatBusinessHour { timezoneName: string; diff --git a/ee/app/livechat-enterprise/server/business-hour/Multiple.ts b/ee/app/livechat-enterprise/server/business-hour/Multiple.ts index d4eb9940aa839..7143193ecfe49 100644 --- a/ee/app/livechat-enterprise/server/business-hour/Multiple.ts +++ b/ee/app/livechat-enterprise/server/business-hour/Multiple.ts @@ -5,10 +5,9 @@ import { IBusinessHourBehavior, } from '../../../../../app/livechat/server/business-hour/AbstractBusinessHour'; import { ILivechatBusinessHour } from '../../../../../definition/ILivechatBusinessHour'; -import { LivechatDepartment } from '../../../../../app/models/server'; -import { LivechatDepartment as Raw } from '../../../../../app/models/server/raw'; import { LivechatDepartmentRaw } from '../../../../../app/models/server/raw/LivechatDepartment'; -import LivechatDepartmentAgents, { LivechatDepartmentAgentsRaw } from '../../../models/server/raw/LivechatDepartmentAgents'; +import { LivechatDepartmentAgentsRaw } from '../../../models/server/raw/LivechatDepartmentAgents'; +import { LivechatDepartment, LivechatDepartmentAgents } from '../../../../../app/models/server/raw'; import { filterBusinessHoursThatMustBeOpened } from '../../../../../app/livechat/server/business-hour/Helper'; import { closeBusinessHour, openBusinessHour, removeBusinessHourByAgentIds } from './Helper'; @@ -18,7 +17,7 @@ interface IBusinessHoursExtraProperties extends ILivechatBusinessHour { } export class MultipleBusinessHoursBehavior extends AbstractBusinessHourBehavior implements IBusinessHourBehavior { - private DepartmentsRepository: LivechatDepartmentRaw = Raw; + private DepartmentsRepository: LivechatDepartmentRaw = LivechatDepartment; private DepartmentsAgentsRepository: LivechatDepartmentAgentsRaw = LivechatDepartmentAgents; From 36db46060f90432bb3a8f8328df829e76640d328 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Wed, 14 Oct 2020 17:44:08 -0300 Subject: [PATCH 161/198] Type cast LivechatDepartmentAgentsRaw --- ee/app/livechat-enterprise/server/business-hour/Multiple.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ee/app/livechat-enterprise/server/business-hour/Multiple.ts b/ee/app/livechat-enterprise/server/business-hour/Multiple.ts index 7143193ecfe49..35b1177712498 100644 --- a/ee/app/livechat-enterprise/server/business-hour/Multiple.ts +++ b/ee/app/livechat-enterprise/server/business-hour/Multiple.ts @@ -19,7 +19,7 @@ interface IBusinessHoursExtraProperties extends ILivechatBusinessHour { export class MultipleBusinessHoursBehavior extends AbstractBusinessHourBehavior implements IBusinessHourBehavior { private DepartmentsRepository: LivechatDepartmentRaw = LivechatDepartment; - private DepartmentsAgentsRepository: LivechatDepartmentAgentsRaw = LivechatDepartmentAgents; + private DepartmentsAgentsRepository = LivechatDepartmentAgents as LivechatDepartmentAgentsRaw; constructor() { super(); From e4dfd0ef1e716582a3afd3586693bc8176647b8b Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Wed, 14 Oct 2020 17:44:16 -0300 Subject: [PATCH 162/198] Remove SettingType --- client/admin/sidebar/AdminSidebarSettings.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/admin/sidebar/AdminSidebarSettings.tsx b/client/admin/sidebar/AdminSidebarSettings.tsx index fd88b0554d350..f348ca9618c23 100644 --- a/client/admin/sidebar/AdminSidebarSettings.tsx +++ b/client/admin/sidebar/AdminSidebarSettings.tsx @@ -2,7 +2,7 @@ import { Box, Icon, SearchInput, Skeleton } from '@rocket.chat/fuselage'; import { useDebouncedValue } from '@rocket.chat/fuselage-hooks'; import React, { useCallback, useState, useMemo, FC } from 'react'; -import { ISetting, SettingType } from '../../../definition/ISetting'; +import { ISetting } from '../../../definition/ISetting'; import { useSettings } from '../../contexts/SettingsContext'; import { useTranslation } from '../../contexts/TranslationContext'; import Sidebar from '../../components/basic/Sidebar'; @@ -38,7 +38,7 @@ const useSettingsGroups = (filter: string): ISetting[] => { settings .filter(filterPredicate) .map((setting) => { - if (setting.type === SettingType.GROUP) { + if (setting.type === 'group') { return setting._id; } @@ -47,7 +47,7 @@ const useSettingsGroups = (filter: string): ISetting[] => { )); return settings - .filter(({ type, group, _id }) => type === SettingType.GROUP && groupIds.includes(group || _id)) + .filter(({ type, group, _id }) => type === 'group' && groupIds.includes(group || _id)) .sort((a, b) => t(a.i18nLabel || a._id).localeCompare(t(b.i18nLabel || b._id))); }, [settings, filterPredicate, t]); }; From 87fbee2f4657d0dbfb6791e5fa96ce77cb754359 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Wed, 14 Oct 2020 18:03:40 -0300 Subject: [PATCH 163/198] Remove mongodb connection close on SIGTERM --- app/models/server/models/_oplogHandle.ts | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/app/models/server/models/_oplogHandle.ts b/app/models/server/models/_oplogHandle.ts index 3a44e36ec4f32..34164b0ab88cc 100644 --- a/app/models/server/models/_oplogHandle.ts +++ b/app/models/server/models/_oplogHandle.ts @@ -201,13 +201,3 @@ export const getOplogHandle = async (): Promise { - if (!oplogHandle) { - process.exit(0); - } - - // gracefully closes oplog connection on SIGTERM - await (await oplogHandle).stop(); - process.exit(0); -}); From ce4c0e95300b3d4ec980158d872b44ac811f773b Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Thu, 15 Oct 2020 09:59:54 -0300 Subject: [PATCH 164/198] Remove some TODOs --- ee/server/services/DDPStreamer/Client.ts | 2 +- ee/server/services/DDPStreamer/Server.ts | 3 --- ee/server/services/DDPStreamer/Streamer.ts | 2 +- .../services/DDPStreamer/configureServer.ts | 17 ------------ ee/server/services/DDPStreamer/constants.ts | 11 +------- ee/server/services/StreamHub/StreamHub.ts | 27 ------------------- ee/server/services/StreamHub/utils.ts | 5 ---- ee/server/services/StreamHub/watchUsers.ts | 7 ++++- server/modules/watchers/watchers.module.ts | 2 ++ 9 files changed, 11 insertions(+), 65 deletions(-) delete mode 100644 ee/server/services/StreamHub/utils.ts diff --git a/ee/server/services/DDPStreamer/Client.ts b/ee/server/services/DDPStreamer/Client.ts index 50c6d711ade08..24d93c7483386 100644 --- a/ee/server/services/DDPStreamer/Client.ts +++ b/ee/server/services/DDPStreamer/Client.ts @@ -100,7 +100,7 @@ export class Client extends EventEmitter { } this.callMethod(packet); break; - case DDP_EVENTS.SUSBCRIBE: + case DDP_EVENTS.SUBSCRIBE: if (!packet.name) { return this.ws.close(WS_ERRORS.CLOSE_PROTOCOL_ERROR); } diff --git a/ee/server/services/DDPStreamer/Server.ts b/ee/server/services/DDPStreamer/Server.ts index 269b41111795b..536bc0fcc939d 100644 --- a/ee/server/services/DDPStreamer/Server.ts +++ b/ee/server/services/DDPStreamer/Server.ts @@ -17,9 +17,6 @@ type Methods = { // eslint-disable-next-line @typescript-eslint/camelcase export const SERVER_ID = ejson.stringify({ server_id: '0' }); -// TODO: remove, web-client still receives the object of the logged in user, verify if that works -// export const User = new Publish('user'); - export class Server extends EventEmitter { private _subscriptions = new Map(); diff --git a/ee/server/services/DDPStreamer/Streamer.ts b/ee/server/services/DDPStreamer/Streamer.ts index f9bd5cc192f4a..da1022f3da779 100644 --- a/ee/server/services/DDPStreamer/Streamer.ts +++ b/ee/server/services/DDPStreamer/Streamer.ts @@ -37,7 +37,6 @@ export class Stream extends Streamer { return super.sendToManySubscriptions(subscriptions, origin, eventName, args, getMsg); } - // TODO: missing typing const options = { fin: true, // sending a single fragment message rsv1: false, // don"t set rsv1 bit (no compression) @@ -46,6 +45,7 @@ export class Stream extends Streamer { readOnly: false, // the data can be modified as needed }; + // TODO: missing typing const data = { meteor: [Buffer.concat((WebSocket as any).Sender.frame(Buffer.from(`a${ JSON.stringify([getMsg]) }`), options))], normal: [Buffer.concat((WebSocket as any).Sender.frame(Buffer.from(getMsg), options))], diff --git a/ee/server/services/DDPStreamer/configureServer.ts b/ee/server/services/DDPStreamer/configureServer.ts index 73dbb13c1454a..8e544864e2fd9 100644 --- a/ee/server/services/DDPStreamer/configureServer.ts +++ b/ee/server/services/DDPStreamer/configureServer.ts @@ -10,23 +10,6 @@ export const server = new Server(); export const events = new EventEmitter(); -// TODO: remove, this was replaced by stream-notify-user/[user-id]/userData -// server.subscribe('userData', async function(publication) { -// if (!publication.userId) { -// throw new Error('user should be connected'); -// } - -// const key = `${ STREAMER_EVENTS.USER_CHANGED }/${ publication.userId }`; -// await User.addSubscription(publication, key); -// publication.once('stop', () => User.removeSubscription(publication, key)); -// publication.ready(); -// }); - -// TODO: remove, this was replaced by stream-notify-logged/user-status, check if sending this data -// server.subscribe('activeUsers', function(publication) { -// publication.ready(); -// }); - const loginServiceConfigurationCollection = 'meteor_accounts_loginServiceConfiguration'; const loginServiceConfigurationPublication = 'meteor.loginServiceConfiguration'; const loginServices = new Map(); diff --git a/ee/server/services/DDPStreamer/constants.ts b/ee/server/services/DDPStreamer/constants.ts index dfeebef91bf6a..f3f92d05f9b45 100644 --- a/ee/server/services/DDPStreamer/constants.ts +++ b/ee/server/services/DDPStreamer/constants.ts @@ -21,7 +21,7 @@ export const DDP_EVENTS = { UPDATED: 'updated', PING: 'ping', PONG: 'pong', - SUSBCRIBE: 'sub', + SUBSCRIBE: 'sub', CONNECT: 'connect', CONNECTED: 'connected', SUBSCRIPTIONS: 'subs', @@ -45,12 +45,3 @@ export const WS_ERRORS_MESSAGES = { }; export const TIMEOUT = 1000 * 30; // 30 seconds - -// export const STREAM_NAMES = { -// STREAMER_PREFIX: 'stream-', - -// ROOMS_CHANGED: 'rooms-changed', -// ROOM_DATA: 'room-data', // TODO both data are the same plx merge them - -// 'my-message': '__my_messages__', -// }; diff --git a/ee/server/services/StreamHub/StreamHub.ts b/ee/server/services/StreamHub/StreamHub.ts index 99eb37ebd1690..0c265fccbad39 100755 --- a/ee/server/services/StreamHub/StreamHub.ts +++ b/ee/server/services/StreamHub/StreamHub.ts @@ -110,32 +110,5 @@ export class StreamHub extends ServiceClass implements IServiceClass { }); UsersCol.watch([], { fullDocument: 'updateLookup' }).on('change', watchUsers); - - // TODO: check the improvement mentioned below that was ignored on code unification - // Messages.watch([{ - // $addFields: { - // tmpfields: { - // $objectToArray: '$updateDescription.updatedFields', - // }, - // } }, { - // $match: { - // 'tmpfields.k': { - // $nin: ['u.username'], // avoid flood the streamer with messages changes (by username change) - // }, - // } }], { fullDocument: 'updateLookup' }).on('change', watchMessages); - - // SettingsCol.watch([{ - // $addFields: { - // tmpfields: { - // $objectToArray: '$updateDescription.updatedFields', - // }, - // }, - // }, { - // $match: { - // 'tmpfields.k': { - // $in: ['value'], // avoid flood the streamer with messages changes (by username change) - // }, - // }, - // }], { fullDocument: 'updateLookup' }).on('change', watchSettings); } } diff --git a/ee/server/services/StreamHub/utils.ts b/ee/server/services/StreamHub/utils.ts deleted file mode 100644 index 92cfa674656d1..0000000000000 --- a/ee/server/services/StreamHub/utils.ts +++ /dev/null @@ -1,5 +0,0 @@ -export const normalize: {[key: string]: string} = { - update: 'updated', - insert: 'inserted', - remove: 'removed', -}; diff --git a/ee/server/services/StreamHub/watchUsers.ts b/ee/server/services/StreamHub/watchUsers.ts index 75ed84695265e..9ccc9828da0b6 100644 --- a/ee/server/services/StreamHub/watchUsers.ts +++ b/ee/server/services/StreamHub/watchUsers.ts @@ -1,9 +1,14 @@ import { ChangeEvent } from 'mongodb'; -import { normalize } from './utils'; import { IUser } from '../../../../definition/IUser'; import { api } from '../../../../server/sdk/api'; +const normalize: {[key: string]: string} = { + update: 'updated', + insert: 'inserted', + remove: 'removed', +}; + export async function watchUsers(event: ChangeEvent): Promise { switch (event.operationType) { case 'insert': diff --git a/server/modules/watchers/watchers.module.ts b/server/modules/watchers/watchers.module.ts index 798b2a650d7b5..cc39b39cf6f38 100644 --- a/server/modules/watchers/watchers.module.ts +++ b/server/modules/watchers/watchers.module.ts @@ -274,6 +274,8 @@ export function initWatchers({ api.broadcast('watch.rooms', { clientAction, room }); }); + // TODO: Prevent flood from database on username change, what causes changes on all past messages from that user + // and most of those messages are not loaded by the clients. watch(Users, ({ clientAction, id, data, diff }) => { api.broadcast('watch.users', { clientAction, data, diff, id }); }); From 2921c2d3d09ade628b7581d41589bbbca6b49c89 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Thu, 15 Oct 2020 15:24:49 -0300 Subject: [PATCH 165/198] Remove external dependencies from license module --- ee/app/license/server/index.ts | 1 + ee/app/license/server/license.ts | 18 ++++++------------ ee/app/license/server/startup.js | 3 --- ee/server/index.js | 1 + 4 files changed, 8 insertions(+), 15 deletions(-) diff --git a/ee/app/license/server/index.ts b/ee/app/license/server/index.ts index 20af6ca034195..2761e593dedd0 100644 --- a/ee/app/license/server/index.ts +++ b/ee/app/license/server/index.ts @@ -1,3 +1,4 @@ +import './license.internalService'; import './settings'; import './methods'; import './startup'; diff --git a/ee/app/license/server/license.ts b/ee/app/license/server/license.ts index bfe5103423f9c..b092cb137e8d0 100644 --- a/ee/app/license/server/license.ts +++ b/ee/app/license/server/license.ts @@ -1,12 +1,9 @@ import { EventEmitter } from 'events'; import { Users } from '../../../../app/models/server'; -import { addRoleRestrictions } from '../../authorization/lib/addRoleRestrictions'; -import { resetEnterprisePermissions } from '../../authorization/server/resetEnterprisePermissions'; import { getBundleModules, isBundle, getBundleFromModule } from './bundles'; import decrypt from './decrypt'; import { getTagColor } from './getTagColor'; -import { api } from '../../../../server/sdk/api'; const EnterpriseLicenses = new EventEmitter(); @@ -31,7 +28,6 @@ export interface IValidLicense { } let maxGuestUsers = 0; -let addedRoleRestrictions = false; class LicenseClass { private url: string|null = null; @@ -63,7 +59,7 @@ class LicenseClass { modules.forEach((module) => { this.modules.add(module); - api.broadcast('license.module', { module, valid: true }); + EnterpriseLicenses.emit('module', { module, valid: true }); EnterpriseLicenses.emit(`valid:${ module }`); }); }); @@ -76,7 +72,7 @@ class LicenseClass { : [licenseModule]; modules.forEach((module) => { - api.broadcast('license.module', { module, valid: false }); + EnterpriseLicenses.emit('module', { module, valid: false }); EnterpriseLicenses.emit(`invalid:${ module }`); }); }); @@ -117,12 +113,6 @@ class LicenseClass { }); this.validate(); - - if (!addedRoleRestrictions && this.hasAnyValidLicense()) { - addRoleRestrictions(); - resetEnterprisePermissions(); - addedRoleRestrictions = true; - } } hasModule(module: string): boolean { @@ -277,6 +267,10 @@ export function onLicense(feature: string, cb: (...args: any[]) => void): void { EnterpriseLicenses.once(`valid:${ feature }`, cb); } +export function onModule(cb: (...args: any[]) => void): void { + EnterpriseLicenses.on('module', cb); +} + export function onValidateLicenses(cb: (...args: any[]) => void): void { EnterpriseLicenses.on('validate', cb); } diff --git a/ee/app/license/server/startup.js b/ee/app/license/server/startup.js index 98a210cb0030b..af6384fc71536 100644 --- a/ee/app/license/server/startup.js +++ b/ee/app/license/server/startup.js @@ -1,9 +1,6 @@ import { settings } from '../../../../app/settings/server'; import { callbacks } from '../../../../app/callbacks'; import { addLicense, setURL } from './license'; -import './settings'; -import './methods'; -import './license.internalService'; settings.get('Site_Url', (key, value) => { if (value) { diff --git a/ee/server/index.js b/ee/server/index.js index 08141919ad56b..1c61f8ca5d85c 100644 --- a/ee/server/index.js +++ b/ee/server/index.js @@ -1,6 +1,7 @@ import './broker'; import '../app/models'; +import '../app/license/server/index'; import '../app/api-enterprise/server/index'; import '../app/auditing/server/index'; import '../app/authorization/server/index'; From 532c5d5c6bf076811b755fe2e247f18f583935ac Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Thu, 15 Oct 2020 15:26:30 -0300 Subject: [PATCH 166/198] Hook up license events to micro services --- .../license/server/license.internalService.ts | 35 +++++++++++++++++-- server/sdk/types/IAuthorization.ts | 1 + server/services/authorization/service.ts | 5 ++- 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/ee/app/license/server/license.internalService.ts b/ee/app/license/server/license.internalService.ts index dba506f8a2027..f03d771aa51a2 100644 --- a/ee/app/license/server/license.internalService.ts +++ b/ee/app/license/server/license.internalService.ts @@ -1,11 +1,40 @@ import { ServiceClass } from '../../../../server/sdk/types/ServiceClass'; import { api } from '../../../../server/sdk/api'; import { ILicense } from '../../../../server/sdk/types/ILicense'; -import { hasLicense, isEnterprise, getModules } from './license'; +import { hasLicense, isEnterprise, getModules, onValidateLicenses, onModule } from './license'; +import { resetEnterprisePermissions } from '../../authorization/server/resetEnterprisePermissions'; +import { Authorization } from '../../../../server/sdk'; +import { guestPermissions } from '../../authorization/lib/guestPermissions'; -class License extends ServiceClass implements ILicense { +class LicenseService extends ServiceClass implements ILicense { protected name = 'license'; + constructor() { + super(); + + onValidateLicenses((): void => { + if (!isEnterprise()) { + return; + } + + Authorization.addRoleRestrictions('guest', guestPermissions); + resetEnterprisePermissions(); + }); + + onModule((licenseModule) => { + api.broadcast('license.module', licenseModule); + }); + } + + async started(): Promise { + if (!isEnterprise()) { + return; + } + + Authorization.addRoleRestrictions('guest', guestPermissions); + resetEnterprisePermissions(); + } + hasLicense(feature: string): boolean { return hasLicense(feature); } @@ -19,4 +48,4 @@ class License extends ServiceClass implements ILicense { } } -api.registerService(new License()); +api.registerService(new LicenseService()); diff --git a/server/sdk/types/IAuthorization.ts b/server/sdk/types/IAuthorization.ts index bedfcc64085ed..eaad0b59cf7e4 100644 --- a/server/sdk/types/IAuthorization.ts +++ b/server/sdk/types/IAuthorization.ts @@ -7,5 +7,6 @@ export interface IAuthorization { hasAllPermission(userId: string, permissions: string[], scope?: string): Promise; hasPermission(userId: string, permissionId: string, scope?: string): Promise; hasAtLeastOnePermission(userId: string, permissions: string[], scope?: string): Promise; + addRoleRestrictions(role: string, permissions: string[]): Promise; canAccessRoom: RoomAccessValidator; } diff --git a/server/services/authorization/service.ts b/server/services/authorization/service.ts index 1165d68d1455a..3d08dbeae194b 100644 --- a/server/services/authorization/service.ts +++ b/server/services/authorization/service.ts @@ -1,4 +1,3 @@ -// import mem from 'mem'; import { Db, Collection } from 'mongodb'; import mem from 'mem'; @@ -74,6 +73,10 @@ export class Authorization extends ServiceClass implements IAuthorization { return canAccessRoom(...args); } + async addRoleRestrictions(role: string, permissions: string[]): Promise { + AuthorizationUtils.addRolePermissionWhiteList(role, permissions); + } + private async rolesHasPermission(permission: string, roles: string[]): Promise { // TODO this AuthorizationUtils should be brought to this service. currently its state is kept on the application only, but it needs to kept here if (AuthorizationUtils.isPermissionRestrictedForRoleList(permission, roles)) { From fcc7440094ae49ca797fafc1e3df6424c79a27c4 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Thu, 15 Oct 2020 15:51:31 -0300 Subject: [PATCH 167/198] Remove AuthorizationUtils TODO --- server/services/authorization/service.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/server/services/authorization/service.ts b/server/services/authorization/service.ts index 3d08dbeae194b..25c7180b242a8 100644 --- a/server/services/authorization/service.ts +++ b/server/services/authorization/service.ts @@ -78,7 +78,6 @@ export class Authorization extends ServiceClass implements IAuthorization { } private async rolesHasPermission(permission: string, roles: string[]): Promise { - // TODO this AuthorizationUtils should be brought to this service. currently its state is kept on the application only, but it needs to kept here if (AuthorizationUtils.isPermissionRestrictedForRoleList(permission, roles)) { return false; } From ab379adf05db2de296add6e773b582a405bd123c Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Thu, 15 Oct 2020 17:01:20 -0300 Subject: [PATCH 168/198] Convert listeners/notifications to module --- ee/server/broker.ts | 24 +++++---- ee/server/services/DDPStreamer/DDPStreamer.ts | 3 ++ ee/server/services/DDPStreamer/service.ts | 3 -- .../listeners/listeners.module.ts} | 54 +++++++++---------- server/sdk/lib/LocalBroker.ts | 27 ++++------ server/sdk/types/ServiceClass.ts | 18 ++++--- server/services/meteor/service.ts | 5 +- server/services/startup.ts | 3 -- 8 files changed, 67 insertions(+), 70 deletions(-) rename server/{services/listeners/notification.ts => modules/listeners/listeners.module.ts} (77%) diff --git a/ee/server/broker.ts b/ee/server/broker.ts index d3f2078b1aa39..bb2221a4fcb7c 100644 --- a/ee/server/broker.ts +++ b/ee/server/broker.ts @@ -103,18 +103,20 @@ class NetworkBroker implements IBroker { const service: ServiceSchema = { name, actions: {}, - // Prevent listen events when not allowed except by `license.module` - events: Object.fromEntries(Object.entries(instance.getEvents()).map(([event, fn]) => { - if (!this.whitelist.events.includes(event)) { - const originalFn = fn; - fn = (...args: any[]): any => { - if (this.allowed) { - return originalFn(...args); - } - }; + events: instance.getEvents().reduce>((map, eventName) => { + if (this.whitelist.events.includes(eventName)) { + map[eventName] = (data: Parameters): void => instance.emit(eventName, ...data); + return map; } - return [event, (data: any[]): void => fn(...data)]; - })), + + map[eventName] = (data: Parameters): any => { + if (this.allowed) { + return instance.emit(eventName, ...data); + } + }; + + return map; + }, {}), }; if (!service.events || !service.actions) { diff --git a/ee/server/services/DDPStreamer/DDPStreamer.ts b/ee/server/services/DDPStreamer/DDPStreamer.ts index 41e1e695dec92..bd0952b9223e6 100644 --- a/ee/server/services/DDPStreamer/DDPStreamer.ts +++ b/ee/server/services/DDPStreamer/DDPStreamer.ts @@ -10,6 +10,7 @@ import { ServiceClass } from '../../../../server/sdk/types/ServiceClass'; import { events } from './configureServer'; import notifications from './streams/index'; import { StreamerCentral } from '../../../../server/modules/streamer/streamer.module'; +import { ListenersModule } from '../../../../server/modules/listeners/listeners.module'; const { PORT: port = 4000, @@ -104,6 +105,8 @@ export class DDPStreamer extends ServiceClass { constructor() { super(); + new ListenersModule(this, notifications); + // [STREAMER_EVENTS.STREAM]([streamer, eventName, payload]) { this.onEvent('stream', ([streamer, eventName, args]): void => { const stream = StreamerCentral.instances[streamer]; diff --git a/ee/server/services/DDPStreamer/service.ts b/ee/server/services/DDPStreamer/service.ts index d69a56b616e4c..2dcaaadea5eec 100755 --- a/ee/server/services/DDPStreamer/service.ts +++ b/ee/server/services/DDPStreamer/service.ts @@ -2,8 +2,5 @@ import '../../broker'; import { api } from '../../../../server/sdk/api'; import { DDPStreamer } from './DDPStreamer'; -import { NotificationService } from '../../../../server/services/listeners/notification'; -import notifications from './streams/index'; api.registerService(new DDPStreamer()); -api.registerService(new NotificationService(notifications)); diff --git a/server/services/listeners/notification.ts b/server/modules/listeners/listeners.module.ts similarity index 77% rename from server/services/listeners/notification.ts rename to server/modules/listeners/listeners.module.ts index 0b177e5db58d1..9ced8686ad70a 100644 --- a/server/services/listeners/notification.ts +++ b/server/modules/listeners/listeners.module.ts @@ -1,5 +1,5 @@ -import { ServiceClass } from '../../sdk/types/ServiceClass'; -import { NotificationsModule } from '../../modules/notifications/notifications.module'; +import { IServiceClass } from '../../sdk/types/ServiceClass'; +import { NotificationsModule } from '../notifications/notifications.module'; import { EnterpriseSettings, MeteorService } from '../../sdk/index'; import { IRoutingManagerConfig } from '../../../definition/IRoutingManagerConfig'; @@ -12,28 +12,24 @@ const STATUS_MAP: {[k: string]: number} = { export const minimongoChangeMap: Record = { inserted: 'added', updated: 'changed', removed: 'removed' }; -// TODO: Convert to module and implement/import on monolith and on DDPStreamer -export class NotificationService extends ServiceClass { - protected name = 'notification'; - +export class ListenersModule { constructor( + service: IServiceClass, notifications: NotificationsModule, ) { - super(); - - this.onEvent('emoji.deleteCustom', (emoji) => { + service.onEvent('emoji.deleteCustom', (emoji) => { notifications.notifyLogged('deleteEmojiCustom', { emojiData: emoji, }); }); - this.onEvent('emoji.updateCustom', (emoji) => { + service.onEvent('emoji.updateCustom', (emoji) => { notifications.notifyLogged('updateEmojiCustom', { emojiData: emoji, }); }); - this.onEvent('notify.ephemeralMessage', (uid, rid, message) => { + service.onEvent('notify.ephemeralMessage', (uid, rid, message) => { notifications.notifyLogged(`${ uid }/message`, { groupable: false, ...message, @@ -43,45 +39,45 @@ export class NotificationService extends ServiceClass { }); }); - this.onEvent('permission.changed', ({ clientAction, data }) => { + service.onEvent('permission.changed', ({ clientAction, data }) => { notifications.notifyLogged('permissions-changed', clientAction, data); }); - this.onEvent('room.avatarUpdate', ({ _id: rid, avatarETag: etag }) => { + service.onEvent('room.avatarUpdate', ({ _id: rid, avatarETag: etag }) => { notifications.notifyLogged('updateAvatar', { rid, etag, }); }); - this.onEvent('user.avatarUpdate', ({ username, avatarETag: etag }) => { + service.onEvent('user.avatarUpdate', ({ username, avatarETag: etag }) => { notifications.notifyLogged('updateAvatar', { username, etag, }); }); - this.onEvent('user.deleted', ({ _id: userId }) => { + service.onEvent('user.deleted', ({ _id: userId }) => { notifications.notifyLogged('Users:Deleted', { userId, }); }); - this.onEvent('user.deleteCustomStatus', (userStatus) => { + service.onEvent('user.deleteCustomStatus', (userStatus) => { notifications.notifyLogged('deleteCustomUserStatus', { userStatusData: userStatus, }); }); - this.onEvent('user.nameChanged', (user) => { + service.onEvent('user.nameChanged', (user) => { notifications.notifyLogged('Users:NameChanged', user); }); - this.onEvent('user.roleUpdate', (update) => { + service.onEvent('user.roleUpdate', (update) => { notifications.notifyLogged('roles-change', update); }); - this.onEvent('userpresence', ({ user }) => { + service.onEvent('userpresence', ({ user }) => { const { _id, username, status, statusText, } = user; @@ -92,13 +88,13 @@ export class NotificationService extends ServiceClass { notifications.notifyLogged('user-status', [_id, username, STATUS_MAP[status], statusText]); }); - this.onEvent('user.updateCustomStatus', (userStatus) => { + service.onEvent('user.updateCustomStatus', (userStatus) => { notifications.notifyLogged('updateCustomUserStatus', { userStatusData: userStatus, }); }); - this.onEvent('watch.messages', ({ message }) => { + service.onEvent('watch.messages', ({ message }) => { if (!message.rid) { return; } @@ -111,7 +107,7 @@ export class NotificationService extends ServiceClass { notifications.streamRoomMessage.emitWithoutBroadcast(message.rid, message); }); - this.onEvent('watch.subscriptions', ({ clientAction, subscription }) => { + service.onEvent('watch.subscriptions', ({ clientAction, subscription }) => { if (!subscription.u?._id) { return; } @@ -131,7 +127,7 @@ export class NotificationService extends ServiceClass { ); }); - this.onEvent('watch.roles', ({ clientAction, role }): void => { + service.onEvent('watch.roles', ({ clientAction, role }): void => { const payload = { type: clientAction, ...role, @@ -148,7 +144,7 @@ export class NotificationService extends ServiceClass { return autoAssignAgent; } - this.onEvent('watch.inquiries', async ({ clientAction, inquiry, diff }): Promise => { + service.onEvent('watch.inquiries', async ({ clientAction, inquiry, diff }): Promise => { const config = await getRoutingManagerConfig(); if (config.autoAssignAgent) { return; @@ -178,7 +174,7 @@ export class NotificationService extends ServiceClass { } }); - this.onEvent('watch.settings', async ({ clientAction, setting }): Promise => { + service.onEvent('watch.settings', async ({ clientAction, setting }): Promise => { if (setting._id === 'Livechat_Routing_Method') { autoAssignAgent = undefined; } @@ -211,7 +207,7 @@ export class NotificationService extends ServiceClass { notifications.notifyLogged('private-settings-changed', clientAction, value); }); - this.onEvent('watch.rooms', ({ clientAction, room }): void => { + service.onEvent('watch.rooms', ({ clientAction, room }): void => { // this emit will cause the user to receive a 'rooms-changed' event notifications.streamUser.__emit(room._id, clientAction, room); @@ -219,7 +215,7 @@ export class NotificationService extends ServiceClass { notifications.streamRoomData.emitWithoutBroadcast(room._id, clientAction, room); }); - this.onEvent('watch.users', ({ clientAction, data, diff, id }): void => { + service.onEvent('watch.users', ({ clientAction, data, diff, id }): void => { switch (clientAction) { case 'updated': notifications.notifyUserInThisInstance(id, 'userData', { diff, type: clientAction }); @@ -233,7 +229,7 @@ export class NotificationService extends ServiceClass { } }); - this.onEvent('watch.integrationHistory', ({ clientAction, data, diff, id }): void => { + service.onEvent('watch.integrationHistory', ({ clientAction, data, diff, id }): void => { if (!data?.integration?._id) { return; } @@ -249,7 +245,7 @@ export class NotificationService extends ServiceClass { } }); - this.onEvent('watch.livechatDepartmentAgents', ({ clientAction, data }): void => { + service.onEvent('watch.livechatDepartmentAgents', ({ clientAction, data }): void => { const { agentId } = data; if (!agentId) { return; diff --git a/server/sdk/lib/LocalBroker.ts b/server/sdk/lib/LocalBroker.ts index c70a57c9808af..636e81e51f24b 100644 --- a/server/sdk/lib/LocalBroker.ts +++ b/server/sdk/lib/LocalBroker.ts @@ -1,3 +1,5 @@ +import { EventEmitter } from 'events'; + import { IBroker, IBrokerNode } from '../types/IBroker'; import { ServiceClass } from '../types/ServiceClass'; import { asyncLocalStorage } from '..'; @@ -6,7 +8,7 @@ import { EventSignatures } from './Events'; export class LocalBroker implements IBroker { private methods = new Map(); - private events = new Map>(); + private events = new EventEmitter(); async call(method: string, data: any): Promise { const result = await asyncLocalStorage.run({ @@ -26,12 +28,9 @@ export class LocalBroker implements IBroker { destroyService(instance: ServiceClass): void { const namespace = instance.getName(); - for (const [event, fn] of Object.entries(instance.getEvents())) { - const fns = this.events.get(event); - if (fns) { - fns.delete(fn); - } - } + instance.getEvents().forEach((eventName) => { + this.events.removeListener(eventName, instance.emit); + }); const methods = instance.constructor?.name === 'Object' ? Object.getOwnPropertyNames(instance) : Object.getOwnPropertyNames(Object.getPrototypeOf(instance)); for (const method of methods) { @@ -46,11 +45,9 @@ export class LocalBroker implements IBroker { createService(instance: ServiceClass): void { const namespace = instance.getName(); - for (const [event, fn] of Object.entries(instance.getEvents())) { - const fns = this.events.get(event) || new Set(); - fns.add(fn); - this.events.set(event, fns); - } + instance.getEvents().forEach((eventName) => { + this.events.on(eventName, instance.emit); + }); const methods = instance.constructor?.name === 'Object' ? Object.getOwnPropertyNames(instance) : Object.getOwnPropertyNames(Object.getPrototypeOf(instance)); for (const method of methods) { @@ -64,10 +61,8 @@ export class LocalBroker implements IBroker { } async broadcast(event: T, ...args: Parameters): Promise { - const fns = this.events.get(event); - if (fns) { - fns.forEach((fn) => fn(...args)); - } + // Pass the event name twice to forward it to the instance's emit method + this.events.emit(event, event, ...args); } async nodeList(): Promise { diff --git a/server/sdk/types/ServiceClass.ts b/server/sdk/types/ServiceClass.ts index 70c9b16fa5d1c..1aff77275030b 100644 --- a/server/sdk/types/ServiceClass.ts +++ b/server/sdk/types/ServiceClass.ts @@ -1,3 +1,5 @@ +import { EventEmitter } from 'events'; + import { asyncLocalStorage } from '..'; import { IBroker, IBrokerNode } from './IBroker'; import { EventSignatures } from '../lib/Events'; @@ -28,6 +30,8 @@ export interface IServiceClass { onNodeUpdated?({ node }: {node: IBrokerNode }): void; onNodeDisconnected?({ node, unexpected }: {node: IBrokerNode; unexpected: boolean}): Promise; + onEvent(event: T, handler: EventSignatures[T]): void; + created?(): Promise; started?(): Promise; stopped?(): Promise; @@ -36,10 +40,10 @@ export interface IServiceClass { export abstract class ServiceClass implements IServiceClass { protected name: string; - protected events = new Map(); + protected events = new EventEmitter(); - getEvents(): Record { - return Object.fromEntries(this.events.entries()); + getEvents(): Array { + return this.events.eventNames() as unknown as Array; } getName(): string { @@ -51,10 +55,10 @@ export abstract class ServiceClass implements IServiceClass { } public onEvent(event: T, handler: EventSignatures[T]): void { - if (this.events.has(event)) { - throw new Error(`event "${ event }" already registered`); - } + this.events.on(event, handler); + } - this.events.set(event, handler); + public emit(event: T, ...args: Parameters): void { + this.events.emit(event, ...args); } } diff --git a/server/services/meteor/service.ts b/server/services/meteor/service.ts index 361a6682a0e29..ccb09b96c997f 100644 --- a/server/services/meteor/service.ts +++ b/server/services/meteor/service.ts @@ -13,11 +13,12 @@ import { settings } from '../../../app/settings/server/functions/settings'; import { setValue, updateValue } from '../../../app/settings/server/raw'; import { IRoutingManagerConfig } from '../../../definition/IRoutingManagerConfig'; import { RoutingManager } from '../../../app/livechat/server/lib/RoutingManager'; -import { minimongoChangeMap } from '../listeners/notification'; import { onlineAgents, monitorAgents } from '../../../app/livechat/server/lib/stream/agentStatus'; import { IUser } from '../../../definition/IUser'; import { matrixBroadCastActions } from '../../stream/streamBroadcast'; import { integrations } from '../../../app/integrations/server/lib/triggerHandler'; +import { ListenersModule, minimongoChangeMap } from '../../modules/listeners/listeners.module'; +import notifications from '../../../app/notifications/server/lib/Notifications'; const autoUpdateRecords = new Map(); @@ -116,6 +117,8 @@ export class MeteorService extends ServiceClass implements IMeteor { constructor() { super(); + new ListenersModule(this, notifications); + this.onEvent('watch.settings', async ({ clientAction, setting }): Promise => { if (clientAction !== 'removed') { settings.storeSettingValue(setting, false); diff --git a/server/services/startup.ts b/server/services/startup.ts index 9c8a3162b7724..bca10b8b45dc2 100644 --- a/server/services/startup.ts +++ b/server/services/startup.ts @@ -3,9 +3,6 @@ import { MongoInternals } from 'meteor/mongo'; import { api } from '../sdk/api'; import { Authorization } from './authorization/service'; import { MeteorService } from './meteor/service'; -import { NotificationService } from './listeners/notification'; -import notifications from '../../app/notifications/server/lib/Notifications'; api.registerService(new Authorization(MongoInternals.defaultRemoteCollectionDriver().mongo.db)); api.registerService(new MeteorService()); -api.registerService(new NotificationService(notifications)); From 780d48d6072d9848bfe5f811315b3cb35462c67d Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Thu, 15 Oct 2020 17:29:12 -0300 Subject: [PATCH 169/198] Remove uncessary todo --- server/modules/listeners/listeners.module.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/server/modules/listeners/listeners.module.ts b/server/modules/listeners/listeners.module.ts index 9ced8686ad70a..5d5cb2be49656 100644 --- a/server/modules/listeners/listeners.module.ts +++ b/server/modules/listeners/listeners.module.ts @@ -211,7 +211,6 @@ export class ListenersModule { // this emit will cause the user to receive a 'rooms-changed' event notifications.streamUser.__emit(room._id, clientAction, room); - // TODO validate emitWithoutBroadcast notifications.streamRoomData.emitWithoutBroadcast(room._id, clientAction, room); }); From b54c04f8c18025f05a5743981b0174ae1583797d Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Thu, 15 Oct 2020 17:41:01 -0300 Subject: [PATCH 170/198] Remove unecessary type cast --- ee/server/services/DDPStreamer/Streamer.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/ee/server/services/DDPStreamer/Streamer.ts b/ee/server/services/DDPStreamer/Streamer.ts index da1022f3da779..2932b52a09178 100644 --- a/ee/server/services/DDPStreamer/Streamer.ts +++ b/ee/server/services/DDPStreamer/Streamer.ts @@ -45,10 +45,9 @@ export class Stream extends Streamer { readOnly: false, // the data can be modified as needed }; - // TODO: missing typing const data = { - meteor: [Buffer.concat((WebSocket as any).Sender.frame(Buffer.from(`a${ JSON.stringify([getMsg]) }`), options))], - normal: [Buffer.concat((WebSocket as any).Sender.frame(Buffer.from(getMsg), options))], + meteor: [Buffer.concat(WebSocket.Sender.frame(Buffer.from(`a${ JSON.stringify([getMsg]) }`), options))], + normal: [Buffer.concat(WebSocket.Sender.frame(Buffer.from(getMsg), options))], }; for await (const { subscription } of subscriptions) { @@ -58,8 +57,7 @@ export class Stream extends Streamer { if (await this.isEmitAllowed(subscription, eventName, ...args)) { await new Promise((resolve) => { - // TODO: missing typing - (subscription.client.ws as any)._sender.sendFrame( + subscription.client.ws._sender.sendFrame( data[subscription.client.meteorClient ? 'meteor' : 'normal'], resolve, ); From cb0088629d521c626f43db8bdc6427ff82eff73a Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Thu, 15 Oct 2020 17:52:25 -0300 Subject: [PATCH 171/198] Build services Docker images --- .github/workflows/build_and_test.yml | 34 +++++++++++++++++++ ee/server/services/Dockerfile | 8 ++--- .../services/{Account => account}/Account.ts | 0 .../{Account => account}/lib/utils.ts | 0 .../services/{Account => account}/service.ts | 0 .../service.ts | 0 .../{DDPStreamer => ddp-streamer}/Client.ts | 0 .../DDPStreamer.ts | 0 .../Publication.ts | 0 .../{DDPStreamer => ddp-streamer}/Server.ts | 0 .../{DDPStreamer => ddp-streamer}/Streamer.ts | 0 .../configureServer.ts | 0 .../constants.ts | 0 .../lib/utils.ts | 0 .../{DDPStreamer => ddp-streamer}/service.ts | 0 .../streams/index.ts | 0 .../types/IPacket.ts | 0 .../types/ws.d.ts | 0 ee/server/services/docker-compose.yml | 22 ++++++------ ee/server/services/ecosystem.config.js | 10 +++--- .../{Presence => presence}/Presence.ts | 0 .../actions/newConnection.ts | 0 .../actions/removeConnection.ts | 0 .../actions/removeLostConnections.ts | 0 .../actions/setStatus.ts | 0 .../actions/updateUserPresence.ts | 0 .../lib/processConnectionStatus.ts | 0 .../{Presence => presence}/service.ts | 0 .../{StreamHub => stream-hub}/StreamHub.ts | 0 .../{StreamHub => stream-hub}/service.ts | 0 .../{StreamHub => stream-hub}/watchUsers.ts | 0 31 files changed, 54 insertions(+), 20 deletions(-) rename ee/server/services/{Account => account}/Account.ts (100%) rename ee/server/services/{Account => account}/lib/utils.ts (100%) rename ee/server/services/{Account => account}/service.ts (100%) rename ee/server/services/{Authorization => authorization}/service.ts (100%) rename ee/server/services/{DDPStreamer => ddp-streamer}/Client.ts (100%) rename ee/server/services/{DDPStreamer => ddp-streamer}/DDPStreamer.ts (100%) rename ee/server/services/{DDPStreamer => ddp-streamer}/Publication.ts (100%) rename ee/server/services/{DDPStreamer => ddp-streamer}/Server.ts (100%) rename ee/server/services/{DDPStreamer => ddp-streamer}/Streamer.ts (100%) rename ee/server/services/{DDPStreamer => ddp-streamer}/configureServer.ts (100%) rename ee/server/services/{DDPStreamer => ddp-streamer}/constants.ts (100%) rename ee/server/services/{DDPStreamer => ddp-streamer}/lib/utils.ts (100%) rename ee/server/services/{DDPStreamer => ddp-streamer}/service.ts (100%) rename ee/server/services/{DDPStreamer => ddp-streamer}/streams/index.ts (100%) rename ee/server/services/{DDPStreamer => ddp-streamer}/types/IPacket.ts (100%) rename ee/server/services/{DDPStreamer => ddp-streamer}/types/ws.d.ts (100%) rename ee/server/services/{Presence => presence}/Presence.ts (100%) rename ee/server/services/{Presence => presence}/actions/newConnection.ts (100%) rename ee/server/services/{Presence => presence}/actions/removeConnection.ts (100%) rename ee/server/services/{Presence => presence}/actions/removeLostConnections.ts (100%) rename ee/server/services/{Presence => presence}/actions/setStatus.ts (100%) rename ee/server/services/{Presence => presence}/actions/updateUserPresence.ts (100%) rename ee/server/services/{Presence => presence}/lib/processConnectionStatus.ts (100%) rename ee/server/services/{Presence => presence}/service.ts (100%) rename ee/server/services/{StreamHub => stream-hub}/StreamHub.ts (100%) rename ee/server/services/{StreamHub => stream-hub}/service.ts (100%) rename ee/server/services/{StreamHub => stream-hub}/watchUsers.ts (100%) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 382c86009fbe1..10845ee05b58e 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -487,3 +487,37 @@ jobs: docker build -t ${IMAGE}:develop . docker push ${IMAGE}:develop + + services-image-build: + runs-on: ubuntu-latest + # TODO uncomment 'needs' before merging! + # needs: deploy + if: github.event.pull_request.head.repo.full_name == github.repository + + strategy: + matrix: + service: ["account", "authorization", "ddp-streamer", "presence", "stream-hub"] + + steps: + - uses: actions/checkout@v2 + + - name: Use Node.js 12.18.4 + uses: actions/setup-node@v1 + with: + node-version: "12.18.4" + + - name: Build Docker images + env: + DOCKER_USER: ${{ secrets.DOCKER_USER }} + DOCKER_PASS: ${{ secrets.DOCKER_PASS }} + run: | + cd ./ee/server/services + npm i + npm run build + + echo "Building Docker image for service: ${{ matrix.service }}" + + docker build --build-arg SERVICE=${{ matrix.service }} -t rocketchat/${{ matrix.service }}-service . + + docker login -u $DOCKER_USER -p $DOCKER_PASS + docker push rocketchat/${{ matrix.service }}-service diff --git a/ee/server/services/Dockerfile b/ee/server/services/Dockerfile index 3ee00e074f7bb..bf8a4cf1d4a3a 100644 --- a/ee/server/services/Dockerfile +++ b/ee/server/services/Dockerfile @@ -17,14 +17,14 @@ WORKDIR /app COPY --from=build /app . +# add dist/ folder from tsc so we don't need to add all rocket.chat repo ADD ./dist . -ENV NODE_ENV=production -ENV PORT=80 +ENV NODE_ENV=production \ + PORT=3000 WORKDIR /app/ee/server/services/${SERVICE} -EXPOSE 9458 -EXPOSE 80 +EXPOSE 3000 9458 CMD ["node", "service.js"] diff --git a/ee/server/services/Account/Account.ts b/ee/server/services/account/Account.ts similarity index 100% rename from ee/server/services/Account/Account.ts rename to ee/server/services/account/Account.ts diff --git a/ee/server/services/Account/lib/utils.ts b/ee/server/services/account/lib/utils.ts similarity index 100% rename from ee/server/services/Account/lib/utils.ts rename to ee/server/services/account/lib/utils.ts diff --git a/ee/server/services/Account/service.ts b/ee/server/services/account/service.ts similarity index 100% rename from ee/server/services/Account/service.ts rename to ee/server/services/account/service.ts diff --git a/ee/server/services/Authorization/service.ts b/ee/server/services/authorization/service.ts similarity index 100% rename from ee/server/services/Authorization/service.ts rename to ee/server/services/authorization/service.ts diff --git a/ee/server/services/DDPStreamer/Client.ts b/ee/server/services/ddp-streamer/Client.ts similarity index 100% rename from ee/server/services/DDPStreamer/Client.ts rename to ee/server/services/ddp-streamer/Client.ts diff --git a/ee/server/services/DDPStreamer/DDPStreamer.ts b/ee/server/services/ddp-streamer/DDPStreamer.ts similarity index 100% rename from ee/server/services/DDPStreamer/DDPStreamer.ts rename to ee/server/services/ddp-streamer/DDPStreamer.ts diff --git a/ee/server/services/DDPStreamer/Publication.ts b/ee/server/services/ddp-streamer/Publication.ts similarity index 100% rename from ee/server/services/DDPStreamer/Publication.ts rename to ee/server/services/ddp-streamer/Publication.ts diff --git a/ee/server/services/DDPStreamer/Server.ts b/ee/server/services/ddp-streamer/Server.ts similarity index 100% rename from ee/server/services/DDPStreamer/Server.ts rename to ee/server/services/ddp-streamer/Server.ts diff --git a/ee/server/services/DDPStreamer/Streamer.ts b/ee/server/services/ddp-streamer/Streamer.ts similarity index 100% rename from ee/server/services/DDPStreamer/Streamer.ts rename to ee/server/services/ddp-streamer/Streamer.ts diff --git a/ee/server/services/DDPStreamer/configureServer.ts b/ee/server/services/ddp-streamer/configureServer.ts similarity index 100% rename from ee/server/services/DDPStreamer/configureServer.ts rename to ee/server/services/ddp-streamer/configureServer.ts diff --git a/ee/server/services/DDPStreamer/constants.ts b/ee/server/services/ddp-streamer/constants.ts similarity index 100% rename from ee/server/services/DDPStreamer/constants.ts rename to ee/server/services/ddp-streamer/constants.ts diff --git a/ee/server/services/DDPStreamer/lib/utils.ts b/ee/server/services/ddp-streamer/lib/utils.ts similarity index 100% rename from ee/server/services/DDPStreamer/lib/utils.ts rename to ee/server/services/ddp-streamer/lib/utils.ts diff --git a/ee/server/services/DDPStreamer/service.ts b/ee/server/services/ddp-streamer/service.ts similarity index 100% rename from ee/server/services/DDPStreamer/service.ts rename to ee/server/services/ddp-streamer/service.ts diff --git a/ee/server/services/DDPStreamer/streams/index.ts b/ee/server/services/ddp-streamer/streams/index.ts similarity index 100% rename from ee/server/services/DDPStreamer/streams/index.ts rename to ee/server/services/ddp-streamer/streams/index.ts diff --git a/ee/server/services/DDPStreamer/types/IPacket.ts b/ee/server/services/ddp-streamer/types/IPacket.ts similarity index 100% rename from ee/server/services/DDPStreamer/types/IPacket.ts rename to ee/server/services/ddp-streamer/types/IPacket.ts diff --git a/ee/server/services/DDPStreamer/types/ws.d.ts b/ee/server/services/ddp-streamer/types/ws.d.ts similarity index 100% rename from ee/server/services/DDPStreamer/types/ws.d.ts rename to ee/server/services/ddp-streamer/types/ws.d.ts diff --git a/ee/server/services/docker-compose.yml b/ee/server/services/docker-compose.yml index d89fd96000320..a288a705477d7 100644 --- a/ee/server/services/docker-compose.yml +++ b/ee/server/services/docker-compose.yml @@ -6,8 +6,8 @@ services: build: context: . args: - SERVICE: Authorization - # image: registry.rocket.chat/microservices_authorization-service:latest + SERVICE: authorization + image: rocketchat/authorization-service:latest env_file: .config/services/service.env depends_on: - nats @@ -17,8 +17,8 @@ services: build: context: . args: - SERVICE: Account - # image: registry.rocket.chat/microservices_accounts-service:latest + SERVICE: account + image: rocketchat/account-service:latest env_file: .config/services/service.env depends_on: - nats @@ -28,8 +28,8 @@ services: build: context: . args: - SERVICE: Presence - # image: registry.rocket.chat/microservices_presence-service:latest + SERVICE: presence + image: rocketchat/presence-service:latest env_file: .config/services/service.env depends_on: - nats @@ -39,15 +39,15 @@ services: build: context: . args: - SERVICE: DDPStreamer - # image: registry.rocket.chat/microservices_ddp-streamer:latest + SERVICE: ddp-streamer + image: rocketchat/ddp-streamer-service:latest env_file: .config/services/service.env depends_on: - nats - traefik labels: traefik.enable: true - traefik.http.services.ddp-streamer-service.loadbalancer.server.port: 80 + traefik.http.services.ddp-streamer-service.loadbalancer.server.port: 3000 traefik.http.routers.ddp-streamer-service.service: ddp-streamer-service stream-hub-service: @@ -55,8 +55,8 @@ services: build: context: . args: - SERVICE: StreamHub - # image: registry.rocket.chat/microservices_mongodb-stream-hub:latest + SERVICE: stream-hub + image: rocketchat/stream-hub-service:latest env_file: .config/services/service.env depends_on: - nats diff --git a/ee/server/services/ecosystem.config.js b/ee/server/services/ecosystem.config.js index 1c61906d6fc08..cd815c120094e 100644 --- a/ee/server/services/ecosystem.config.js +++ b/ee/server/services/ecosystem.config.js @@ -2,16 +2,16 @@ const watch = ['.', '../broker.ts', '../../../server/sdk']; module.exports = { apps: [{ - name: 'Authorization', + name: 'authorization', watch: [...watch, '../../../server/services/authorization'], }, { - name: 'Presence', + name: 'presence', }, { - name: 'Account', + name: 'account', }, { - name: 'StreamHub', + name: 'stream-hub', }, { - name: 'DDPStreamer', + name: 'ddp-streamer', }].map((app) => Object.assign(app, { script: app.script || `ts-node ${ app.name }/service.ts`, watch: app.watch || ['.', '../broker.ts', '../../../server/sdk', '../../../server/modules'], diff --git a/ee/server/services/Presence/Presence.ts b/ee/server/services/presence/Presence.ts similarity index 100% rename from ee/server/services/Presence/Presence.ts rename to ee/server/services/presence/Presence.ts diff --git a/ee/server/services/Presence/actions/newConnection.ts b/ee/server/services/presence/actions/newConnection.ts similarity index 100% rename from ee/server/services/Presence/actions/newConnection.ts rename to ee/server/services/presence/actions/newConnection.ts diff --git a/ee/server/services/Presence/actions/removeConnection.ts b/ee/server/services/presence/actions/removeConnection.ts similarity index 100% rename from ee/server/services/Presence/actions/removeConnection.ts rename to ee/server/services/presence/actions/removeConnection.ts diff --git a/ee/server/services/Presence/actions/removeLostConnections.ts b/ee/server/services/presence/actions/removeLostConnections.ts similarity index 100% rename from ee/server/services/Presence/actions/removeLostConnections.ts rename to ee/server/services/presence/actions/removeLostConnections.ts diff --git a/ee/server/services/Presence/actions/setStatus.ts b/ee/server/services/presence/actions/setStatus.ts similarity index 100% rename from ee/server/services/Presence/actions/setStatus.ts rename to ee/server/services/presence/actions/setStatus.ts diff --git a/ee/server/services/Presence/actions/updateUserPresence.ts b/ee/server/services/presence/actions/updateUserPresence.ts similarity index 100% rename from ee/server/services/Presence/actions/updateUserPresence.ts rename to ee/server/services/presence/actions/updateUserPresence.ts diff --git a/ee/server/services/Presence/lib/processConnectionStatus.ts b/ee/server/services/presence/lib/processConnectionStatus.ts similarity index 100% rename from ee/server/services/Presence/lib/processConnectionStatus.ts rename to ee/server/services/presence/lib/processConnectionStatus.ts diff --git a/ee/server/services/Presence/service.ts b/ee/server/services/presence/service.ts similarity index 100% rename from ee/server/services/Presence/service.ts rename to ee/server/services/presence/service.ts diff --git a/ee/server/services/StreamHub/StreamHub.ts b/ee/server/services/stream-hub/StreamHub.ts similarity index 100% rename from ee/server/services/StreamHub/StreamHub.ts rename to ee/server/services/stream-hub/StreamHub.ts diff --git a/ee/server/services/StreamHub/service.ts b/ee/server/services/stream-hub/service.ts similarity index 100% rename from ee/server/services/StreamHub/service.ts rename to ee/server/services/stream-hub/service.ts diff --git a/ee/server/services/StreamHub/watchUsers.ts b/ee/server/services/stream-hub/watchUsers.ts similarity index 100% rename from ee/server/services/StreamHub/watchUsers.ts rename to ee/server/services/stream-hub/watchUsers.ts From 719ccf181a61e3a0536164c0c164e387a2f85b70 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Thu, 15 Oct 2020 17:55:55 -0300 Subject: [PATCH 172/198] Add npm install to root repo --- .github/workflows/build_and_test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 10845ee05b58e..8d340fa504e67 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -511,6 +511,8 @@ jobs: DOCKER_USER: ${{ secrets.DOCKER_USER }} DOCKER_PASS: ${{ secrets.DOCKER_PASS }} run: | + npm i + cd ./ee/server/services npm i npm run build From bc1014ceb885a5f052cfb2f7b5b5dc0987a293bb Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Thu, 15 Oct 2020 19:24:20 -0300 Subject: [PATCH 173/198] Fix error on localbroker emit --- server/sdk/types/ServiceClass.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/server/sdk/types/ServiceClass.ts b/server/sdk/types/ServiceClass.ts index 1aff77275030b..5b28aedac3f0d 100644 --- a/server/sdk/types/ServiceClass.ts +++ b/server/sdk/types/ServiceClass.ts @@ -42,6 +42,10 @@ export abstract class ServiceClass implements IServiceClass { protected events = new EventEmitter(); + constructor() { + this.emit = this.emit.bind(this); + } + getEvents(): Array { return this.events.eventNames() as unknown as Array; } From fc5f9bb18b53df9f23b08a19c0508abdbd0ebe1b Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Fri, 16 Oct 2020 11:38:31 -0300 Subject: [PATCH 174/198] Use ts-node --files to run on PM2 --- ee/server/services/ecosystem.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ee/server/services/ecosystem.config.js b/ee/server/services/ecosystem.config.js index cd815c120094e..e7da5b8e973be 100644 --- a/ee/server/services/ecosystem.config.js +++ b/ee/server/services/ecosystem.config.js @@ -13,7 +13,7 @@ module.exports = { }, { name: 'ddp-streamer', }].map((app) => Object.assign(app, { - script: app.script || `ts-node ${ app.name }/service.ts`, + script: app.script || `ts-node --files ${ app.name }/service.ts`, watch: app.watch || ['.', '../broker.ts', '../../../server/sdk', '../../../server/modules'], instances: 1, env: { From ddd277c48155ce773f54832aba7208e375bb585f Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Fri, 16 Oct 2020 15:03:58 -0300 Subject: [PATCH 175/198] Upgrade mongodb type definition --- mongodb.aug.d.ts | 8 -------- package-lock.json | 6 +++--- package.json | 2 +- 3 files changed, 4 insertions(+), 12 deletions(-) delete mode 100644 mongodb.aug.d.ts diff --git a/mongodb.aug.d.ts b/mongodb.aug.d.ts deleted file mode 100644 index 450b440065b23..0000000000000 --- a/mongodb.aug.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -import 'mongodb'; - -declare module 'mongodb' { - // eslint-disable-next-line @typescript-eslint/interface-name-prefix - export interface FindOneOptions { - awaitData?: boolean; - } -} diff --git a/package-lock.json b/package-lock.json index 1d8e66e52c3b9..7b94f5646503c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8445,9 +8445,9 @@ } }, "@types/mongodb": { - "version": "3.5.26", - "resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.5.26.tgz", - "integrity": "sha512-p0X2VJgIBNHfNBdZdzzG8eQ/3bf6mQoXDT0UhVyVEdSzXEa1+2pFcwGvEZp72sjztyBwfRKlgrXMjCVavLcuGg==", + "version": "3.5.28", + "resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.5.28.tgz", + "integrity": "sha512-wSbda4QQ3QFsWUHJzC5TSYImtRDtPhPb+lW7ICMtWvvtQMQRXbVTz/Z1P5A3XujK5bZIAV7vk5CXQG6Adz7+Cw==", "dev": true, "requires": { "@types/bson": "*", diff --git a/package.json b/package.json index 4639d29b418ab..f0ade71d22f8d 100644 --- a/package.json +++ b/package.json @@ -71,7 +71,7 @@ "@types/mocha": "^8.0.3", "@types/mock-require": "^2.0.0", "@types/moment-timezone": "^0.5.30", - "@types/mongodb": "^3.5.26", + "@types/mongodb": "^3.5.28", "@types/msgpack5": "^3.4.1", "@types/node": "^10.12.14", "@types/react-dom": "^16.9.8", From 3a5a87c92cc82eaf8a095babf10f1b1f56479c83 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Fri, 16 Oct 2020 15:39:37 -0300 Subject: [PATCH 176/198] Revert "Upgrade mongodb type definition" This reverts commit ddd277c48155ce773f54832aba7208e375bb585f. --- mongodb.aug.d.ts | 8 ++++++++ package-lock.json | 6 +++--- package.json | 2 +- 3 files changed, 12 insertions(+), 4 deletions(-) create mode 100644 mongodb.aug.d.ts diff --git a/mongodb.aug.d.ts b/mongodb.aug.d.ts new file mode 100644 index 0000000000000..450b440065b23 --- /dev/null +++ b/mongodb.aug.d.ts @@ -0,0 +1,8 @@ +import 'mongodb'; + +declare module 'mongodb' { + // eslint-disable-next-line @typescript-eslint/interface-name-prefix + export interface FindOneOptions { + awaitData?: boolean; + } +} diff --git a/package-lock.json b/package-lock.json index 7b94f5646503c..1d8e66e52c3b9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8445,9 +8445,9 @@ } }, "@types/mongodb": { - "version": "3.5.28", - "resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.5.28.tgz", - "integrity": "sha512-wSbda4QQ3QFsWUHJzC5TSYImtRDtPhPb+lW7ICMtWvvtQMQRXbVTz/Z1P5A3XujK5bZIAV7vk5CXQG6Adz7+Cw==", + "version": "3.5.26", + "resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.5.26.tgz", + "integrity": "sha512-p0X2VJgIBNHfNBdZdzzG8eQ/3bf6mQoXDT0UhVyVEdSzXEa1+2pFcwGvEZp72sjztyBwfRKlgrXMjCVavLcuGg==", "dev": true, "requires": { "@types/bson": "*", diff --git a/package.json b/package.json index f0ade71d22f8d..4639d29b418ab 100644 --- a/package.json +++ b/package.json @@ -71,7 +71,7 @@ "@types/mocha": "^8.0.3", "@types/mock-require": "^2.0.0", "@types/moment-timezone": "^0.5.30", - "@types/mongodb": "^3.5.28", + "@types/mongodb": "^3.5.26", "@types/msgpack5": "^3.4.1", "@types/node": "^10.12.14", "@types/react-dom": "^16.9.8", From fe5882823de455a57ef13d800074ff1db8b90f93 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Fri, 16 Oct 2020 16:35:23 -0300 Subject: [PATCH 177/198] Add DISABLE_DB_WATCH env var to disable monolith oplog --- app/models/server/models/_BaseDb.js | 10 ++++++++-- app/models/server/models/_oplogHandle.ts | 20 +++++++++++++------- app/models/server/raw/index.ts | 2 +- ee/server/services/README.md | 4 ++-- server/startup/serverRunning.js | 2 +- 5 files changed, 25 insertions(+), 13 deletions(-) diff --git a/app/models/server/models/_BaseDb.js b/app/models/server/models/_BaseDb.js index 7201335c92e1f..fa4cc5326ec2e 100644 --- a/app/models/server/models/_BaseDb.js +++ b/app/models/server/models/_BaseDb.js @@ -48,6 +48,14 @@ export class BaseDb extends EventEmitter { this.wrapModel(); + if (!process.env.DISABLE_DB_WATCH) { + this.initDbWatch(); + } + + this.tryEnsureIndex({ _updatedAt: 1 }, options._updatedAtIndexOptions); + } + + initDbWatch() { const _oplogHandle = Promise.await(getOplogHandle()); // When someone start listening for changes we start oplog if available @@ -85,8 +93,6 @@ export class BaseDb extends EventEmitter { if (_oplogHandle) { this.on('newListener', handleListener); } - - this.tryEnsureIndex({ _updatedAt: 1 }, options._updatedAtIndexOptions); } get baseName() { diff --git a/app/models/server/models/_oplogHandle.ts b/app/models/server/models/_oplogHandle.ts index 34164b0ab88cc..f6e8b10ce21c2 100644 --- a/app/models/server/models/_oplogHandle.ts +++ b/app/models/server/models/_oplogHandle.ts @@ -180,17 +180,23 @@ class CustomOplogHandle { let oplogHandle: Promise; -// @ts-ignore -// eslint-disable-next-line no-undef -if (Package['disable-oplog']) { - try { - oplogHandle = Promise.await(new CustomOplogHandle().start()); - } catch (e) { - console.error(e.message); +if (!process.env.DISABLE_DB_WATCH) { + // @ts-ignore + // eslint-disable-next-line no-undef + if (Package['disable-oplog']) { + try { + oplogHandle = Promise.await(new CustomOplogHandle().start()); + } catch (e) { + console.error(e.message); + } } } export const getOplogHandle = async (): Promise => { + if (process.env.DISABLE_DB_WATCH) { + return; + } + if (oplogHandle) { return oplogHandle; } diff --git a/app/models/server/raw/index.ts b/app/models/server/raw/index.ts index 53678b1e7a54a..b1722b74ef097 100644 --- a/app/models/server/raw/index.ts +++ b/app/models/server/raw/index.ts @@ -111,7 +111,7 @@ const map = { [Integrations.col.collectionName]: IntegrationsModel, }; -initWatchers({ +!process.env.DISABLE_DB_WATCH && initWatchers({ Messages, Users, Subscriptions, diff --git a/ee/server/services/README.md b/ee/server/services/README.md index 326dccdb3ebb8..8ab18a7d26a32 100644 --- a/ee/server/services/README.md +++ b/ee/server/services/README.md @@ -10,10 +10,10 @@ Start NATS first, you can it via Docker: docker run --rm -d -p 4222:4222 nats ``` -Then run Rocket.Chat as usual with an additional `TRANSPORTER` env var: +Then run Rocket.Chat as usual with an additional `TRANSPORTER` and `DISABLE_DB_WATCH` env vars: ``` -TRANSPORTER=nats://localhost:4222 MOLECULER_LOG_LEVEL=debug meteor +TRANSPORTER=nats://localhost:4222 MOLECULER_LOG_LEVEL=debug DISABLE_DB_WATCH=true meteor ``` Set up an Enterprise license going to Admin > Enterprise. diff --git a/server/startup/serverRunning.js b/server/startup/serverRunning.js index 9b7611951c5ca..67ed3ee4a62c4 100644 --- a/server/startup/serverRunning.js +++ b/server/startup/serverRunning.js @@ -47,7 +47,7 @@ Meteor.startup(function() { msg = msg.join('\n'); - if (!oplogEnabled) { + if (!process.env.DISABLE_DB_WATCH && !oplogEnabled) { msg += ['', '', 'OPLOG / REPLICASET IS REQUIRED TO RUN ROCKET.CHAT, MORE INFORMATION AT:', 'https://go.rocket.chat/i/oplog-required'].join('\n'); SystemLogger.error_box(msg, 'SERVER ERROR'); From 04591e905c3eb9190ed7df6ad598b3c62b26b429 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Fri, 16 Oct 2020 17:21:51 -0300 Subject: [PATCH 178/198] Increase mongodb connection poll to 15 since we have 14 change streams --- ee/server/services/mongo.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/ee/server/services/mongo.ts b/ee/server/services/mongo.ts index f7c3ef58093c6..ffee982aa1651 100644 --- a/ee/server/services/mongo.ts +++ b/ee/server/services/mongo.ts @@ -10,6 +10,7 @@ const name = /^mongodb:\/\/.*?(?::[0-9]+)?\/([^?]*)/.exec(MONGO_URL)?.[1]; const client = new MongoClient(MONGO_URL, { useUnifiedTopology: true, useNewUrlParser: true, + poolSize: 15, }); export enum Collections { From 2c37fdda31343bc2d0662ea208ac2423fbde4886 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Fri, 16 Oct 2020 17:51:39 -0300 Subject: [PATCH 179/198] Increase number of connections for stream hub --- ee/server/services/mongo.ts | 14 +++++++------- ee/server/services/stream-hub/StreamHub.ts | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ee/server/services/mongo.ts b/ee/server/services/mongo.ts index ffee982aa1651..840d2b64d7950 100644 --- a/ee/server/services/mongo.ts +++ b/ee/server/services/mongo.ts @@ -7,12 +7,6 @@ const { const name = /^mongodb:\/\/.*?(?::[0-9]+)?\/([^?]*)/.exec(MONGO_URL)?.[1]; -const client = new MongoClient(MONGO_URL, { - useUnifiedTopology: true, - useNewUrlParser: true, - poolSize: 15, -}); - export enum Collections { Subscriptions = 'rocketchat_subscription', UserSession = 'usersSessions', @@ -24,8 +18,14 @@ export enum Collections { } let db: Db; -export async function getConnection(): Promise { +export async function getConnection(poolSize = 5): Promise { if (!db) { + const client = new MongoClient(MONGO_URL, { + useUnifiedTopology: true, + useNewUrlParser: true, + poolSize, + }); + await client.connect(); db = client.db(name); } diff --git a/ee/server/services/stream-hub/StreamHub.ts b/ee/server/services/stream-hub/StreamHub.ts index 0c265fccbad39..886927326c79c 100755 --- a/ee/server/services/stream-hub/StreamHub.ts +++ b/ee/server/services/stream-hub/StreamHub.ts @@ -21,7 +21,7 @@ export class StreamHub extends ServiceClass implements IServiceClass { protected name = 'hub'; async created(): Promise { - const db = await getConnection(); + const db = await getConnection(15); const Trash = db.collection('rocketchat_trash'); From e3ce73e9c2591de55780771efaf3de2aa38dfd37 Mon Sep 17 00:00:00 2001 From: Alan Sikora Date: Mon, 19 Oct 2020 10:07:27 -0300 Subject: [PATCH 180/198] fxing user presence duplications --- app/api/server/v1/users.js | 2 -- imports/startup/client/listenActiveUsers.js | 1 - imports/startup/server/index.js | 1 - imports/users-presence/server/activeUsers.js | 36 -------------------- imports/users-presence/server/index.js | 1 - server/modules/listeners/listeners.module.ts | 2 +- 6 files changed, 1 insertion(+), 42 deletions(-) delete mode 100644 imports/users-presence/server/activeUsers.js delete mode 100644 imports/users-presence/server/index.js diff --git a/app/api/server/v1/users.js b/app/api/server/v1/users.js index b4adea3365d3f..bba27ab83549d 100644 --- a/app/api/server/v1/users.js +++ b/app/api/server/v1/users.js @@ -22,7 +22,6 @@ import { setStatusText } from '../../../lib/server'; import { findUsersToAutocomplete } from '../lib/users'; import { getUserForCheck, emailCheck } from '../../../2fa/server/code'; import { resetUserE2EEncriptionKey } from '../../../../server/lib/resetUserE2EKey'; -import { setUserStatus } from '../../../../imports/users-presence/server/activeUsers'; API.v1.addRoute('users.create', { authRequired: true }, { post() { @@ -425,7 +424,6 @@ API.v1.addRoute('users.setStatus', { authRequired: true }, { statusDefault: status, }, }); - setUserStatus(user, status); } else { throw new Meteor.Error('error-invalid-status', 'Valid status types include online, away, offline, and busy.', { method: 'users.setStatus', diff --git a/imports/startup/client/listenActiveUsers.js b/imports/startup/client/listenActiveUsers.js index becc10008aa49..90805464a1304 100644 --- a/imports/startup/client/listenActiveUsers.js +++ b/imports/startup/client/listenActiveUsers.js @@ -3,7 +3,6 @@ import { Accounts } from 'meteor/accounts-base'; import { Notifications } from '../../../app/notifications/client'; -// mirror of object in /imports/users-presence/server/activeUsers.js - keep updated const STATUS_MAP = [ 'offline', 'online', diff --git a/imports/startup/server/index.js b/imports/startup/server/index.js index 91df55e9d4f26..8b7c5bd8020b1 100644 --- a/imports/startup/server/index.js +++ b/imports/startup/server/index.js @@ -1,3 +1,2 @@ import '../../message-read-receipt/server'; import '../../personal-access-tokens/server'; -import '../../users-presence/server'; diff --git a/imports/users-presence/server/activeUsers.js b/imports/users-presence/server/activeUsers.js deleted file mode 100644 index 48ddf7626f4c7..0000000000000 --- a/imports/users-presence/server/activeUsers.js +++ /dev/null @@ -1,36 +0,0 @@ -import { UserPresenceEvents } from 'meteor/konecty:user-presence'; - -import { settings } from '../../../app/settings/server'; -import { api } from '../../../server/sdk/api'; - -// mirror of object in /imports/startup/client/listenActiveUsers.js - keep updated -export const STATUS_MAP = { - offline: 0, - online: 1, - away: 2, - busy: 3, -}; - -export const setUserStatus = (user, status/* , statusConnection*/) => { - const { - _id: uid, - username, - statusText, - } = user; - - // since this callback can be called by only one instance in the cluster - // we need to broadcast the change to all instances - api.broadcast('userpresence', { user: { status, _id: uid, username, statusText } }); // remove username -}; - -let TroubleshootDisablePresenceBroadcast; -settings.get('Troubleshoot_Disable_Presence_Broadcast', (key, value) => { - if (TroubleshootDisablePresenceBroadcast === value) { return; } - TroubleshootDisablePresenceBroadcast = value; - - if (value) { - return UserPresenceEvents.removeListener('setUserStatus', setUserStatus); - } - - UserPresenceEvents.on('setUserStatus', setUserStatus); -}); diff --git a/imports/users-presence/server/index.js b/imports/users-presence/server/index.js deleted file mode 100644 index 8b7cdc0d71e5a..0000000000000 --- a/imports/users-presence/server/index.js +++ /dev/null @@ -1 +0,0 @@ -import './activeUsers'; diff --git a/server/modules/listeners/listeners.module.ts b/server/modules/listeners/listeners.module.ts index 5d5cb2be49656..8673266188763 100644 --- a/server/modules/listeners/listeners.module.ts +++ b/server/modules/listeners/listeners.module.ts @@ -85,7 +85,7 @@ export class ListenersModule { return; } - notifications.notifyLogged('user-status', [_id, username, STATUS_MAP[status], statusText]); + notifications.notifyLoggedInThisInstance('user-status', [_id, username, STATUS_MAP[status], statusText]); }); service.onEvent('user.updateCustomStatus', (userStatus) => { From 65a2801d3580945c21a934cbc30f939f40bc92f0 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Mon, 19 Oct 2020 13:48:52 -0300 Subject: [PATCH 181/198] Change monolith change stream to use 15 connections as well --- app/models/server/models/_oplogHandle.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/server/models/_oplogHandle.ts b/app/models/server/models/_oplogHandle.ts index f6e8b10ce21c2..c3ad936669d2e 100644 --- a/app/models/server/models/_oplogHandle.ts +++ b/app/models/server/models/_oplogHandle.ts @@ -70,7 +70,7 @@ class CustomOplogHandle { this.client = new MongoClient(oplogUrl, { useUnifiedTopology: true, useNewUrlParser: true, - ...!this.usingChangeStream && { poolSize: 1 }, + poolSize: this.usingChangeStream ? 15 : 1, }); await this.client.connect(); From 5316be1c3a3dcde6e17e2708f54aa86c215c67bc Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Mon, 19 Oct 2020 14:16:51 -0300 Subject: [PATCH 182/198] Emit stdout to local clients only --- app/logger/server/streamer.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/logger/server/streamer.js b/app/logger/server/streamer.js index 24f7ed60c3a2d..3bff9032aafbe 100644 --- a/app/logger/server/streamer.js +++ b/app/logger/server/streamer.js @@ -54,13 +54,14 @@ export const StdOut = new class extends EventEmitter { Meteor.startup(() => { const handler = (string, item) => { - // TODO change to 'emit' from 'emitWithoutBroadcast' because ddp-streamer needs to receive this as well but we may need to think a way to broadcast only if needed - notifications.streamStdout.emit('stdout', { + // TODO having this as 'emitWithoutBroadcast' will not sent this data to ddp-streamer, so this data + // won't be available when using micro services. + notifications.streamStdout.emitWithoutBroadcast('stdout', { ...item, }); }; - // does not emit to StdOut if moleculer log level set to debug because it creates an infinite loop + // do not emit to StdOut if moleculer log level set to debug because it creates an infinite loop if (String(process.env.MOLECULER_LOG_LEVEL).toLowerCase() !== 'debug') { StdOut.on('write', handler); } From c2e5dde047d79ac5bc2e30b08013965a9a112ba6 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Mon, 19 Oct 2020 15:10:48 -0300 Subject: [PATCH 183/198] Broadcast status changes from monolith only --- app/api/server/v1/users.js | 2 ++ ee/server/services/stream-hub/StreamHub.ts | 3 -- ee/server/services/stream-hub/watchUsers.ts | 37 -------------------- imports/startup/client/listenActiveUsers.js | 1 + imports/startup/server/index.js | 1 + imports/users-presence/server/activeUsers.js | 36 +++++++++++++++++++ imports/users-presence/server/index.js | 1 + 7 files changed, 41 insertions(+), 40 deletions(-) delete mode 100644 ee/server/services/stream-hub/watchUsers.ts create mode 100644 imports/users-presence/server/activeUsers.js create mode 100644 imports/users-presence/server/index.js diff --git a/app/api/server/v1/users.js b/app/api/server/v1/users.js index bba27ab83549d..b4adea3365d3f 100644 --- a/app/api/server/v1/users.js +++ b/app/api/server/v1/users.js @@ -22,6 +22,7 @@ import { setStatusText } from '../../../lib/server'; import { findUsersToAutocomplete } from '../lib/users'; import { getUserForCheck, emailCheck } from '../../../2fa/server/code'; import { resetUserE2EEncriptionKey } from '../../../../server/lib/resetUserE2EKey'; +import { setUserStatus } from '../../../../imports/users-presence/server/activeUsers'; API.v1.addRoute('users.create', { authRequired: true }, { post() { @@ -424,6 +425,7 @@ API.v1.addRoute('users.setStatus', { authRequired: true }, { statusDefault: status, }, }); + setUserStatus(user, status); } else { throw new Meteor.Error('error-invalid-status', 'Valid status types include online, away, offline, and busy.', { method: 'users.setStatus', diff --git a/ee/server/services/stream-hub/StreamHub.ts b/ee/server/services/stream-hub/StreamHub.ts index 886927326c79c..a19341891b26a 100755 --- a/ee/server/services/stream-hub/StreamHub.ts +++ b/ee/server/services/stream-hub/StreamHub.ts @@ -1,4 +1,3 @@ -import { watchUsers } from './watchUsers'; import { getConnection } from '../mongo'; import { ServiceClass, IServiceClass } from '../../../../server/sdk/types/ServiceClass'; import { initWatchers } from '../../../../server/modules/watchers/watchers.module'; @@ -108,7 +107,5 @@ export class StreamHub extends ServiceClass implements IServiceClass { } }); }); - - UsersCol.watch([], { fullDocument: 'updateLookup' }).on('change', watchUsers); } } diff --git a/ee/server/services/stream-hub/watchUsers.ts b/ee/server/services/stream-hub/watchUsers.ts deleted file mode 100644 index 9ccc9828da0b6..0000000000000 --- a/ee/server/services/stream-hub/watchUsers.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { ChangeEvent } from 'mongodb'; - -import { IUser } from '../../../../definition/IUser'; -import { api } from '../../../../server/sdk/api'; - -const normalize: {[key: string]: string} = { - update: 'updated', - insert: 'inserted', - remove: 'removed', -}; - -export async function watchUsers(event: ChangeEvent): Promise { - switch (event.operationType) { - case 'insert': - case 'update': - const { updatedFields } = 'updateDescription' in event ? event.updateDescription : { updatedFields: undefined }; - // const message = await Messages.findOne(documentKey); - const user = event.fullDocument; - - if (!user) { - break; - } - - // Streamer.emitWithoutBroadcast('__my_messages__', message, {}); - if (updatedFields) { - if (updatedFields.status || updatedFields.statusText) { - const { status, _id, username, statusText } = user; // remove username - api.broadcast('userpresence', { action: normalize[event.operationType], user: { status, _id, username, statusText } }); // remove username - // RocketChat.Logger.info('User: userpresence', { status, _id, username, statusText }); - } - } - - // RocketChat.Logger.info('User record', user); - // return Streamer[method]({ stream: STREAM_NAMES['room-messages'], eventName: message.rid, args: message }); - // publishMessage(operationType, message); - } -} diff --git a/imports/startup/client/listenActiveUsers.js b/imports/startup/client/listenActiveUsers.js index 90805464a1304..becc10008aa49 100644 --- a/imports/startup/client/listenActiveUsers.js +++ b/imports/startup/client/listenActiveUsers.js @@ -3,6 +3,7 @@ import { Accounts } from 'meteor/accounts-base'; import { Notifications } from '../../../app/notifications/client'; +// mirror of object in /imports/users-presence/server/activeUsers.js - keep updated const STATUS_MAP = [ 'offline', 'online', diff --git a/imports/startup/server/index.js b/imports/startup/server/index.js index 8b7c5bd8020b1..91df55e9d4f26 100644 --- a/imports/startup/server/index.js +++ b/imports/startup/server/index.js @@ -1,2 +1,3 @@ import '../../message-read-receipt/server'; import '../../personal-access-tokens/server'; +import '../../users-presence/server'; diff --git a/imports/users-presence/server/activeUsers.js b/imports/users-presence/server/activeUsers.js new file mode 100644 index 0000000000000..48ddf7626f4c7 --- /dev/null +++ b/imports/users-presence/server/activeUsers.js @@ -0,0 +1,36 @@ +import { UserPresenceEvents } from 'meteor/konecty:user-presence'; + +import { settings } from '../../../app/settings/server'; +import { api } from '../../../server/sdk/api'; + +// mirror of object in /imports/startup/client/listenActiveUsers.js - keep updated +export const STATUS_MAP = { + offline: 0, + online: 1, + away: 2, + busy: 3, +}; + +export const setUserStatus = (user, status/* , statusConnection*/) => { + const { + _id: uid, + username, + statusText, + } = user; + + // since this callback can be called by only one instance in the cluster + // we need to broadcast the change to all instances + api.broadcast('userpresence', { user: { status, _id: uid, username, statusText } }); // remove username +}; + +let TroubleshootDisablePresenceBroadcast; +settings.get('Troubleshoot_Disable_Presence_Broadcast', (key, value) => { + if (TroubleshootDisablePresenceBroadcast === value) { return; } + TroubleshootDisablePresenceBroadcast = value; + + if (value) { + return UserPresenceEvents.removeListener('setUserStatus', setUserStatus); + } + + UserPresenceEvents.on('setUserStatus', setUserStatus); +}); diff --git a/imports/users-presence/server/index.js b/imports/users-presence/server/index.js new file mode 100644 index 0000000000000..8b7cdc0d71e5a --- /dev/null +++ b/imports/users-presence/server/index.js @@ -0,0 +1 @@ +import './activeUsers'; From 770fcbe735aae35cd5cc7ebda6dee2d2dec22c38 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Mon, 19 Oct 2020 18:47:14 -0300 Subject: [PATCH 184/198] BaseRaw: Allow findOne to receive string to find by id --- app/models/server/raw/BaseRaw.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/models/server/raw/BaseRaw.ts b/app/models/server/raw/BaseRaw.ts index 47c5dcca1f7ce..4f6e91455beeb 100644 --- a/app/models/server/raw/BaseRaw.ts +++ b/app/models/server/raw/BaseRaw.ts @@ -45,6 +45,11 @@ export class BaseRaw implements IBaseRaw { async findOne(query = {}, options: FindOneOptions = {}): Promise { const optionsDef = this._ensureDefaultFields(options); + + if (typeof query === 'string') { + return this.findOneById(query, options); + } + return await this.col.findOne(query, optionsDef) ?? undefined; } From 582b7419da8c2d6d08ee1c3d76f8cf4de9bcea72 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Tue, 20 Oct 2020 22:06:41 -0300 Subject: [PATCH 185/198] Fix trash find collection name --- app/models/server/raw/BaseRaw.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/app/models/server/raw/BaseRaw.ts b/app/models/server/raw/BaseRaw.ts index 4f6e91455beeb..93acfb8e11f90 100644 --- a/app/models/server/raw/BaseRaw.ts +++ b/app/models/server/raw/BaseRaw.ts @@ -8,14 +8,18 @@ export interface IBaseRaw { col: Collection; } +const baseName = 'rocketchat_'; + export class BaseRaw implements IBaseRaw { public defaultFields?: Record; + protected name; + constructor( public readonly col: Collection, public readonly trash?: Collection, ) { - // + this.name = this.col.collectionName.replace(baseName, ''); } _ensureDefaultFields(options: FindOneOptions): FindOneOptions { @@ -74,7 +78,7 @@ export class BaseRaw implements IBaseRaw { // Trash trashFind(query: FilterQuery, options: FindOneOptions): Cursor | undefined { return this.trash?.find({ - __collection__: this.col.collectionName, + __collection__: this.name, ...query, }, options); } @@ -82,7 +86,7 @@ export class BaseRaw implements IBaseRaw { async trashFindOneById(_id: string, options: FindOneOptions = {}): Promise { const query: object = { _id, - __collection__: this.col.collectionName, + __collection__: this.name, }; return await this.trash?.findOne(query, options) ?? undefined; From 9e9039fc77d97c8c9096f43b1fdd5766706fbe35 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Tue, 20 Oct 2020 22:07:26 -0300 Subject: [PATCH 186/198] Broadcast to watch.subscription even if not found on trash --- server/modules/watchers/watchers.module.ts | 24 ++++++++++++---------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/server/modules/watchers/watchers.module.ts b/server/modules/watchers/watchers.module.ts index cc39b39cf6f38..243be7e5534ac 100644 --- a/server/modules/watchers/watchers.module.ts +++ b/server/modules/watchers/watchers.module.ts @@ -106,24 +106,26 @@ export function initWatchers({ } }); - watch(Subscriptions, async ({ clientAction, id, data }) => { + watch(Subscriptions, async ({ clientAction, id }) => { switch (clientAction) { case 'inserted': - case 'updated': + case 'updated': { // Override data cuz we do not publish all fields - data = await Subscriptions.findOneById(id, { projection: subscriptionFields }); + const subscription = await Subscriptions.findOneById(id, { projection: subscriptionFields }); + if (!subscription) { + return; + } + api.broadcast('watch.subscriptions', { clientAction, subscription }); break; + } - case 'removed': - data = await Subscriptions.trashFindOneById(id, { projection: { u: 1, rid: 1 } }); + case 'removed': { + const trash = await Subscriptions.trashFindOneById(id, { projection: { u: 1, rid: 1 } }); + const subscription = trash || { _id: id }; + api.broadcast('watch.subscriptions', { clientAction, subscription }); break; + } } - - if (!data) { - return; - } - - api.broadcast('watch.subscriptions', { clientAction, subscription: data }); }); watch(Roles, async ({ clientAction, id, data, diff }) => { From 4a0f5691c3374d8341d8d2636eee693ba1865429 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Tue, 20 Oct 2020 22:14:33 -0300 Subject: [PATCH 187/198] Add typing to BaseRaw.name --- app/models/server/raw/BaseRaw.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/server/raw/BaseRaw.ts b/app/models/server/raw/BaseRaw.ts index 93acfb8e11f90..f602de361df0a 100644 --- a/app/models/server/raw/BaseRaw.ts +++ b/app/models/server/raw/BaseRaw.ts @@ -13,7 +13,7 @@ const baseName = 'rocketchat_'; export class BaseRaw implements IBaseRaw { public defaultFields?: Record; - protected name; + protected name: string; constructor( public readonly col: Collection, From 63bf9de5e55551466681ad390a6bb6c274512ddb Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Tue, 20 Oct 2020 23:51:47 -0300 Subject: [PATCH 188/198] Add context to broker lifecycle events --- ee/server/broker.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ee/server/broker.ts b/ee/server/broker.ts index bb2221a4fcb7c..3246768f4ffc3 100644 --- a/ee/server/broker.ts +++ b/ee/server/broker.ts @@ -137,7 +137,12 @@ class NetworkBroker implements IBroker { } if (lifecycle[method]) { - service[method] = i[method].bind(i); + service[method] = (): void => asyncLocalStorage.run({ + id: '', + nodeID: this.broker.nodeID, + requestID: null, + broker: this, + }, i[method].bind(i)); continue; } From a4859130890047ffc23ea30e46ffdab821c028d7 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Tue, 20 Oct 2020 23:53:33 -0300 Subject: [PATCH 189/198] Add broadcast on presence if status has changed --- .../presence/actions/updateUserPresence.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/ee/server/services/presence/actions/updateUserPresence.ts b/ee/server/services/presence/actions/updateUserPresence.ts index 99dd126dfe7f1..805de4742dd0b 100755 --- a/ee/server/services/presence/actions/updateUserPresence.ts +++ b/ee/server/services/presence/actions/updateUserPresence.ts @@ -4,12 +4,13 @@ import { getCollection, Collections } from '../../mongo'; import { IUserSession } from '../../../../../definition/IUserSession'; import { IUser } from '../../../../../definition/IUser'; import { USER_STATUS } from '../../../../../definition/UserStatus'; - -// export default afterAll; +import { api } from '../../../../../server/sdk/api'; const projection = { projection: { + username: 1, statusDefault: 1, + statusText: 1, }, }; @@ -25,7 +26,14 @@ export async function updateUserPresence(uid: string): Promise { const userSessions = await UserSession.findOne(query) || { connections: [] }; const { statusDefault = USER_STATUS.OFFLINE } = user; const { status, statusConnection } = processPresenceAndStatus(userSessions.connections, statusDefault); - User.updateOne(query, { + const result = await User.updateOne(query, { $set: { status, statusConnection }, }); + + if (result.modifiedCount > 0) { + api.broadcast('userpresence', { + action: 'updated', + user: { _id: uid, username: user.username, status, statusText: user.statusText }, + }); + } } From 2eed85f442290de26b35866e51bde3c34b65b5e8 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Wed, 21 Oct 2020 00:04:03 -0300 Subject: [PATCH 190/198] Broadcast user presense on setStatus --- ee/server/services/presence/actions/setStatus.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/ee/server/services/presence/actions/setStatus.ts b/ee/server/services/presence/actions/setStatus.ts index 347c3841abd68..0396e28043cc4 100755 --- a/ee/server/services/presence/actions/setStatus.ts +++ b/ee/server/services/presence/actions/setStatus.ts @@ -1,7 +1,9 @@ import { processPresenceAndStatus } from '../lib/processConnectionStatus'; import { getCollection, Collections } from '../../mongo'; +import { IUser } from '../../../../../definition/IUser'; import { USER_STATUS } from '../../../../../definition/UserStatus'; import { IUserSession } from '../../../../../definition/IUserSession'; +import { api } from '../../../../../server/sdk/api'; export async function setStatus(uid: string, statusDefault: USER_STATUS, statusText?: string): Promise { const query = { _id: uid }; @@ -29,6 +31,14 @@ export async function setStatus(uid: string, statusDefault: USER_STATUS, statusT }, ); + if (result.modifiedCount > 0) { + const user = await User.findOne(query, { projection: { username: 1 } }); + api.broadcast('userpresence', { + action: 'updated', + user: { _id: uid, username: user?.username, status, statusText }, + }); + } + return !!result.modifiedCount; } From a46cf3c2e02b4854b495557421dd371c506143ca Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Fri, 23 Oct 2020 15:22:25 -0300 Subject: [PATCH 191/198] Fix trash collection name --- ee/server/services/stream-hub/StreamHub.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ee/server/services/stream-hub/StreamHub.ts b/ee/server/services/stream-hub/StreamHub.ts index a19341891b26a..83f02c164ba15 100755 --- a/ee/server/services/stream-hub/StreamHub.ts +++ b/ee/server/services/stream-hub/StreamHub.ts @@ -22,7 +22,7 @@ export class StreamHub extends ServiceClass implements IServiceClass { async created(): Promise { const db = await getConnection(15); - const Trash = db.collection('rocketchat_trash'); + const Trash = db.collection('rocketchat__trash'); const UsersCol = db.collection('users'); From c345e65da4eace66ca095bb657858f5f9ee0ad1c Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Fri, 23 Oct 2020 15:29:51 -0300 Subject: [PATCH 192/198] Fix IUser --- definition/IUser.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/definition/IUser.ts b/definition/IUser.ts index 313fb69035a46..aeb36c261ad56 100644 --- a/definition/IUser.ts +++ b/definition/IUser.ts @@ -86,7 +86,6 @@ export interface IRole { export interface IUser { _id: string; - avatarETag: string; createdAt: Date; roles: string[]; type: string; From 7d37c5a67a9c0a3f8135c58a5726881ccb2128cb Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Tue, 27 Oct 2020 20:14:17 -0300 Subject: [PATCH 193/198] Fix lint and typecheck --- app/apps/server/bridges/messages.js | 3 ++- app/lib/server/functions/setRoomAvatar.js | 3 ++- definition/IMessage.ts | 4 ++-- definition/ISubscription.ts | 2 +- ee/server/services/stream-hub/StreamHub.ts | 2 +- package-lock.json | 6 +++--- server/modules/watchers/watchers.module.ts | 1 + 7 files changed, 12 insertions(+), 9 deletions(-) diff --git a/app/apps/server/bridges/messages.js b/app/apps/server/bridges/messages.js index 62854219cdecd..2e65ff8033b09 100644 --- a/app/apps/server/bridges/messages.js +++ b/app/apps/server/bridges/messages.js @@ -2,6 +2,7 @@ import { Messages, Users, Subscriptions } from '../../../models/server'; import { updateMessage } from '../../../lib/server/functions/updateMessage'; import { executeSendMessage } from '../../../lib/server/methods/sendMessage'; import { api } from '../../../../server/sdk/api'; +import notifications from '../../../notifications/server/lib/Notifications'; export class AppMessageBridge { constructor(orch) { @@ -80,7 +81,7 @@ export class AppMessageBridge { async typing({ scope, id, username, isTyping }) { switch (scope) { case 'room': - Notifications.notifyRoom(id, 'typing', username, isTyping); + notifications.notifyRoom(id, 'typing', username, isTyping); return; default: throw new Error('Unrecognized typing scope provided'); diff --git a/app/lib/server/functions/setRoomAvatar.js b/app/lib/server/functions/setRoomAvatar.js index 16c904fd436fa..73fd40227717f 100644 --- a/app/lib/server/functions/setRoomAvatar.js +++ b/app/lib/server/functions/setRoomAvatar.js @@ -4,6 +4,7 @@ import { RocketChatFile } from '../../../file'; import { FileUpload } from '../../../file-upload'; import { Rooms, Avatars, Messages } from '../../../models/server'; import { api } from '../../../../server/sdk/api'; +import { notifications } from '../../../notifications/server'; export const setRoomAvatar = function(rid, dataURI, user) { const fileStore = FileUpload.getStore('Avatars'); @@ -13,7 +14,7 @@ export const setRoomAvatar = function(rid, dataURI, user) { if (!dataURI) { fileStore.deleteByRoomId(rid); Messages.createRoomSettingsChangedWithTypeRoomIdMessageAndUser('room_changed_avatar', rid, '', user); - Notifications.notifyLogged('updateAvatar', { rid }); + notifications.notifyLogged('updateAvatar', { rid }); return Rooms.unsetAvatarData(rid); } diff --git a/definition/IMessage.ts b/definition/IMessage.ts index 2d03c99d58dac..177b8d25e28f0 100644 --- a/definition/IMessage.ts +++ b/definition/IMessage.ts @@ -1,12 +1,12 @@ import { IRocketChatRecord } from './IRocketChatRecord'; -import { IUser, Username } from './IUser'; +import { IUser } from './IUser'; import { ChannelName, RoomID } from './IRoom'; export interface IMessage extends IRocketChatRecord { rid: RoomID; msg: string; ts: Date; - mentions?: Array | { + mentions?: { _id: string; name?: string; }[]; diff --git a/definition/ISubscription.ts b/definition/ISubscription.ts index 4a71119833d99..268475735fae9 100644 --- a/definition/ISubscription.ts +++ b/definition/ISubscription.ts @@ -13,7 +13,7 @@ export interface ISubscription extends IRocketChatRecord { name: string; - alert?: true; + alert?: boolean; unread: number; t: RoomType; ls: Date; diff --git a/ee/server/services/stream-hub/StreamHub.ts b/ee/server/services/stream-hub/StreamHub.ts index 31ff5cd643289..b364fe1c18263 100755 --- a/ee/server/services/stream-hub/StreamHub.ts +++ b/ee/server/services/stream-hub/StreamHub.ts @@ -81,7 +81,7 @@ export class StreamHub extends ServiceClass implements IServiceClass { } } - const unset = {}; + const unset: Record = {}; if (event.updateDescription.removedFields) { for (const key in event.updateDescription.removedFields) { if (event.updateDescription.removedFields.hasOwnProperty(key)) { diff --git a/package-lock.json b/package-lock.json index 0971a43e96a5e..6b9611fec5cf3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18325,7 +18325,7 @@ }, "minimist": { "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "resolved": false, "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "dev": true, "optional": true @@ -18353,7 +18353,7 @@ }, "mkdirp": { "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "resolved": false, "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "dev": true, "optional": true, @@ -18526,7 +18526,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": false, "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true, "optional": true diff --git a/server/modules/watchers/watchers.module.ts b/server/modules/watchers/watchers.module.ts index 243be7e5534ac..b38b8c9e47df4 100644 --- a/server/modules/watchers/watchers.module.ts +++ b/server/modules/watchers/watchers.module.ts @@ -54,6 +54,7 @@ interface IChange { id: string; data?: T; diff?: Record; + unset?: Record; } type Watcher = (model: IBaseRaw, fn: (event: IChange) => void) => void; From b4539308fc7664cdc74971a0d5ef5c6982876f9e Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Tue, 27 Oct 2020 21:08:43 -0300 Subject: [PATCH 194/198] Fix OmnichannelQueueRaw --- app/models/server/raw/OmnichannelQueue.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/app/models/server/raw/OmnichannelQueue.ts b/app/models/server/raw/OmnichannelQueue.ts index dc75499f38b44..74a2e1d9f8029 100644 --- a/app/models/server/raw/OmnichannelQueue.ts +++ b/app/models/server/raw/OmnichannelQueue.ts @@ -1,15 +1,9 @@ /* eslint-disable @typescript-eslint/explicit-function-return-type */ -import { - Collection, -} from 'mongodb'; - import { BaseRaw } from './BaseRaw'; import { IOmnichannelQueueStatus } from '../../../../definition/IOmnichannel'; const UNIQUE_QUEUE_ID = 'queue'; -export class OmnichannelQueueRaw extends BaseRaw { - public readonly col!: Collection; - +export class OmnichannelQueueRaw extends BaseRaw { initQueue() { return this.col.updateOne({ _id: UNIQUE_QUEUE_ID, From 33c695c07d2636d99702cf2eda1a6d1d76e433a1 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Tue, 27 Oct 2020 21:23:33 -0300 Subject: [PATCH 195/198] Finish services Docker image build --- .github/workflows/build_and_test.yml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index a543833f9231a..be0e9131ce385 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -490,8 +490,7 @@ jobs: services-image-build: runs-on: ubuntu-latest - # TODO uncomment 'needs' before merging! - # needs: deploy + needs: deploy if: github.event.pull_request.head.repo.full_name == github.repository strategy: @@ -506,10 +505,13 @@ jobs: with: node-version: "12.18.4" + - name: Login to DockerHub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKER_USER }} + password: ${{ secrets.DOCKER_PASS }} + - name: Build Docker images - env: - DOCKER_USER: ${{ secrets.DOCKER_USER }} - DOCKER_PASS: ${{ secrets.DOCKER_PASS }} run: | npm i @@ -521,5 +523,4 @@ jobs: docker build --build-arg SERVICE=${{ matrix.service }} -t rocketchat/${{ matrix.service }}-service . - docker login -u $DOCKER_USER -p $DOCKER_PASS docker push rocketchat/${{ matrix.service }}-service From fffe5263241b4a982bfaad63876cf87e339320f2 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Tue, 27 Oct 2020 22:05:56 -0300 Subject: [PATCH 196/198] Fix Change Stream $unset --- app/models/server/models/_oplogHandle.ts | 5 ++++- server/modules/watchers/watchers.module.ts | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/app/models/server/models/_oplogHandle.ts b/app/models/server/models/_oplogHandle.ts index c3ad936669d2e..e5cae5affdf40 100644 --- a/app/models/server/models/_oplogHandle.ts +++ b/app/models/server/models/_oplogHandle.ts @@ -156,7 +156,10 @@ class CustomOplogHandle { // o: event.fullDocument, o: { $set: event.updateDescription.updatedFields, - $unset: event.updateDescription.removedFields, + $unset: event.updateDescription.removedFields.reduce((obj, field) => { + obj[field as string] = true; + return obj; + }, {} as Record), }, }, }); diff --git a/server/modules/watchers/watchers.module.ts b/server/modules/watchers/watchers.module.ts index b38b8c9e47df4..038a50f12928e 100644 --- a/server/modules/watchers/watchers.module.ts +++ b/server/modules/watchers/watchers.module.ts @@ -279,8 +279,8 @@ export function initWatchers({ // TODO: Prevent flood from database on username change, what causes changes on all past messages from that user // and most of those messages are not loaded by the clients. - watch(Users, ({ clientAction, id, data, diff }) => { - api.broadcast('watch.users', { clientAction, data, diff, id }); + watch(Users, ({ clientAction, id, data, diff, unset }) => { + api.broadcast('watch.users', { clientAction, data, diff, unset, id }); }); watch(LoginServiceConfiguration, async ({ clientAction, id }) => { From a7dc4c40682c5d1375a49c0eca6d3635589df79c Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Tue, 27 Oct 2020 22:13:25 -0300 Subject: [PATCH 197/198] Suggestions from review --- app/lib/server/functions/setRoomAvatar.js | 3 +-- imports/users-presence/server/activeUsers.js | 4 ++-- server/sdk/index.ts | 2 +- server/sdk/lib/Api.ts | 2 -- 4 files changed, 4 insertions(+), 7 deletions(-) diff --git a/app/lib/server/functions/setRoomAvatar.js b/app/lib/server/functions/setRoomAvatar.js index 73fd40227717f..dd81ef8e19a8f 100644 --- a/app/lib/server/functions/setRoomAvatar.js +++ b/app/lib/server/functions/setRoomAvatar.js @@ -4,7 +4,6 @@ import { RocketChatFile } from '../../../file'; import { FileUpload } from '../../../file-upload'; import { Rooms, Avatars, Messages } from '../../../models/server'; import { api } from '../../../../server/sdk/api'; -import { notifications } from '../../../notifications/server'; export const setRoomAvatar = function(rid, dataURI, user) { const fileStore = FileUpload.getStore('Avatars'); @@ -14,7 +13,7 @@ export const setRoomAvatar = function(rid, dataURI, user) { if (!dataURI) { fileStore.deleteByRoomId(rid); Messages.createRoomSettingsChangedWithTypeRoomIdMessageAndUser('room_changed_avatar', rid, '', user); - notifications.notifyLogged('updateAvatar', { rid }); + api.broadcast('room.avatarUpdate', { rid }); return Rooms.unsetAvatarData(rid); } diff --git a/imports/users-presence/server/activeUsers.js b/imports/users-presence/server/activeUsers.js index 48ddf7626f4c7..f3225b933b8ef 100644 --- a/imports/users-presence/server/activeUsers.js +++ b/imports/users-presence/server/activeUsers.js @@ -13,14 +13,14 @@ export const STATUS_MAP = { export const setUserStatus = (user, status/* , statusConnection*/) => { const { - _id: uid, + _id, username, statusText, } = user; // since this callback can be called by only one instance in the cluster // we need to broadcast the change to all instances - api.broadcast('userpresence', { user: { status, _id: uid, username, statusText } }); // remove username + api.broadcast('userpresence', { user: { status, _id, username, statusText } }); // remove username }; let TroubleshootDisablePresenceBroadcast; diff --git a/server/sdk/index.ts b/server/sdk/index.ts index 42a24ab3a13e9..d697f7722bf30 100644 --- a/server/sdk/index.ts +++ b/server/sdk/index.ts @@ -16,7 +16,7 @@ export const Account = proxifyWithWait('accounts'); export const License = proxifyWithWait('license'); export const MeteorService = proxifyWithWait('meteor'); -// Calls without wait. Means that the service is optional and the result may be ane error +// Calls without wait. Means that the service is optional and the result may be an error // of service/method not available export const EnterpriseSettings = proxify('ee-settings'); diff --git a/server/sdk/lib/Api.ts b/server/sdk/lib/Api.ts index 0af4c76a5dd31..9b646f479e585 100644 --- a/server/sdk/lib/Api.ts +++ b/server/sdk/lib/Api.ts @@ -37,12 +37,10 @@ export class Api { } async call(method: string, data: any): Promise { - // console.log('api call', method, this.broker.constructor.name); return this.broker.call(method, data); } async waitAndCall(method: string, data: any): Promise { - // console.log('api call', method, this.broker.constructor.name); return this.broker.waitAndCall(method, data); } From bd60936c34e55ffc3cfdf480f323cdd4cf98af45 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Tue, 27 Oct 2020 22:22:30 -0300 Subject: [PATCH 198/198] Remove msgpack5 --- ee/server/services/package-lock.json | 29 ------------------ ee/server/services/package.json | 2 -- package-lock.json | 46 ++-------------------------- package.json | 4 +-- 4 files changed, 4 insertions(+), 77 deletions(-) diff --git a/ee/server/services/package-lock.json b/ee/server/services/package-lock.json index ece759bfa1dd1..0ca4fac5f308d 100644 --- a/ee/server/services/package-lock.json +++ b/ee/server/services/package-lock.json @@ -212,15 +212,6 @@ "debug": "^4.1.1" } }, - "@types/bl": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@types/bl/-/bl-2.1.0.tgz", - "integrity": "sha512-1TdA9IXOy4sdqn8vgieQ6GZAiHiPNrOiO1s2GJjuYPw4QVY7gYoVjkW049avj33Ez7IcIvu43hQsMsoUFbCn2g==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, "@types/color-name": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", @@ -233,15 +224,6 @@ "integrity": "sha1-oMuiYNUAYxDch3kFRj2D1fPN8RI=", "dev": true }, - "@types/msgpack5": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/@types/msgpack5/-/msgpack5-3.4.1.tgz", - "integrity": "sha512-E3wILUjTXONukpiI6tmqpLwf7eV3MVTdxpjz56FqNn7koMF/6sSPUh5TxMlwgoOhyeejxwVoNZUiDcdqChKkAw==", - "dev": true, - "requires": { - "@types/bl": "*" - } - }, "@types/node": { "version": "14.6.4", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.6.4.tgz", @@ -1419,17 +1401,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, - "msgpack5": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/msgpack5/-/msgpack5-4.2.1.tgz", - "integrity": "sha512-Xo7nE9ZfBVonQi1rSopNAqPdts/QHyuSEUwIEzAkB+V2FtmkkLUbP6MyVqVVQxsZYI65FpvW3Bb8Z9ZWEjbgHQ==", - "requires": { - "bl": "^2.0.1", - "inherits": "^2.0.3", - "readable-stream": "^2.3.6", - "safe-buffer": "^5.1.2" - } - }, "mute-stream": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", diff --git a/ee/server/services/package.json b/ee/server/services/package.json index 1f1bb72fdfa6c..c3575f1a26cb7 100644 --- a/ee/server/services/package.json +++ b/ee/server/services/package.json @@ -24,7 +24,6 @@ "mem": "^6.1.0", "moleculer": "^0.14.10", "mongodb": "^3.6.1", - "msgpack5": "^4.2.1", "nats": "^1.4.8", "underscore.string": "^3.3.5", "uuid": "^7.0.3", @@ -33,7 +32,6 @@ "devDependencies": { "@types/ejson": "^2.1.2", "@types/node": "^14.6.4", - "@types/msgpack5": "^3.4.1", "@types/ws": "^7.2.6", "pm2": "^4.4.1", "ts-node": "^9.0.0", diff --git a/package-lock.json b/package-lock.json index 6b9611fec5cf3..8f29596875bd4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8150,15 +8150,6 @@ "integrity": "sha512-nohgNyv+1ViVcubKBh0+XiNJ3dO8nYu///9aJ4cgSqv70gBL+94SNy/iC2NLzKPT2Zt/QavrOkBVbZRLZmw6NQ==", "dev": true }, - "@types/bl": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@types/bl/-/bl-2.1.0.tgz", - "integrity": "sha512-1TdA9IXOy4sdqn8vgieQ6GZAiHiPNrOiO1s2GJjuYPw4QVY7gYoVjkW049avj33Ez7IcIvu43hQsMsoUFbCn2g==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, "@types/bn.js": { "version": "4.11.6", "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-4.11.6.tgz", @@ -8456,15 +8447,6 @@ "@types/node": "*" } }, - "@types/msgpack5": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/@types/msgpack5/-/msgpack5-3.4.1.tgz", - "integrity": "sha512-E3wILUjTXONukpiI6tmqpLwf7eV3MVTdxpjz56FqNn7koMF/6sSPUh5TxMlwgoOhyeejxwVoNZUiDcdqChKkAw==", - "dev": true, - "requires": { - "@types/bl": "*" - } - }, "@types/node": { "version": "10.12.14", "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.14.tgz", @@ -18325,7 +18307,7 @@ }, "minimist": { "version": "0.0.8", - "resolved": false, + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "dev": true, "optional": true @@ -18353,7 +18335,7 @@ }, "mkdirp": { "version": "0.5.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "dev": true, "optional": true, @@ -18526,7 +18508,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true, "optional": true @@ -25399,28 +25381,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, - "msgpack5": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/msgpack5/-/msgpack5-4.2.1.tgz", - "integrity": "sha512-Xo7nE9ZfBVonQi1rSopNAqPdts/QHyuSEUwIEzAkB+V2FtmkkLUbP6MyVqVVQxsZYI65FpvW3Bb8Z9ZWEjbgHQ==", - "requires": { - "bl": "^2.0.1", - "inherits": "^2.0.3", - "readable-stream": "^2.3.6", - "safe-buffer": "^5.1.2" - }, - "dependencies": { - "bl": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz", - "integrity": "sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==", - "requires": { - "readable-stream": "^2.3.5", - "safe-buffer": "^5.1.1" - } - } - } - }, "mute-stream": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", diff --git a/package.json b/package.json index c13826de6ca91..ff8a09120bac1 100644 --- a/package.json +++ b/package.json @@ -66,14 +66,13 @@ "@types/bcrypt": "^3.0.0", "@types/chai": "^4.2.12", "@types/chai-spies": "^1.0.1", - "@types/ejson": "^2.1.2", "@types/clipboard": "^2.0.1", + "@types/ejson": "^2.1.2", "@types/meteor": "^1.4.49", "@types/mocha": "^8.0.3", "@types/mock-require": "^2.0.0", "@types/moment-timezone": "^0.5.30", "@types/mongodb": "^3.5.26", - "@types/msgpack5": "^3.4.1", "@types/node": "^10.12.14", "@types/react-dom": "^16.9.8", "@types/semver": "^7.3.3", @@ -225,7 +224,6 @@ "moment": "^2.27.0", "moment-timezone": "^0.5.31", "mongodb": "^3.6.0", - "msgpack5": "^4.2.1", "nats": "^1.4.8", "node-dogstatsd": "^0.0.7", "node-gcm": "0.14.4",