Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[OTHER] More Listeners for Apps & Utilize Promises inside Apps #10335

Merged
merged 15 commits into from
Apr 18, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Allow updating of an app. Add buttons to view logs and update app. Fi…
…x various issues related to the apps
  • Loading branch information
graywolf336 committed Apr 4, 2018
commit b7dda74ed459ee8e8a1bfb935eb8506979188a76
2 changes: 1 addition & 1 deletion packages/rocketchat-apps/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ An orchestrator is the file/class which is responsible for orchestrating (starti
A bridge is a file/class which is responsible for bridging the Rocket.Chat system's data and the App system's data. They are implementations of the interfaces inside of the Rocket.Chat Apps-engine project `src/server/bridges`. They allow the two systems to talk to each other (hince the name bridge, as they "bridge the gap").

## What is a "Converter"?
A converter does what the name implies, it handles converting from one system's data type into the other's.
A converter does what the name implies, it handles converting from one system's data type into the other's. **Note**: This causes a schema to be forced on the rooms and messages.
24 changes: 22 additions & 2 deletions packages/rocketchat-apps/client/admin/appInstall.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,17 @@ Template.appInstall.onCreated(function() {
instance.file = new ReactiveVar('');
instance.isInstalling = new ReactiveVar(false);
instance.appUrl = new ReactiveVar('');
instance.isUpdatingId = new ReactiveVar('');

// Allow passing in a url as a query param to show installation of
if (FlowRouter.getQueryParam('url')) {
instance.appUrl.set(FlowRouter.getQueryParam('url'));
FlowRouter.setQueryParams({ url: null });
}

if (FlowRouter.getQueryParam('isUpdatingId')) {
instance.isUpdatingId.set(FlowRouter.getQueryParam('isUpdatingId'));
}
});

Template.appInstall.events({
Expand All @@ -55,13 +60,21 @@ Template.appInstall.events({
if (url) {
try {
t.isInstalling.set(true);
const result = await RocketChat.API.post('apps', { url });
let result;

if (t.isUpdatingId.get()) {
result = await RocketChat.API.post(`apps/${ t.isUpdatingId.get() }`, { url });
} else {
result = await RocketChat.API.post('apps', { url });
}

FlowRouter.go(`/admin/apps/${ result.app.id }`);
} catch (err) {
console.warn('err', err);
} finally {
t.isInstalling.set(false);
}

return;
}

Expand All @@ -85,7 +98,14 @@ Template.appInstall.events({

t.isInstalling.set(true);
try {
const result = await RocketChat.API.upload('apps', data);
let result;

if (t.isUpdatingId.get()) {
result = await RocketChat.API.upload(`apps/${ t.isUpdatingId.get() }`, data);
} else {
result = await RocketChat.API.upload('apps', data);
}

FlowRouter.go(`/admin/apps/${ result.app.id }`);
} catch (err) {
console.warn('err', err);
Expand Down
4 changes: 3 additions & 1 deletion packages/rocketchat-apps/client/admin/appManage.html
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
<div class="rc-apps-details__content">
<div class="rc-apps-details__row">
<div class="rc-apps-details__name">{{name}}</div>
<div class="rc-apps-details__version">v {{version}}</div>
<div class="rc-apps-details__version">v{{version}}</div>
</div>
<div class="rc-apps-details__row">{{description}}</div>
<div class="rc-apps-details__row">
Expand Down Expand Up @@ -236,6 +236,8 @@
<button class="rc-button rc-button--secondary js-cancel">{{ _ "Cancel" }}</button>
<button class="rc-button rc-button--primary js-save" disabled='{{disabled}}'>{{ _ "Save" }}</button>
<button class="rc-button rc-button--cancel js-uninstall">{{ _ "Uninstall" }}</button>
<button class="rc-button rc-button--secondary js-update">{{_ "Update" }}</button>
<button class="rc-button rc-button--secondary js-view-logs">{{_ "View_Logs" }}</button>
</div>
</div>
{{else if hasError}}
Expand Down
6 changes: 5 additions & 1 deletion packages/rocketchat-apps/client/admin/appManage.js
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,11 @@ Template.appManage.events({
});
},

'click .logs': (e, t) => {
'click .js-update': (e, t) => {
FlowRouter.go(`/admin/app/install?isUpdatingId=${ t.id.get() }`);
},

'click .js-view-logs': (e, t) => {
FlowRouter.go(`/admin/apps/${ t.id.get() }/logs`);
},

Expand Down
37 changes: 23 additions & 14 deletions packages/rocketchat-apps/server/communication/rest.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,15 +71,10 @@ export class AppsRestApi {
return RocketChat.API.v1.failure({ error: 'Failed to get a file to install for the App. '});
}

const item = Meteor.wrapAsync((callback) => {
manager.add(buff.toString('base64'), false).then((rl) => callback(undefined, rl)).catch((e) => {
console.warn('Error!', e);
callback(e);
});
})();
const prl = Promise.await(manager.add(buff.toString('base64'), false));

const info = item.getInfo();
info.status = item.getStatus();
const info = prl.getInfo();
info.status = prl.getStatus();

return RocketChat.API.v1.success({ app: info });
}
Expand Down Expand Up @@ -116,13 +111,27 @@ export class AppsRestApi {
console.log('Updating:', this.urlParams.id);
// TODO: Verify permissions

const buff = fileHandler(this.request, 'app');
const item = Meteor.wrapAsync((callback) => {
manager.update(buff.toString('base64')).then((rl) => callback(rl)).catch((e) => callback(e));
});
let buff;

if (this.bodyParams.url) {
const result = HTTP.call('GET', this.bodyParams.url, { npmRequestOptions: { encoding: 'base64' }});

if (result.statusCode !== 200 || !result.headers['content-type'] || result.headers['content-type'] !== 'application/zip') {
return RocketChat.API.v1.failure({ error: 'Invalid url. It doesn\'t exist or is not "application/zip".' });
}

buff = Buffer.from(result.content, 'base64');
} else {
buff = fileHandler(this.request, 'app');
}

if (!buff) {
return RocketChat.API.v1.failure({ error: 'Failed to get a file to install for the App. '});
}

const info = item.getInfo();
info.status = item.getStatus();
const prl = Promise.await(manager.update(buff.toString('base64')));
const info = prl.getInfo();
info.status = prl.getStatus();

return RocketChat.API.v1.success({ app: info });
},
Expand Down
19 changes: 12 additions & 7 deletions packages/rocketchat-apps/server/converters/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,18 @@ export class AppMessagesConverter {
}

const room = RocketChat.models.Rooms.findOneById(message.room.id);
const user = RocketChat.models.Users.findOneById(message.sender.id);

if (!room || !user) {
throw new Error('Invalid user or room provided on the message.');
if (!room) {
throw new Error('Invalid room provided on the message.');
}

let u;
if (message.sender && message.sender.id) {
const user = RocketChat.models.Users.findOneById(message.sender.id);
u = {
_id: user._id,
username: user.username
};
}

let editedBy;
Expand All @@ -67,10 +75,7 @@ export class AppMessagesConverter {
return {
_id: message.id || Random.id(),
rid: room._id,
u: {
_id: user._id,
username: user.username
},
u,
msg: message.text,
ts: message.createdAt || new Date(),
_updatedAt: message.updatedAt || new Date(),
Expand Down
58 changes: 36 additions & 22 deletions packages/rocketchat-lib/server/functions/createRoom.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,36 +16,46 @@ RocketChat.createRoom = function(type, name, owner, members, readOnly, extraData
throw new Meteor.Error('error-invalid-user', 'Invalid user', { function: 'RocketChat.createRoom' });
}

const slugifiedRoomName = RocketChat.getValidRoomName(name);

const now = new Date();
if (!_.contains(members, owner.username)) {
members.push(owner.username);
}

if (type === 'c') {
RocketChat.callbacks.run('beforeCreateChannel', owner, {
t: 'c',
name: slugifiedRoomName,
fname: name,
ts: now,
ro: readOnly === true,
sysMes: readOnly !== true,
usernames: members,
u: {
_id: owner._id,
username: owner.username
}
});
}

extraData = Object.assign({}, extraData, {
const now = new Date();
let room = Object.assign({
name,
fname: RocketChat.getValidRoomName(name),
t: type,
usernames: members,
u: {
_id: owner._id,
username: owner.username
}
}, extraData, {
ts: now,
ro: readOnly === true,
sysMes: readOnly !== true
});

const room = RocketChat.models.Rooms.createWithTypeNameUserAndUsernames(type, slugifiedRoomName, name, owner, members, extraData);
if (Apps && Apps.isLoaded()) {
const prevent = Apps.getBridges().getListenerBridge().roomEvent('IPreRoomCreatePrevent', room);
if (prevent) {
throw new Meteor.Error('error-app-prevented-creation', 'A Rocket.Chat App prevented the room creation.');
}

let result;
result = Apps.getBridges().getListenerBridge().roomEvent('IPreRoomCreateExtend', room);
result = Apps.getBridges().getListenerBridge().roomEvent('IPreRoomCreateModify', result);

if (typeof result === 'object') {
room = Object.assign(room, result);
}
}

if (type === 'c') {
RocketChat.callbacks.run('beforeCreateChannel', owner, room);
}

room = RocketChat.models.Rooms.createWithFullRoomData(room);

for (const username of members) {
const member = RocketChat.models.Users.findOneByUsername(username, { fields: { username: 1 }});
Expand Down Expand Up @@ -79,8 +89,12 @@ RocketChat.createRoom = function(type, name, owner, members, readOnly, extraData
});
}

if (Apps && Apps.isLoaded()) {
Apps.getBridges().getListenerBridge().roomEvent('IPostRoomCreate', room);
}

return {
rid: room._id,
name: slugifiedRoomName
name: room.fname
};
};
28 changes: 14 additions & 14 deletions packages/rocketchat-lib/server/functions/sendMessage.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,6 @@ RocketChat.sendMessage = function(user, message, room, upsert = false) {
}
}

if (message.parseUrls !== false) {
const urls = message.msg.match(/([A-Za-z]{3,9}):\/\/([-;:&=\+\$,\w]+@{1})?([-A-Za-z0-9\.]+)+:?(\d+)?((\/[-\+=!:~%\/\.@\,\(\)\w]*)?\??([-\+=&!:;%@\/\.\,\w]+)?(?:#([^\s\)]+))?)?/g);

if (urls) {
message.urls = urls.map(function(url) {
return {
url
};
});
}
}

if (RocketChat.settings.get('Message_Read_Receipt_Enabled')) {
message.unread = true;
}
Expand All @@ -45,15 +33,27 @@ RocketChat.sendMessage = function(user, message, room, upsert = false) {
if (message && Apps && Apps.isLoaded()) {
const prevent = Apps.getBridges().getListenerBridge().messageEvent('IPreMessageSentPrevent', message);
if (prevent) {
return false;
throw new Meteor.Error('error-app-prevented-sending', 'A Rocket.Chat App prevented the messaging sending.');
}

let result;
result = Apps.getBridges().getListenerBridge().messageEvent('IPreMessageSentExtend', message);
result = Apps.getBridges().getListenerBridge().messageEvent('IPreMessageSentModify', result);

if (typeof result === 'object') {
message = result;
message = Object.assign(message, result);
}
}

if (message.parseUrls) {
const urls = message.msg.match(/([A-Za-z]{3,9}):\/\/([-;:&=\+\$,\w]+@{1})?([-A-Za-z0-9\.]+)+:?(\d+)?((\/[-\+=!:~%\/\.@\,\(\)\w]*)?\??([-\+=&!:;%@\/\.\,\w]+)?(?:#([^\s\)]+))?)?/g);

if (urls) {
message.urls = urls.map(function(url) {
return {
url
};
});
}
}

Expand Down
7 changes: 7 additions & 0 deletions packages/rocketchat-lib/server/models/Rooms.js
Original file line number Diff line number Diff line change
Expand Up @@ -800,6 +800,13 @@ class ModelRooms extends RocketChat.models._Base {
return room;
}

createWithFullRoomData(room) {
delete room._id;

room._id = this.insert(room);
return room;
}


// REMOVE
removeById(_id) {
Expand Down