Skip to content

Commit

Permalink
Merge branch 'wrapper-call-meteor-methods-over-rest' of github.com:Ro…
Browse files Browse the repository at this point in the history
…cketChat/Rocket.Chat into wrapper-call-meteor-methods-over-rest

* 'wrapper-call-meteor-methods-over-rest' of github.com:RocketChat/Rocket.Chat:
  Use EJSON to transport data
  Fix design review (#17133)
  Regression: Direct message creation by REST (#17109)
  Overwrite rate limiter
  Add setting to turn it off
  • Loading branch information
ggazzo committed Apr 2, 2020
2 parents e27cdbf + 38909f7 commit 2d039dc
Show file tree
Hide file tree
Showing 13 changed files with 113 additions and 85 deletions.
1 change: 1 addition & 0 deletions app/api/server/lib/rooms.js
Expand Up @@ -18,6 +18,7 @@ export async function findAdminRooms({ uid, filter, types = [], pagination: { of
unmuted: 1,
ro: 1,
default: 1,
featured: 1,
topic: 1,
msgs: 1,
archived: 1,
Expand Down
12 changes: 10 additions & 2 deletions app/api/server/v1/im.js
Expand Up @@ -32,10 +32,18 @@ function findDirectMessageRoom(params, user) {

API.v1.addRoute(['dm.create', 'im.create'], { authRequired: true }, {
post() {
const findResult = findDirectMessageRoom(this.requestParams(), this.user);
const { username, usernames } = this.requestParams();

const users = username ? [username] : usernames && usernames.split(',').map((username) => username.trim());

if (!users) {
throw new Meteor.Error('error-room-not-found', 'The required "username" or "usernames" param provided does not match any direct message');
}

const room = Meteor.call('createDirectMessage', ...users);

return API.v1.success({
room: findResult.room,
room: { ...room, _id: room.rid },
});
},
});
Expand Down
19 changes: 10 additions & 9 deletions app/api/server/v1/misc.js
@@ -1,6 +1,7 @@
import { Meteor } from 'meteor/meteor';
import { check } from 'meteor/check';
import { TAPi18n } from 'meteor/rocketchat:tap-i18n';
import { EJSON } from 'meteor/ejson';
import s from 'underscore.string';

import { hasRole, hasPermission } from '../../../authorization/server';
Expand Down Expand Up @@ -217,22 +218,22 @@ API.v1.addRoute('stdout.queue', { authRequired: true }, {
},
});

const methodCall = {
const methodCall = () => ({
post() {
check(this.bodyParams, {
method: String,
params: Array,
params: String,
});

const { method, params } = this.bodyParams;

const result = Meteor.call(method, ...params);
const result = Meteor.call(method, ...params ? EJSON.parse(params) : []);

return API.v1.success({ result });
return API.v1.success({ result: EJSON.stringify(result) });
},
};
});

// had to create different endpoint for authenticated and non-authenticated calls
// because restivus does not 'this.userId' when 'authRequired: false'
API.v1.addRoute('method.call', { authRequired: true }, methodCall);
API.v1.addRoute('method.callAnon', { authRequired: false }, methodCall);
// had to create two different endpoints for authenticated and non-authenticated calls
// because restivus does not provide 'this.userId' if 'authRequired: false'
API.v1.addRoute('method.call', { authRequired: true, rateLimiterOptions: false }, methodCall());
API.v1.addRoute('method.callAnon', { authRequired: false, rateLimiterOptions: false }, methodCall());
4 changes: 4 additions & 0 deletions app/lib/server/startup/settings.js
Expand Up @@ -2854,6 +2854,10 @@ settings.addGroup('Troubleshoot', function() {
type: 'boolean',
alert: 'Troubleshoot_Disable_Workspace_Sync_Alert',
});
this.add('Troubleshoot_Use_REST_For_DDP_Calls', false, {
type: 'boolean',
alert: 'Troubleshoot_Use_REST_For_DDP_Calls_Alert',
});
});

settings.init();
6 changes: 4 additions & 2 deletions app/ui-admin/client/rooms/adminRooms.html
Expand Up @@ -34,7 +34,8 @@
<th width="20%"><div class="table-fake-th">{{_ "Type"}}</div></th>
<th width="20%"><div class="table-fake-th">{{_ "Users"}}</div></th>
<th width="10%"><div class="table-fake-th">{{_ "Msgs"}}</div></th>
<th width="20%"><div class="table-fake-th">{{_ "Default"}}</div></th>
<th width="10%"><div class="table-fake-th">{{_ "Default"}}</div></th>
<th width="10%"><div class="table-fake-th">{{_ "Featured"}}</div></th>
</tr>
</thead>
<tbody>
Expand All @@ -51,7 +52,8 @@
<td width="20%"><div class="rc-table-wrapper">{{type}}</div></td>
<td width="20%"><div class="rc-table-wrapper">{{usersCount}}</div></td>
<td width="10%"><div class="rc-table-wrapper">{{msgs}}</div></td>
<td width="20%"><div class="rc-table-wrapper">{{default}}</div></td>
<td width="10%"><div class="rc-table-wrapper">{{default}}</div></td>
<td width="10%"><div class="rc-table-wrapper">{{#if featured}}True{{else}}False{{/if}}</div></td>
</tr>
{{else}} {{# with searchText}}
<tr class="table-no-click">
Expand Down
7 changes: 7 additions & 0 deletions app/ui-master/server/inject.js
Expand Up @@ -51,6 +51,13 @@ Meteor.startup(() => {
`);
}

settings.get('Troubleshoot_Use_REST_For_DDP_Calls', (key, value) => {
if (!value) {
return injectIntoHead(key, '');
}
injectIntoHead(key, '<script>window.USE_REST_FOR_DDP_CALLS = true;</script>');
});

settings.get('Assets_SvgFavicon_Enable', (key, value) => {
const standardFavicons = `
<link rel="icon" sizes="16x16" type="image/png" href="assets/favicon_16.png" />
Expand Down
37 changes: 17 additions & 20 deletions app/ui/client/views/app/components/Directory/ChannelsTab.js
@@ -1,21 +1,24 @@
import React, { useMemo, useState, useCallback } from 'react';
import { Box, Margins, Table, Flex, Avatar, Tag } from '@rocket.chat/fuselage';
import { Box, Margins, Table, Avatar, Tag, Icon } from '@rocket.chat/fuselage';
import { useMediaQuery } from '@rocket.chat/fuselage-hooks';

import { useEndpointData } from '../../../../../../../ee/app/engagement-dashboard/client/hooks/useEndpointData';
import { DirectoryTable, Th } from './DirectoryTable';
import { DirectoryTable, Th, Markdown } from './DirectoryTable';
import { useTranslation } from '../../../../../../../client/contexts/TranslationContext';
import { useRoute } from '../../../../../../../client/contexts/RouterContext';
import { useQuery, useFormatDate } from '../hooks';
import { roomTypes } from '../../../../../../utils/client';

const style = { whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' };

function RoomTags({ room }) {
const t = useTranslation();
return <Margins inline='x2'>
{room.default && <Tag variant='primary'>{t('default')}</Tag>}
{room.featured && <Tag variant='primary'>{t('featured')}</Tag>}
</Margins>;
return <Box mi='x4' alignItems='center' display='flex'>
<Margins inline='x2'>
{room.default && <Tag variant='primary'>{t('default')}</Tag>}
{room.featured && <Tag variant='primary'>{t('featured')}</Tag>}
</Margins>
</Box>;
}

export function ChannelsTab() {
Expand Down Expand Up @@ -57,21 +60,15 @@ export function ChannelsTab() {
const formatDate = useFormatDate();
const renderRow = useCallback(({ _id, ts, name, fname, description, usersCount, lastMessage, topic, ...room }) => <Table.Row key={_id} onKeyDown={onClick(name)} onClick={onClick(name)} tabIndex={0} role='link' action>
<Table.Cell>
<Flex.Container>
<Box>
<Flex.Item>
<Avatar size='x40' title={fname || name} url={`%40${ fname || name }`} />
</Flex.Item>
<Margins inline='x8'>
<Flex.Item grow={1}>
<Box>
<Box textStyle='p2'>{fname || name} <RoomTags room={room} style={style} /></Box>
{topic && <Box textStyle='p1' textColor='hint' style={style}>{topic}</Box> }
</Box>
</Flex.Item>
</Margins>
<Box display='flex'>
<Avatar size='x40' title={fname || name} url={`%40${ fname || name }`} flexGrow={0} />
<Box grow={1} mi='x8' style={style}>
<Box display='flex' alignItems='center'>
<Icon name={roomTypes.getIcon(room)} textColor='hint' /> <Box textStyle='p2' textColor='default' mi='x4'>{fname || name}</Box><RoomTags room={room} style={style} />
</Box>
{topic && <Markdown textStyle='p1' textColor='hint' style={style}>{topic}</Markdown> }
</Box>
</Flex.Container>
</Box>
</Table.Cell>
<Table.Cell textStyle='p1' textColor='hint' style={style}>
{usersCount}
Expand Down
29 changes: 15 additions & 14 deletions app/ui/client/views/app/components/Directory/DirectoryTable.js
@@ -1,8 +1,9 @@
import React, { useMemo, useState, useEffect, useCallback } from 'react';
import { Box, Icon, Margins, Pagination, Skeleton, Table, Flex, TextInput, Tile } from '@rocket.chat/fuselage';
import { Box, Icon, Pagination, Skeleton, Table, Flex, TextInput, Tile } from '@rocket.chat/fuselage';

import { useTranslation } from '../../../../../../../client/contexts/TranslationContext';
import { useDebounce } from '../hooks';
import { Markdown as mrkd } from '../../../../../../markdown/client';

function SortIcon({ direction }) {
return <Box is='svg' width='x16' height='x16' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'>
Expand All @@ -14,27 +15,27 @@ function SortIcon({ direction }) {
export function Th({ children, active, direction, sort, onClick, align, ...props }) {
const fn = useMemo(() => () => onClick && onClick(sort), [sort, onClick]);
return <Table.Cell clickable={!!sort} onClick={fn} { ...props }>
<Flex.Container alignItems='center' wrap='no-wrap'>
<Box>{children}{sort && <SortIcon mod-active={active} direction={active && direction} />}</Box>
</Flex.Container>
<Box display='flex' alignItems='center' wrap='no-wrap'>{children}{sort && <SortIcon mod-active={active} direction={active && direction} />}</Box>
</Table.Cell>;
}

export function Markdown({ children, ...props }) {
return React.Children.map(children, function(text, index) {
return <Box { ...props } key={index} dangerouslySetInnerHTML={{ __html: mrkd.parse(text) }}/>;
});
}

const LoadingRow = ({ cols }) => <Table.Row>
<Table.Cell>
<Flex.Container>
<Box>
<Flex.Item>
<Skeleton variant='rect' height={40} width={40} />
</Flex.Item>
<Margins inline='x8'>
<Flex.Item grow={1}>
<Box>
<Skeleton width='100%' />
<Skeleton width='100%' />
</Box>
</Flex.Item>
</Margins>
<Box mi='x8' grow={1}>
<Skeleton width='100%' />
<Skeleton width='100%' />
</Box>
</Box>
</Flex.Container>
</Table.Cell>
Expand Down Expand Up @@ -78,9 +79,9 @@ export function DirectoryTable({
return <>
<Flex.Container direction='column'>
<Box>
<Margins block='x16'>
<Box mb='x16' display='flex'>
<TextInput placeholder={searchPlaceholder} addon={<Icon name='magnifier' size='x20'/>} onChange={handleChange} value={text} />
</Margins>
</Box>
{channels && !channels.length
? <Tile textStyle='p1' elevation='0' textColor='info' style={{ textAlign: 'center' }}>
{t('No_data_found')}
Expand Down
23 changes: 7 additions & 16 deletions app/ui/client/views/app/components/Directory/UserTab.js
@@ -1,5 +1,5 @@
import React, { useMemo, useState, useCallback } from 'react';
import { Box, Margins, Table, Flex, Avatar } from '@rocket.chat/fuselage';
import { Box, Table, Flex, Avatar } from '@rocket.chat/fuselage';
import { useMediaQuery } from '@rocket.chat/fuselage-hooks';

import { useEndpointData } from '../../../../../../../ee/app/engagement-dashboard/client/hooks/useEndpointData';
Expand Down Expand Up @@ -34,11 +34,9 @@ export function UserTab({
}
setSort([id, 'asc']);
};
const bioStyle = useMemo(() => ({ ...style, width: '200px' }), [mediaQuery]);

const header = useMemo(() => [
<Th key={'name'} direction={sort[1]} active={sort[0] === 'name'} onClick={onHeaderClick} sort='name'>{t('Name')}</Th>,
<Th key={'bio'} direction={sort[1]} active={sort[0] === 'bio'} onClick={onHeaderClick} sort='bio' style={bioStyle}>{t('Bio')}</Th>,
mediaQuery && canViewFullOtherUserInfo && <Th key={'email'} direction={sort[1]} active={sort[0] === 'email'} onClick={onHeaderClick} sort='email' style={{ width: '200px' }} >{t('Email')}</Th>,
federation && <Th key={'origin'} direction={sort[1]} active={sort[0] === 'origin'} onClick={onHeaderClick} sort='origin' style={{ width: '200px' }} >{t('Domain')}</Th>,
mediaQuery && <Th key={'createdAt'} direction={sort[1]} active={sort[0] === 'createdAt'} onClick={onHeaderClick} sort='createdAt' style={{ width: '200px' }}>{t('Joined_at')}</Th>,
Expand All @@ -64,22 +62,15 @@ export function UserTab({
<Flex.Item>
<Avatar size='x40' title={username} url={username} />
</Flex.Item>
<Margins inline='x8'>
<Flex.Item grow={1}>
<Box style={style}>
<Box textStyle='p2' style={style}>{name || username}</Box>
<Box textStyle='p1' textColor='hint' style={style}>{name && username}</Box>
</Box>
</Flex.Item>
</Margins>
<Box style={style} grow={1} mi='x8'>
<Box display='flex'>
<Box textStyle='p2' style={style} textColor='default'>{name || username}</Box> <Box mi='x4'/> <Box textStyle='p1' textColor='hint' style={style}>{username}</Box>
</Box>
<Box textStyle='p1' textColor='hint' style={style}> {bio} </Box>
</Box>
</Box>
</Flex.Container>
</Table.Cell>
<Table.Cell textStyle='p1' textColor='hint' style={ bioStyle }>
<Box is='div' style={ style }>
{bio}
</Box>
</Table.Cell>
{mediaQuery && canViewFullOtherUserInfo
&& <Table.Cell style={style} >
{emails && emails[0].address}
Expand Down
40 changes: 23 additions & 17 deletions client/lib/meteorCallWrapper.ts
@@ -1,26 +1,32 @@
import { Meteor } from 'meteor/meteor';
import { Tracker } from 'meteor/tracker';
import { EJSON } from 'meteor/ejson';

import { APIClient } from '../../app/utils/client';

Meteor.call = function _meteorCallOverREST(method: string, ...params: any): void {
const endpoint = Tracker.nonreactive(() => (!Meteor.userId() ? 'method.callAnon' : 'method.call'));
((): void => {
if (!window.USE_REST_FOR_DDP_CALLS) {
return;
}
Meteor.call = function _meteorCallOverREST(method: string, ...params: any): void {
const endpoint = Tracker.nonreactive(() => (!Meteor.userId() ? 'method.callAnon' : 'method.call'));

const restParams = {
method,
params,
};
let callback = params.pop();
if (typeof callback !== 'function') {
params.push(callback);
callback = (): void => {
// empty
};
}

let callback = params.pop();
if (typeof callback !== 'function') {
params.push(callback);
callback = (): void => {
// empty
const restParams = {
method,
params: params && EJSON.stringify(params),
};
}

// not using async to not change Meteor.call return type
APIClient.v1.post(endpoint, restParams)
.then(({ result }) => callback(null, result))
.catch((error) => callback(error));
};
// not using async to not change Meteor.call return type
APIClient.v1.post(endpoint, restParams)
.then(({ result }) => callback(null, EJSON.parse(result)))
.catch((error) => callback(error));
};
})();
8 changes: 8 additions & 0 deletions client/window.ts
@@ -0,0 +1,8 @@
export {};

declare global {
// eslint-disable-next-line @typescript-eslint/interface-name-prefix
interface Window {
USE_REST_FOR_DDP_CALLS?: boolean;
}
}
2 changes: 2 additions & 0 deletions packages/rocketchat-i18n/i18n/en.i18n.json
Expand Up @@ -3373,6 +3373,8 @@
"Troubleshoot_Disable_Data_Exporter_Processor_Alert": "This setting stops the processing of all export requests from users, so they will not receive the link to download their data!",
"Troubleshoot_Disable_Workspace_Sync": "Disable Workspace Sync",
"Troubleshoot_Disable_Workspace_Sync_Alert": "This setting stops the sync of this server with Rocket.Chat's cloud and may cause issues with marketplace and enteprise licenses!",
"Troubleshoot_Use_REST_For_DDP_Calls": "Use REST instead of websocket for Meteor calls",
"Troubleshoot_Use_REST_For_DDP_Calls_Alert": "Force web client to use REST requests instead of using websockets for Meteor method calls.",
"Tuesday": "Tuesday",
"Turn_OFF": "Turn OFF",
"Turn_ON": "Turn ON",
Expand Down
10 changes: 5 additions & 5 deletions server/methods/browseChannels.js
Expand Up @@ -85,6 +85,7 @@ Meteor.methods({
...sort,
},
fields: {
t: 1,
description: 1,
topic: 1,
name: 1,
Expand All @@ -95,6 +96,7 @@ Meteor.methods({
default: 1,
featured: 1,
usersCount: 1,
prid: 1,
},
});

Expand All @@ -114,8 +116,6 @@ Meteor.methods({
return;
}

const exceptions = [user.username];

const forcedSearchFields = workspace === 'all' && ['username', 'name', 'emails.address'];

const options = {
Expand All @@ -133,11 +133,11 @@ Meteor.methods({

let result;
if (workspace === 'all') {
result = Users.findByActiveUsersExcept(text, exceptions, options, forcedSearchFields);
result = Users.findByActiveUsersExcept(text, [], options, forcedSearchFields);
} else if (workspace === 'external') {
result = Users.findByActiveExternalUsersExcept(text, exceptions, options, forcedSearchFields, getFederationDomain());
result = Users.findByActiveExternalUsersExcept(text, [], options, forcedSearchFields, getFederationDomain());
} else {
result = Users.findByActiveLocalUsersExcept(text, exceptions, options, forcedSearchFields, getFederationDomain());
result = Users.findByActiveLocalUsersExcept(text, [], options, forcedSearchFields, getFederationDomain());
}

const total = result.count(); // count ignores the `skip` and `limit` options
Expand Down

0 comments on commit 2d039dc

Please sign in to comment.