[NEW] Encrypted Discussions (#2813)
* I18n key fix * Add encrypted switch * Remove unused i18n keys * Add enabled to encryption reducer * Show encrypted option on CreateDiscussionView only when e2e encryption is properly set * Add localSearch and use it on search * Use encrypted from parent channel * Fix method calls as rest api with 2fa enabled * Fix logout after reset keys * Use encryption reducer instead of lib directly to check render * Check for room type logic to display encryption option on create discussion * Check toggle-room-e2e-encryption permission on RoomActionsView * Check for encryption status instead of setting on server * Fix * Disable switch instead of hide it * Fix spotlight for DMs * Fix server test
This commit is contained in:
parent
a1bd97f658
commit
db5074ab70
|
@ -69,4 +69,4 @@ export const INVITE_LINKS = createRequestTypes('INVITE_LINKS', [
|
||||||
export const SETTINGS = createRequestTypes('SETTINGS', ['CLEAR', 'ADD']);
|
export const SETTINGS = createRequestTypes('SETTINGS', ['CLEAR', 'ADD']);
|
||||||
export const APP_STATE = createRequestTypes('APP_STATE', ['FOREGROUND', 'BACKGROUND']);
|
export const APP_STATE = createRequestTypes('APP_STATE', ['FOREGROUND', 'BACKGROUND']);
|
||||||
export const ENTERPRISE_MODULES = createRequestTypes('ENTERPRISE_MODULES', ['CLEAR', 'SET']);
|
export const ENTERPRISE_MODULES = createRequestTypes('ENTERPRISE_MODULES', ['CLEAR', 'SET']);
|
||||||
export const ENCRYPTION = createRequestTypes('ENCRYPTION', ['INIT', 'STOP', 'DECODE_KEY', 'SET_BANNER']);
|
export const ENCRYPTION = createRequestTypes('ENCRYPTION', ['INIT', 'STOP', 'DECODE_KEY', 'SET', 'SET_BANNER']);
|
||||||
|
|
|
@ -12,6 +12,14 @@ export function encryptionStop() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function encryptionSet(enabled = false, banner = null) {
|
||||||
|
return {
|
||||||
|
type: types.ENCRYPTION.SET,
|
||||||
|
enabled,
|
||||||
|
banner
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function encryptionSetBanner(banner) {
|
export function encryptionSetBanner(banner) {
|
||||||
return {
|
return {
|
||||||
type: types.ENCRYPTION.SET_BANNER,
|
type: types.ENCRYPTION.SET_BANNER,
|
||||||
|
|
|
@ -562,7 +562,6 @@ export default {
|
||||||
Username: 'اسم المستخدم',
|
Username: 'اسم المستخدم',
|
||||||
Username_or_email: 'اسم المستخدم أو البريد الالكتروني',
|
Username_or_email: 'اسم المستخدم أو البريد الالكتروني',
|
||||||
Uses_server_configuration: 'يستخدم إعداد الخادم',
|
Uses_server_configuration: 'يستخدم إعداد الخادم',
|
||||||
Usually_a_discussion_starts_with_a_question_like_How_do_I_upload_a_picture: 'عادةً، النقاش يبدأ بسؤال، على سبيل المثال: كيف أرفع صورة؟',
|
|
||||||
Validating: 'يتم التحقق',
|
Validating: 'يتم التحقق',
|
||||||
Registration_Succeeded: 'تم التسجيل بنجاح',
|
Registration_Succeeded: 'تم التسجيل بنجاح',
|
||||||
Verify: 'تحقق',
|
Verify: 'تحقق',
|
||||||
|
@ -597,7 +596,6 @@ export default {
|
||||||
You_need_to_access_at_least_one_RocketChat_server_to_share_something: 'تحتاج إلى الوصول إلى خادم Rocket.Chat واحد على الأقل لمشاركة شيء ما',
|
You_need_to_access_at_least_one_RocketChat_server_to_share_something: 'تحتاج إلى الوصول إلى خادم Rocket.Chat واحد على الأقل لمشاركة شيء ما',
|
||||||
You_need_to_verifiy_your_email_address_to_get_notications: 'يجب تأكيد البريد الإلكتروني حتى تصلك الإشعارات',
|
You_need_to_verifiy_your_email_address_to_get_notications: 'يجب تأكيد البريد الإلكتروني حتى تصلك الإشعارات',
|
||||||
Your_certificate: 'شهادتك',
|
Your_certificate: 'شهادتك',
|
||||||
Your_message: 'رسالتك',
|
|
||||||
Your_invite_link_will_expire_after__usesLeft__uses: 'سوف تنتهي صلاحية رابط الدعوة الخاص بك بعد {{usesLeft}} استخدامات',
|
Your_invite_link_will_expire_after__usesLeft__uses: 'سوف تنتهي صلاحية رابط الدعوة الخاص بك بعد {{usesLeft}} استخدامات',
|
||||||
Your_invite_link_will_expire_on__date__or_after__usesLeft__uses: 'ستنتهي صلاحية رابط الدعوة الخاص بك في {{date}} أو بعد {{usesLeft}} استخدامات',
|
Your_invite_link_will_expire_on__date__or_after__usesLeft__uses: 'ستنتهي صلاحية رابط الدعوة الخاص بك في {{date}} أو بعد {{usesLeft}} استخدامات',
|
||||||
Your_invite_link_will_expire_on__date__: 'ستنتهي صلاحية رابط الدعوة الخاص بك في {{date}}',
|
Your_invite_link_will_expire_on__date__: 'ستنتهي صلاحية رابط الدعوة الخاص بك في {{date}}',
|
||||||
|
|
|
@ -565,7 +565,6 @@ export default {
|
||||||
Username: 'Benutzername',
|
Username: 'Benutzername',
|
||||||
Username_or_email: 'Benutzername oder E-Mail-Adresse',
|
Username_or_email: 'Benutzername oder E-Mail-Adresse',
|
||||||
Uses_server_configuration: 'Nutzt Servereinstellungen',
|
Uses_server_configuration: 'Nutzt Servereinstellungen',
|
||||||
Usually_a_discussion_starts_with_a_question_like_How_do_I_upload_a_picture: 'Üblicherweise beginnt eine Diskussion mit einer Frage, beispielsweise: "Wie lade ich ein Bild hoch?"',
|
|
||||||
Validating: 'Validierung',
|
Validating: 'Validierung',
|
||||||
Registration_Succeeded: 'Registrierung erfolgreich!',
|
Registration_Succeeded: 'Registrierung erfolgreich!',
|
||||||
Verify: 'Überprüfen',
|
Verify: 'Überprüfen',
|
||||||
|
@ -600,7 +599,6 @@ export default {
|
||||||
You_need_to_access_at_least_one_RocketChat_server_to_share_something: 'Du benötigst Zugang zu mindestens einem Rocket.Chat-Server um etwas zu teilen.',
|
You_need_to_access_at_least_one_RocketChat_server_to_share_something: 'Du benötigst Zugang zu mindestens einem Rocket.Chat-Server um etwas zu teilen.',
|
||||||
You_need_to_verifiy_your_email_address_to_get_notications: 'Du musst deine Email-Adresse bestätigen um Benachrichtigungen zu erhalten.',
|
You_need_to_verifiy_your_email_address_to_get_notications: 'Du musst deine Email-Adresse bestätigen um Benachrichtigungen zu erhalten.',
|
||||||
Your_certificate: 'Dein Zertifikat',
|
Your_certificate: 'Dein Zertifikat',
|
||||||
Your_message: 'Deine Nachricht',
|
|
||||||
Your_invite_link_will_expire_after__usesLeft__uses: 'Dein Einladungs-Link wird nach {{usesLeft}} Benutzungen ablaufen.',
|
Your_invite_link_will_expire_after__usesLeft__uses: 'Dein Einladungs-Link wird nach {{usesLeft}} Benutzungen ablaufen.',
|
||||||
Your_invite_link_will_expire_on__date__or_after__usesLeft__uses: 'Dein Einladungs-Link wird am {{date}} oder nach {{usesLeft}} Benutzungen ablaufen.',
|
Your_invite_link_will_expire_on__date__or_after__usesLeft__uses: 'Dein Einladungs-Link wird am {{date}} oder nach {{usesLeft}} Benutzungen ablaufen.',
|
||||||
Your_invite_link_will_expire_on__date__: 'Dein Einladungs-Link wird am {{date}} ablaufen.',
|
Your_invite_link_will_expire_on__date__: 'Dein Einladungs-Link wird am {{date}} ablaufen.',
|
||||||
|
|
|
@ -223,7 +223,7 @@ export default {
|
||||||
Enter_Your_Encryption_Password_desc1: 'This will allow you to access your encrypted private groups and direct messages.',
|
Enter_Your_Encryption_Password_desc1: 'This will allow you to access your encrypted private groups and direct messages.',
|
||||||
Enter_Your_Encryption_Password_desc2: 'You need to enter the password to encode/decode messages every place you use the chat.',
|
Enter_Your_Encryption_Password_desc2: 'You need to enter the password to encode/decode messages every place you use the chat.',
|
||||||
Encryption_error_title: 'Your encryption password seems wrong',
|
Encryption_error_title: 'Your encryption password seems wrong',
|
||||||
Encryption_error_desc: 'Wasn\'t possible to decode your encryption key to be imported.',
|
Encryption_error_desc: 'It wasn\'t possible to decode your encryption key to be imported.',
|
||||||
Everyone_can_access_this_channel: 'Everyone can access this channel',
|
Everyone_can_access_this_channel: 'Everyone can access this channel',
|
||||||
Error_uploading: 'Error uploading',
|
Error_uploading: 'Error uploading',
|
||||||
Expiration_Days: 'Expiration (Days)',
|
Expiration_Days: 'Expiration (Days)',
|
||||||
|
@ -565,7 +565,6 @@ export default {
|
||||||
Username: 'Username',
|
Username: 'Username',
|
||||||
Username_or_email: 'Username or email',
|
Username_or_email: 'Username or email',
|
||||||
Uses_server_configuration: 'Uses server configuration',
|
Uses_server_configuration: 'Uses server configuration',
|
||||||
Usually_a_discussion_starts_with_a_question_like_How_do_I_upload_a_picture: 'Usually, a discussion starts with a question, like "How do I upload a picture?"',
|
|
||||||
Validating: 'Validating',
|
Validating: 'Validating',
|
||||||
Registration_Succeeded: 'Registration Succeeded!',
|
Registration_Succeeded: 'Registration Succeeded!',
|
||||||
Verify: 'Verify',
|
Verify: 'Verify',
|
||||||
|
@ -600,7 +599,6 @@ export default {
|
||||||
You_need_to_access_at_least_one_RocketChat_server_to_share_something: 'You need to access at least one Rocket.Chat server to share something.',
|
You_need_to_access_at_least_one_RocketChat_server_to_share_something: 'You need to access at least one Rocket.Chat server to share something.',
|
||||||
You_need_to_verifiy_your_email_address_to_get_notications: 'You need to verify your email address to get notifications',
|
You_need_to_verifiy_your_email_address_to_get_notications: 'You need to verify your email address to get notifications',
|
||||||
Your_certificate: 'Your Certificate',
|
Your_certificate: 'Your Certificate',
|
||||||
Your_message: 'Your message',
|
|
||||||
Your_invite_link_will_expire_after__usesLeft__uses: 'Your invite link will expire after {{usesLeft}} uses.',
|
Your_invite_link_will_expire_after__usesLeft__uses: 'Your invite link will expire after {{usesLeft}} uses.',
|
||||||
Your_invite_link_will_expire_on__date__or_after__usesLeft__uses: 'Your invite link will expire on {{date}} or after {{usesLeft}} uses.',
|
Your_invite_link_will_expire_on__date__or_after__usesLeft__uses: 'Your invite link will expire on {{date}} or after {{usesLeft}} uses.',
|
||||||
Your_invite_link_will_expire_on__date__: 'Your invite link will expire on {{date}}.',
|
Your_invite_link_will_expire_on__date__: 'Your invite link will expire on {{date}}.',
|
||||||
|
|
|
@ -531,7 +531,6 @@ export default {
|
||||||
Username: 'Nom d\'utilisateur',
|
Username: 'Nom d\'utilisateur',
|
||||||
Username_or_email: 'Nom d\'utilisateur ou address e-mail',
|
Username_or_email: 'Nom d\'utilisateur ou address e-mail',
|
||||||
Uses_server_configuration: 'Utilise la configuration du serveur',
|
Uses_server_configuration: 'Utilise la configuration du serveur',
|
||||||
Usually_a_discussion_starts_with_a_question_like_How_do_I_upload_a_picture: 'Habituellement, une discussion commence par une question, comme "Comment télécharger une image?"',
|
|
||||||
Validating: 'Validation',
|
Validating: 'Validation',
|
||||||
Registration_Succeeded: 'Inscription réussie!',
|
Registration_Succeeded: 'Inscription réussie!',
|
||||||
Verify: 'Vérifier',
|
Verify: 'Vérifier',
|
||||||
|
@ -565,7 +564,6 @@ export default {
|
||||||
Logged_out_by_server: 'Vous avez été déconnecté par le serveur. Veuillez vous reconnecter.',
|
Logged_out_by_server: 'Vous avez été déconnecté par le serveur. Veuillez vous reconnecter.',
|
||||||
You_need_to_access_at_least_one_RocketChat_server_to_share_something: 'Vous devez accéder à au moins un serveur Rocket.Chat pour partager quelque chose.',
|
You_need_to_access_at_least_one_RocketChat_server_to_share_something: 'Vous devez accéder à au moins un serveur Rocket.Chat pour partager quelque chose.',
|
||||||
Your_certificate: 'Votre Certificat',
|
Your_certificate: 'Votre Certificat',
|
||||||
Your_message: 'Votre message',
|
|
||||||
Your_invite_link_will_expire_after__usesLeft__uses: 'Votre lien d\'invitation expirera après {{usesLeft}} utilisations.',
|
Your_invite_link_will_expire_after__usesLeft__uses: 'Votre lien d\'invitation expirera après {{usesLeft}} utilisations.',
|
||||||
Your_invite_link_will_expire_on__date__or_after__usesLeft__uses: 'Votre lien d\'invitation expirera le {{date}} ou après {{usesLeft}} utilisations.',
|
Your_invite_link_will_expire_on__date__or_after__usesLeft__uses: 'Votre lien d\'invitation expirera le {{date}} ou après {{usesLeft}} utilisations.',
|
||||||
Your_invite_link_will_expire_on__date__: 'Votre lien d\'invitation expirera le {{date}}.',
|
Your_invite_link_will_expire_on__date__: 'Votre lien d\'invitation expirera le {{date}}.',
|
||||||
|
|
|
@ -560,7 +560,6 @@ export default {
|
||||||
Username: 'Username',
|
Username: 'Username',
|
||||||
Username_or_email: 'Username o email',
|
Username_or_email: 'Username o email',
|
||||||
Uses_server_configuration: 'Usa la configurazione del server',
|
Uses_server_configuration: 'Usa la configurazione del server',
|
||||||
Usually_a_discussion_starts_with_a_question_like_How_do_I_upload_a_picture: 'Una discussione inizia solitamente con una domanda, ad esempio: "Come posso caricare una foto?"',
|
|
||||||
Validating: 'Validazione',
|
Validating: 'Validazione',
|
||||||
Registration_Succeeded: 'Registrazione completata!',
|
Registration_Succeeded: 'Registrazione completata!',
|
||||||
Verify: 'Verifica',
|
Verify: 'Verifica',
|
||||||
|
@ -595,7 +594,6 @@ export default {
|
||||||
You_need_to_access_at_least_one_RocketChat_server_to_share_something: 'Devi accedere ad almeno un server Rocket.Chat prima di condividere qualcosa.',
|
You_need_to_access_at_least_one_RocketChat_server_to_share_something: 'Devi accedere ad almeno un server Rocket.Chat prima di condividere qualcosa.',
|
||||||
You_need_to_verifiy_your_email_address_to_get_notications: 'Devi verificare il tuo indirizzo e-mail per ricevere le notifiche',
|
You_need_to_verifiy_your_email_address_to_get_notications: 'Devi verificare il tuo indirizzo e-mail per ricevere le notifiche',
|
||||||
Your_certificate: 'Il tuo certificato',
|
Your_certificate: 'Il tuo certificato',
|
||||||
Your_message: 'Il tuo messaggio',
|
|
||||||
Your_invite_link_will_expire_after__usesLeft__uses: 'Il tuo link di invito scadrà dopo {{usesLeft}} utilizzi.',
|
Your_invite_link_will_expire_after__usesLeft__uses: 'Il tuo link di invito scadrà dopo {{usesLeft}} utilizzi.',
|
||||||
Your_invite_link_will_expire_on__date__or_after__usesLeft__uses: 'Il tuo link di invito scadrà il {{date}} oppure dopo {{usesLeft}} utilizzi.',
|
Your_invite_link_will_expire_on__date__or_after__usesLeft__uses: 'Il tuo link di invito scadrà il {{date}} oppure dopo {{usesLeft}} utilizzi.',
|
||||||
Your_invite_link_will_expire_on__date__: 'Il tuo link di invito scadrà il {{date}}.',
|
Your_invite_link_will_expire_on__date__: 'Il tuo link di invito scadrà il {{date}}.',
|
||||||
|
|
|
@ -522,7 +522,6 @@ export default {
|
||||||
Username: 'Usuário',
|
Username: 'Usuário',
|
||||||
Username_or_email: 'Usuário ou email',
|
Username_or_email: 'Usuário ou email',
|
||||||
Uses_server_configuration: 'Usar configuração do servidor',
|
Uses_server_configuration: 'Usar configuração do servidor',
|
||||||
Usually_a_discussion_starts_with_a_question_like_How_do_I_upload_a_picture: 'Normalmente, uma discussão começa com uma pergunta como: Como faço para enviar uma foto?',
|
|
||||||
Verify: 'Verificar',
|
Verify: 'Verificar',
|
||||||
Verify_email_title: 'Registrado com sucesso!',
|
Verify_email_title: 'Registrado com sucesso!',
|
||||||
Verify_email_desc: 'Nós lhe enviamos um e-mail para confirmar o seu registro. Se você não receber um e-mail em breve, por favor retorne e tente novamente.',
|
Verify_email_desc: 'Nós lhe enviamos um e-mail para confirmar o seu registro. Se você não receber um e-mail em breve, por favor retorne e tente novamente.',
|
||||||
|
@ -541,7 +540,6 @@ export default {
|
||||||
You_are_in_preview_mode: 'Está é uma prévia do canal',
|
You_are_in_preview_mode: 'Está é uma prévia do canal',
|
||||||
You_are_offline: 'Você está offline',
|
You_are_offline: 'Você está offline',
|
||||||
You_can_search_using_RegExp_eg: 'Você pode usar expressões regulares, por exemplo `/^text$/i`',
|
You_can_search_using_RegExp_eg: 'Você pode usar expressões regulares, por exemplo `/^text$/i`',
|
||||||
Your_message: 'Sua mensagem',
|
|
||||||
You_need_to_verifiy_your_email_address_to_get_notications: 'Você precisa confirmar seu endereço de e-mail para obter notificações',
|
You_need_to_verifiy_your_email_address_to_get_notications: 'Você precisa confirmar seu endereço de e-mail para obter notificações',
|
||||||
You_colon: 'Você: ',
|
You_colon: 'Você: ',
|
||||||
you_were_mentioned: 'você foi mencionado',
|
you_were_mentioned: 'você foi mencionado',
|
||||||
|
|
|
@ -563,7 +563,6 @@ export default {
|
||||||
Username: 'Имя пользователя',
|
Username: 'Имя пользователя',
|
||||||
Username_or_email: 'Имя пользователя или email',
|
Username_or_email: 'Имя пользователя или email',
|
||||||
Uses_server_configuration: 'Используется конфигурация сервера',
|
Uses_server_configuration: 'Используется конфигурация сервера',
|
||||||
Usually_a_discussion_starts_with_a_question_like_How_do_I_upload_a_picture: 'Обычно Обсуждение начинается с какого-либо вопроса, например, "Как мне загрузить эту картинку?"',
|
|
||||||
Validating: 'Проверка',
|
Validating: 'Проверка',
|
||||||
Registration_Succeeded: 'Регистрация Успешна!',
|
Registration_Succeeded: 'Регистрация Успешна!',
|
||||||
Verify: 'Проверить',
|
Verify: 'Проверить',
|
||||||
|
@ -598,7 +597,6 @@ export default {
|
||||||
You_need_to_access_at_least_one_RocketChat_server_to_share_something: 'Вам нужно получить доступ как минимум к одному серверу Rocket.Chat, чтобы поделиться чем-то.',
|
You_need_to_access_at_least_one_RocketChat_server_to_share_something: 'Вам нужно получить доступ как минимум к одному серверу Rocket.Chat, чтобы поделиться чем-то.',
|
||||||
You_need_to_verifiy_your_email_address_to_get_notications: 'Вам необходимо проверить ваш email адрес, чтобы получать уведомления',
|
You_need_to_verifiy_your_email_address_to_get_notications: 'Вам необходимо проверить ваш email адрес, чтобы получать уведомления',
|
||||||
Your_certificate: 'Ваш сертификат',
|
Your_certificate: 'Ваш сертификат',
|
||||||
Your_message: 'Ваше сообщение',
|
|
||||||
Your_invite_link_will_expire_after__usesLeft__uses: 'Ваша ссылка-приглашение станет не действительной после {{usesLeft}} ее использований.',
|
Your_invite_link_will_expire_after__usesLeft__uses: 'Ваша ссылка-приглашение станет не действительной после {{usesLeft}} ее использований.',
|
||||||
Your_invite_link_will_expire_on__date__or_after__usesLeft__uses: 'Ваша ссылка-приглашение станет не действительной {{date}} или после {{usesLeft}} ее использований.',
|
Your_invite_link_will_expire_on__date__or_after__usesLeft__uses: 'Ваша ссылка-приглашение станет не действительной {{date}} или после {{usesLeft}} ее использований.',
|
||||||
Your_invite_link_will_expire_on__date__: 'Срок действия вашей ссылки-приглашения будет окончен {{date}}.',
|
Your_invite_link_will_expire_on__date__: 'Срок действия вашей ссылки-приглашения будет окончен {{date}}.',
|
||||||
|
|
|
@ -563,7 +563,6 @@ export default {
|
||||||
Username: '用户名',
|
Username: '用户名',
|
||||||
Username_or_email: '用户名或邮箱',
|
Username_or_email: '用户名或邮箱',
|
||||||
Uses_server_configuration: '使用服务器设置',
|
Uses_server_configuration: '使用服务器设置',
|
||||||
Usually_a_discussion_starts_with_a_question_like_How_do_I_upload_a_picture: '通常, 一个讨论会由一个问题开始, 例如 \\"如何上传图片?\\"',
|
|
||||||
Validating: '正在验证',
|
Validating: '正在验证',
|
||||||
Registration_Succeeded: '注册成功',
|
Registration_Succeeded: '注册成功',
|
||||||
Verify: '验证',
|
Verify: '验证',
|
||||||
|
@ -598,7 +597,6 @@ export default {
|
||||||
You_need_to_access_at_least_one_RocketChat_server_to_share_something: '您需要访问至少一台Rocket.Chat服务器才能共享某些内容。',
|
You_need_to_access_at_least_one_RocketChat_server_to_share_something: '您需要访问至少一台Rocket.Chat服务器才能共享某些内容。',
|
||||||
You_need_to_verifiy_your_email_address_to_get_notications: '您需要先验证您的邮箱以启用通知',
|
You_need_to_verifiy_your_email_address_to_get_notications: '您需要先验证您的邮箱以启用通知',
|
||||||
Your_certificate: '你的证书',
|
Your_certificate: '你的证书',
|
||||||
Your_message: '你的信息',
|
|
||||||
Your_invite_link_will_expire_after__usesLeft__uses: '您的邀请链接将在{{usesLeft}}使用后到期。',
|
Your_invite_link_will_expire_after__usesLeft__uses: '您的邀请链接将在{{usesLeft}}使用后到期。',
|
||||||
Your_invite_link_will_expire_on__date__or_after__usesLeft__uses: '您的邀请链接将于{{date}}或{{usesLeft}}使用后到期。',
|
Your_invite_link_will_expire_on__date__or_after__usesLeft__uses: '您的邀请链接将于{{date}}或{{usesLeft}}使用后到期。',
|
||||||
Your_invite_link_will_expire_on__date__: '您的邀请链接将于{{date}}到期。',
|
Your_invite_link_will_expire_on__date__: '您的邀请链接将于{{date}}到期。',
|
||||||
|
|
|
@ -563,7 +563,6 @@ export default {
|
||||||
Username: '使用者名稱',
|
Username: '使用者名稱',
|
||||||
Username_or_email: '使用者名稱或電子郵件',
|
Username_or_email: '使用者名稱或電子郵件',
|
||||||
Uses_server_configuration: '使用伺服器設定',
|
Uses_server_configuration: '使用伺服器設定',
|
||||||
Usually_a_discussion_starts_with_a_question_like_How_do_I_upload_a_picture: '通常,討論會由一個問題開始,像是 \\\'如何上傳一個圖片?\\\'',
|
|
||||||
Validating: '正在驗證',
|
Validating: '正在驗證',
|
||||||
Registration_Succeeded: '註冊成功',
|
Registration_Succeeded: '註冊成功',
|
||||||
Verify: '驗證',
|
Verify: '驗證',
|
||||||
|
@ -598,7 +597,6 @@ export default {
|
||||||
You_need_to_access_at_least_one_RocketChat_server_to_share_something: '您需要至少連接一個 Rocket.Chat 伺服器才能共享某些内容。',
|
You_need_to_access_at_least_one_RocketChat_server_to_share_something: '您需要至少連接一個 Rocket.Chat 伺服器才能共享某些内容。',
|
||||||
You_need_to_verifiy_your_email_address_to_get_notications: '您需要先驗證您的電子郵件以啟用通知',
|
You_need_to_verifiy_your_email_address_to_get_notications: '您需要先驗證您的電子郵件以啟用通知',
|
||||||
Your_certificate: '你的證書',
|
Your_certificate: '你的證書',
|
||||||
Your_message: '你的訊息',
|
|
||||||
Your_invite_link_will_expire_after__usesLeft__uses: '您的邀請連結將在{{usesLeft}}使用後到期。',
|
Your_invite_link_will_expire_after__usesLeft__uses: '您的邀請連結將在{{usesLeft}}使用後到期。',
|
||||||
Your_invite_link_will_expire_on__date__or_after__usesLeft__uses: '您的邀請連結將於{{date}}或{{usesLeft}}使用後到期。',
|
Your_invite_link_will_expire_on__date__or_after__usesLeft__uses: '您的邀請連結將於{{date}}或{{usesLeft}}使用後到期。',
|
||||||
Your_invite_link_will_expire_on__date__: '您的邀請連結將於{{date}}到期。',
|
Your_invite_link_will_expire_on__date__: '您的邀請連結將於{{date}}到期。',
|
||||||
|
|
|
@ -68,10 +68,6 @@ class Encryption {
|
||||||
return this.readyPromise;
|
return this.readyPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
get hasPrivateKey() {
|
|
||||||
return !!this.privateKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop Encryption client
|
// Stop Encryption client
|
||||||
stop = () => {
|
stop = () => {
|
||||||
this.userId = null;
|
this.userId = null;
|
||||||
|
|
|
@ -596,25 +596,19 @@ const RocketChat = {
|
||||||
readMessages,
|
readMessages,
|
||||||
resendMessage,
|
resendMessage,
|
||||||
|
|
||||||
async search({ text, filterUsers = true, filterRooms = true }) {
|
async localSearch({ text, filterUsers = true, filterRooms = true }) {
|
||||||
const searchText = text.trim();
|
const searchText = text.trim();
|
||||||
|
|
||||||
if (this.oldPromise) {
|
|
||||||
this.oldPromise('cancel');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (searchText === '') {
|
if (searchText === '') {
|
||||||
delete this.oldPromise;
|
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const db = database.active;
|
const db = database.active;
|
||||||
const likeString = sanitizeLikeString(searchText);
|
const likeString = sanitizeLikeString(searchText);
|
||||||
let data = await db.collections.get('subscriptions').query(
|
let data = await db.collections.get('subscriptions').query(
|
||||||
Q.or(
|
Q.or(
|
||||||
Q.where('name', Q.like(`%${ likeString }%`)),
|
Q.where('name', Q.like(`%${ likeString }%`)),
|
||||||
Q.where('fname', Q.like(`%${ likeString }%`))
|
Q.where('fname', Q.like(`%${ likeString }%`))
|
||||||
)
|
),
|
||||||
|
Q.experimentalSortBy('room_updated_at', Q.desc)
|
||||||
).fetch();
|
).fetch();
|
||||||
|
|
||||||
if (filterUsers && !filterRooms) {
|
if (filterUsers && !filterRooms) {
|
||||||
|
@ -627,18 +621,35 @@ const RocketChat = {
|
||||||
|
|
||||||
data = data.map((sub) => {
|
data = data.map((sub) => {
|
||||||
if (sub.t !== 'd') {
|
if (sub.t !== 'd') {
|
||||||
return ({
|
return {
|
||||||
rid: sub.rid,
|
rid: sub.rid,
|
||||||
name: sub.name,
|
name: sub.name,
|
||||||
fname: sub.fname,
|
fname: sub.fname,
|
||||||
avatarETag: sub.avatarETag,
|
avatarETag: sub.avatarETag,
|
||||||
t: sub.t,
|
t: sub.t,
|
||||||
|
encrypted: sub.encrypted,
|
||||||
search: true
|
search: true
|
||||||
});
|
};
|
||||||
}
|
}
|
||||||
return sub;
|
return sub;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
|
||||||
|
async search({ text, filterUsers = true, filterRooms = true }) {
|
||||||
|
const searchText = text.trim();
|
||||||
|
|
||||||
|
if (this.oldPromise) {
|
||||||
|
this.oldPromise('cancel');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (searchText === '') {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = await this.localSearch({ text, filterUsers, filterRooms });
|
||||||
|
|
||||||
const usernames = data.map(sub => sub.name);
|
const usernames = data.map(sub => sub.name);
|
||||||
try {
|
try {
|
||||||
if (data.length < 7) {
|
if (data.length < 7) {
|
||||||
|
@ -697,11 +708,11 @@ const RocketChat = {
|
||||||
},
|
},
|
||||||
|
|
||||||
createDiscussion({
|
createDiscussion({
|
||||||
prid, pmid, t_name, reply, users
|
prid, pmid, t_name, reply, users, encrypted
|
||||||
}) {
|
}) {
|
||||||
// RC 1.0.0
|
// RC 1.0.0
|
||||||
return this.post('rooms.createDiscussion', {
|
return this.post('rooms.createDiscussion', {
|
||||||
prid, pmid, t_name, reply, users
|
prid, pmid, t_name, reply, users, encrypted
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -865,14 +876,7 @@ const RocketChat = {
|
||||||
methodCallWrapper(method, ...params) {
|
methodCallWrapper(method, ...params) {
|
||||||
const { API_Use_REST_For_DDP_Calls } = reduxStore.getState().settings;
|
const { API_Use_REST_For_DDP_Calls } = reduxStore.getState().settings;
|
||||||
if (API_Use_REST_For_DDP_Calls) {
|
if (API_Use_REST_For_DDP_Calls) {
|
||||||
return new Promise(async(resolve, reject) => {
|
return this.post(`method.call/${ method }`, { message: JSON.stringify({ method, params }) });
|
||||||
const data = await this.post(`method.call/${ method }`, { message: JSON.stringify({ method, params }) });
|
|
||||||
const response = JSON.parse(data.message);
|
|
||||||
if (response?.error) {
|
|
||||||
return reject(response.error);
|
|
||||||
}
|
|
||||||
return resolve(response.result);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
return this.methodCall(method, ...params);
|
return this.methodCall(method, ...params);
|
||||||
},
|
},
|
||||||
|
@ -1067,14 +1071,30 @@ const RocketChat = {
|
||||||
},
|
},
|
||||||
post(...args) {
|
post(...args) {
|
||||||
return new Promise(async(resolve, reject) => {
|
return new Promise(async(resolve, reject) => {
|
||||||
|
const isMethodCall = args[0]?.startsWith('method.call/');
|
||||||
try {
|
try {
|
||||||
const result = await this.sdk.post(...args);
|
const result = await this.sdk.post(...args);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* if API_Use_REST_For_DDP_Calls is enabled and it's a method call,
|
||||||
|
* responses have a different object structure
|
||||||
|
*/
|
||||||
|
if (isMethodCall) {
|
||||||
|
const response = JSON.parse(result.message);
|
||||||
|
if (response?.error) {
|
||||||
|
throw response.error;
|
||||||
|
}
|
||||||
|
return resolve(response.result);
|
||||||
|
}
|
||||||
return resolve(result);
|
return resolve(result);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e.data && (e.data.errorType === 'totp-required' || e.data.errorType === 'totp-invalid')) {
|
const errorType = isMethodCall ? e?.error : e?.data?.errorType;
|
||||||
const { details } = e.data;
|
const totpInvalid = 'totp-invalid';
|
||||||
|
const totpRequired = 'totp-required';
|
||||||
|
if ([totpInvalid, totpRequired].includes(errorType)) {
|
||||||
|
const { details } = isMethodCall ? e : e?.data;
|
||||||
try {
|
try {
|
||||||
await twoFactor({ method: details?.method, invalid: e.data.errorType === 'totp-invalid' });
|
await twoFactor({ method: details?.method, invalid: errorType === totpInvalid });
|
||||||
return resolve(this.post(...args));
|
return resolve(this.post(...args));
|
||||||
} catch {
|
} catch {
|
||||||
// twoFactor was canceled
|
// twoFactor was canceled
|
||||||
|
|
|
@ -1,11 +1,18 @@
|
||||||
import { ENCRYPTION } from '../actions/actionsTypes';
|
import { ENCRYPTION } from '../actions/actionsTypes';
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
|
enabled: false,
|
||||||
banner: null
|
banner: null
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function encryption(state = initialState, action) {
|
export default function encryption(state = initialState, action) {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
|
case ENCRYPTION.SET:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
enabled: action.enabled,
|
||||||
|
banner: action.banner
|
||||||
|
};
|
||||||
case ENCRYPTION.SET_BANNER:
|
case ENCRYPTION.SET_BANNER:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
|
|
@ -2,7 +2,7 @@ import EJSON from 'ejson';
|
||||||
import { takeLatest, select, put } from 'redux-saga/effects';
|
import { takeLatest, select, put } from 'redux-saga/effects';
|
||||||
|
|
||||||
import { ENCRYPTION } from '../actions/actionsTypes';
|
import { ENCRYPTION } from '../actions/actionsTypes';
|
||||||
import { encryptionSetBanner } from '../actions/encryption';
|
import { encryptionSet } from '../actions/encryption';
|
||||||
import { Encryption } from '../lib/encryption';
|
import { Encryption } from '../lib/encryption';
|
||||||
import Navigation from '../lib/Navigation';
|
import Navigation from '../lib/Navigation';
|
||||||
import {
|
import {
|
||||||
|
@ -52,14 +52,14 @@ const handleEncryptionInit = function* handleEncryptionInit() {
|
||||||
// A private key was received from the server, but it's not saved locally yet
|
// A private key was received from the server, but it's not saved locally yet
|
||||||
// Show the banner asking for the password
|
// Show the banner asking for the password
|
||||||
if (!storedPrivateKey && keys?.privateKey) {
|
if (!storedPrivateKey && keys?.privateKey) {
|
||||||
yield put(encryptionSetBanner(E2E_BANNER_TYPE.REQUEST_PASSWORD));
|
yield put(encryptionSet(false, E2E_BANNER_TYPE.REQUEST_PASSWORD));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the user has a private key stored, but never entered the password
|
// If the user has a private key stored, but never entered the password
|
||||||
const storedRandomPassword = yield UserPreferences.getStringAsync(`${ server }-${ E2E_RANDOM_PASSWORD_KEY }`);
|
const storedRandomPassword = yield UserPreferences.getStringAsync(`${ server }-${ E2E_RANDOM_PASSWORD_KEY }`);
|
||||||
if (storedRandomPassword) {
|
if (storedRandomPassword) {
|
||||||
yield put(encryptionSetBanner(E2E_BANNER_TYPE.SAVE_PASSWORD));
|
yield put(encryptionSet(true, E2E_BANNER_TYPE.SAVE_PASSWORD));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch stored public e2e key for this server
|
// Fetch stored public e2e key for this server
|
||||||
|
@ -72,10 +72,11 @@ const handleEncryptionInit = function* handleEncryptionInit() {
|
||||||
if (storedPublicKey && storedPrivateKey) {
|
if (storedPublicKey && storedPrivateKey) {
|
||||||
// Persist these keys
|
// Persist these keys
|
||||||
yield Encryption.persistKeys(server, storedPublicKey, storedPrivateKey);
|
yield Encryption.persistKeys(server, storedPublicKey, storedPrivateKey);
|
||||||
|
yield put(encryptionSet(true));
|
||||||
} else {
|
} else {
|
||||||
// Create new keys since the user doesn't have any
|
// Create new keys since the user doesn't have any
|
||||||
yield Encryption.createKeys(user.id, server);
|
yield Encryption.createKeys(user.id, server);
|
||||||
yield put(encryptionSetBanner(E2E_BANNER_TYPE.SAVE_PASSWORD));
|
yield put(encryptionSet(true, E2E_BANNER_TYPE.SAVE_PASSWORD));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decrypt all pending messages/subscriptions
|
// Decrypt all pending messages/subscriptions
|
||||||
|
@ -87,7 +88,7 @@ const handleEncryptionInit = function* handleEncryptionInit() {
|
||||||
|
|
||||||
const handleEncryptionStop = function* handleEncryptionStop() {
|
const handleEncryptionStop = function* handleEncryptionStop() {
|
||||||
// Hide encryption banner
|
// Hide encryption banner
|
||||||
yield put(encryptionSetBanner());
|
yield put(encryptionSet());
|
||||||
// Stop Encryption client
|
// Stop Encryption client
|
||||||
Encryption.stop();
|
Encryption.stop();
|
||||||
};
|
};
|
||||||
|
@ -112,7 +113,7 @@ const handleEncryptionDecodeKey = function* handleEncryptionDecodeKey({ password
|
||||||
Encryption.initialize(user.id);
|
Encryption.initialize(user.id);
|
||||||
|
|
||||||
// Hide encryption banner
|
// Hide encryption banner
|
||||||
yield put(encryptionSetBanner());
|
yield put(encryptionSet(true));
|
||||||
|
|
||||||
Navigation.back();
|
Navigation.back();
|
||||||
} catch {
|
} catch {
|
||||||
|
|
|
@ -112,6 +112,7 @@ export default {
|
||||||
CREATE_DISCUSSION_CREATE_F: 'create_discussion_create_f',
|
CREATE_DISCUSSION_CREATE_F: 'create_discussion_create_f',
|
||||||
CREATE_DISCUSSION_SELECT_CHANNEL: 'create_discussion_select_channel',
|
CREATE_DISCUSSION_SELECT_CHANNEL: 'create_discussion_select_channel',
|
||||||
CREATE_DISCUSSION_SELECT_USERS: 'create_discussion_select_users',
|
CREATE_DISCUSSION_SELECT_USERS: 'create_discussion_select_users',
|
||||||
|
CREATE_DISCUSSION_TOGGLE_ENCRY: 'create_discussion_toggle_encry',
|
||||||
|
|
||||||
// PROFILE VIEW
|
// PROFILE VIEW
|
||||||
PROFILE_PICK_AVATAR: 'profile_pick_avatar',
|
PROFILE_PICK_AVATAR: 'profile_pick_avatar',
|
||||||
|
|
|
@ -85,7 +85,7 @@ class CreateChannelView extends React.Component {
|
||||||
error: PropTypes.object,
|
error: PropTypes.object,
|
||||||
failure: PropTypes.bool,
|
failure: PropTypes.bool,
|
||||||
isFetching: PropTypes.bool,
|
isFetching: PropTypes.bool,
|
||||||
e2eEnabled: PropTypes.bool,
|
encryptionEnabled: PropTypes.bool,
|
||||||
users: PropTypes.array.isRequired,
|
users: PropTypes.array.isRequired,
|
||||||
user: PropTypes.shape({
|
user: PropTypes.shape({
|
||||||
id: PropTypes.string,
|
id: PropTypes.string,
|
||||||
|
@ -107,7 +107,7 @@ class CreateChannelView extends React.Component {
|
||||||
channelName, type, readOnly, broadcast, encrypted
|
channelName, type, readOnly, broadcast, encrypted
|
||||||
} = this.state;
|
} = this.state;
|
||||||
const {
|
const {
|
||||||
users, isFetching, e2eEnabled, theme
|
users, isFetching, encryptionEnabled, theme
|
||||||
} = this.props;
|
} = this.props;
|
||||||
if (nextProps.theme !== theme) {
|
if (nextProps.theme !== theme) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -130,7 +130,7 @@ class CreateChannelView extends React.Component {
|
||||||
if (nextProps.isFetching !== isFetching) {
|
if (nextProps.isFetching !== isFetching) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (nextProps.e2eEnabled !== e2eEnabled) {
|
if (nextProps.encryptionEnabled !== encryptionEnabled) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (!equal(nextProps.users, users)) {
|
if (!equal(nextProps.users, users)) {
|
||||||
|
@ -230,9 +230,9 @@ class CreateChannelView extends React.Component {
|
||||||
|
|
||||||
renderEncrypted() {
|
renderEncrypted() {
|
||||||
const { type, encrypted } = this.state;
|
const { type, encrypted } = this.state;
|
||||||
const { e2eEnabled } = this.props;
|
const { encryptionEnabled } = this.props;
|
||||||
|
|
||||||
if (!e2eEnabled) {
|
if (!encryptionEnabled) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -366,7 +366,7 @@ class CreateChannelView extends React.Component {
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
baseUrl: state.server.server,
|
baseUrl: state.server.server,
|
||||||
isFetching: state.createChannel.isFetching,
|
isFetching: state.createChannel.isFetching,
|
||||||
e2eEnabled: state.settings.E2E_Enable,
|
encryptionEnabled: state.encryption.enabled,
|
||||||
users: state.selectedUsers.users,
|
users: state.selectedUsers.users,
|
||||||
user: getUserSelector(state)
|
user: getUserSelector(state)
|
||||||
});
|
});
|
||||||
|
|
|
@ -18,7 +18,7 @@ const SelectChannel = ({
|
||||||
|
|
||||||
const getChannels = debounce(async(keyword = '') => {
|
const getChannels = debounce(async(keyword = '') => {
|
||||||
try {
|
try {
|
||||||
const res = await RocketChat.search({ text: keyword, filterUsers: false });
|
const res = await RocketChat.localSearch({ text: keyword });
|
||||||
setChannels(res);
|
setChannels(res);
|
||||||
} catch {
|
} catch {
|
||||||
// do nothing
|
// do nothing
|
||||||
|
@ -47,7 +47,7 @@ const SelectChannel = ({
|
||||||
value={initial && [initial]}
|
value={initial && [initial]}
|
||||||
disabled={initial}
|
disabled={initial}
|
||||||
options={channels.map(channel => ({
|
options={channels.map(channel => ({
|
||||||
value: channel.rid,
|
value: channel,
|
||||||
text: { text: RocketChat.getRoomTitle(channel) },
|
text: { text: RocketChat.getRoomTitle(channel) },
|
||||||
imageUrl: getAvatar(channel)
|
imageUrl: getAvatar(channel)
|
||||||
}))}
|
}))}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { ScrollView, Text } from 'react-native';
|
import { ScrollView, Text, Switch } from 'react-native';
|
||||||
import isEqual from 'lodash/isEqual';
|
import isEqual from 'lodash/isEqual';
|
||||||
|
|
||||||
import Loading from '../../containers/Loading';
|
import Loading from '../../containers/Loading';
|
||||||
|
@ -10,7 +10,7 @@ import scrollPersistTaps from '../../utils/scrollPersistTaps';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
import * as HeaderButton from '../../containers/HeaderButton';
|
import * as HeaderButton from '../../containers/HeaderButton';
|
||||||
import StatusBar from '../../containers/StatusBar';
|
import StatusBar from '../../containers/StatusBar';
|
||||||
import { themes } from '../../constants/colors';
|
import { SWITCH_TRACK_COLOR, themes } from '../../constants/colors';
|
||||||
import { withTheme } from '../../theme';
|
import { withTheme } from '../../theme';
|
||||||
import { getUserSelector } from '../../selectors/login';
|
import { getUserSelector } from '../../selectors/login';
|
||||||
import TextInput from '../../containers/TextInput';
|
import TextInput from '../../containers/TextInput';
|
||||||
|
@ -26,6 +26,7 @@ import styles from './styles';
|
||||||
import SafeAreaView from '../../containers/SafeAreaView';
|
import SafeAreaView from '../../containers/SafeAreaView';
|
||||||
import { goRoom } from '../../utils/goRoom';
|
import { goRoom } from '../../utils/goRoom';
|
||||||
import { logEvent, events } from '../../utils/log';
|
import { logEvent, events } from '../../utils/log';
|
||||||
|
import { E2E_ROOM_TYPES } from '../../lib/encryption/constants';
|
||||||
|
|
||||||
class CreateChannelView extends React.Component {
|
class CreateChannelView extends React.Component {
|
||||||
propTypes = {
|
propTypes = {
|
||||||
|
@ -41,7 +42,8 @@ class CreateChannelView extends React.Component {
|
||||||
theme: PropTypes.string,
|
theme: PropTypes.string,
|
||||||
isMasterDetail: PropTypes.bool,
|
isMasterDetail: PropTypes.bool,
|
||||||
blockUnauthenticatedAccess: PropTypes.bool,
|
blockUnauthenticatedAccess: PropTypes.bool,
|
||||||
serverVersion: PropTypes.string
|
serverVersion: PropTypes.string,
|
||||||
|
encryptionEnabled: PropTypes.bool
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
@ -54,7 +56,8 @@ class CreateChannelView extends React.Component {
|
||||||
message,
|
message,
|
||||||
name: message?.msg || '',
|
name: message?.msg || '',
|
||||||
users: [],
|
users: [],
|
||||||
reply: ''
|
reply: '',
|
||||||
|
encrypted: props.encryptionEnabled
|
||||||
};
|
};
|
||||||
this.setHeader();
|
this.setHeader();
|
||||||
}
|
}
|
||||||
|
@ -109,13 +112,13 @@ class CreateChannelView extends React.Component {
|
||||||
|
|
||||||
submit = () => {
|
submit = () => {
|
||||||
const {
|
const {
|
||||||
name: t_name, channel: { prid, rid }, message: { id: pmid }, reply, users
|
name: t_name, channel: { prid, rid }, message: { id: pmid }, reply, users, encrypted
|
||||||
} = this.state;
|
} = this.state;
|
||||||
const { create } = this.props;
|
const { create } = this.props;
|
||||||
|
|
||||||
// create discussion
|
// create discussion
|
||||||
create({
|
create({
|
||||||
prid: prid || rid, pmid, t_name, reply, users
|
prid: prid || rid, pmid, t_name, reply, users, encrypted
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -134,7 +137,7 @@ class CreateChannelView extends React.Component {
|
||||||
|
|
||||||
selectChannel = ({ value }) => {
|
selectChannel = ({ value }) => {
|
||||||
logEvent(events.CREATE_DISCUSSION_SELECT_CHANNEL);
|
logEvent(events.CREATE_DISCUSSION_SELECT_CHANNEL);
|
||||||
this.setState({ channel: { rid: value } });
|
this.setState({ channel: value, encrypted: value?.encrypted });
|
||||||
}
|
}
|
||||||
|
|
||||||
selectUsers = ({ value }) => {
|
selectUsers = ({ value }) => {
|
||||||
|
@ -142,10 +145,17 @@ class CreateChannelView extends React.Component {
|
||||||
this.setState({ users: value });
|
this.setState({ users: value });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onEncryptedChange = (value) => {
|
||||||
|
logEvent(events.CREATE_DISCUSSION_TOGGLE_ENCRY);
|
||||||
|
this.setState({ encrypted: value });
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { name, users } = this.state;
|
|
||||||
const {
|
const {
|
||||||
server, user, loading, blockUnauthenticatedAccess, theme, serverVersion
|
name, users, encrypted, channel
|
||||||
|
} = this.state;
|
||||||
|
const {
|
||||||
|
server, user, loading, blockUnauthenticatedAccess, theme, serverVersion, encryptionEnabled
|
||||||
} = this.props;
|
} = this.props;
|
||||||
return (
|
return (
|
||||||
<KeyboardView
|
<KeyboardView
|
||||||
|
@ -185,15 +195,17 @@ class CreateChannelView extends React.Component {
|
||||||
serverVersion={serverVersion}
|
serverVersion={serverVersion}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
/>
|
/>
|
||||||
<TextInput
|
{encryptionEnabled && E2E_ROOM_TYPES[channel?.t]
|
||||||
multiline
|
? (
|
||||||
textAlignVertical='top'
|
<>
|
||||||
label={I18n.t('Your_message')}
|
<Text style={[styles.label, { color: themes[theme].titleText }]}>{I18n.t('Encrypted')}</Text>
|
||||||
inputStyle={styles.multiline}
|
<Switch
|
||||||
theme={theme}
|
value={encrypted}
|
||||||
placeholder={I18n.t('Usually_a_discussion_starts_with_a_question_like_How_do_I_upload_a_picture')}
|
onValueChange={this.onEncryptedChange}
|
||||||
onChangeText={text => this.setState({ reply: text })}
|
trackColor={SWITCH_TRACK_COLOR}
|
||||||
/>
|
/>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
<Loading visible={loading} />
|
<Loading visible={loading} />
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
|
@ -211,7 +223,8 @@ const mapStateToProps = state => ({
|
||||||
result: state.createDiscussion.result,
|
result: state.createDiscussion.result,
|
||||||
blockUnauthenticatedAccess: state.settings.Accounts_AvatarBlockUnauthenticatedAccess ?? true,
|
blockUnauthenticatedAccess: state.settings.Accounts_AvatarBlockUnauthenticatedAccess ?? true,
|
||||||
serverVersion: state.share.server.version || state.server.version,
|
serverVersion: state.share.server.version || state.server.version,
|
||||||
isMasterDetail: state.app.isMasterDetail
|
isMasterDetail: state.app.isMasterDetail,
|
||||||
|
encryptionEnabled: state.encryption.enabled
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => ({
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
|
|
@ -80,12 +80,18 @@ class E2EEncryptionSecurityView extends React.Component {
|
||||||
title: I18n.t('Are_you_sure_question_mark'),
|
title: I18n.t('Are_you_sure_question_mark'),
|
||||||
message: I18n.t('E2E_encryption_reset_message'),
|
message: I18n.t('E2E_encryption_reset_message'),
|
||||||
confirmationText: I18n.t('E2E_encryption_reset_confirmation'),
|
confirmationText: I18n.t('E2E_encryption_reset_confirmation'),
|
||||||
onPress: () => {
|
onPress: async() => {
|
||||||
logEvent(events.E2E_SEC_RESET_OWN_KEY);
|
logEvent(events.E2E_SEC_RESET_OWN_KEY);
|
||||||
try {
|
try {
|
||||||
RocketChat.e2eResetOwnKey();
|
const res = await RocketChat.e2eResetOwnKey();
|
||||||
const { logout } = this.props;
|
/**
|
||||||
logout();
|
* It might return an empty object when TOTP is enabled,
|
||||||
|
* that's why we're using strict equality to boolean
|
||||||
|
*/
|
||||||
|
if (res === true) {
|
||||||
|
const { logout } = this.props;
|
||||||
|
logout();
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(e);
|
log(e);
|
||||||
showErrorAlert(I18n.t('E2E_encryption_reset_error'));
|
showErrorAlert(I18n.t('E2E_encryption_reset_error'));
|
||||||
|
@ -96,9 +102,8 @@ class E2EEncryptionSecurityView extends React.Component {
|
||||||
|
|
||||||
renderChangePassword = () => {
|
renderChangePassword = () => {
|
||||||
const { newPassword } = this.state;
|
const { newPassword } = this.state;
|
||||||
const { theme } = this.props;
|
const { theme, encryptionEnabled } = this.props;
|
||||||
const { hasPrivateKey } = Encryption;
|
if (!encryptionEnabled) {
|
||||||
if (!hasPrivateKey) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
|
@ -161,7 +166,8 @@ class E2EEncryptionSecurityView extends React.Component {
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
server: state.server.server,
|
server: state.server.server,
|
||||||
user: getUserSelector(state)
|
user: getUserSelector(state),
|
||||||
|
encryptionEnabled: state.encryption.enabled
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => ({
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
@ -179,6 +185,7 @@ E2EEncryptionSecurityView.propTypes = {
|
||||||
id: PropTypes.string
|
id: PropTypes.string
|
||||||
}),
|
}),
|
||||||
server: PropTypes.string,
|
server: PropTypes.string,
|
||||||
|
encryptionEnabled: PropTypes.bool,
|
||||||
logout: PropTypes.func
|
logout: PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ import {
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
import semver from 'semver';
|
||||||
|
|
||||||
import Touch from '../../utils/touch';
|
import Touch from '../../utils/touch';
|
||||||
import { setLoading as setLoadingAction } from '../../actions/selectedUsers';
|
import { setLoading as setLoadingAction } from '../../actions/selectedUsers';
|
||||||
|
@ -46,11 +47,12 @@ class RoomActionsView extends React.Component {
|
||||||
route: PropTypes.object,
|
route: PropTypes.object,
|
||||||
leaveRoom: PropTypes.func,
|
leaveRoom: PropTypes.func,
|
||||||
jitsiEnabled: PropTypes.bool,
|
jitsiEnabled: PropTypes.bool,
|
||||||
e2eEnabled: PropTypes.bool,
|
encryptionEnabled: PropTypes.bool,
|
||||||
setLoadingInvite: PropTypes.func,
|
setLoadingInvite: PropTypes.func,
|
||||||
closeRoom: PropTypes.func,
|
closeRoom: PropTypes.func,
|
||||||
theme: PropTypes.string,
|
theme: PropTypes.string,
|
||||||
fontScale: PropTypes.number
|
fontScale: PropTypes.number,
|
||||||
|
serverVersion: PropTypes.string
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
@ -71,7 +73,8 @@ class RoomActionsView extends React.Component {
|
||||||
canInviteUser: false,
|
canInviteUser: false,
|
||||||
canForwardGuest: false,
|
canForwardGuest: false,
|
||||||
canReturnQueue: false,
|
canReturnQueue: false,
|
||||||
canEdit: false
|
canEdit: false,
|
||||||
|
canToggleEncryption: false
|
||||||
};
|
};
|
||||||
if (room && room.observe && room.rid) {
|
if (room && room.observe && room.rid) {
|
||||||
this.roomObservable = room.observe();
|
this.roomObservable = room.observe();
|
||||||
|
@ -120,6 +123,7 @@ class RoomActionsView extends React.Component {
|
||||||
this.canAddUser();
|
this.canAddUser();
|
||||||
this.canInviteUser();
|
this.canInviteUser();
|
||||||
this.canEdit();
|
this.canEdit();
|
||||||
|
this.canToggleEncryption();
|
||||||
|
|
||||||
// livechat permissions
|
// livechat permissions
|
||||||
if (room.t === 'l') {
|
if (room.t === 'l') {
|
||||||
|
@ -152,7 +156,6 @@ class RoomActionsView extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line react/sort-comp
|
|
||||||
canAddUser = async() => {
|
canAddUser = async() => {
|
||||||
const { room, joined } = this.state;
|
const { room, joined } = this.state;
|
||||||
const { rid, t } = room;
|
const { rid, t } = room;
|
||||||
|
@ -193,6 +196,15 @@ class RoomActionsView extends React.Component {
|
||||||
this.setState({ canEdit });
|
this.setState({ canEdit });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
canToggleEncryption = async() => {
|
||||||
|
const { room } = this.state;
|
||||||
|
const { rid } = room;
|
||||||
|
const permissions = await RocketChat.hasPermission(['toggle-room-e2e-encryption'], rid);
|
||||||
|
|
||||||
|
const canToggleEncryption = permissions && permissions['toggle-room-e2e-encryption'];
|
||||||
|
this.setState({ canToggleEncryption });
|
||||||
|
}
|
||||||
|
|
||||||
canViewMembers = async() => {
|
canViewMembers = async() => {
|
||||||
const { room } = this.state;
|
const { room } = this.state;
|
||||||
const { rid, t, broadcast } = room;
|
const { rid, t, broadcast } = room;
|
||||||
|
@ -235,13 +247,21 @@ class RoomActionsView extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
renderEncryptedSwitch = () => {
|
renderEncryptedSwitch = () => {
|
||||||
const { room } = this.state;
|
const { room, canToggleEncryption, canEdit } = this.state;
|
||||||
const { encrypted } = room;
|
const { encrypted } = room;
|
||||||
|
const { serverVersion } = this.props;
|
||||||
|
let hasPermission = false;
|
||||||
|
if (serverVersion && semver.lt(semver.coerce(serverVersion), '3.11.0')) {
|
||||||
|
hasPermission = canEdit;
|
||||||
|
} else {
|
||||||
|
hasPermission = canToggleEncryption;
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<Switch
|
<Switch
|
||||||
value={encrypted}
|
value={encrypted}
|
||||||
trackColor={SWITCH_TRACK_COLOR}
|
trackColor={SWITCH_TRACK_COLOR}
|
||||||
onValueChange={this.toggleEncrypted}
|
onValueChange={this.toggleEncrypted}
|
||||||
|
disabled={!hasPermission}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -480,15 +500,12 @@ class RoomActionsView extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
renderE2EEncryption = () => {
|
renderE2EEncryption = () => {
|
||||||
const {
|
const { room } = this.state;
|
||||||
room, canEdit
|
const { encryptionEnabled } = this.props;
|
||||||
} = this.state;
|
|
||||||
const { e2eEnabled } = this.props;
|
|
||||||
|
|
||||||
// If can edit this room
|
// If this room type can be encrypted
|
||||||
// If this room type can be Encrypted
|
// If e2e is enabled
|
||||||
// If e2e is enabled for this server
|
if (E2E_ROOM_TYPES[room?.t] && encryptionEnabled) {
|
||||||
if (canEdit && E2E_ROOM_TYPES[room?.t] && e2eEnabled) {
|
|
||||||
return (
|
return (
|
||||||
<List.Section>
|
<List.Section>
|
||||||
<List.Separator />
|
<List.Separator />
|
||||||
|
@ -847,7 +864,8 @@ class RoomActionsView extends React.Component {
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
jitsiEnabled: state.settings.Jitsi_Enabled || false,
|
jitsiEnabled: state.settings.Jitsi_Enabled || false,
|
||||||
e2eEnabled: state.settings.E2E_Enable || false
|
encryptionEnabled: state.encryption.enabled,
|
||||||
|
serverVersion: state.server.version
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => ({
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
|
|
@ -60,7 +60,7 @@ class RoomInfoEditView extends React.Component {
|
||||||
route: PropTypes.object,
|
route: PropTypes.object,
|
||||||
deleteRoom: PropTypes.func,
|
deleteRoom: PropTypes.func,
|
||||||
serverVersion: PropTypes.string,
|
serverVersion: PropTypes.string,
|
||||||
e2eEnabled: PropTypes.bool,
|
encryptionEnabled: PropTypes.bool,
|
||||||
theme: PropTypes.string
|
theme: PropTypes.string
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -414,7 +414,7 @@ class RoomInfoEditView extends React.Component {
|
||||||
const {
|
const {
|
||||||
name, nameError, description, topic, announcement, t, ro, reactWhenReadOnly, room, joinCode, saving, permissions, archived, enableSysMes, encrypted, avatar
|
name, nameError, description, topic, announcement, t, ro, reactWhenReadOnly, room, joinCode, saving, permissions, archived, enableSysMes, encrypted, avatar
|
||||||
} = this.state;
|
} = this.state;
|
||||||
const { serverVersion, e2eEnabled, theme } = this.props;
|
const { serverVersion, encryptionEnabled, theme } = this.props;
|
||||||
const { dangerColor } = themes[theme];
|
const { dangerColor } = themes[theme];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -561,7 +561,7 @@ class RoomInfoEditView extends React.Component {
|
||||||
{this.renderSystemMessages()}
|
{this.renderSystemMessages()}
|
||||||
</SwitchContainer>
|
</SwitchContainer>
|
||||||
) : null}
|
) : null}
|
||||||
{e2eEnabled ? (
|
{encryptionEnabled ? (
|
||||||
<SwitchContainer
|
<SwitchContainer
|
||||||
value={encrypted}
|
value={encrypted}
|
||||||
disabled={!t}
|
disabled={!t}
|
||||||
|
@ -665,7 +665,7 @@ class RoomInfoEditView extends React.Component {
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
serverVersion: state.share.server.version || state.server.version,
|
serverVersion: state.share.server.version || state.server.version,
|
||||||
e2eEnabled: state.settings.E2E_Enable || false
|
encryptionEnabled: state.encryption.enabled
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => ({
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
|
Loading…
Reference in New Issue