From fcefbc077433a763ce0fd8af5af101433358e17f Mon Sep 17 00:00:00 2001 From: graywolf336 Date: Wed, 21 Feb 2018 16:38:46 -0600 Subject: [PATCH 1/3] Work on getting Rocket.Chat Apps to work on multi-instance rocket.chat instances --- .../client/communication/websockets.js | 2 ++ packages/rocketchat-apps/package.js | 2 +- .../server/bridges/activation.js | 30 +++++++--------- .../server/communication/index.js | 5 +-- .../server/communication/rest.js | 2 +- .../server/communication/websockets.js | 36 +++++++++++++++++-- .../rocketchat-apps/server/orchestrator.js | 2 +- .../client/lib/RestApiClient.js | 2 +- 8 files changed, 55 insertions(+), 26 deletions(-) diff --git a/packages/rocketchat-apps/client/communication/websockets.js b/packages/rocketchat-apps/client/communication/websockets.js index 290e23ef74c6..1dd694ae89ce 100644 --- a/packages/rocketchat-apps/client/communication/websockets.js +++ b/packages/rocketchat-apps/client/communication/websockets.js @@ -8,9 +8,11 @@ export class AppWebsocketReceiver { this.streamer.on('command/disabled', this.onCommandDisabled.bind(this)); this.streamer.on('command/updated', this.onCommandUpdated.bind(this)); this.streamer.on('command/removed', this.onCommandDisabled.bind(this)); + console.log('apps websocket listener'); } onAppAdded(appId) { + console.log('app added'); RocketChat.API.get(`apps/${ appId }/languages`).then((result) => { this.orch.parseAndLoadLanguages(result.languages); }); diff --git a/packages/rocketchat-apps/package.js b/packages/rocketchat-apps/package.js index 0619a74e4350..a145ff3c86e9 100644 --- a/packages/rocketchat-apps/package.js +++ b/packages/rocketchat-apps/package.js @@ -87,6 +87,6 @@ Package.onUse(function(api) { Npm.depends({ 'busboy': '0.2.13', - '@rocket.chat/apps-engine': '0.3.5', + '@rocket.chat/apps-engine': '0.3.7', '@rocket.chat/apps-ts-definition': '0.7.6' }); diff --git a/packages/rocketchat-apps/server/bridges/activation.js b/packages/rocketchat-apps/server/bridges/activation.js index a67165bf4653..821eaaa6731f 100644 --- a/packages/rocketchat-apps/server/bridges/activation.js +++ b/packages/rocketchat-apps/server/bridges/activation.js @@ -3,33 +3,27 @@ export class AppActivationBridge { this.orch = orch; } - appEnabled(app) { - console.log(`The App ${ app.getName() } (${ app.getID() }) has been enabled.`); - } + appAdded(app) { + console.log(`The ${ app.getName() } App (${ app.getID() }) has been added.`); - appDisabled(app) { - console.log(`The App ${ app.getName() } (${ app.getID() }) has been disabled.`); + this.orch.getNotifier().appAdded(app.getID()); } - appLoaded(app, enabled) { - console.log(`The App ${ app.getName() } (${ app.getID() }) has been loaded and enabled? ${ enabled }`); + appUpdated(app) { + console.log(`The ${ app.getName() } App (${ app.getID() }) has been updated.`); - if (enabled) { - this.orch.getNotifier().appAdded(app.getID()); - } + this.orch.getNotifier().appUpdated(app.getID()); } - appUpdated(app, enabled) { - console.log(`The App ${ app.getName() } (${ app.getID() }) has been updated and enabled? ${ enabled }`); + appRemoved(app) { + console.log(`The ${ app.getName() } App (${ app.getID() }) has been removed.`); - if (enabled) { - this.orch.getNotifier().appUpdated(app.getID()); - } + this.orch.getNotifier().appRemoved(app.getID()); } - appRemoved(app) { - console.log(`The App ${ app.getName() } (${ app.getID() }) has been removed.`); + appStatusChanged(app, status) { + console.log(`The ${ app.getName() } App (${ app.getID() }) status has changed: ${ status }`); - this.orch.getNotifier().appRemoved(app.getID()); + this.orch.getNotifier().appStatusUpdated(app.getID(), status); } } diff --git a/packages/rocketchat-apps/server/communication/index.js b/packages/rocketchat-apps/server/communication/index.js index 0e3c11484f0a..d62c3a27feb7 100644 --- a/packages/rocketchat-apps/server/communication/index.js +++ b/packages/rocketchat-apps/server/communication/index.js @@ -1,9 +1,10 @@ import { AppMethods} from './methods'; import { AppsRestApi } from './rest'; -import { AppWebsocketNotifier } from './websockets'; +import { AppWebsocketNotifier, AppWebsocketListener } from './websockets'; export { AppMethods, AppsRestApi, - AppWebsocketNotifier + AppWebsocketNotifier, + AppWebsocketListener }; diff --git a/packages/rocketchat-apps/server/communication/rest.js b/packages/rocketchat-apps/server/communication/rest.js index a10df7c10843..972466d32a47 100644 --- a/packages/rocketchat-apps/server/communication/rest.js +++ b/packages/rocketchat-apps/server/communication/rest.js @@ -158,7 +158,7 @@ export class AppsRestApi { } }); - this.api.addRoute(':id/languages', { authRequired: true }, { + this.api.addRoute(':id/languages', { authRequired: false }, { get() { console.log(`Getting ${ this.urlParams.id }'s languages..`); const prl = manager.getOneById(this.urlParams.id); diff --git a/packages/rocketchat-apps/server/communication/websockets.js b/packages/rocketchat-apps/server/communication/websockets.js index 80d17e028726..a43eb9238032 100644 --- a/packages/rocketchat-apps/server/communication/websockets.js +++ b/packages/rocketchat-apps/server/communication/websockets.js @@ -1,13 +1,41 @@ +export class AppWebsocketListener { + constructor(orch, streamer) { + this.orch = orch; + this.streamer = streamer; + + this.streamer.on('app/added', this.onAppAdded.bind(this)); + this.streamer.on('app/statusUpdate', this.onAppStatusUpdated.bind(this)); + this.streamer.on('app/removed', this.onAppRemoved.bind(this)); + console.log('hello from the app websocket listener'); + } + + onAppAdded(appId) { + console.log('On App Added!', appId); + this.orch.getManager().loadOne(appId).then(() => console.log('yay')); + } + + onAppStatusUpdated({ appId, status }) { + console.log('App Status Update:', appId, status); + } + + onAppRemoved(appId) { + console.log('On App Removed!', appId); + } +} + export class AppWebsocketNotifier { - constructor() { - this.streamer = new Meteor.Streamer('apps', { retransmit: false }); + constructor(orch) { + this.streamer = new Meteor.Streamer('apps', { retransmit: true, retransmitToSelf: true }); this.streamer.allowRead('all'); this.streamer.allowEmit('all'); this.streamer.allowWrite('none'); + + this.listener = new AppWebsocketListener(orch, this.streamer); } appAdded(appId) { this.streamer.emit('app/added', appId); + console.log('emitting: "app/added"', appId); } appRemoved(appId) { @@ -18,6 +46,10 @@ export class AppWebsocketNotifier { this.streamer.emit('app/updated', appId); } + appStatusUpdated(appId, status) { + this.streamer.emit('app/statusUpdate', { appId, status }); + } + commandAdded(command) { this.streamer.emit('command/added', command); } diff --git a/packages/rocketchat-apps/server/orchestrator.js b/packages/rocketchat-apps/server/orchestrator.js index a5178ee07e0b..bccd0a9dfd88 100644 --- a/packages/rocketchat-apps/server/orchestrator.js +++ b/packages/rocketchat-apps/server/orchestrator.js @@ -29,7 +29,7 @@ class AppServerOrchestrator { this._communicators = new Map(); this._communicators.set('methods', new AppMethods(this._manager)); - this._communicators.set('notifier', new AppWebsocketNotifier()); + this._communicators.set('notifier', new AppWebsocketNotifier(this)); this._communicators.set('restapi', new AppsRestApi(this, this._manager)); } diff --git a/packages/rocketchat-lib/client/lib/RestApiClient.js b/packages/rocketchat-lib/client/lib/RestApiClient.js index 273652a5cfb2..cadcbaed02e7 100644 --- a/packages/rocketchat-lib/client/lib/RestApiClient.js +++ b/packages/rocketchat-lib/client/lib/RestApiClient.js @@ -44,7 +44,7 @@ RocketChat.API = { return new Promise(function _rlRestApiGet(resolve, reject) { jQuery.ajax({ method, - url: `${ Meteor.absoluteUrl() }api/${ endpoint }${ query }`, + url: `${ window.location.origin }/api/${ endpoint }${ query }`, headers: { 'Content-Type': 'application/json', 'X-User-Id': localStorage['Meteor.userId'], From 9974fd54a4cb46bd1c6fc05a7b4621077e3567f0 Mon Sep 17 00:00:00 2001 From: graywolf336 Date: Thu, 22 Feb 2018 23:25:29 -0600 Subject: [PATCH 2/3] Change the streamer to allow for server-to-server emitting without reaching the web clients. --- packages/rocketchat-apps/client/admin/apps.js | 36 ++++++ .../client/communication/index.js | 4 +- .../client/communication/websockets.js | 57 +++++++++- packages/rocketchat-apps/package.js | 2 +- .../server/communication/index.js | 7 +- .../server/communication/websockets.js | 103 ++++++++++++++---- .../rocketchat-apps/server/orchestrator.js | 4 +- .../server/storage/logs-storage.js | 3 - server/stream/streamBroadcast.js | 10 +- 9 files changed, 184 insertions(+), 42 deletions(-) diff --git a/packages/rocketchat-apps/client/admin/apps.js b/packages/rocketchat-apps/client/admin/apps.js index b8726a53cf52..4b92e3346478 100644 --- a/packages/rocketchat-apps/client/admin/apps.js +++ b/packages/rocketchat-apps/client/admin/apps.js @@ -1,12 +1,48 @@ +import { AppEvents } from '../communication'; + Template.apps.onCreated(function() { const instance = this; this.ready = new ReactiveVar(false); this.apps = new ReactiveVar([]); RocketChat.API.get('apps').then((result) => { + console.log(result.apps); instance.apps.set(result.apps); instance.ready.set(true); }); + + instance.onAppAdded = function _appOnAppAdded(appId) { + RocketChat.API.get(`apps/${ appId }`).then((result) => { + const apps = instance.apps.get(); + apps.push(result.app); + instance.apps.set(apps); + }); + }; + + instance.onAppRemoved = function _appOnAppRemoved(appId) { + const apps = instance.apps.get(); + + let index = -1; + apps.find((item, i) => { + if (item.id === appId) { + index = i; + return true; + } + }); + + apps.splice(index, 1); + instance.apps.set(apps); + }; + + window.Apps.getWsListener().registerListener(AppEvents.APP_ADDED, instance.onAppAdded); + window.Apps.getWsListener().registerListener(AppEvents.APP_REMOVED, instance.onAppAdded); +}); + +Template.apps.onDestroyed(function() { + const instance = this; + + window.Apps.getWsListener().unregisterListener(AppEvents.APP_ADDED, instance.onAppAdded); + window.Apps.getWsListener().unregisterListener(AppEvents.APP_REMOVED, instance.onAppAdded); }); Template.apps.helpers({ diff --git a/packages/rocketchat-apps/client/communication/index.js b/packages/rocketchat-apps/client/communication/index.js index 54c32e3cedf0..8878b65fcf21 100644 --- a/packages/rocketchat-apps/client/communication/index.js +++ b/packages/rocketchat-apps/client/communication/index.js @@ -1,3 +1,3 @@ -import { AppWebsocketReceiver } from './websockets'; +import { AppWebsocketReceiver, AppEvents } from './websockets'; -export { AppWebsocketReceiver }; +export { AppWebsocketReceiver, AppEvents }; diff --git a/packages/rocketchat-apps/client/communication/websockets.js b/packages/rocketchat-apps/client/communication/websockets.js index 1dd694ae89ce..c9df584bedf1 100644 --- a/packages/rocketchat-apps/client/communication/websockets.js +++ b/packages/rocketchat-apps/client/communication/websockets.js @@ -1,14 +1,46 @@ +export const AppEvents = Object.freeze({ + APP_ADDED: 'app/added', + APP_REMOVED: 'app/removed', + APP_UPDATED: 'app/updated', + APP_STATUS_CHANGE: 'app/statusUpdate', + COMMAND_ADDED: 'command/added', + COMMAND_DISABLED: 'command/disabled', + COMMAND_UPDATED: 'command/updated', + COMMAND_REMOVED: 'command/removed' +}); + export class AppWebsocketReceiver { constructor(orch) { this.orch = orch; this.streamer = new Meteor.Streamer('apps'); - this.streamer.on('app/added', this.onAppAdded.bind(this)); - this.streamer.on('command/added', this.onCommandAdded.bind(this)); - this.streamer.on('command/disabled', this.onCommandDisabled.bind(this)); - this.streamer.on('command/updated', this.onCommandUpdated.bind(this)); - this.streamer.on('command/removed', this.onCommandDisabled.bind(this)); + this.streamer.on(AppEvents.APP_ADDED, this.onAppAdded.bind(this)); + this.streamer.on(AppEvents.APP_REMOVED, this.onAppRemoved.bind(this)); + this.streamer.on(AppEvents.APP_UPDATED, this.onAppUpdated.bind(this)); + this.streamer.on(AppEvents.APP_STATUS_CHANGE, this.onAppStatusUpdated.bind(this)); + this.streamer.on(AppEvents.COMMAND_ADDED, this.onCommandAdded.bind(this)); + this.streamer.on(AppEvents.COMMAND_DISABLED, this.onCommandDisabled.bind(this)); + this.streamer.on(AppEvents.COMMAND_UPDATED, this.onCommandUpdated.bind(this)); + this.streamer.on(AppEvents.COMMAND_REMOVED, this.onCommandDisabled.bind(this)); console.log('apps websocket listener'); + + this.listeners = {}; + + Object.keys(AppEvents).forEach((v) => { + this.listeners[AppEvents[v]] = []; + }); + + console.log(this.listeners); + } + + registerListener(event, listener) { + console.log('Registering a listener for:', event); + this.listeners[event].push(listener); + } + + unregisterListener(event, listener) { + console.log('Unregistering a listener for:', event); + this.listeners[event].splice(this.listeners[event].indexOf(listener), 1); } onAppAdded(appId) { @@ -16,6 +48,21 @@ export class AppWebsocketReceiver { RocketChat.API.get(`apps/${ appId }/languages`).then((result) => { this.orch.parseAndLoadLanguages(result.languages); }); + + this.listeners[AppEvents.APP_ADDED].forEach((listener) => listener(appId)); + } + + onAppRemoved(appId) { + console.log('app removed', appId); + this.listeners[AppEvents.APP_REMOVED].forEach((listener) => listener(appId)); + } + + onAppUpdated(appId) { + console.log('app updated', appId); + } + + onAppStatusUpdated({ appId, status }) { + console.log('App Status Update:', appId, status); } onCommandAdded(command) { diff --git a/packages/rocketchat-apps/package.js b/packages/rocketchat-apps/package.js index a145ff3c86e9..a81e5d7123c5 100644 --- a/packages/rocketchat-apps/package.js +++ b/packages/rocketchat-apps/package.js @@ -87,6 +87,6 @@ Package.onUse(function(api) { Npm.depends({ 'busboy': '0.2.13', - '@rocket.chat/apps-engine': '0.3.7', + '@rocket.chat/apps-engine': '0.3.9', '@rocket.chat/apps-ts-definition': '0.7.6' }); diff --git a/packages/rocketchat-apps/server/communication/index.js b/packages/rocketchat-apps/server/communication/index.js index d62c3a27feb7..ad9090a71b4b 100644 --- a/packages/rocketchat-apps/server/communication/index.js +++ b/packages/rocketchat-apps/server/communication/index.js @@ -1,10 +1,11 @@ import { AppMethods} from './methods'; import { AppsRestApi } from './rest'; -import { AppWebsocketNotifier, AppWebsocketListener } from './websockets'; +import { AppEvents, AppServerNotifier, AppServerListener } from './websockets'; export { AppMethods, AppsRestApi, - AppWebsocketNotifier, - AppWebsocketListener + AppEvents, + AppServerNotifier, + AppServerListener }; diff --git a/packages/rocketchat-apps/server/communication/websockets.js b/packages/rocketchat-apps/server/communication/websockets.js index a43eb9238032..30e3c5464515 100644 --- a/packages/rocketchat-apps/server/communication/websockets.js +++ b/packages/rocketchat-apps/server/communication/websockets.js @@ -1,68 +1,123 @@ -export class AppWebsocketListener { - constructor(orch, streamer) { +import { AppStatus, AppStatusUtils } from '@rocket.chat/apps-ts-definition/AppStatus'; + +export const AppEvents = Object.freeze({ + APP_ADDED: 'app/added', + APP_REMOVED: 'app/removed', + APP_UPDATED: 'app/updated', + APP_STATUS_CHANGE: 'app/statusUpdate', + COMMAND_ADDED: 'command/added', + COMMAND_DISABLED: 'command/disabled', + COMMAND_UPDATED: 'command/updated', + COMMAND_REMOVED: 'command/removed' +}); + +export class AppServerListener { + constructor(orch, engineStreamer, clientStreamer) { this.orch = orch; - this.streamer = streamer; + this.engineStreamer = engineStreamer; + this.clientStreamer = clientStreamer; - this.streamer.on('app/added', this.onAppAdded.bind(this)); - this.streamer.on('app/statusUpdate', this.onAppStatusUpdated.bind(this)); - this.streamer.on('app/removed', this.onAppRemoved.bind(this)); - console.log('hello from the app websocket listener'); + this.engineStreamer.on(AppEvents.APP_ADDED, this.onAppAdded.bind(this)); + this.engineStreamer.on(AppEvents.APP_STATUS_CHANGE, this.onAppStatusUpdated.bind(this)); + this.engineStreamer.on(AppEvents.APP_REMOVED, this.onAppRemoved.bind(this)); + this.engineStreamer.on(AppEvents.COMMAND_ADDED, this.onCommandAdded.bind(this)); + this.engineStreamer.on(AppEvents.COMMAND_DISABLED, this.onCommandDisabled.bind(this)); + this.engineStreamer.on(AppEvents.COMMAND_UPDATED, this.onCommandUpdated.bind(this)); + this.engineStreamer.on(AppEvents.COMMAND_REMOVED, this.onCommandRemoved.bind(this)); } onAppAdded(appId) { - console.log('On App Added!', appId); - this.orch.getManager().loadOne(appId).then(() => console.log('yay')); + console.log('On App Added! :)', appId); + this.orch.getManager().loadOne(appId).then(() => this.clientStreamer.emit(AppEvents.APP_ADDED, appId)); } onAppStatusUpdated({ appId, status }) { console.log('App Status Update:', appId, status); + + if (AppStatusUtils.isEnabled(status)) { + this.orch.getManager().enable(appId).then(() => this.clientStreamer.emit(AppEvents.APP_STATUS_CHANGE, { appId, status })); + } else if (AppStatusUtils.isDisabled(status)) { + this.orch.getManager().disable(appId, AppStatus.MANUALLY_DISABLED === status).then(() => this.clientStreamer.emit(AppEvents.APP_STATUS_CHANGE, { appId, status })); + } } onAppRemoved(appId) { console.log('On App Removed!', appId); + this.orch.getManager().remove(appId).then(() => this.clientStreamer.emit(AppEvents.APP_REMOVED, appId)); + } + + onCommandAdded(command) { + this.clientStreamer.emit(AppEvents.COMMAND_ADDED, command); + } + + onCommandDisabled(command) { + this.clientStreamer.emit(AppEvents.COMMAND_DISABLED, command); + } + + onCommandUpdated(command) { + this.clientStreamer.emit(AppEvents.COMMAND_UPDATED, command); + } + + onCommandRemoved(command) { + this.clientStreamer.emit(AppEvents.COMMAND_REMOVED, command); } } -export class AppWebsocketNotifier { +export class AppServerNotifier { constructor(orch) { - this.streamer = new Meteor.Streamer('apps', { retransmit: true, retransmitToSelf: true }); - this.streamer.allowRead('all'); - this.streamer.allowEmit('all'); - this.streamer.allowWrite('none'); + 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 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.listener = new AppWebsocketListener(orch, this.streamer); + this.listener = new AppServerListener(orch, this.engineStreamer, this.clientStreamer); } appAdded(appId) { - this.streamer.emit('app/added', appId); - console.log('emitting: "app/added"', appId); + this.engineStreamer.emit('app/added', appId); + this.clientStreamer.emit('app/added', appId); } appRemoved(appId) { - this.streamer.emit('app/removed', appId); + this.engineStreamer.emit('app/removed', appId); + this.clientStreamer.emit('app/removed', appId); } appUpdated(appId) { - this.streamer.emit('app/updated', appId); + this.engineStreamer.emit('app/updated', appId); + this.clientStreamer.emit('app/updated', appId); } appStatusUpdated(appId, status) { - this.streamer.emit('app/statusUpdate', { appId, status }); + this.engineStreamer.emit('app/statusUpdate', { appId, status }); + this.clientStreamer.emit('app/statusUpdate', { appId, status }); } commandAdded(command) { - this.streamer.emit('command/added', command); + this.engineStreamer.emit('command/added', command); + this.clientStreamer.emit('command/added', command); } commandDisabled(command) { - this.streamer.emit('command/disabled', command); + this.engineStreamer.emit('command/disabled', command); + this.clientStreamer.emit('command/disabled', command); } commandUpdated(command) { - this.streamer.emit('command/updated', command); + this.engineStreamer.emit('command/updated', command); + this.clientStreamer.emit('command/updated', command); } commandRemoved(command) { - this.streamer.emit('command/removed', command); + this.engineStreamer.emit('command/removed', command); + this.clientStreamer.emit('command/removed', command); } } diff --git a/packages/rocketchat-apps/server/orchestrator.js b/packages/rocketchat-apps/server/orchestrator.js index bccd0a9dfd88..71a6f6b92075 100644 --- a/packages/rocketchat-apps/server/orchestrator.js +++ b/packages/rocketchat-apps/server/orchestrator.js @@ -1,5 +1,5 @@ import { RealAppBridges } from './bridges'; -import { AppMethods, AppsRestApi, AppWebsocketNotifier } from './communication'; +import { AppMethods, AppsRestApi, AppServerNotifier } from './communication'; import { AppMessagesConverter, AppRoomsConverter, AppSettingsConverter, AppUsersConverter } from './converters'; import { AppsLogsModel, AppsModel, AppsPersistenceModel, AppRealStorage, AppRealLogsStorage } from './storage'; @@ -29,7 +29,7 @@ class AppServerOrchestrator { this._communicators = new Map(); this._communicators.set('methods', new AppMethods(this._manager)); - this._communicators.set('notifier', new AppWebsocketNotifier(this)); + this._communicators.set('notifier', new AppServerNotifier(this)); this._communicators.set('restapi', new AppsRestApi(this, this._manager)); } diff --git a/packages/rocketchat-apps/server/storage/logs-storage.js b/packages/rocketchat-apps/server/storage/logs-storage.js index baaa817331f1..a184526883bc 100644 --- a/packages/rocketchat-apps/server/storage/logs-storage.js +++ b/packages/rocketchat-apps/server/storage/logs-storage.js @@ -22,14 +22,11 @@ export class AppRealLogsStorage extends AppLogStorage { } storeEntries(appId, logger) { - console.log(appId); return new Promise((resolve, reject) => { const item = AppConsole.toStorageEntry(appId, logger); try { - console.log(item); const id = this.db.insert(item); - console.log(id); resolve(this.db.findOneById(id)); } catch (e) { diff --git a/server/stream/streamBroadcast.js b/server/stream/streamBroadcast.js index 0ff335dd8a2c..1b6954ca50f6 100644 --- a/server/stream/streamBroadcast.js +++ b/server/stream/streamBroadcast.js @@ -141,11 +141,17 @@ Meteor.methods({ return 'not-authorized'; } - if (!Meteor.StreamerCentral.instances[streamName]) { + const instance = Meteor.StreamerCentral.instances[streamName]; + if (!instance) { return 'stream-not-exists'; } - Meteor.StreamerCentral.instances[streamName]._emit(eventName, args); + if (instance.serverOnly) { + const scope = {}; + instance.emitWithScope(eventName, scope, ...args); + } else { + Meteor.StreamerCentral.instances[streamName]._emit(eventName, args); + } } }); From b468b06f99e17fa875b6c55ac09b8d80940f30de Mon Sep 17 00:00:00 2001 From: graywolf336 Date: Mon, 26 Feb 2018 13:03:07 -0600 Subject: [PATCH 3/3] Multi-instance servers now support Rocket.Chat Apps and get notified of updates --- .../client/admin/appInstall.js | 4 - .../rocketchat-apps/client/admin/appManage.js | 51 +++++++++++-- packages/rocketchat-apps/client/admin/apps.js | 1 - .../client/communication/websockets.js | 17 ++--- packages/rocketchat-apps/package.js | 2 +- .../server/bridges/activation.js | 8 -- .../rocketchat-apps/server/bridges/bridges.js | 6 ++ .../rocketchat-apps/server/bridges/details.js | 13 ++++ .../server/communication/websockets.js | 75 +++++++++++++------ 9 files changed, 123 insertions(+), 54 deletions(-) create mode 100644 packages/rocketchat-apps/server/bridges/details.js diff --git a/packages/rocketchat-apps/client/admin/appInstall.js b/packages/rocketchat-apps/client/admin/appInstall.js index 57fa44dd729a..e32ec62a694c 100644 --- a/packages/rocketchat-apps/client/admin/appInstall.js +++ b/packages/rocketchat-apps/client/admin/appInstall.js @@ -24,7 +24,6 @@ Template.appInstall.onCreated(function() { // Allow passing in a url as a query param to show installation of if (FlowRouter.getQueryParam('url')) { - console.log('Url:', FlowRouter.getQueryParam('url')); instance.appUrl.set(FlowRouter.getQueryParam('url')); FlowRouter.setQueryParams({ url: null }); } @@ -36,11 +35,8 @@ Template.appInstall.events({ // Handle url installations if (url) { - console.log('Installing via url.'); t.isInstalling.set(true); RocketChat.API.post('apps', { url }).then((result) => { - console.log('result', result); - FlowRouter.go(`/admin/apps/${ result.app.id }`); }).catch((err) => { console.warn('err', err); diff --git a/packages/rocketchat-apps/client/admin/appManage.js b/packages/rocketchat-apps/client/admin/appManage.js index 284a070853b6..8341928043b9 100644 --- a/packages/rocketchat-apps/client/admin/appManage.js +++ b/packages/rocketchat-apps/client/admin/appManage.js @@ -1,6 +1,8 @@ import _ from 'underscore'; import s from 'underscore.string'; +import { AppEvents } from '../communication'; + Template.appManage.onCreated(function() { const instance = this; this.id = new ReactiveVar(FlowRouter.getParam('appId')); @@ -13,24 +15,58 @@ Template.appManage.onCreated(function() { const id = this.id.get(); + function _morphSettings(settings) { + Object.keys(settings).forEach((k) => { + settings[k].i18nPlaceholder = settings[k].i18nPlaceholder || ' '; + settings[k].value = settings[k].value || settings[k].packageValue; + settings[k].oldValue = settings[k].value; + }); + + instance.settings.set(settings); + } + Promise.all([ RocketChat.API.get(`apps/${ id }`), RocketChat.API.get(`apps/${ id }/settings`) ]).then((results) => { instance.app.set(results[0].app); + _morphSettings(results[1].settings); - Object.keys(results[1].settings).forEach((k) => { - results[1].settings[k].i18nPlaceholder = results[1].settings[k].i18nPlaceholder || ' '; - results[1].settings[k].value = results[1].settings[k].value || results[1].settings[k].packageValue; - results[1].settings[k].oldValue = results[1].settings[k].value; - }); - - instance.settings.set(results[1].settings); this.ready.set(true); }).catch((e) => { instance.hasError.set(true); instance.theError.set(e.message); }); + + instance.onStatusChanged = function _onStatusChanged({ appId, status }) { + if (appId !== id) { + return; + } + + const app = instance.app.get(); + app.status = status; + instance.app.set(app); + }; + + instance.onSettingUpdated = function _onSettingUpdated({ appId }) { + if (appId !== id) { + return; + } + + RocketChat.API.get(`apps/${ id }/settings`).then((result) => { + _morphSettings(result.settings); + }); + }; + + window.Apps.getWsListener().registerListener(AppEvents.APP_STATUS_CHANGE, instance.onStatusChanged); + window.Apps.getWsListener().registerListener(AppEvents.APP_SETTING_UPDATED, instance.onSettingUpdated); +}); + +Template.apps.onDestroyed(function() { + const instance = this; + + window.Apps.getWsListener().unregisterListener(AppEvents.APP_STATUS_CHANGE, instance.onStatusChanged); + window.Apps.getWsListener().unregisterListener(AppEvents.APP_SETTING_UPDATED, instance.onSettingUpdated); }); Template.appManage.helpers({ @@ -173,7 +209,6 @@ Template.appManage.events({ 'change .input-monitor, keyup .input-monitor': _.throttle(function(e, t) { let value = s.trim($(e.target).val()); - console.log(value); switch (this.type) { case 'int': value = parseInt(value); diff --git a/packages/rocketchat-apps/client/admin/apps.js b/packages/rocketchat-apps/client/admin/apps.js index 4b92e3346478..9582e1e494ab 100644 --- a/packages/rocketchat-apps/client/admin/apps.js +++ b/packages/rocketchat-apps/client/admin/apps.js @@ -6,7 +6,6 @@ Template.apps.onCreated(function() { this.apps = new ReactiveVar([]); RocketChat.API.get('apps').then((result) => { - console.log(result.apps); instance.apps.set(result.apps); instance.ready.set(true); }); diff --git a/packages/rocketchat-apps/client/communication/websockets.js b/packages/rocketchat-apps/client/communication/websockets.js index c9df584bedf1..2f2ccc9aa7e2 100644 --- a/packages/rocketchat-apps/client/communication/websockets.js +++ b/packages/rocketchat-apps/client/communication/websockets.js @@ -3,6 +3,7 @@ export const AppEvents = Object.freeze({ APP_REMOVED: 'app/removed', APP_UPDATED: 'app/updated', APP_STATUS_CHANGE: 'app/statusUpdate', + APP_SETTING_UPDATED: 'app/settingUpdated', COMMAND_ADDED: 'command/added', COMMAND_DISABLED: 'command/disabled', COMMAND_UPDATED: 'command/updated', @@ -18,33 +19,28 @@ export class AppWebsocketReceiver { this.streamer.on(AppEvents.APP_REMOVED, this.onAppRemoved.bind(this)); this.streamer.on(AppEvents.APP_UPDATED, this.onAppUpdated.bind(this)); this.streamer.on(AppEvents.APP_STATUS_CHANGE, this.onAppStatusUpdated.bind(this)); + this.streamer.on(AppEvents.APP_SETTING_UPDATED, this.onAppSettingUpdated.bind(this)); this.streamer.on(AppEvents.COMMAND_ADDED, this.onCommandAdded.bind(this)); this.streamer.on(AppEvents.COMMAND_DISABLED, this.onCommandDisabled.bind(this)); this.streamer.on(AppEvents.COMMAND_UPDATED, this.onCommandUpdated.bind(this)); this.streamer.on(AppEvents.COMMAND_REMOVED, this.onCommandDisabled.bind(this)); - console.log('apps websocket listener'); this.listeners = {}; Object.keys(AppEvents).forEach((v) => { this.listeners[AppEvents[v]] = []; }); - - console.log(this.listeners); } registerListener(event, listener) { - console.log('Registering a listener for:', event); this.listeners[event].push(listener); } unregisterListener(event, listener) { - console.log('Unregistering a listener for:', event); this.listeners[event].splice(this.listeners[event].indexOf(listener), 1); } onAppAdded(appId) { - console.log('app added'); RocketChat.API.get(`apps/${ appId }/languages`).then((result) => { this.orch.parseAndLoadLanguages(result.languages); }); @@ -53,16 +49,19 @@ export class AppWebsocketReceiver { } onAppRemoved(appId) { - console.log('app removed', appId); this.listeners[AppEvents.APP_REMOVED].forEach((listener) => listener(appId)); } onAppUpdated(appId) { - console.log('app updated', appId); + this.listeners[AppEvents.APP_UPDATED].forEach((listener) => listener(appId)); } onAppStatusUpdated({ appId, status }) { - console.log('App Status Update:', appId, status); + this.listeners[AppEvents.APP_STATUS_CHANGE].forEach((listener) => listener({ appId, status })); + } + + onAppSettingUpdated({ appId }) { + this.listeners[AppEvents.APP_SETTING_UPDATED].forEach((listener) => listener({ appId })); } onCommandAdded(command) { diff --git a/packages/rocketchat-apps/package.js b/packages/rocketchat-apps/package.js index a81e5d7123c5..3a96da97e885 100644 --- a/packages/rocketchat-apps/package.js +++ b/packages/rocketchat-apps/package.js @@ -87,6 +87,6 @@ Package.onUse(function(api) { Npm.depends({ 'busboy': '0.2.13', - '@rocket.chat/apps-engine': '0.3.9', + '@rocket.chat/apps-engine': '0.4.0', '@rocket.chat/apps-ts-definition': '0.7.6' }); diff --git a/packages/rocketchat-apps/server/bridges/activation.js b/packages/rocketchat-apps/server/bridges/activation.js index 821eaaa6731f..b15bcc0c4e24 100644 --- a/packages/rocketchat-apps/server/bridges/activation.js +++ b/packages/rocketchat-apps/server/bridges/activation.js @@ -4,26 +4,18 @@ export class AppActivationBridge { } appAdded(app) { - console.log(`The ${ app.getName() } App (${ app.getID() }) has been added.`); - this.orch.getNotifier().appAdded(app.getID()); } appUpdated(app) { - console.log(`The ${ app.getName() } App (${ app.getID() }) has been updated.`); - this.orch.getNotifier().appUpdated(app.getID()); } appRemoved(app) { - console.log(`The ${ app.getName() } App (${ app.getID() }) has been removed.`); - this.orch.getNotifier().appRemoved(app.getID()); } appStatusChanged(app, status) { - console.log(`The ${ app.getName() } App (${ app.getID() }) status has changed: ${ status }`); - this.orch.getNotifier().appStatusUpdated(app.getID(), status); } } diff --git a/packages/rocketchat-apps/server/bridges/bridges.js b/packages/rocketchat-apps/server/bridges/bridges.js index 1294e16e1de8..ff7dc3cd0149 100644 --- a/packages/rocketchat-apps/server/bridges/bridges.js +++ b/packages/rocketchat-apps/server/bridges/bridges.js @@ -1,6 +1,7 @@ import { AppBridges } from '@rocket.chat/apps-engine/server/bridges'; import { AppActivationBridge } from './activation'; +import { AppDetailChangesBridge } from './details'; import { AppCommandsBridge } from './commands'; import { AppEnvironmentalVariableBridge } from './environmental'; import { AppHttpBridge } from './http'; @@ -16,6 +17,7 @@ export class RealAppBridges extends AppBridges { this._actBridge = new AppActivationBridge(orch); this._cmdBridge = new AppCommandsBridge(orch); + this._detBridge = new AppDetailChangesBridge(orch); this._envBridge = new AppEnvironmentalVariableBridge(orch); this._httpBridge = new AppHttpBridge(); this._msgBridge = new AppMessageBridge(orch); @@ -49,6 +51,10 @@ export class RealAppBridges extends AppBridges { return this._actBridge; } + getAppDetailChangesBridge() { + return this._detBridge; + } + getRoomBridge() { return this._roomBridge; } diff --git a/packages/rocketchat-apps/server/bridges/details.js b/packages/rocketchat-apps/server/bridges/details.js new file mode 100644 index 000000000000..dea680abe07a --- /dev/null +++ b/packages/rocketchat-apps/server/bridges/details.js @@ -0,0 +1,13 @@ +export class AppDetailChangesBridge { + constructor(orch) { + this.orch = orch; + } + + onAppSettingsChange(appId, setting) { + try { + this.orch.getNotifier().appSettingsChange(appId, setting); + } catch (e) { + console.warn('failed to notify about the setting change.', appId); + } + } +} diff --git a/packages/rocketchat-apps/server/communication/websockets.js b/packages/rocketchat-apps/server/communication/websockets.js index 30e3c5464515..f9cd49f4a1b6 100644 --- a/packages/rocketchat-apps/server/communication/websockets.js +++ b/packages/rocketchat-apps/server/communication/websockets.js @@ -5,6 +5,7 @@ export const AppEvents = Object.freeze({ APP_REMOVED: 'app/removed', APP_UPDATED: 'app/updated', APP_STATUS_CHANGE: 'app/statusUpdate', + APP_SETTING_UPDATED: 'app/settingUpdated', COMMAND_ADDED: 'command/added', COMMAND_DISABLED: 'command/disabled', COMMAND_UPDATED: 'command/updated', @@ -12,13 +13,15 @@ export const AppEvents = Object.freeze({ }); export class AppServerListener { - constructor(orch, engineStreamer, clientStreamer) { + constructor(orch, engineStreamer, clientStreamer, recieved) { this.orch = orch; this.engineStreamer = engineStreamer; this.clientStreamer = clientStreamer; + this.recieved = recieved; this.engineStreamer.on(AppEvents.APP_ADDED, this.onAppAdded.bind(this)); this.engineStreamer.on(AppEvents.APP_STATUS_CHANGE, this.onAppStatusUpdated.bind(this)); + this.engineStreamer.on(AppEvents.APP_SETTING_UPDATED, this.onAppSettingUpdated.bind(this)); this.engineStreamer.on(AppEvents.APP_REMOVED, this.onAppRemoved.bind(this)); this.engineStreamer.on(AppEvents.COMMAND_ADDED, this.onCommandAdded.bind(this)); this.engineStreamer.on(AppEvents.COMMAND_DISABLED, this.onCommandDisabled.bind(this)); @@ -27,22 +30,29 @@ export class AppServerListener { } onAppAdded(appId) { - console.log('On App Added! :)', appId); this.orch.getManager().loadOne(appId).then(() => this.clientStreamer.emit(AppEvents.APP_ADDED, appId)); } onAppStatusUpdated({ appId, status }) { - console.log('App Status Update:', appId, status); + this.recieved.set(`${ AppEvents.APP_STATUS_CHANGE }_${ appId }`, { appId, status, when: new Date() }); if (AppStatusUtils.isEnabled(status)) { - this.orch.getManager().enable(appId).then(() => this.clientStreamer.emit(AppEvents.APP_STATUS_CHANGE, { appId, status })); + this.orch.getManager().enable(appId) + .then(() => this.clientStreamer.emit(AppEvents.APP_STATUS_CHANGE, { appId, status })); } else if (AppStatusUtils.isDisabled(status)) { - this.orch.getManager().disable(appId, AppStatus.MANUALLY_DISABLED === status).then(() => this.clientStreamer.emit(AppEvents.APP_STATUS_CHANGE, { appId, status })); + this.orch.getManager().disable(appId, AppStatus.MANUALLY_DISABLED === status) + .then(() => this.clientStreamer.emit(AppEvents.APP_STATUS_CHANGE, { appId, status })); } } + onAppSettingUpdated({ appId, setting }) { + this.recieved.set(`${ AppEvents.APP_SETTING_UPDATED }_${ appId }_${ setting.id }`, { appId, setting, when: new Date() }); + + this.orch.getManager().getSettingsManager().updateAppSetting(appId, setting) + .then(() => this.clientStreamer.emit(AppEvents.APP_SETTING_UPDATED, { appId })); + } + onAppRemoved(appId) { - console.log('On App Removed!', appId); this.orch.getManager().remove(appId).then(() => this.clientStreamer.emit(AppEvents.APP_REMOVED, appId)); } @@ -78,46 +88,65 @@ export class AppServerNotifier { this.clientStreamer.allowEmit('all'); this.clientStreamer.allowWrite('none'); - this.listener = new AppServerListener(orch, this.engineStreamer, this.clientStreamer); + this.recieved = new Map(); + this.listener = new AppServerListener(orch, this.engineStreamer, this.clientStreamer, this.recieved); } appAdded(appId) { - this.engineStreamer.emit('app/added', appId); - this.clientStreamer.emit('app/added', appId); + this.engineStreamer.emit(AppEvents.APP_ADDED, appId); + this.clientStreamer.emit(AppEvents.APP_ADDED, appId); } appRemoved(appId) { - this.engineStreamer.emit('app/removed', appId); - this.clientStreamer.emit('app/removed', appId); + this.engineStreamer.emit(AppEvents.APP_REMOVED, appId); + this.clientStreamer.emit(AppEvents.APP_REMOVED, appId); } appUpdated(appId) { - this.engineStreamer.emit('app/updated', appId); - this.clientStreamer.emit('app/updated', appId); + this.engineStreamer.emit(AppEvents.APP_UPDATED, appId); + this.clientStreamer.emit(AppEvents.APP_UPDATED, appId); } appStatusUpdated(appId, status) { - this.engineStreamer.emit('app/statusUpdate', { appId, status }); - this.clientStreamer.emit('app/statusUpdate', { appId, status }); + if (this.recieved.has(`${ AppEvents.APP_STATUS_CHANGE }_${ appId }`)) { + const details = this.recieved.get(`${ AppEvents.APP_STATUS_CHANGE }_${ appId }`); + if (details.status === status) { + this.recieved.delete(`${ AppEvents.APP_STATUS_CHANGE }_${ appId }`); + return; + } + } + + this.engineStreamer.emit(AppEvents.APP_STATUS_CHANGE, { appId, status }); + this.clientStreamer.emit(AppEvents.APP_STATUS_CHANGE, { appId, status }); + } + + appSettingsChange(appId, setting) { + if (this.recieved.has(`${ AppEvents.APP_SETTING_UPDATED }_${ appId }_${ setting.id }`)) { + this.recieved.delete(`${ AppEvents.APP_SETTING_UPDATED }_${ appId }_${ setting.id }`); + return; + } + + this.engineStreamer.emit(AppEvents.APP_SETTING_UPDATED, { appId, setting }); + this.clientStreamer.emit(AppEvents.APP_SETTING_UPDATED, { appId }); } commandAdded(command) { - this.engineStreamer.emit('command/added', command); - this.clientStreamer.emit('command/added', command); + this.engineStreamer.emit(AppEvents.COMMAND_ADDED, command); + this.clientStreamer.emit(AppEvents.COMMAND_ADDED, command); } commandDisabled(command) { - this.engineStreamer.emit('command/disabled', command); - this.clientStreamer.emit('command/disabled', command); + this.engineStreamer.emit(AppEvents.COMMAND_DISABLED, command); + this.clientStreamer.emit(AppEvents.COMMAND_DISABLED, command); } commandUpdated(command) { - this.engineStreamer.emit('command/updated', command); - this.clientStreamer.emit('command/updated', command); + this.engineStreamer.emit(AppEvents.COMMAND_UPDATED, command); + this.clientStreamer.emit(AppEvents.COMMAND_UPDATED, command); } commandRemoved(command) { - this.engineStreamer.emit('command/removed', command); - this.clientStreamer.emit('command/removed', command); + this.engineStreamer.emit(AppEvents.COMMAND_REMOVED, command); + this.clientStreamer.emit(AppEvents.COMMAND_REMOVED, command); } }