Chore: Partially migrate 2FA client code to TypeScript (#23419)

pull/23439/head
Tasso Evangelista 4 years ago committed by GitHub
parent a1f5943160
commit 655353257c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 0
      app/2fa/README.md
  2. 7
      app/2fa/client/TOTPCrowd.js
  3. 4
      app/2fa/client/TOTPGoogle.js
  4. 7
      app/2fa/client/TOTPLDAP.js
  5. 19
      app/2fa/client/TOTPOAuth.js
  6. 20
      app/2fa/client/TOTPPassword.js
  7. 7
      app/2fa/client/TOTPSaml.js
  8. 64
      app/2fa/client/callWithTwoFactorRequired.js
  9. 2
      app/2fa/client/index.ts
  10. 51
      app/2fa/client/lib/2fa.js
  11. 49
      app/2fa/client/overrideMeteorCall.ts
  12. 7
      app/apps/client/orchestrator.js
  13. 14
      app/chatpal-search/client/template/admin.js
  14. 4
      app/lib/client/methods/sendMessage.js
  15. 4
      app/livechat/client/views/app/tabbar/agentEdit.js
  16. 12
      app/livechat/client/views/app/tabbar/visitorEdit.js
  17. 17
      app/livechat/client/views/app/tabbar/visitorForward.js
  18. 8
      app/livechat/client/views/app/tabbar/visitorTranscript.js
  19. 8
      app/livestream/client/views/liveStreamTab.js
  20. 4
      app/message-pin/client/actionButton.js
  21. 22
      app/message-pin/client/pinMessage.js
  22. 4
      app/message-star/client/actionButton.js
  23. 14
      app/message-star/client/starMessage.js
  24. 6
      app/otr/client/rocketchat.otr.room.js
  25. 5
      app/search/client/search/search.js
  26. 4
      app/threads/client/messageAction/follow.js
  27. 4
      app/threads/client/messageAction/unfollow.js
  28. 4
      app/tokenpass/client/tokenpassChannelSettings.js
  29. 31
      app/ui-login/client/login/form.js
  30. 6
      app/ui-login/client/login/services.js
  31. 4
      app/ui-login/client/username/username.js
  32. 6
      app/ui-utils/client/lib/MessageAction.js
  33. 6
      app/ui/client/lib/accounts.js
  34. 6
      app/ui/client/lib/chatMessages.js
  35. 11
      app/ui/client/views/app/editStatus.js
  36. 10
      app/ui/client/views/app/invite.js
  37. 2
      app/utils/client/lib/RestApiClient.js
  38. 6
      app/videobridge/client/actionLink.js
  39. 8
      app/webdav/client/addWebdavAccount.js
  40. 8
      app/webdav/client/selectWebdavAccount.js
  41. 4
      app/webdav/client/webdavFilePicker.js
  42. 29
      client/components/TwoFactorModal/TwoFactorModal.tsx
  43. 2
      client/contexts/ToastMessagesContext.ts
  44. 48
      client/lib/2fa/overrideLoginMethod.ts
  45. 82
      client/lib/2fa/process2faReturn.ts
  46. 32
      client/lib/2fa/utils.ts
  47. 21
      client/lib/toast.ts
  48. 9
      client/methods/updateMessage.js
  49. 41
      client/providers/ToastMessagesProvider.tsx
  50. 7
      client/startup/emailVerification.ts
  51. 4
      client/startup/rootUrlChange.ts
  52. 4
      client/startup/routes.ts
  53. 16
      client/views/admin/apps/helpers.js
  54. 27
      client/views/room/Header/Omnichannel/QuickActions/hooks/useQuickActions.tsx
  55. 10
      ee/app/canned-responses/client/views/tabbar/cannedResponses.js
  56. 6
      server/main.d.ts

@ -1,8 +1,9 @@
import { Meteor } from 'meteor/meteor';
import { Accounts } from 'meteor/accounts-base';
import { Utils2fa } from './lib/2fa';
import '../../crowd/client/index';
import { reportError } from '../../../client/lib/2fa/utils';
import { overrideLoginMethod } from '../../../client/lib/2fa/overrideLoginMethod';
Meteor.loginWithCrowdAndTOTP = function(username, password, code, callback) {
const loginRequest = {
@ -20,7 +21,7 @@ Meteor.loginWithCrowdAndTOTP = function(username, password, code, callback) {
}],
userCallback(error) {
if (error) {
Utils2fa.reportError(error, callback);
reportError(error, callback);
} else {
callback && callback();
}
@ -31,5 +32,5 @@ Meteor.loginWithCrowdAndTOTP = function(username, password, code, callback) {
const { loginWithCrowd } = Meteor;
Meteor.loginWithCrowd = function(username, password, callback) {
Utils2fa.overrideLoginMethod(loginWithCrowd, [username, password], callback, Meteor.loginWithCrowdAndTOTP);
overrideLoginMethod(loginWithCrowd, [username, password], callback, Meteor.loginWithCrowdAndTOTP);
};

@ -3,7 +3,7 @@ import { Accounts } from 'meteor/accounts-base';
import { Google } from 'meteor/google-oauth';
import _ from 'underscore';
import { Utils2fa } from './lib/2fa';
import { overrideLoginMethod } from '../../../client/lib/2fa/overrideLoginMethod';
const loginWithGoogleAndTOTP = function(options, code, callback) {
// support a callback without options
@ -37,5 +37,5 @@ const loginWithGoogleAndTOTP = function(options, code, callback) {
const { loginWithGoogle } = Meteor;
Meteor.loginWithGoogle = function(options, cb) {
Utils2fa.overrideLoginMethod(loginWithGoogle, [options], cb, loginWithGoogleAndTOTP);
overrideLoginMethod(loginWithGoogle, [options], cb, loginWithGoogleAndTOTP);
};

@ -1,8 +1,9 @@
import { Meteor } from 'meteor/meteor';
import { Accounts } from 'meteor/accounts-base';
import { Utils2fa } from './lib/2fa';
import '../../../client/startup/ldap';
import { reportError } from '../../../client/lib/2fa/utils';
import { overrideLoginMethod } from '../../../client/lib/2fa/overrideLoginMethod';
Meteor.loginWithLDAPAndTOTP = function(...args) {
// Pull username and password
@ -34,7 +35,7 @@ Meteor.loginWithLDAPAndTOTP = function(...args) {
}],
userCallback(error) {
if (error) {
Utils2fa.reportError(error, callback);
reportError(error, callback);
} else {
callback && callback();
}
@ -47,5 +48,5 @@ const { loginWithLDAP } = Meteor;
Meteor.loginWithLDAP = function(...args) {
const callback = typeof args[args.length - 1] === 'function' ? args.pop() : null;
Utils2fa.overrideLoginMethod(loginWithLDAP, args, callback, Meteor.loginWithLDAPAndTOTP, args[0]);
overrideLoginMethod(loginWithLDAP, args, callback, Meteor.loginWithLDAPAndTOTP, args[0]);
};

@ -8,9 +8,10 @@ import { MeteorDeveloperAccounts } from 'meteor/meteor-developer-oauth';
import { Linkedin } from 'meteor/pauli:linkedin-oauth';
import { OAuth } from 'meteor/oauth';
import { Utils2fa } from './lib/2fa';
import { process2faReturn } from './callWithTwoFactorRequired';
import { process2faReturn } from '../../../client/lib/2fa/process2faReturn';
import { CustomOAuth } from '../../custom-oauth';
import { convertError } from '../../../client/lib/2fa/utils';
import { overrideLoginMethod } from '../../../client/lib/2fa/overrideLoginMethod';
let lastCredentialToken = null;
let lastCredentialSecret = null;
@ -36,7 +37,7 @@ Accounts.oauth.tryLoginAfterPopupClosed = function(credentialToken, callback, to
Accounts.callLoginMethod({
methodArguments: [methodArgument],
userCallback: callback && function(err) {
callback(Utils2fa.convertError(err));
callback(convertError(err));
} });
};
@ -74,31 +75,31 @@ const loginWithOAuthTokenAndTOTP = createOAuthTotpLoginMethod();
const loginWithFacebookAndTOTP = createOAuthTotpLoginMethod(() => Facebook);
const { loginWithFacebook } = Meteor;
Meteor.loginWithFacebook = function(options, cb) {
Utils2fa.overrideLoginMethod(loginWithFacebook, [options], cb, loginWithFacebookAndTOTP);
overrideLoginMethod(loginWithFacebook, [options], cb, loginWithFacebookAndTOTP);
};
const loginWithGithubAndTOTP = createOAuthTotpLoginMethod(() => Github);
const { loginWithGithub } = Meteor;
Meteor.loginWithGithub = function(options, cb) {
Utils2fa.overrideLoginMethod(loginWithGithub, [options], cb, loginWithGithubAndTOTP);
overrideLoginMethod(loginWithGithub, [options], cb, loginWithGithubAndTOTP);
};
const loginWithMeteorDeveloperAccountAndTOTP = createOAuthTotpLoginMethod(() => MeteorDeveloperAccounts);
const { loginWithMeteorDeveloperAccount } = Meteor;
Meteor.loginWithMeteorDeveloperAccount = function(options, cb) {
Utils2fa.overrideLoginMethod(loginWithMeteorDeveloperAccount, [options], cb, loginWithMeteorDeveloperAccountAndTOTP);
overrideLoginMethod(loginWithMeteorDeveloperAccount, [options], cb, loginWithMeteorDeveloperAccountAndTOTP);
};
const loginWithTwitterAndTOTP = createOAuthTotpLoginMethod(() => Twitter);
const { loginWithTwitter } = Meteor;
Meteor.loginWithTwitter = function(options, cb) {
Utils2fa.overrideLoginMethod(loginWithTwitter, [options], cb, loginWithTwitterAndTOTP);
overrideLoginMethod(loginWithTwitter, [options], cb, loginWithTwitterAndTOTP);
};
const loginWithLinkedinAndTOTP = createOAuthTotpLoginMethod(() => Linkedin);
const { loginWithLinkedin } = Meteor;
Meteor.loginWithLinkedin = function(options, cb) {
Utils2fa.overrideLoginMethod(loginWithLinkedin, [options], cb, loginWithLinkedinAndTOTP);
overrideLoginMethod(loginWithLinkedin, [options], cb, loginWithLinkedinAndTOTP);
};
Accounts.onPageLoadLogin((loginAttempt) => {
@ -133,6 +134,6 @@ CustomOAuth.prototype.configureLogin = function(...args) {
const oldMethod = Meteor[loginWithService];
Meteor[loginWithService] = function(options, cb) {
Utils2fa.overrideLoginMethod(oldMethod, [options], cb, loginWithOAuthTokenAndTOTP);
overrideLoginMethod(oldMethod, [options], cb, loginWithOAuthTokenAndTOTP);
};
};

@ -1,10 +1,10 @@
import { Meteor } from 'meteor/meteor';
import { Accounts } from 'meteor/accounts-base';
import toastr from 'toastr';
import { Utils2fa } from './lib/2fa';
import { t } from '../../utils';
import { process2faReturn } from './callWithTwoFactorRequired';
import { process2faReturn } from '../../../client/lib/2fa/process2faReturn';
import { isTotpInvalidError, reportError } from '../../../client/lib/2fa/utils';
import { dispatchToastMessage } from '../../../client/lib/toast';
Meteor.loginWithPasswordAndTOTP = function(selector, password, code, callback) {
if (typeof selector === 'string') {
@ -27,7 +27,7 @@ Meteor.loginWithPasswordAndTOTP = function(selector, password, code, callback) {
}],
userCallback(error) {
if (error) {
Utils2fa.reportError(error, callback);
reportError(error, callback);
} else {
callback && callback();
}
@ -45,12 +45,16 @@ Meteor.loginWithPassword = function(email, password, cb) {
emailOrUsername: email,
onCode: (code) => {
Meteor.loginWithPasswordAndTOTP(email, password, code, (error) => {
if (error && error.error === 'totp-invalid') {
toastr.error(t('Invalid_two_factor_code'));
if (isTotpInvalidError(error)) {
dispatchToastMessage({
type: 'error',
message: t('Invalid_two_factor_code'),
});
cb();
} else {
cb(error);
return;
}
cb(error);
});
},
});

@ -1,8 +1,9 @@
import { Meteor } from 'meteor/meteor';
import { Accounts } from 'meteor/accounts-base';
import { Utils2fa } from './lib/2fa';
import '../../meteor-accounts-saml/client/saml_client';
import { reportError } from '../../../client/lib/2fa/utils';
import { overrideLoginMethod } from '../../../client/lib/2fa/overrideLoginMethod';
Meteor.loginWithSamlTokenAndTOTP = function(credentialToken, code, callback) {
Accounts.callLoginMethod({
@ -17,7 +18,7 @@ Meteor.loginWithSamlTokenAndTOTP = function(credentialToken, code, callback) {
}],
userCallback(error) {
if (error) {
Utils2fa.reportError(error, callback);
reportError(error, callback);
} else {
callback && callback();
}
@ -28,5 +29,5 @@ Meteor.loginWithSamlTokenAndTOTP = function(credentialToken, code, callback) {
const { loginWithSamlToken } = Meteor;
Meteor.loginWithSamlToken = function(options, callback) {
Utils2fa.overrideLoginMethod(loginWithSamlToken, [options], callback, Meteor.loginWithSamlTokenAndTOTP);
overrideLoginMethod(loginWithSamlToken, [options], callback, Meteor.loginWithSamlTokenAndTOTP);
};

@ -1,64 +0,0 @@
import { Meteor } from 'meteor/meteor';
import { SHA256 } from 'meteor/sha';
import toastr from 'toastr';
import { t } from '../../utils/client';
import { imperativeModal } from '../../../client/lib/imperativeModal';
import TwoFactorModal from '../../../client/components/TwoFactorModal';
export function process2faReturn({ error, result, originalCallback, onCode, emailOrUsername }) {
if (!error || (error.error !== 'totp-required' && error.errorType !== 'totp-required')) {
return originalCallback(error, result);
}
const method = error.details && error.details.method;
if (!emailOrUsername && Meteor.user()) {
emailOrUsername = Meteor.user().username;
}
imperativeModal.open({
component: TwoFactorModal,
props: {
method,
onConfirm: (code, method) => {
onCode(method === 'password' ? SHA256(code) : code, method);
imperativeModal.close();
},
onClose: () => {
imperativeModal.close();
originalCallback(new Meteor.Error('totp-canceled'));
},
emailOrUsername,
},
});
}
const { call } = Meteor;
Meteor.call = function(ddpMethod, ...args) {
let callback = args.pop();
if (typeof callback !== 'function') {
args.push(callback);
callback = () => {};
}
return call(ddpMethod, ...args, function(error, result) {
process2faReturn({
error,
result,
originalCallback: callback,
onCode: (code, method) => {
call(ddpMethod, ...args, { twoFactorCode: code, twoFactorMethod: method }, (error, result) => {
if (error && error.error === 'totp-invalid') {
error.toastrShowed = true;
toastr.error(t('Invalid_two_factor_code'));
return callback(error);
}
callback(error, result);
});
},
});
});
};

@ -1,7 +1,7 @@
import './callWithTwoFactorRequired';
import './TOTPPassword';
import './TOTPOAuth';
import './TOTPGoogle';
import './TOTPSaml';
import './TOTPLDAP';
import './TOTPCrowd';
import './overrideMeteorCall';

@ -1,51 +0,0 @@
import { Meteor } from 'meteor/meteor';
import toastr from 'toastr';
import { Accounts } from 'meteor/accounts-base';
import { t } from '../../../utils/client';
import { process2faReturn } from '../callWithTwoFactorRequired';
export class Utils2fa {
static reportError(error, callback) {
if (callback) {
callback(error);
} else {
throw error;
}
}
static convertError(err) {
if (err && err instanceof Meteor.Error && err.error === Accounts.LoginCancelledError.numericError) {
return new Accounts.LoginCancelledError(err.reason);
}
return err;
}
static overrideLoginMethod(loginMethod, loginArgs, cb, loginMethodTOTP, emailOrUsername) {
loginMethod.apply(this, loginArgs.concat([(error) => {
if (!error || error.error !== 'totp-required') {
return cb(error);
}
process2faReturn({
error,
emailOrUsername,
originalCallback: cb,
onCode: (code) => {
loginMethodTOTP && loginMethodTOTP.apply(this, loginArgs.concat([code, (error) => {
if (error) {
console.log(error);
}
if (error && error.error === 'totp-invalid') {
toastr.error(t('Invalid_two_factor_code'));
cb();
} else {
cb(error);
}
}]));
},
});
}]));
}
}

@ -0,0 +1,49 @@
import { Meteor } from 'meteor/meteor';
import { t } from '../../utils/client';
import { process2faReturn } from '../../../client/lib/2fa/process2faReturn';
import { isTotpInvalidError } from '../../../client/lib/2fa/utils';
import { dispatchToastMessage } from '../../../client/lib/toast';
const { call } = Meteor;
type Callback = {
(error: unknown): void;
(error: unknown, result: unknown): void;
};
const callWithTotp = (methodName: string, args: unknown[], callback: Callback) =>
(twoFactorCode: string, twoFactorMethod: string): unknown =>
call(methodName, ...args, { twoFactorCode, twoFactorMethod }, (error: unknown, result: unknown): void => {
if (isTotpInvalidError(error)) {
(error as { toastrShowed?: true }).toastrShowed = true;
dispatchToastMessage({
type: 'error',
message: t('Invalid_two_factor_code'),
});
callback(error);
return;
}
callback(error, result);
});
const callWithoutTotp = (methodName: string, args: unknown[], callback: Callback) =>
(): unknown =>
call(methodName, ...args, (error: unknown, result: unknown): void => {
process2faReturn({
error,
result,
onCode: callWithTotp(methodName, args, callback),
originalCallback: callback,
emailOrUsername: undefined,
});
});
Meteor.call = function(methodName: string, ...args: unknown[]): unknown {
const callback = args.length > 0 && typeof args[args.length - 1] === 'function'
? args.pop() as Callback
: (): void => undefined;
return callWithoutTotp(methodName, args, callback)();
};

@ -1,7 +1,7 @@
import { AppClientManager } from '@rocket.chat/apps-engine/client/AppClientManager';
import { Meteor } from 'meteor/meteor';
import toastr from 'toastr';
import { dispatchToastMessage } from '../../../client/lib/toast';
import { hasAtLeastOnePermission } from '../../authorization';
import { settings } from '../../settings/client';
import { CachedCollectionManager } from '../../ui-cached-collection';
@ -52,7 +52,10 @@ class AppClientOrchestrator {
handleError = (error) => {
console.error(error);
if (hasAtLeastOnePermission(['manage-apps'])) {
toastr.error(error.message);
dispatchToastMessage({
type: 'error',
message: error.message,
});
}
}

@ -2,10 +2,10 @@ import { Meteor } from 'meteor/meteor';
import { ReactiveVar } from 'meteor/reactive-var';
import { Template } from 'meteor/templating';
import { TAPi18n } from 'meteor/rocketchat:tap-i18n';
import toastr from 'toastr';
import { settings } from '../../../settings';
import { hasRole } from '../../../authorization';
import { dispatchToastMessage } from '../../../../client/lib/toast';
Template.ChatpalAdmin.onCreated(function() {
const re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
@ -32,22 +32,22 @@ Template.ChatpalAdmin.events({
const email = e.target.email.value;
const tac = e.target.readtac.checked;
if (!tac) { return toastr.error(TAPi18n.__('Chatpal_ERROR_TAC_must_be_checked')); }
if (!email || email === '') { return toastr.error(TAPi18n.__('Chatpal_ERROR_Email_must_be_set')); }
if (!t.validateEmail(email)) { return toastr.error(TAPi18n.__('Chatpal_ERROR_Email_must_be_valid')); }
if (!tac) { return dispatchToastMessage({ type: 'error', message: TAPi18n.__('Chatpal_ERROR_TAC_must_be_checked') }); }
if (!email || email === '') { return dispatchToastMessage({ type: 'error', message: TAPi18n.__('Chatpal_ERROR_Email_must_be_set') }); }
if (!t.validateEmail(email)) { return dispatchToastMessage({ type: 'error', message: TAPi18n.__('Chatpal_ERROR_Email_must_be_valid') }); }
// TODO register
try {
Meteor.call('chatpalUtilsCreateKey', email, (err, key) => {
if (!key) { return toastr.error(TAPi18n.__('Chatpal_ERROR_username_already_exists')); }
if (!key) { return dispatchToastMessage({ type: 'error', message: TAPi18n.__('Chatpal_ERROR_username_already_exists') }); }
toastr.info(TAPi18n.__('Chatpal_created_key_successfully'));
dispatchToastMessage({ type: 'info', message: TAPi18n.__('Chatpal_created_key_successfully') });
t.apiKey.set(key);
});
} catch (e) {
console.log(e);
toastr.error(TAPi18n.__('Chatpal_ERROR_username_already_exists'));// TODO error messages
dispatchToastMessage({ type: 'error', message: TAPi18n.__('Chatpal_ERROR_username_already_exists') });// TODO error messages
}
},
});

@ -1,13 +1,13 @@
import { Meteor } from 'meteor/meteor';
import { TimeSync } from 'meteor/mizzao:timesync';
import s from 'underscore.string';
import toastr from 'toastr';
import { ChatMessage } from '../../../models';
import { settings } from '../../../settings';
import { callbacks } from '../../../callbacks';
import { promises } from '../../../promises/client';
import { t } from '../../../utils/client';
import { dispatchToastMessage } from '../../../../client/lib/toast';
Meteor.methods({
sendMessage(message) {
@ -16,7 +16,7 @@ Meteor.methods({
}
const messageAlreadyExists = message._id && ChatMessage.findOne({ _id: message._id });
if (messageAlreadyExists) {
return toastr.error(t('Message_Already_Sent'));
return dispatchToastMessage({ type: 'error', message: t('Message_Already_Sent') });
}
const user = Meteor.user();
message.ts = isNaN(TimeSync.serverOffset()) ? new Date() : new Date(Date.now() + TimeSync.serverOffset());

@ -1,13 +1,13 @@
import { Meteor } from 'meteor/meteor';
import { ReactiveVar } from 'meteor/reactive-var';
import { Template } from 'meteor/templating';
import toastr from 'toastr';
import { getCustomFormTemplate } from '../customTemplates/register';
import './agentEdit.html';
import { hasPermission } from '../../../../../authorization';
import { t, APIClient } from '../../../../../utils/client';
import { handleError } from '../../../../../../client/lib/utils/handleError';
import { dispatchToastMessage } from '../../../../../../client/lib/toast';
Template.agentEdit.helpers({
canEditDepartment() {
@ -83,7 +83,7 @@ Template.agentEdit.events({
return handleError(error);
}
toastr.success(t('Saved'));
dispatchToastMessage({ type: 'success', message: t('Saved') });
return this.back && this.back(_id);
});
},

@ -1,13 +1,13 @@
import { Meteor } from 'meteor/meteor';
import { ReactiveVar } from 'meteor/reactive-var';
import { Template } from 'meteor/templating';
import toastr from 'toastr';
import { t } from '../../../../../utils';
import { hasAtLeastOnePermission, hasPermission, hasRole } from '../../../../../authorization';
import './visitorEdit.html';
import { APIClient } from '../../../../../utils/client';
import { getCustomFormTemplate } from '../customTemplates/register';
import { dispatchToastMessage } from '../../../../../../client/lib/toast';
const CUSTOM_FIELDS_COUNT = 100;
@ -177,9 +177,15 @@ Template.visitorEdit.events({
Meteor.call('livechat:saveInfo', userData, roomData, (err) => {
if (err) {
toastr.error(t(err.error));
dispatchToastMessage({
type: 'error',
message: t(err.error),
});
} else {
toastr.success(t('Saved'));
dispatchToastMessage({
type: 'success',
message: t('Saved'),
});
this.save();
}
});

@ -2,12 +2,12 @@ import { Meteor } from 'meteor/meteor';
import { ReactiveVar } from 'meteor/reactive-var';
import { FlowRouter } from 'meteor/kadira:flow-router';
import { Template } from 'meteor/templating';
import toastr from 'toastr';
import { ChatRoom } from '../../../../../models';
import { t } from '../../../../../utils';
import './visitorForward.html';
import { APIClient } from '../../../../../utils/client';
import { dispatchToastMessage } from '../../../../../../client/lib/toast';
Template.visitorForward.helpers({
visitor() {
@ -128,13 +128,22 @@ Template.visitorForward.events({
Meteor.call('livechat:transfer', transferData, (error, result) => {
if (error) {
toastr.error(t(error.error));
dispatchToastMessage({
type: 'error',
message: t(error.error),
});
} else if (result) {
this.save();
toastr.success(t('Transferred'));
dispatchToastMessage({
type: 'success',
message: t('Transferred'),
});
FlowRouter.go('/');
} else {
toastr.warning(t('No_available_agents_to_transfer'));
dispatchToastMessage({
type: 'warning',
message: t('No_available_agents_to_transfer'),
});
}
});
},

@ -1,8 +1,8 @@
import { Meteor } from 'meteor/meteor';
import { ReactiveVar } from 'meteor/reactive-var';
import { Template } from 'meteor/templating';
import toastr from 'toastr';
import { dispatchToastMessage } from '../../../../../../client/lib/toast';
import { handleError } from '../../../../../../client/lib/utils/handleError';
import { t, isEmail, roomTypes } from '../../../../../utils';
import { APIClient } from '../../../../../utils/client';
@ -100,7 +100,7 @@ Template.visitorTranscript.events({
return handleError(err);
}
toastr.success(t('Your_email_has_been_queued_for_sending'));
dispatchToastMessage({ type: 'success', message: t('Your_email_has_been_queued_for_sending') });
this.save();
});
},
@ -123,7 +123,7 @@ Template.visitorTranscript.events({
return handleError(err);
}
toastr.success(t('Livechat_transcript_has_been_requested'));
dispatchToastMessage({ type: 'success', message: t('Livechat_transcript_has_been_requested') });
this.save();
});
},
@ -139,7 +139,7 @@ Template.visitorTranscript.events({
return handleError(err);
}
toastr.success(t('Livechat_transcript_request_has_been_canceled'));
dispatchToastMessage({ type: 'success', message: t('Livechat_transcript_request_has_been_canceled') });
this.save();
});
},

@ -4,7 +4,6 @@ import { Blaze } from 'meteor/blaze';
import { Session } from 'meteor/session';
import { Template } from 'meteor/templating';
import { TAPi18n } from 'meteor/rocketchat:tap-i18n';
import toastr from 'toastr';
import { auth } from '../oauth.js';
import { RocketChatAnnouncement } from '../../../lib';
@ -15,6 +14,7 @@ import { callbacks } from '../../../callbacks';
import { hasAllPermission } from '../../../authorization';
import { Users, Rooms } from '../../../models';
import { handleError } from '../../../../client/lib/utils/handleError';
import { dispatchToastMessage } from '../../../../client/lib/toast';
export const call = (...args) => new Promise(function(resolve, reject) {
Meteor.call(...args, function(err, result) {
@ -135,7 +135,7 @@ Template.liveStreamTab.events({
i.streamingOptions.set(clearedObject);
const roomAnnouncement = new RocketChatAnnouncement().getByRoom(i.data.rid);
if (roomAnnouncement.getMessage() !== '') { roomAnnouncement.clear(); }
return toastr.success(TAPi18n.__('Livestream_source_changed_succesfully'));
return dispatchToastMessage({ type: 'success', message: TAPi18n.__('Livestream_source_changed_succesfully') });
});
},
'click .js-save'(e, i) {
@ -167,7 +167,7 @@ Template.liveStreamTab.events({
}
}
return toastr.success(TAPi18n.__('Livestream_source_changed_succesfully'));
return dispatchToastMessage({ type: 'success', message: TAPi18n.__('Livestream_source_changed_succesfully') });
});
},
'click .streaming-source-settings'(e, i) {
@ -227,7 +227,7 @@ Template.liveStreamTab.events({
roomAnnouncement.clear();
}
}
return toastr.success(TAPi18n.__('Livestream_source_changed_succesfully'));
return dispatchToastMessage({ type: 'success', message: TAPi18n.__('Livestream_source_changed_succesfully') });
});
},
'click .js-popout'(e, i) {

@ -2,7 +2,6 @@ import { Meteor } from 'meteor/meteor';
import { Template } from 'meteor/templating';
import { TAPi18n } from 'meteor/rocketchat:tap-i18n';
import { FlowRouter } from 'meteor/kadira:flow-router';
import toastr from 'toastr';
import { RoomHistoryManager, MessageAction } from '../../ui-utils';
import { messageArgs } from '../../ui-utils/client/lib/messageArgs';
@ -11,6 +10,7 @@ import { hasAtLeastOnePermission } from '../../authorization';
import { Rooms } from '../../models/client';
import { roomTypes } from '../../utils/client';
import { handleError } from '../../../client/lib/utils/handleError';
import { dispatchToastMessage } from '../../../client/lib/toast';
Meteor.startup(function() {
MessageAction.addButton({
@ -106,7 +106,7 @@ Meteor.startup(function() {
const { msg: message } = messageArgs(this);
const permalink = await MessageAction.getPermaLink(message._id);
navigator.clipboard.writeText(permalink);
toastr.success(TAPi18n.__('Copied'));
dispatchToastMessage({ type: 'success', message: TAPi18n.__('Copied') });
},
condition({ subscription }) {
return !!subscription;

@ -1,29 +1,29 @@
import { Meteor } from 'meteor/meteor';
import toastr from 'toastr';
import { TAPi18n } from 'meteor/rocketchat:tap-i18n';
import { settings } from '../../settings';
import { ChatMessage, Subscriptions } from '../../models';
import { dispatchToastMessage } from '../../../client/lib/toast';
Meteor.methods({
pinMessage(message) {
if (!Meteor.userId()) {
toastr.error(TAPi18n.__('error-not-authorized'));
dispatchToastMessage({ type: 'error', message: TAPi18n.__('error-not-authorized') });
return false;
}
if (!settings.get('Message_AllowPinning')) {
toastr.error(TAPi18n.__('pinning-not-allowed'));
dispatchToastMessage({ type: 'error', message: TAPi18n.__('pinning-not-allowed') });
return false;
}
if (Subscriptions.findOne({ rid: message.rid }) == null) {
toastr.error(TAPi18n.__('error-pinning-message'));
dispatchToastMessage({ type: 'error', message: TAPi18n.__('error-pinning-message') });
return false;
}
if (typeof message._id !== 'string') {
toastr.error(TAPi18n.__('error-pinning-message'));
dispatchToastMessage({ type: 'error', message: TAPi18n.__('error-pinning-message') });
return false;
}
toastr.success(TAPi18n.__('Message_has_been_pinned'));
dispatchToastMessage({ type: 'success', message: TAPi18n.__('Message_has_been_pinned') });
return ChatMessage.update({
_id: message._id,
rid: message.rid,
@ -35,22 +35,22 @@ Meteor.methods({
},
unpinMessage(message) {
if (!Meteor.userId()) {
toastr.error(TAPi18n.__('error-not-authorized'));
dispatchToastMessage({ type: 'error', message: TAPi18n.__('error-not-authorized') });
return false;
}
if (!settings.get('Message_AllowPinning')) {
toastr.error(TAPi18n.__('unpinning-not-allowed'));
dispatchToastMessage({ type: 'error', message: TAPi18n.__('unpinning-not-allowed') });
return false;
}
if (Subscriptions.findOne({ rid: message.rid }) == null) {
toastr.error(TAPi18n.__('error-unpinning-message'));
dispatchToastMessage({ type: 'error', message: TAPi18n.__('error-unpinning-message') });
return false;
}
if (typeof message._id !== 'string') {
toastr.error(TAPi18n.__('error-unpinning-message'));
dispatchToastMessage({ type: 'error', message: TAPi18n.__('error-unpinning-message') });
return false;
}
toastr.success(TAPi18n.__('Message_has_been_unpinned'));
dispatchToastMessage({ type: 'success', message: TAPi18n.__('Message_has_been_unpinned') });
return ChatMessage.update({
_id: message._id,
rid: message.rid,

@ -2,7 +2,6 @@ import { Meteor } from 'meteor/meteor';
import { Template } from 'meteor/templating';
import { TAPi18n } from 'meteor/rocketchat:tap-i18n';
import { FlowRouter } from 'meteor/kadira:flow-router';
import toastr from 'toastr';
import { settings } from '../../settings';
import { RoomHistoryManager, MessageAction } from '../../ui-utils';
@ -10,6 +9,7 @@ import { messageArgs } from '../../ui-utils/client/lib/messageArgs';
import { Rooms } from '../../models/client';
import { roomTypes } from '../../utils/client';
import { handleError } from '../../../client/lib/utils/handleError';
import { dispatchToastMessage } from '../../../client/lib/toast';
Meteor.startup(function() {
MessageAction.addButton({
@ -110,7 +110,7 @@ Meteor.startup(function() {
const { msg: message } = messageArgs(this);
const permalink = await MessageAction.getPermaLink(message._id);
navigator.clipboard.writeText(permalink);
toastr.success(TAPi18n.__('Copied'));
dispatchToastMessage({ type: 'success', message: TAPi18n.__('Copied') });
},
condition({ msg, subscription, u }) {
if (subscription == null) {

@ -1,32 +1,32 @@
import { Meteor } from 'meteor/meteor';
import toastr from 'toastr';
import { TAPi18n } from 'meteor/rocketchat:tap-i18n';
import { settings } from '../../settings';
import { ChatMessage, Subscriptions } from '../../models';
import { dispatchToastMessage } from '../../../client/lib/toast';
Meteor.methods({
starMessage(message) {
if (!Meteor.userId()) {
toastr.error(TAPi18n.__('error-starring-message'));
dispatchToastMessage({ type: 'error', message: TAPi18n.__('error-starring-message') });
return false;
}
if (Subscriptions.findOne({ rid: message.rid }) == null) {
toastr.error(TAPi18n.__('error-starring-message'));
dispatchToastMessage({ type: 'error', message: TAPi18n.__('error-starring-message') });
return false;
}
if (!ChatMessage.findOneByRoomIdAndMessageId(message.rid, message._id)) {
toastr.error(TAPi18n.__('error-starring-message'));
dispatchToastMessage({ type: 'error', message: TAPi18n.__('error-starring-message') });
return false;
}
if (!settings.get('Message_AllowStarring')) {
toastr.error(TAPi18n.__('error-starring-message'));
dispatchToastMessage({ type: 'error', message: TAPi18n.__('error-starring-message') });
return false;
}
if (message.starred) {
toastr.success(TAPi18n.__('Message_has_been_starred'));
dispatchToastMessage({ type: 'success', message: TAPi18n.__('Message_has_been_starred') });
} else {
toastr.success(TAPi18n.__('Message_has_been_unstarred'));
dispatchToastMessage({ type: 'success', message: TAPi18n.__('Message_has_been_unstarred') });
}
return ChatMessage.update({
_id: message._id,

@ -6,7 +6,6 @@ import { Tracker } from 'meteor/tracker';
import { TAPi18n } from 'meteor/rocketchat:tap-i18n';
import { TimeSync } from 'meteor/mizzao:timesync';
import _ from 'underscore';
import toastr from 'toastr';
import { OTR } from './rocketchat.otr';
import { Notifications } from '../../notifications';
@ -15,6 +14,7 @@ import { Presence } from '../../../client/lib/presence';
import { goToRoomById } from '../../../client/lib/utils/goToRoomById';
import { imperativeModal } from '../../../client/lib/imperativeModal';
import GenericModal from '../../../client/components/GenericModal';
import { dispatchToastMessage } from '../../../client/lib/toast';
OTR.Room = class {
constructor(userId, roomId) {
@ -95,7 +95,7 @@ OTR.Room = class {
Meteor.call('deleteOldOTRMessages', this.roomId);
})
.catch((e) => {
toastr.error(e);
dispatchToastMessage({ type: 'error', message: e });
});
}
@ -174,7 +174,7 @@ OTR.Room = class {
return data;
})
.catch((e) => {
toastr.error(e);
dispatchToastMessage({ type: 'error', message: e });
return message;
});
}

@ -4,9 +4,10 @@ import { Session } from 'meteor/session';
import { Template } from 'meteor/templating';
import { ReactiveVar } from 'meteor/reactive-var';
import { TAPi18n } from 'meteor/rocketchat:tap-i18n';
import toastr from 'toastr';
import _ from 'underscore';
import { dispatchToastMessage } from '../../../../client/lib/toast';
Template.RocketSearch.onCreated(function() {
this.provider = new ReactiveVar();
this.isActive = new ReactiveVar(false);
@ -32,7 +33,7 @@ Template.RocketSearch.onCreated(function() {
Meteor.call('rocketchatSearch.search', this.scope.text.get(), { rid: Session.get('openedRoom'), uid: Meteor.userId() }, _p, (err, result) => {
if (err) {
toastr.error(TAPi18n.__('Search_message_search_failed'));
dispatchToastMessage({ type: 'error', message: TAPi18n.__('Search_message_search_failed') });
this.scope.searching.set(false);
} else {
this.scope.searching.set(false);

@ -1,7 +1,6 @@
import { Meteor } from 'meteor/meteor';
import { Tracker } from 'meteor/tracker';
import { TAPi18n } from 'meteor/rocketchat:tap-i18n';
import toastr from 'toastr';
import { Messages } from '../../../models/client';
import { settings } from '../../../settings/client';
@ -9,6 +8,7 @@ import { MessageAction } from '../../../ui-utils/client';
import { messageArgs } from '../../../ui-utils/client/lib/messageArgs';
import { roomTypes } from '../../../utils/client';
import { callWithErrorHandling } from '../../../../client/lib/utils/callWithErrorHandling';
import { dispatchToastMessage } from '../../../../client/lib/toast';
Meteor.startup(function() {
Tracker.autorun(() => {
@ -23,7 +23,7 @@ Meteor.startup(function() {
async action() {
const { msg } = messageArgs(this);
callWithErrorHandling('followMessage', { mid: msg._id }).then(() =>
toastr.success(TAPi18n.__('You_followed_this_message')),
dispatchToastMessage({ type: 'success', message: TAPi18n.__('You_followed_this_message') }),
);
},
condition({ msg: { _id, tmid, replies = [] }, u, room }, context) {

@ -1,13 +1,13 @@
import { Meteor } from 'meteor/meteor';
import { Tracker } from 'meteor/tracker';
import { TAPi18n } from 'meteor/rocketchat:tap-i18n';
import toastr from 'toastr';
import { Messages } from '../../../models/client';
import { settings } from '../../../settings/client';
import { MessageAction } from '../../../ui-utils/client';
import { callWithErrorHandling } from '../../../../client/lib/utils/callWithErrorHandling';
import { messageArgs } from '../../../ui-utils/client/lib/messageArgs';
import { dispatchToastMessage } from '../../../../client/lib/toast';
Meteor.startup(function() {
Tracker.autorun(() => {
@ -22,7 +22,7 @@ Meteor.startup(function() {
async action() {
const { msg } = messageArgs(this);
callWithErrorHandling('unfollowMessage', { mid: msg._id }).then(() =>
toastr.success(TAPi18n.__('You_unfollowed_this_message')),
dispatchToastMessage({ type: 'success', message: TAPi18n.__('You_unfollowed_this_message') }),
);
},
condition({ msg: { _id, tmid, replies = [] }, u }, context) {

@ -2,11 +2,11 @@ import { Meteor } from 'meteor/meteor';
import { ReactiveVar } from 'meteor/reactive-var';
import { Template } from 'meteor/templating';
import { TAPi18n } from 'meteor/rocketchat:tap-i18n';
import toastr from 'toastr';
import { t } from '../../utils';
import { ChatRoom } from '../../models';
import { handleError } from '../../../client/lib/utils/handleError';
import { dispatchToastMessage } from '../../../client/lib/toast';
Template.channelSettings__tokenpass.helpers({
addDisabled() {
@ -94,7 +94,7 @@ Template.channelSettings__tokenpass.events({
i.balance.set('');
i.initial = tokenpass;
[...i.findAll('input')].forEach((el) => { el.value = ''; });
return toastr.success(TAPi18n.__('Room_tokenpass_config_changed_successfully'));
return dispatchToastMessage({ type: 'success', message: TAPi18n.__('Room_tokenpass_config_changed_successfully') });
});
},
'click .js-cancel'(e, i) {

@ -5,13 +5,12 @@ import { FlowRouter } from 'meteor/kadira:flow-router';
import { Session } from 'meteor/session';
import { Template } from 'meteor/templating';
import _ from 'underscore';
import s from 'underscore.string';
import toastr from 'toastr';
import { settings } from '../../../settings';
import { callbacks } from '../../../callbacks';
import { t } from '../../../utils';
import { handleError } from '../../../../client/lib/utils/handleError';
import { dispatchToastMessage } from '../../../../client/lib/toast';
Template.loginForm.helpers({
userName() {
@ -74,7 +73,7 @@ Template.loginForm.helpers({
return settings.get('Accounts_ManuallyApproveNewUsers');
},
typedEmail() {
return s.trim(Template.instance().typedEmail);
return Template.instance().typedEmail?.trim();
},
});
@ -87,23 +86,23 @@ Template.loginForm.events({
const state = instance.state.get();
if (formData) {
if (state === 'email-verification') {
Meteor.call('sendConfirmationEmail', s.trim(formData.email), () => {
Meteor.call('sendConfirmationEmail', formData.email?.trim(), () => {
instance.loading.set(false);
callbacks.run('userConfirmationEmailRequested');
toastr.success(t('We_have_sent_registration_email'));
dispatchToastMessage({ type: 'success', message: t('We_have_sent_registration_email') });
return instance.state.set('login');
});
return;
}
if (state === 'forgot-password') {
Meteor.call('sendForgotPasswordEmail', s.trim(formData.email), (err) => {
Meteor.call('sendForgotPasswordEmail', formData.email?.trim(), (err) => {
if (err) {
handleError(err);
return instance.state.set('login');
}
instance.loading.set(false);
callbacks.run('userForgotPasswordEmailRequested');
toastr.success(t('If_this_email_is_registered'));
dispatchToastMessage({ type: 'success', message: t('If_this_email_is_registered') });
return instance.state.set('login');
});
return;
@ -114,14 +113,14 @@ Template.loginForm.events({
instance.loading.set(false);
if (error != null) {
if (error.reason === 'Email already exists.') {
toastr.error(t('Email_already_exists'));
dispatchToastMessage({ type: 'error', message: t('Email_already_exists') });
} else {
handleError(error);
}
return;
}
callbacks.run('userRegistered');
return Meteor.loginWithPassword(s.trim(formData.email), formData.pass, function(error) {
return Meteor.loginWithPassword(formData.email?.trim(), formData.pass, function(error) {
if (error && error.error === 'error-invalid-email') {
return instance.state.set('wait-email-activation');
} if (error && error.error === 'error-user-is-not-activated') {
@ -138,29 +137,29 @@ Template.loginForm.events({
if (settings.get('CROWD_Enable')) {
loginMethod = 'loginWithCrowd';
}
return Meteor[loginMethod](s.trim(formData.emailOrUsername), formData.pass, function(error) {
return Meteor[loginMethod](formData.emailOrUsername?.trim(), formData.pass, function(error) {
instance.loading.set(false);
if (error != null) {
switch (error.error) {
case 'error-user-is-not-activated':
return toastr.error(t('Wait_activation_warning'));
return dispatchToastMessage({ type: 'error', message: t('Wait_activation_warning') });
case 'error-invalid-email':
instance.typedEmail = formData.emailOrUsername;
return instance.state.set('email-verification');
case 'error-app-user-is-not-allowed-to-login':
toastr.error(t('App_user_not_allowed_to_login'));
dispatchToastMessage({ type: 'error', message: t('App_user_not_allowed_to_login') });
break;
case 'error-login-blocked-for-ip':
toastr.error(t('Error_login_blocked_for_ip'));
dispatchToastMessage({ type: 'error', message: t('Error_login_blocked_for_ip') });
break;
case 'error-login-blocked-for-user':
toastr.error(t('Error_login_blocked_for_user'));
dispatchToastMessage({ type: 'error', message: t('Error_login_blocked_for_user') });
break;
case 'error-license-user-limit-reached':
toastr.error(t('error-license-user-limit-reached'));
dispatchToastMessage({ type: 'error', message: t('error-license-user-limit-reached') });
break;
default:
return toastr.error(t('User_not_found_or_incorrect_password'));
return dispatchToastMessage({ type: 'error', message: t('User_not_found_or_incorrect_password') });
}
}
Session.set('forceLogin', false);

@ -2,9 +2,9 @@ import { capitalize } from '@rocket.chat/string-helpers';
import { Meteor } from 'meteor/meteor';
import { Template } from 'meteor/templating';
import { ServiceConfiguration } from 'meteor/service-configuration';
import toastr from 'toastr';
import { CustomOAuth } from '../../../custom-oauth';
import { dispatchToastMessage } from '../../../../client/lib/toast';
Meteor.startup(function() {
return ServiceConfiguration.configurations.find({
@ -84,9 +84,9 @@ Template.loginServices.events({
if (error) {
console.log(JSON.stringify(error));
if (error.reason) {
toastr.error(error.reason);
dispatchToastMessage({ type: 'error', message: error.reason });
} else {
toastr.error(error.message);
dispatchToastMessage({ type: 'error', message: error.message });
}
}
});

@ -3,12 +3,12 @@ import { ReactiveVar } from 'meteor/reactive-var';
import { Template } from 'meteor/templating';
import { Tracker } from 'meteor/tracker';
import _ from 'underscore';
import toastr from 'toastr';
import { settings } from '../../../settings';
import { Button } from '../../../ui';
import { t } from '../../../utils';
import { callbacks } from '../../../callbacks';
import { dispatchToastMessage } from '../../../../client/lib/toast';
Template.username.onCreated(function() {
const self = this;
@ -148,7 +148,7 @@ Template.username.events({
Meteor.call('saveCustomFields', formData, function(err) {
if (err != null) {
toastr.error(err.error);
dispatchToastMessage({ type: 'error', message: err.error });
}
});

@ -1,7 +1,6 @@
import _ from 'underscore';
import { FlowRouter } from 'meteor/kadira:flow-router';
import moment from 'moment';
import toastr from 'toastr';
import mem from 'mem';
import { Meteor } from 'meteor/meteor';
import { TAPi18n } from 'meteor/rocketchat:tap-i18n';
@ -18,6 +17,7 @@ import { imperativeModal } from '../../../../client/lib/imperativeModal';
import ReactionList from '../../../../client/components/modals/ReactionList';
import { call } from '../../../../client/lib/utils/call';
import { canDeleteMessage } from '../../../../client/lib/utils/canDeleteMessage';
import { dispatchToastMessage } from '../../../../client/lib/toast';
export const addMessageToList = (messagesList, message) => {
// checks if the message is not already on the list
@ -228,7 +228,7 @@ Meteor.startup(async function() {
const { msg: message } = messageArgs(this);
const permalink = await MessageAction.getPermaLink(message._id);
navigator.clipboard.writeText(permalink);
toastr.success(TAPi18n.__('Copied'));
dispatchToastMessage({ type: 'success', message: TAPi18n.__('Copied') });
},
condition({ subscription }) {
return !!subscription;
@ -246,7 +246,7 @@ Meteor.startup(async function() {
action() {
const { msg: { msg } } = messageArgs(this);
navigator.clipboard.writeText(msg);
toastr.success(TAPi18n.__('Copied'));
dispatchToastMessage({ type: 'success', message: TAPi18n.__('Copied') });
},
condition({ subscription }) {
return !!subscription;

@ -1,16 +1,16 @@
import { Meteor } from 'meteor/meteor';
import { Accounts } from 'meteor/accounts-base';
import toastr from 'toastr';
import { t } from '../../../utils';
import { dispatchToastMessage } from '../../../../client/lib/toast';
Accounts.onEmailVerificationLink(function(token, done) {
Accounts.verifyEmail(token, function(error) {
if (error == null) {
toastr.success(t('Email_verified'));
dispatchToastMessage({ type: 'success', message: t('Email_verified') });
Meteor.call('afterVerifyEmail');
} else {
toastr.error(error.message);
dispatchToastMessage({ type: 'error', message: error.message });
}
return done();
});

@ -1,5 +1,4 @@
import moment from 'moment';
import toastr from 'toastr';
import _ from 'underscore';
import { Meteor } from 'meteor/meteor';
import { Random } from 'meteor/random';
@ -31,6 +30,7 @@ import { keyCodes } from '../../../../client/lib/utils/keyCodes';
import { prependReplies } from '../../../../client/lib/utils/prependReplies';
import { callWithErrorHandling } from '../../../../client/lib/utils/callWithErrorHandling';
import { handleError } from '../../../../client/lib/utils/handleError';
import { dispatchToastMessage } from '../../../../client/lib/toast';
const messageBoxState = {
@ -477,7 +477,7 @@ export class ChatMessages {
done();
imperativeModal.close();
toastr.success(t('Your_entry_has_been_deleted'));
dispatchToastMessage({ type: 'success', message: t('Your_entry_has_been_deleted') });
};
const onCloseModal = () => {
@ -516,7 +516,7 @@ export class ChatMessages {
currentTsDiff = moment().diff(msgTs, 'minutes');
}
if (currentTsDiff > blockDeleteInMinutes) {
toastr.error(t('Message_deleting_blocked'));
dispatchToastMessage({ type: 'error', message: t('Message_deleting_blocked') });
return;
}
}

@ -1,12 +1,11 @@
import { Meteor } from 'meteor/meteor';
import { Template } from 'meteor/templating';
import toastr from 'toastr';
import s from 'underscore.string';
import { settings } from '../../../../settings';
import { t } from '../../../../utils';
import { popover } from '../../../../ui-utils';
import { getPopoverStatusConfig } from '../..';
import { dispatchToastMessage } from '../../../../../client/lib/toast';
Template.editStatus.helpers({
canChange() {
@ -28,18 +27,16 @@ Template.editStatus.events({
'submit .edit-status__content'(e, instance) {
e.preventDefault();
e.stopPropagation();
const statusText = s.trim(e.target.status.value);
const statusText = e.target.status.value?.trim();
const statusType = e.target.statusType.value;
if (statusText !== this.statusText) {
if (statusText.length > 120) {
toastr.remove();
toastr.error(t('StatusMessage_Too_Long'));
dispatchToastMessage({ type: 'error', message: t('StatusMessage_Too_Long') });
return false;
}
if (!settings.get('Accounts_AllowUserStatusMessageChange')) {
toastr.remove();
toastr.error(t('StatusMessage_Change_Disabled'));
dispatchToastMessage({ type: 'error', message: t('StatusMessage_Change_Disabled') });
return false;
}

@ -3,8 +3,8 @@ import { ReactiveVar } from 'meteor/reactive-var';
import { FlowRouter } from 'meteor/kadira:flow-router';
import { Template } from 'meteor/templating';
import { Session } from 'meteor/session';
import toastr from 'toastr';
import { dispatchToastMessage } from '../../../../../client/lib/toast';
import { settings } from '../../../../settings';
import { t, APIClient } from '../../../../utils';
@ -31,7 +31,7 @@ Template.invite.onCreated(function() {
this.hashReady.set(true);
if (!result || !result.success) {
toastr.error(t('Failed_to_validate_invite_token'));
dispatchToastMessage({ type: 'error', message: t('Failed_to_validate_invite_token') });
return this.inviteIsValid.set(false);
}
@ -42,7 +42,7 @@ Template.invite.onCreated(function() {
}
return this.inviteIsValid.set(result.valid);
}).catch(() => {
toastr.error(t('Failed_to_validate_invite_token'));
dispatchToastMessage({ type: 'error', message: t('Failed_to_validate_invite_token') });
return this.inviteIsValid.set(false);
});
@ -56,7 +56,7 @@ Template.invite.onCreated(function() {
c.stop();
APIClient.v1.post('useInviteToken', { token }).then((result) => {
if (!result || !result.room || !result.room.name) {
toastr.error(t('Failed_to_activate_invite_token'));
dispatchToastMessage({ type: 'error', message: t('Failed_to_activate_invite_token') });
return;
}
@ -66,7 +66,7 @@ Template.invite.onCreated(function() {
FlowRouter.go(`/channel/${ result.room.name }`);
}
}).catch(() => {
toastr.error(t('Failed_to_activate_invite_token'));
dispatchToastMessage({ type: 'error', message: t('Failed_to_activate_invite_token') });
});
}
});

@ -1,7 +1,7 @@
import { Meteor } from 'meteor/meteor';
import { Accounts } from 'meteor/accounts-base';
import { process2faReturn } from '../../../2fa/client/callWithTwoFactorRequired';
import { process2faReturn } from '../../../../client/lib/2fa/process2faReturn';
import { baseURI } from '../../../../client/lib/baseURI';
export const APIClient = {

@ -1,10 +1,10 @@
import { Meteor } from 'meteor/meteor';
import { Session } from 'meteor/session';
import { TAPi18n } from 'meteor/rocketchat:tap-i18n';
import toastr from 'toastr';
import { actionLinks } from '../../action-links/client';
import { Rooms } from '../../models';
import { dispatchToastMessage } from '../../../client/lib/toast';
actionLinks.register('joinJitsiCall', function(message, params, instance) {
if (Session.get('openedRoom')) {
@ -16,11 +16,11 @@ actionLinks.register('joinJitsiCall', function(message, params, instance) {
const jitsiTimeout = new Date((room && room.jitsiTimeout) || currentTime).getTime();
if (room && room?.muted?.includes(username)) {
toastr.error(TAPi18n.__('You_have_been_muted', ''));
dispatchToastMessage({ type: 'error', message: TAPi18n.__('You_have_been_muted', '') });
} else if (jitsiTimeout > currentTime) {
instance.tabBar.open('video');
} else {
toastr.info(TAPi18n.__('Call Already Ended', ''));
dispatchToastMessage({ type: 'info', message: TAPi18n.__('Call Already Ended', '') });
}
}
});

@ -2,10 +2,10 @@ import { Meteor } from 'meteor/meteor';
import { ReactiveVar } from 'meteor/reactive-var';
import { Template } from 'meteor/templating';
import _ from 'underscore';
import toastr from 'toastr';
import { modal } from '../../ui-utils';
import { t } from '../../utils';
import { dispatchToastMessage } from '../../../client/lib/toast';
Template.addWebdavAccount.helpers({
btnAddNewServer() {
@ -28,12 +28,12 @@ Template.addWebdavAccount.events({
modal.close();
instance.loading.set(false);
if (error) {
return toastr.error(t(error.error));
return dispatchToastMessage({ type: 'error', message: t(error.error) });
}
if (!success) {
return toastr.error(t('Error'));
return dispatchToastMessage({ type: 'error', message: t('Error') });
}
toastr.success(t('webdav-account-saved'));
dispatchToastMessage({ type: 'success', message: t('webdav-account-saved') });
});
},
});

@ -1,10 +1,10 @@
import { Meteor } from 'meteor/meteor';
import { Template } from 'meteor/templating';
import toastr from 'toastr';
import { modal } from '../../ui-utils';
import { t } from '../../utils';
import { WebdavAccounts } from '../../models';
import { dispatchToastMessage } from '../../../client/lib/toast';
Template.selectWebdavAccount.helpers({
webdavAccounts() {
@ -30,12 +30,12 @@ Template.selectWebdavAccount.events({
const fileData = new Uint8Array(arrayBuffer);
Meteor.call('uploadFileToWebdav', accountId, fileData, name, (error, response) => {
if (error) {
return toastr.error(t(error.error));
return dispatchToastMessage({ type: 'error', message: t(error.error) });
}
if (!response.success) {
return toastr.error(t(response.message));
return dispatchToastMessage({ type: 'error', message: t(response.message) });
}
return toastr.success(t('File_uploaded'));
return dispatchToastMessage({ type: 'success', message: t('File_uploaded') });
});
}
};

@ -1,7 +1,6 @@
import { Meteor } from 'meteor/meteor';
import { Template } from 'meteor/templating';
import _ from 'underscore';
import toastr from 'toastr';
import { Session } from 'meteor/session';
import { Handlebars } from 'meteor/ui';
import { ReactiveVar } from 'meteor/reactive-var';
@ -12,6 +11,7 @@ import { modal } from '../../ui-utils/client';
import { t } from '../../utils/client';
import { fileUploadHandler } from '../../file-upload/client';
import { call } from '../../../client/lib/utils/call';
import { dispatchToastMessage } from '../../../client/lib/toast';
function sortTable(data, sortBy, sortDirection) {
if (sortDirection === 'desc') {
@ -238,7 +238,7 @@ Template.webdavFilePicker.events({
instance.isLoading.set(false);
if (!response || !response.success) {
modal.close();
return toastr.error(t('Failed_to_get_webdav_file'));
return dispatchToastMessage({ type: 'error', message: t('Failed_to_get_webdav_file') });
}
const blob = new Blob([response.data], { type: response.type });
// converting to file object

@ -13,29 +13,32 @@ export enum Method {
export type OnConfirm = (code: string, method: Method) => void;
type TwoFactorModalProps = {
method: Method;
onConfirm: OnConfirm;
onClose: () => void;
emailOrUsername: string;
};
const TwoFactorModal = ({
method,
onConfirm,
onClose,
emailOrUsername,
}: TwoFactorModalProps): ReactElement => {
if (method === Method.TOTP) {
} & (
| {
method: 'totp' | 'password';
}
| {
method: 'email';
emailOrUsername: string;
}
);
const TwoFactorModal = ({ onConfirm, onClose, ...props }: TwoFactorModalProps): ReactElement => {
if (props.method === Method.TOTP) {
return <TwoFactorTotp onConfirm={onConfirm} onClose={onClose} />;
}
if (method === Method.EMAIL) {
if (props.method === Method.EMAIL) {
const { emailOrUsername } = props;
return (
<TwoFactorEmail onConfirm={onConfirm} onClose={onClose} emailOrUsername={emailOrUsername} />
);
}
if (method === Method.PASSWORD) {
if (props.method === Method.PASSWORD) {
return <TwoFactorPassword onConfirm={onConfirm} onClose={onClose} />;
}

@ -1,6 +1,6 @@
import { createContext, useContext } from 'react';
export type ToastMessagePayload = {
type ToastMessagePayload = {
type: 'success' | 'info' | 'warning' | 'error';
message: string | Error;
title?: string;

@ -0,0 +1,48 @@
import { t } from '../../../app/utils/client';
import { dispatchToastMessage } from '../toast';
import { process2faReturn } from './process2faReturn';
import { isTotpInvalidError, isTotpRequiredError } from './utils';
type LoginCallback = {
(error: unknown): void;
(error: unknown, result: unknown): void;
};
type LoginMethod<A extends unknown[]> = (...args: [...args: A, cb: LoginCallback]) => void;
type LoginMethodWithTotp<A extends unknown[]> = (
...args: [...args: A, code: string, cb: LoginCallback]
) => void;
export const overrideLoginMethod = <A extends unknown[]>(
loginMethod: LoginMethod<A>,
loginArgs: A,
callback: LoginCallback,
loginMethodTOTP: LoginMethodWithTotp<A>,
emailOrUsername: string,
): void => {
loginMethod.call(null, ...loginArgs, (error: unknown, result?: unknown) => {
if (!isTotpRequiredError(error)) {
callback(error);
return;
}
process2faReturn({
error,
result,
emailOrUsername,
originalCallback: callback,
onCode: (code: string) => {
loginMethodTOTP?.call(null, ...loginArgs, code, (error: unknown) => {
if (isTotpInvalidError(error)) {
dispatchToastMessage({ type: 'error', message: t('Invalid_two_factor_code') });
callback(null);
return;
}
callback(error);
});
},
});
});
};

@ -0,0 +1,82 @@
import { Meteor } from 'meteor/meteor';
import { SHA256 } from 'meteor/sha';
import TwoFactorModal from '../../components/TwoFactorModal';
import { imperativeModal } from '../imperativeModal';
import { isTotpRequiredError } from './utils';
const twoFactorMethods = ['totp', 'email', 'password'] as const;
type TwoFactorMethod = typeof twoFactorMethods[number];
const isTwoFactorMethod = (method: string): method is TwoFactorMethod =>
twoFactorMethods.includes(method as TwoFactorMethod);
const hasRequiredTwoFactorMethod = (
error: Meteor.Error,
): error is Meteor.Error & { details: { method: TwoFactorMethod } } => {
const details = error.details as unknown;
return (
typeof details === 'object' &&
details !== null &&
typeof (details as { method: unknown }).method === 'string' &&
isTwoFactorMethod((details as { method: string }).method)
);
};
function assertModalProps(props: {
method: TwoFactorMethod;
emailOrUsername?: string;
}): asserts props is
| { method: 'totp' }
| { method: 'password' }
| { method: 'email'; emailOrUsername: string } {
if (props.method === 'email' && typeof props.emailOrUsername !== 'string') {
throw new Error('Invalid Two Factor method');
}
}
export function process2faReturn({
error,
result,
originalCallback,
onCode,
emailOrUsername,
}: {
error: unknown;
result: unknown;
originalCallback: {
(error: unknown): void;
(error: unknown, result: unknown): void;
};
onCode: (code: string, method: string) => void;
emailOrUsername: string | null | undefined;
}): void {
if (!isTotpRequiredError(error) || !hasRequiredTwoFactorMethod(error)) {
originalCallback(error, result);
return;
}
const props = {
method: error.details.method,
emailOrUsername: emailOrUsername ?? Meteor.user()?.username,
};
assertModalProps(props);
imperativeModal.open({
component: TwoFactorModal,
props: {
...props,
onConfirm: (code: string, method: string): void => {
imperativeModal.close();
onCode(method === 'password' ? SHA256(code) : code, method);
},
onClose: (): void => {
imperativeModal.close();
originalCallback(new Meteor.Error('totp-canceled'));
},
},
});
}

@ -0,0 +1,32 @@
import { Accounts } from 'meteor/accounts-base';
import { Meteor } from 'meteor/meteor';
export const isTotpRequiredError = (
error: unknown,
): error is Meteor.Error & { error: 'totp-required' } =>
(error as { error?: unknown } | undefined)?.error === 'totp-required';
export const isTotpInvalidError = (
error: unknown,
): error is Meteor.Error & { error: 'totp-invalid' } =>
(error as { error?: unknown } | undefined)?.error === 'totp-invalid';
export const isLoginCancelledError = (error: unknown): error is Meteor.Error =>
error instanceof Meteor.Error && error.error === Accounts.LoginCancelledError.numericError;
export const reportError = <T>(error: T, callback?: (error?: T) => void): void => {
if (callback) {
callback(error);
return;
}
throw error;
};
export const convertError = <T>(error: T): Accounts.LoginCancelledError | T => {
if (isLoginCancelledError(error)) {
return new Accounts.LoginCancelledError(error.reason);
}
return error;
};

@ -0,0 +1,21 @@
import { Emitter } from '@rocket.chat/emitter';
export type ToastMessagePayload = {
type: 'success' | 'info' | 'warning' | 'error';
message: string | Error;
title?: string;
options?: object;
};
const emitter = new Emitter<{
notify: ToastMessagePayload;
}>();
export const dispatchToastMessage = (payload: ToastMessagePayload): void => {
// TODO: buffer it if there is no subscriber
emitter.emit('notify', payload);
};
export const subscribeToToastMessages = (
callback: (payload: ToastMessagePayload) => void,
): (() => void) => emitter.on('notify', callback);

@ -2,7 +2,6 @@ import { Meteor } from 'meteor/meteor';
import { TimeSync } from 'meteor/mizzao:timesync';
import { Tracker } from 'meteor/tracker';
import moment from 'moment';
import toastr from 'toastr';
import _ from 'underscore';
import { hasAtLeastOnePermission } from '../../app/authorization/client';
@ -10,6 +9,7 @@ import { callbacks } from '../../app/callbacks/client';
import { ChatMessage } from '../../app/models/client';
import { settings } from '../../app/settings/client';
import { t } from '../../app/utils/client';
import { dispatchToastMessage } from '../lib/toast';
Meteor.methods({
updateMessage(message) {
@ -32,7 +32,10 @@ Meteor.methods({
const me = Meteor.users.findOne(Meteor.userId());
if (!(hasPermission || (editAllowed && editOwn))) {
toastr.error(t('error-action-not-allowed', { action: t('Message_editing') }));
dispatchToastMessage({
type: 'error',
message: t('error-action-not-allowed', { action: t('Message_editing') }),
});
return false;
}
@ -43,7 +46,7 @@ Meteor.methods({
if (msgTs) {
const currentTsDiff = moment().diff(msgTs, 'minutes');
if (currentTsDiff > blockEditInMinutes) {
toastr.error(t('error-message-editing-blocked'));
dispatchToastMessage({ type: 'error', message: t('error-message-editing-blocked') });
return false;
}
}

@ -1,28 +1,33 @@
import React, { FC } from 'react';
import React, { FC, useEffect } from 'react';
import toastr from 'toastr';
import { ToastMessagesContext, ToastMessagePayload } from '../contexts/ToastMessagesContext';
import { ToastMessagesContext } from '../contexts/ToastMessagesContext';
import { dispatchToastMessage, subscribeToToastMessages } from '../lib/toast';
import { handleError } from '../lib/utils/handleError';
const dispatch = ({ type, message, title, options }: ToastMessagePayload): void => {
if (type === 'error' && typeof message === 'object') {
handleError(message);
return;
}
const contextValue = {
dispatch: dispatchToastMessage,
};
if (typeof message !== 'string') {
message = `[${message.name}] ${message.message}`;
}
const ToastMessagesProvider: FC = ({ children }) => {
useEffect(
() =>
subscribeToToastMessages(({ type, message, title, options }) => {
if (type === 'error' && typeof message === 'object') {
handleError(message);
return;
}
toastr[type](message, title, options);
};
if (typeof message !== 'string') {
message = `[${message.name}] ${message.message}`;
}
const contextValue = {
dispatch,
};
toastr[type](message, title, options);
}),
[],
);
const ToastMessagesProvider: FC = ({ children }) => (
<ToastMessagesContext.Provider children={children} value={contextValue} />
);
return <ToastMessagesContext.Provider children={children} value={contextValue} />;
};
export default ToastMessagesProvider;

@ -2,9 +2,9 @@ import { Meteor } from 'meteor/meteor';
import { TAPi18n } from 'meteor/rocketchat:tap-i18n';
import { Session } from 'meteor/session';
import { Tracker } from 'meteor/tracker';
import toastr from 'toastr';
import { settings } from '../../app/settings/client';
import { dispatchToastMessage } from '../lib/toast';
Meteor.startup(() => {
Tracker.autorun(() => {
@ -17,7 +17,10 @@ Meteor.startup(() => {
settings.get('Accounts_EmailVerification') === true &&
!Session.get('Accounts_EmailVerification_Warning')
) {
toastr.warning(TAPi18n.__('You_have_not_verified_your_email'));
dispatchToastMessage({
type: 'warning',
message: TAPi18n.__('You_have_not_verified_your_email'),
});
Session.set('Accounts_EmailVerification_Warning', true);
}
});

@ -1,6 +1,5 @@
import { Meteor } from 'meteor/meteor';
import { Tracker } from 'meteor/tracker';
import toastr from 'toastr';
import { hasRole } from '../../app/authorization/client';
import { Roles } from '../../app/models/client';
@ -8,6 +7,7 @@ import { settings } from '../../app/settings/client';
import { t } from '../../app/utils/client';
import UrlChangeModal from '../components/UrlChangeModal';
import { imperativeModal } from '../lib/imperativeModal';
import { dispatchToastMessage } from '../lib/toast';
import { isSyncReady } from '../lib/userData';
Meteor.startup(() => {
@ -34,7 +34,7 @@ Meteor.startup(() => {
const confirm = (): void => {
imperativeModal.close();
Meteor.call('saveSetting', 'Site_Url', currentUrl, () => {
toastr.success(t('Saved'));
dispatchToastMessage({ type: 'success', message: t('Saved') });
});
};
imperativeModal.open({

@ -3,12 +3,12 @@ import { Meteor } from 'meteor/meteor';
import { Session } from 'meteor/session';
import { Tracker } from 'meteor/tracker';
import { lazy } from 'react';
import toastr from 'toastr';
import { KonchatNotification } from '../../app/ui/client';
import { IUser } from '../../definition/IUser';
import { appLayout } from '../lib/appLayout';
import { createTemplateForComponent } from '../lib/portals/createTemplateForComponent';
import { dispatchToastMessage } from '../lib/toast';
import { handleError } from '../lib/utils/handleError';
const SetupWizardRoute = lazy(() => import('../views/setupWizard/SetupWizardRoute'));
@ -64,7 +64,7 @@ FlowRouter.route('/home', {
(Meteor as any).loginWithSamlToken(token, (error?: any) => {
if (error) {
if (error.reason) {
toastr.error(error.reason);
dispatchToastMessage({ type: 'error', message: error.reason });
} else {
handleError(error);
}

@ -1,9 +1,9 @@
import { AppStatus } from '@rocket.chat/apps-engine/definition/AppStatus';
import semver from 'semver';
import toastr from 'toastr';
import { Utilities } from '../../../../app/apps/lib/misc/Utilities';
import { t } from '../../../../app/utils/client';
import { dispatchToastMessage } from '../../../lib/toast';
export const appEnabledStatuses = [AppStatus.AUTO_ENABLED, AppStatus.MANUALLY_ENABLED];
@ -53,7 +53,7 @@ export function handleInstallError(apiError) {
}
}
toastr.error(message);
dispatchToastMessage({ type: 'error', message });
}
const shouldHandleErrorAsWarning = (message) => {
@ -65,16 +65,22 @@ const shouldHandleErrorAsWarning = (message) => {
export const handleAPIError = (error) => {
const message =
(error.xhr && error.xhr.responseJSON && error.xhr.responseJSON.error) || error.message;
shouldHandleErrorAsWarning(message) ? toastr.warning(message) : toastr.error(message);
if (shouldHandleErrorAsWarning(message)) {
dispatchToastMessage({ type: 'warning', message });
return;
}
dispatchToastMessage({ type: 'error', message });
};
export const warnStatusChange = (appName, status) => {
if (appErroredStatuses.includes(status)) {
toastr.error(t(`App_status_${status}`), appName);
dispatchToastMessage({ type: 'error', message: (t(`App_status_${status}`), appName) });
return;
}
toastr.info(t(`App_status_${status}`), appName);
dispatchToastMessage({ type: 'info', message: (t(`App_status_${status}`), appName) });
};
export const appButtonProps = ({

@ -2,7 +2,6 @@ import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
import { FlowRouter } from 'meteor/kadira:flow-router';
import { Session } from 'meteor/session';
import React, { useCallback, useState, useEffect } from 'react';
import toastr from 'toastr';
import { RoomManager } from '../../../../../../../app/ui-utils/client';
import { IOmnichannelRoom } from '../../../../../../../definition/IRoom';
@ -97,12 +96,15 @@ export const useQuickActions = (
await requestTranscript(rid, email, subject);
closeModal();
RoomManager.close(`l${rid}`);
toastr.success(t('Livechat_transcript_has_been_requested'));
dispatchToastMessage({
type: 'success',
message: t('Livechat_transcript_has_been_requested'),
});
} catch (error: any) {
handleError(error);
}
},
[closeModal, requestTranscript, rid, t],
[closeModal, dispatchToastMessage, requestTranscript, rid, t],
);
const sendTranscript = useMethod('livechat:sendTranscript');
@ -124,12 +126,15 @@ export const useQuickActions = (
const handleDiscardTranscript = useCallback(async () => {
try {
await discardTranscript(rid);
toastr.success(t('Livechat_transcript_request_has_been_canceled'));
dispatchToastMessage({
type: 'success',
message: t('Livechat_transcript_request_has_been_canceled'),
});
closeModal();
} catch (error: any) {
handleError(error);
}
}, [closeModal, discardTranscript, rid, t]);
}, [closeModal, discardTranscript, dispatchToastMessage, rid, t]);
const forwardChat = useMethod('livechat:transfer');
@ -164,14 +169,14 @@ export const useQuickActions = (
departmentId ? t('error-no-agents-online-in-department') : t('error-forwarding-chat'),
);
}
toastr.success(t('Transferred'));
dispatchToastMessage({ type: 'success', message: t('Transferred') });
FlowRouter.go('/');
closeModal();
} catch (error: any) {
handleError(error);
}
},
[closeModal, forwardChat, rid, t],
[closeModal, dispatchToastMessage, forwardChat, rid, t],
);
const closeChat = useMethod('livechat:closeRoom');
@ -181,12 +186,12 @@ export const useQuickActions = (
try {
await closeChat(rid, comment, { clientAction: true, tags });
closeModal();
toastr.success(t('Chat_closed_successfully'));
dispatchToastMessage({ type: 'success', message: t('Chat_closed_successfully') });
} catch (error: any) {
handleError(error);
}
},
[closeChat, closeModal, rid, t],
[closeChat, closeModal, dispatchToastMessage, rid, t],
);
const onHoldChat = useEndpoint('POST', 'livechat/room.onHold');
@ -195,11 +200,11 @@ export const useQuickActions = (
try {
await onHoldChat({ roomId: rid });
closeModal();
toastr.success(t('Chat_On_Hold_Successfully'));
dispatchToastMessage({ type: 'success', message: t('Chat_On_Hold_Successfully') });
} catch (error: any) {
handleError(error);
}
}, [onHoldChat, closeModal, rid, t]);
}, [onHoldChat, rid, closeModal, dispatchToastMessage, t]);
const openModal = useMutableCallback(async (id: string) => {
switch (id) {

@ -1,12 +1,12 @@
import { ReactiveVar } from 'meteor/reactive-var';
import { Meteor } from 'meteor/meteor';
import { Template } from 'meteor/templating';
import toastr from 'toastr';
import { CannedResponse } from '../../collections/CannedResponse';
import { t } from '../../../../../../app/utils';
import { chatMessages } from '../../../../../../app/ui/client';
import { handleError } from '../../../../../../client/lib/utils/handleError';
import { dispatchToastMessage } from '../../../../../../client/lib/toast';
import './cannedResponses.html';
@ -136,7 +136,7 @@ Template.cannedResponses.events({
const cannedResponse = CannedResponse.findOne({ _id });
if (!cannedResponse) {
toastr.success(t('Invalid Canned Response'));
dispatchToastMessage({ type: 'success', message: t('Invalid Canned Response') });
}
instance.cannedResponse.set(cannedResponse);
@ -156,7 +156,7 @@ Template.cannedResponses.events({
return handleError(error);
}
toastr.success(t('Canned_Response_Removed'));
dispatchToastMessage({ type: 'success', message: t('Canned_Response_Removed') });
instance.action.set(null);
});
},
@ -223,7 +223,7 @@ Template.cannedResponses.events({
const context = instance.context.get();
if (context === 'department') {
if (!instance.departmentId) {
toastr.error(t('Invalid_Department'));
dispatchToastMessage({ type: 'error', message: t('Invalid_Department') });
return;
}
@ -238,7 +238,7 @@ Template.cannedResponses.events({
return handleError(error);
}
toastr.success(t('Saved'));
dispatchToastMessage({ type: 'success', message: t('Saved') });
instance.action.set(null);
});
},

6
server/main.d.ts vendored

@ -28,6 +28,12 @@ declare module 'meteor/accounts-base' {
function _generateStampedLoginToken(): {token: string; when: Date};
function _runLoginHandlers(methodInvocation: Function, loginRequest: Record<string, any>): Record<string, any> | undefined;
export class ConfigError extends Error {}
export class LoginCancelledError extends Error {
public static readonly numericError: number;
}
}
}

Loading…
Cancel
Save