diff --git a/.circleci/config.yml b/.circleci/config.yml
index 9b681e2b6..ac5e942a3 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -359,7 +359,7 @@ jobs:
- run:
name: Test
command: |
- yarn test
+ yarn test -w 8
- run:
name: Codecov
diff --git a/__tests__/Storyshots.test.js b/__tests__/Storyshots.test.js
index 60f449144..1315f9bb5 100644
--- a/__tests__/Storyshots.test.js
+++ b/__tests__/Storyshots.test.js
@@ -1,4 +1,5 @@
-import initStoryshots from '@storybook/addon-storyshots';
+import initStoryshots, { Stories2SnapsConverter } from '@storybook/addon-storyshots';
+import { render } from '@testing-library/react-native';
jest.mock('rn-fetch-blob', () => ({
fs: {
@@ -19,4 +20,15 @@ jest.mock('react-native-file-viewer', () => ({
jest.mock('../app/lib/database', () => jest.fn(() => null));
global.Date.now = jest.fn(() => new Date('2019-10-10').getTime());
-initStoryshots();
+const converter = new Stories2SnapsConverter();
+
+initStoryshots({
+ test: ({ story, context }) => {
+ const snapshotFilename = converter.getSnapshotFileName(context);
+ const storyElement = story.render();
+ const { update, toJSON } = render(storyElement);
+ update(storyElement);
+ const json = toJSON();
+ expect(JSON.stringify(json)).toMatchSpecificSnapshot(snapshotFilename);
+ }
+});
diff --git a/__tests__/__snapshots__/Storyshots.test.js.snap b/__tests__/__snapshots__/Storyshots.test.js.snap
deleted file mode 100644
index f85a69ddf..000000000
--- a/__tests__/__snapshots__/Storyshots.test.js.snap
+++ /dev/null
@@ -1,83616 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Storyshots Avatar Avatar by path 1`] = `
-
-
-
-
-
-`;
-
-exports[`Storyshots Avatar Avatar by roomId 1`] = `
-
-
-
-
-
-`;
-
-exports[`Storyshots Avatar Avatar by text 1`] = `
-
-
-
-
-
-`;
-
-exports[`Storyshots Avatar Avatar by url 1`] = `
-
-
-
-
-
-`;
-
-exports[`Storyshots Avatar Channel 1`] = `
-
-
-
-
-
-`;
-
-exports[`Storyshots Avatar Children 1`] = `
-
-
-
-
-
-
-
-
-`;
-
-exports[`Storyshots Avatar Custom borderRadius 1`] = `
-
-
-
-
-
-`;
-
-exports[`Storyshots Avatar Custom style 1`] = `
-
-
-
-
-
-`;
-
-exports[`Storyshots Avatar Direct 1`] = `
-
-
-
-
-
-`;
-
-exports[`Storyshots Avatar Emoji 1`] = `
-
-
-
-
-
-`;
-
-exports[`Storyshots Avatar Static 1`] = `
-
-
-
-
-
-`;
-
-exports[`Storyshots Avatar Touchable 1`] = `
-
-
-
-
-
-
-
-`;
-
-exports[`Storyshots Avatar With ETag 1`] = `
-
-
-
-
-
-`;
-
-exports[`Storyshots Avatar Without ETag 1`] = `
-
-
-
-
-
-`;
-
-exports[`Storyshots Avatar Wrong server 1`] = `
-
-
-
-
-
-`;
-
-exports[`Storyshots BackgroundContainer basic 1`] = `
-
-
-
-
-
-`;
-
-exports[`Storyshots BackgroundContainer black theme - loading 1`] = `
-
-
-
-
-
-
-`;
-
-exports[`Storyshots BackgroundContainer black theme - text 1`] = `
-
-
-
-
-
- Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries
-
-
-`;
-
-exports[`Storyshots BackgroundContainer dark theme - loading 1`] = `
-
-
-
-
-
-
-`;
-
-exports[`Storyshots BackgroundContainer dark theme - text 1`] = `
-
-
-
-
-
- Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries
-
-
-`;
-
-exports[`Storyshots BackgroundContainer loading 1`] = `
-
-
-
-
-
-
-`;
-
-exports[`Storyshots BackgroundContainer long text 1`] = `
-
-
-
-
-
- Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries
-
-
-`;
-
-exports[`Storyshots BackgroundContainer text 1`] = `
-
-
-
-
-
- Text here
-
-
-`;
-
-exports[`Storyshots CannedResponseItem Itens 1`] = `
-Array [
-
-
-
-
- !
- !FAQ4
-
-
- Private
-
-
-
-
- Use
-
-
-
-
- “
- ZCVXZVXCZVZXVZXCVZXCVXZCVZX
- ”
-
-
- ,
-
-
-
-
- !
- test4mobilePrivate
-
-
- Private
-
-
-
-
- Use
-
-
-
-
- “
- test for mobile private
- ”
-
-
-
-
- HQ
-
-
-
-
- Closed
-
-
-
-
- HQ
-
-
-
-
- Problem in Product Y
-
-
-
-
- HQ
-
-
-
-
- Closed
-
-
-
-
- Problem in Product Y
-
-
-
- ,
-]
-`;
-
-exports[`Storyshots Header Buttons badge 1`] = `
-
-
-
-
-
-
-
-
-
- 1
-
-
-
-
-
-
-
-
-
- 1
-
-
-
-
-
-
-
-
-
- 1
-
-
-
-
-
-
-
-`;
-
-exports[`Storyshots Header Buttons common 1`] = `
-Array [
-
-
-
-
-
-
-
-
-
-
-
- ,
-
-
-
-
-
-
-
-
-
-
-
- ,
-
-
-
-
-
- Cancel
-
-
-
-
-
- ,
-
-
-
-
-
-
-
-
-
-
-
- ,
-
-
-
-
-
-
-
-
-
-
-
- ,
-
-
-
-
-
-
-
-
-
-
-
- ,
-
-
-
-
-
-
-
-
-
-
-
- ,
-]
-`;
-
-exports[`Storyshots Header Buttons icons 1`] = `
-Array [
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ,
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ,
-]
-`;
-
-exports[`Storyshots Header Buttons themes 1`] = `
-Array [
-
-
-
-
-
-
-
-
-
-
-
-
-
- Threads
-
-
-
-
-
-
-
-
- 1
-
-
-
-
-
- ,
-
-
-
-
-
-
-
-
-
-
-
-
-
- Threads
-
-
-
-
-
-
-
-
- 1
-
-
-
-
-
- ,
-
-
-
-
-
-
-
-
-
-
-
-
-
- Threads
-
-
-
-
-
-
-
-
- 1
-
-
-
-
-
- ,
-]
-`;
-
-exports[`Storyshots Header Buttons title 1`] = `
-Array [
-
-
-
-
-
- threads
-
-
-
-
-
-
-
- threads
-
-
-
-
- ,
-
-
-
-
-
- threads
-
-
-
-
- search
-
-
-
-
-
-
-
- threads
-
-
-
-
- search
-
-
-
-
- ,
-]
-`;
-
-exports[`Storyshots List alert 1`] = `
-
-
-
-
-
-
-
-
- Chats
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Chats
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`Storyshots List header 1`] = `
-
-
-
-
- Chats
-
-
-
-
- Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries
-
-
-
-
-`;
-
-exports[`Storyshots List icon 1`] = `
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`Storyshots List pressable 1`] = `
-
-
-
-
-
-
-
- Press me
-
-
-
-
-
-
-
-
-
- I'm disabled
-
-
-
-
-
-
-
-`;
-
-exports[`Storyshots List separator 1`] = `
-
-
-
-
-
-`;
-
-exports[`Storyshots List title and subtitle 1`] = `
-
-
-
-
-
-
-
-
- Chats
-
-
-
-
-
-
-
-
-
-
-
- Chats
-
-
-
- All
-
-
-
-
-
-
-
-
-
-
- Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries
-
-
-
- Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries
-
-
-
-
-
-
-
-`;
-
-exports[`Storyshots List with FlatList 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
- 0
-
-
-
-
-
-
-
-
-
-
-
-
-
- 1
-
-
-
-
-
-
-
-
-
-
-
-
-
- 2
-
-
-
-
-
-
-
-
-
-
-
-
-
- 3
-
-
-
-
-
-
-
-
-
-
-
-
-
- 4
-
-
-
-
-
-
-
-
-
-
-
-
-
- 5
-
-
-
-
-
-
-
-
-
-
-
-
-
- 6
-
-
-
-
-
-
-
-
-
-
-
-
-
- 7
-
-
-
-
-
-
-
-
-
-
-
-
-
- 8
-
-
-
-
-
-
-
-
-
-
-
-
-
- 9
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`Storyshots List with bigger font 1`] = `
-
-
-
-
-
-
- Chats
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Chats
-
-
-
- All
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Chats
-
-
-
- All
-
-
-
-
-
-
-
-
-
-
-
-
-
- Chats
-
-
-
-
-
-
- Chats
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Chats
-
-
-
- All
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Chats
-
-
-
- All
-
-
-
-
-
-
-
-
-
-
-
-
-
- Chats
-
-
-
-
-
-
-`;
-
-exports[`Storyshots List with black theme 1`] = `
-
-
-
-
-
-
- Chats
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Chats
-
-
-
- All
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Chats
-
-
-
- All
-
-
-
-
-
-
-
-
-
-
-
-
-
- Chats
-
-
-
-
-
-
- Chats
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Chats
-
-
-
- All
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Chats
-
-
-
- All
-
-
-
-
-
-
-
-
-
-
-
-
-
- Chats
-
-
-
-
-
-
-`;
-
-exports[`Storyshots List with custom colors 1`] = `
-
-
-
-
-
-
-
-
- Chats
-
-
-
-
-
-
-
-
-
-
- Press me!
-
-
-
-
-
-
-
-`;
-
-exports[`Storyshots List with dark theme 1`] = `
-
-
-
-
-
-
- Chats
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Chats
-
-
-
- All
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Chats
-
-
-
- All
-
-
-
-
-
-
-
-
-
-
-
-
-
- Chats
-
-
-
-
-
-
- Chats
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Chats
-
-
-
- All
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Chats
-
-
-
- All
-
-
-
-
-
-
-
-
-
-
-
-
-
- Chats
-
-
-
-
-
-
-`;
-
-exports[`Storyshots List with icon 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Icon Left
-
-
-
-
-
-
-
-
-
-
-
- Icon Right
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries
-
-
-
- Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Show Action Indicator
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`Storyshots List with section and info 1`] = `
-
-
-
-
-
-
-
-
-
-
- Section Item
-
-
-
-
-
-
-
-
-
-
-
- Section Item
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Section Item
-
-
-
-
-
-
-
-
-
-
-
- Section Item
-
-
-
-
-
-
-
-
-
-
- Chats
-
-
-
-
-
-
-
-
- Section Item
-
-
-
-
-
-
-
-
-
-
-
- Section Item
-
-
-
-
-
-
-
-
- Chats
-
-
-
-
-
-
- Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries
-
-
-
-
-
-
-
-
- Section Item
-
-
-
-
-
-
-
-
-
-
-
- Section Item
-
-
-
-
-
-
-
-
- Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries
-
-
-
-
-
-
-`;
-
-exports[`Storyshots List with small font 1`] = `
-
-
-
-
-
-
- Chats
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Chats
-
-
-
- All
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Chats
-
-
-
- All
-
-
-
-
-
-
-
-
-
-
-
-
-
- Chats
-
-
-
-
-
-
- Chats
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Chats
-
-
-
- All
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Chats
-
-
-
- All
-
-
-
-
-
-
-
-
-
-
-
-
-
- Chats
-
-
-
-
-
-
-`;
-
-exports[`Storyshots LoadMore basic 1`] = `
-Array [
-
- Load More
- ,
-
- Load More
- ,
-
- Load Older
- ,
-
- Load Newer
- ,
-]
-`;
-
-exports[`Storyshots LoadMore black theme 1`] = `
-
-
-
- Load Older
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10:00 AM
-
-
-
-
-
- Hey!
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Older message
-
-
-
-
-
-
-
-
-
- Load Newer
-
-
- Load More
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10:00 AM
-
-
-
-
-
- Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- This is the third message
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- This is the second message
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10:00 AM
-
-
-
-
-
- This is the first message
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`Storyshots LoadMore dark theme 1`] = `
-
-
-
- Load Older
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10:00 AM
-
-
-
-
-
- Hey!
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Older message
-
-
-
-
-
-
-
-
-
- Load Newer
-
-
- Load More
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10:00 AM
-
-
-
-
-
- Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- This is the third message
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- This is the second message
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10:00 AM
-
-
-
-
-
- This is the first message
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`Storyshots LoadMore light theme 1`] = `
-
-
-
- Load Older
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10:00 AM
-
-
-
-
-
- Hey!
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Older message
-
-
-
-
-
-
-
-
-
- Load Newer
-
-
- Load More
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10:00 AM
-
-
-
-
-
- Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- This is the third message
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- This is the second message
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10:00 AM
-
-
-
-
-
- This is the first message
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`Storyshots Markdown Block quote 1`] = `
-
-
-
-
-
-
- This is block quote
-
-
-
-
-
-
- this is a normal line
-
-
-
-`;
-
-exports[`Storyshots Markdown Code 1`] = `
-
-
-
- This is
-
-
- inline code
-
-
-
-
- Inline
-
-
- code
-
-
- has
-
-
- back-ticks around
-
-
- it.
-
-
-
- Code block
-
-
-
-`;
-
-exports[`Storyshots Markdown Edited 1`] = `
-
-
-
- This is edited
-
-
- (
- edited
- )
-
-
-
-`;
-
-exports[`Storyshots Markdown Emoji 1`] = `
-
-
-
- Unicode: 😃😇👍
-
-
-
-
- Shortnames:
-
-
- 😂
-
-
- 👍
-
-
-
-
- Custom emojis:
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 😃
-
-
- 👍
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`Storyshots Markdown Hashtag 1`] = `
-
-
-
- #test-channel
-
-
-
-
-
- #unknown
-
-
-
-`;
-
-exports[`Storyshots Markdown Headers 1`] = `
-
-
-
- Header 1
-
-
-
-
- Header 2
-
-
-
-
- Header 3
-
-
-
-
- Header 4
-
-
-
-
- Header 5
-
-
-
-
- Header 6
-
-
-
-`;
-
-exports[`Storyshots Markdown Image 1`] = `
-
-
-
-
-
-`;
-
-exports[`Storyshots Markdown Links 1`] = `
-
-
-
-
- Markdown link
-
-
-
- :
-
-
- [description](url)
-
-
-
-
-
- Formatted Link
-
-
-
- :
-
-
- <url|description>
-
-
-
-`;
-
-exports[`Storyshots Markdown Lists 1`] = `
-
-
-
-
- •
-
-
-
-
-
- Open Source
-
-
-
-
-
-
-
- •
-
-
-
-
-
- Rocket.Chat
-
-
-
-
-
- ◦
-
-
-
-
-
- nodejs
-
-
-
-
-
-
-
- ◦
-
-
-
-
-
- ReactNative
-
-
-
-
-
-
-
-
-
- 1.
-
-
-
-
-
- Open Source
-
-
-
-
-
-
-
- 2.
-
-
-
-
-
- Rocket.Chat
-
-
-
-
-
-`;
-
-exports[`Storyshots Markdown Mentions 1`] = `
-
-
-
-
- rocket.cat
-
-
-
-
-
- name1
-
-
-
-
-
- all
-
-
-
-
-
- here
-
-
-
-
-
- @unknown
-
-
-
-
- Rocket Cat
-
-
-
-
-
- Name
-
-
-
-
-
- all
-
-
-
-
-
- here
-
-
-
-
-
- @unknown
-
-
-
-
-`;
-
-exports[`Storyshots Markdown Preview 1`] = `
-
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
-
-
- a b c d e
-
-
- a b c
-
-
- @rocket.cat @name1 @all @here @unknown #general #unknown
-
-
- Testing: 😃 👍 :marioparty:
-
-
- Fallback from new md to old
-
-
-`;
-
-exports[`Storyshots Markdown Table 1`] = `
-
-
-
-
-
-
-
-
-
- First Header
-
-
-
-
-
-
- Second Header
-
-
-
-
-
-
-
-
- Content from cell 1
-
-
-
-
-
-
- Content from cell 2
-
-
-
-
-
-
-
-
- Content in the first column
-
-
-
-
-
-
- Content in the second column
-
-
-
-
-
-
-
-
- Click to see full table
-
-
-
-`;
-
-exports[`Storyshots Markdown Text 1`] = `
-
-
-
- This is Rocket.Chat
-
-
-
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
-
-
-
-
- a
-
-
-
-
-
-
- b
-
-
-
-
-
-
- c
-
-
-
-
-
-
-
-
-
-
- d
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- e
-
-
-
-
- a b c
-
-
-
-
- Strong emphasis, aka bold, with
-
-
- asterisks
-
-
- or
-
-
- underscores
-
-
-
-`;
-
-exports[`Storyshots Message Archived 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10:00 AM
-
-
-
-
-
- This message is inside an archived room
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`Storyshots Message Basic 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10:00 AM
-
-
-
-
-
- Message
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10:00 AM
-
-
-
-
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`Storyshots Message Block Quote 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10:00 AM
-
-
-
-
-
-
-
-
- Testing block quote
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10:00 AM
-
-
-
-
-
-
-
-
- Testing block quote
-
-
-
-
-
-
- Testing block quote
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`Storyshots Message Broadcast 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10:00 AM
-
-
-
-
-
- Broadcasted message
-
-
-
-
-
-
-
-
-
- Reply
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`Storyshots Message Colored attachments 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10:00 AM
-
-
-
-
-
-
-
-
-
- Field 1
-
-
-
- Value 1
-
-
-
-
-
- Field 2
-
-
-
- Value 2
-
-
-
-
-
-
-
-
-
-
-
-
- Field 1
-
-
-
- Value 1
-
-
-
-
-
- Field 2
-
-
-
- Value 2
-
-
-
-
-
-
-
-
-
-
-
-
- Field 1
-
-
-
- Value 1
-
-
-
-
-
- Field 2
-
-
-
- Value 2
-
-
-
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`Storyshots Message Custom fields 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10:00 AM
-
-
-
-
-
- Message
-
-
-
-
-
-
-
- rocket.cat
-
-
-
-
- Custom fields
-
-
-
-
-
- Field 1
-
-
-
- Value 1
-
-
-
-
-
- Field 2
-
-
-
- Value 2
-
-
-
-
-
- Field 3
-
-
-
- Value 3
-
-
-
-
-
- Field 4
-
-
-
- Value 4
-
-
-
-
-
- Field 5
-
-
-
- Value 5
-
-
-
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`Storyshots Message Custom style 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10:00 AM
-
-
-
-
-
- Message
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`Storyshots Message Date and Unread separators 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- rocket.cat
-
-
-
- 10:00 AM
-
-
-
-
-
- Fourth message
-
-
-
-
-
-
-
-
-
-
- unread
-
-
-
- November 10, 2017
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10:00 AM
-
-
-
-
-
- Third message
-
-
-
-
-
-
-
-
-
-
- unread
-
-
-
-
-
-
-
-
-
-
-
- Second message
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- rocket.cat
-
-
-
- 10:00 AM
-
-
-
-
-
- Second message
-
-
-
-
-
-
-
-
-
-
-
- November 10, 2017
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10:00 AM
-
-
-
-
-
- First message
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`Storyshots Message Discussion 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10:00 AM
-
-
-
- Started a discussion:
-
-
- This is a discussion
-
-
-
-
-
-
-
- No messages yet
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10:00 AM
-
-
-
- Started a discussion:
-
-
- This is a discussion
-
-
-
-
-
-
-
- 1 message
-
-
-
- November 10, 2017
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10:00 AM
-
-
-
- Started a discussion:
-
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
-
-
-
-
-
-
-
- 10 messages
-
-
-
- November 10, 2017
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10:00 AM
-
-
-
- Started a discussion:
-
-
- This is a discussion
-
-
-
-
-
-
-
- +999 messages
-
-
-
- November 10, 2017
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`Storyshots Message Edited 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10:00 AM
-
-
-
-
-
- Message
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`Storyshots Message Editing 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10:00 AM
-
-
-
-
-
- Message being edited
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`Storyshots Message Emojis 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10:00 AM
-
-
-
-
-
- 👊🤙👏
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10:00 AM
-
-
-
-
-
- 👏
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10:00 AM
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10:00 AM
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10:00 AM
-
-
-
-
-
- 🤙
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10:00 AM
-
-
-
-
-
- 🤙
-
-
-
-
-
- 🤙🤙
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`Storyshots Message Encrypted 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10:00 AM
-
-
-
-
-
-
-
- Message
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Message Encrypted without Header
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10:00 AM
-
-
-
-
-
-
-
- Message Encrypted with Reactions
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 😂
-
-
- 1
-
-
-
-
-
-
-
-
-
- 1
-
-
-
-
-
-
- 🤔
-
-
- 1
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Thread with emoji🙂 😂
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Thread reply encrypted
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10:00 AM
-
-
-
-
-
-
-
- Temp message encrypted
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10:00 AM
-
-
-
-
-
-
-
- Message Edited encrypted
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10:00 AM
-
-
-
-
-
-
-
-
-
-
-
-
- This message has error and is encrypted
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10:00 AM
-
-
-
-
-
-
-
- Read Receipt encrypted with Header
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Read Receipt encrypted without Header
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`Storyshots Message Error 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10:00 AM
-
-
-
-
-
-
-
-
-
-
- This message has error
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10:00 AM
-
-
-
-
-
-
-
-
-
-
- This message has error too
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`Storyshots Message Full name 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Diego Mello
-
-
-
- 10:00 AM
-
-
-
-
-
- Message
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`Storyshots Message Grouped messages 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10:00 AM
-
-
-
-
-
- ...
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
-
-
-
- 10:00 AM
-
-
-
-
-
- Different user
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- This is the third message
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- This is the second message
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10:00 AM
-
-
-
-
-
- This is the first message
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`Storyshots Message Ignored 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Message ignored. Tap to display it.
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`Storyshots Message Lists 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10:00 AM
-
-
-
-
-
-
- •
-
-
-
-
-
- Dogs
-
-
-
-
-
- ◦
-
-
-
-
-
- cats
-
-
-
-
-
-
-
- ◦
-
-
-
-
-
- cats
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10:00 AM
-
-
-
-
-
-
- 1.
-
-
-
-
-
- Dogs
-
-
-
-
-
-
-
- 2.
-
-
-
-
-
- Cats
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10:00 AM
-
-
-
-
-
-
- 1.
-
-
-
-
-
- Dogs
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 2.
-
-
-
-
-
- Cats
-
-
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`Storyshots Message Mentions 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10:00 AM
-
-
-
-
-
- rocket.cat
-
-
-
-
-
- diego.mello
-
-
-
-
-
- all
-
-
-
-
-
- here
-
-
-
-
-
- #general
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10:00 AM
-
-
-
-
-
- rocket.cat
-
-
- Lorem ipsum dolor
-
-
- diego.mello
-
-
- sit amet,
-
-
- all
-
-
- consectetur adipiscing
-
-
- here
-
-
- elit, sed do eiusmod tempor
-
-
- #general
-
-
- incididunt ut labore et dolore magna aliqua.
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`Storyshots Message Message with read receipt 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10:00 AM
-
-
-
-
-
- I'm fine!
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- I'm fine!
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10:00 AM
-
-
-
-
-
- I'm fine!
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- I'm fine!
-
-
-
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`Storyshots Message Message with reply 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10:00 AM
-
-
-
-
-
- I'm fine!
-
-
-
-
-
-
-
- I'm a very long long title and I'll break
-
-
-
-
- How are you?
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10:00 AM
-
-
-
-
-
- I'm fine!
-
-
-
-
-
-
-
- rocket.cat
-
-
-
-
- How are you?
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`Storyshots Message Message with thread 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10:00 AM
-
-
-
-
-
- How are you?
-
-
-
-
-
-
- Reply
-
-
-
-
-
-
-
-
-
- 1
-
-
-
-
-
-
-
- 0
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- How are you?
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- I'm fine!
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Thread with emoji🙂 😂
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- I'm fine!
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- I'm fine!
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- How are you?
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Thread with attachment
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Sent an attachment
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`Storyshots Message Reactions 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10:00 AM
-
-
-
-
-
- Reactions
-
-
-
-
-
-
-
- 😂
-
-
- 1
-
-
-
-
-
-
-
-
-
- 99
-
-
-
-
-
-
- 🤔
-
-
- 999
-
-
-
-
-
-
- 🤔
-
-
- 9999
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10:00 AM
-
-
-
-
-
- Multiple Reactions
-
-
-
-
-
-
-
-
-
-
- 1
-
-
-
-
-
-
-
-
-
- 1
-
-
-
-
-
-
-
-
-
- 1
-
-
-
-
-
-
- ❤️
-
-
- 1
-
-
-
-
-
-
- 🐶
-
-
- 1
-
-
-
-
-
-
- 😀
-
-
- 1
-
-
-
-
-
-
- 😬
-
-
- 1
-
-
-
-
-
-
- 😁
-
-
- 1
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`Storyshots Message Sequential thread messages following thread button 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10:00 AM
-
-
-
-
-
- How are you?
-
-
-
-
-
-
- Reply
-
-
-
-
-
-
-
-
-
- 1
-
-
-
-
-
-
-
- 0
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- I'm fine!
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Sent an attachment
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`Storyshots Message Sequential thread messages following thread reply 1`] = `
-
-
-
-
-
-
-
-
-
-
- How are you?
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- I'm fine!
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Cool!
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Sent an attachment
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`Storyshots Message Show a button as attachment 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10:00 AM
-
-
-
-
- Test Button
-
-
-
- Text button
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`Storyshots Message Static avatar 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10:00 AM
-
-
-
-
-
- Message
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`Storyshots Message System messages 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- Message removed
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- has joined the channel
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- Message pinned
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- has left the channel
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- User rocket.cat removed by diego.mello
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- User rocket.cat added by diego.mello
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- User rocket.cat muted by diego.mello
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- User rocket.cat unmuted by diego.mello
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- rocket.cat was set admin by diego.mello
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- rocket.cat is no longer admin by diego.mello
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Room name changed to: New name by diego.mello
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Room description changed to: new description by diego.mello
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Room announcement changed to: new announcement by diego.mello
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Room topic changed to: new topic by diego.mello
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Room type changed to: public by diego.mello
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- This room's encryption has been disabled by diego.mello
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- This room's encryption has been enabled by diego.mello
-
-
-
-
-
-
-
-
-`;
-
-exports[`Storyshots Message Temp 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10:00 AM
-
-
-
-
-
- Temp message
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`Storyshots Message Thumbnail from server 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10:00 AM
-
-
-
-
-
- this is a thumbnail
-
-
-
-
-
-
-
- Title
-
-
-
-
-
-
-
- Image text
-
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`Storyshots Message Time format 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10 November 2017
-
-
-
-
-
- Testing
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`Storyshots Message Two short custom fields with markdown 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10:00 AM
-
-
-
-
-
- Message
-
-
-
-
-
-
-
- rocket.cat
-
-
-
-
- Custom fields
-
-
-
-
-
- Field 1
-
-
-
- Value 1
-
-
-
-
-
- Field 2
-
-
-
-
- Value 2
-
-
-
-
-
-
-
-
-
-
-
- rocket.cat
-
-
-
-
- Custom fields 2
-
-
-
-
-
- Field 1
-
-
-
- Value 1
-
-
-
-
-
- Field 2
-
-
-
- Value 2
-
-
-
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`Storyshots Message URL 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10:00 AM
-
-
-
-
-
-
-
-
-
- Rocket.Chat - Free, Open Source, Enterprise Team Chat
-
-
- Rocket.Chat is the leading open source team chat software solution. Free, unlimited and completely customizable with on-premises and SaaS cloud hosting.
-
-
-
-
-
-
- Google
-
-
- Search the world's information, including webpages, images, videos and more. Google has many special features to help you find exactly what you're looking for.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10:00 AM
-
-
-
-
-
- Message
-
-
-
-
-
-
-
-
-
- Google
-
-
- Search the world's information, including webpages, images, videos and more. Google has many special features to help you find exactly what you're looking for.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Google
-
-
- Search the world's information, including webpages, images, videos and more. Google has many special features to help you find exactly what you're looking for.
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`Storyshots Message With alias 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Diego Mello
-
- @
- diego.mello
-
-
-
-
- 10:00 AM
-
-
-
-
-
- Message
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Diego Mello
-
- @
- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
-
-
-
-
- 10:00 AM
-
-
-
-
-
- Message
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`Storyshots Message With audio 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10:00 AM
-
-
-
-
-
-
-
-
-
- 00:00
-
-
-
-
- This is a description
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- First message
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 00:00
-
-
-
-
- This is a description
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 00:00
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 00:00
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`Storyshots Message With file 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10:00 AM
-
-
-
-
-
-
-
-
- File.pdf
-
-
-
-
-
-
- This is a description
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- File.pdf
-
-
-
-
-
-
- This is a description
-
-
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`Storyshots Message With image 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10:00 AM
-
-
-
-
-
-
-
-
-
-
-
-
- This is a description
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10:00 AM
-
-
-
-
-
-
-
-
-
-
-
-
- This is a description
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`Storyshots Message With video 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10:00 AM
-
-
-
-
-
-
-
-
-
-
- This is a description
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10:00 AM
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`Storyshots Message Without header 1`] = `
-
-
-
-
-
-
-
-
-
-
- Message
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`Storyshots NewMarkdown Block quote 1`] = `
-
-
-
-
-
-
-
- Rocket.Chat to the moon
-
-
-
-
-
-
-
-
-
-
-
- Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries
-
-
-
-
-
-
-`;
-
-exports[`Storyshots NewMarkdown Code 1`] = `
-
-
-
-
-
- inline code
-
-
-
-
-
-
- Multi
-Line
-Code
-
-
-
-`;
-
-exports[`Storyshots NewMarkdown Emoji 1`] = `
-
-
-
- 💚
-
-
- 😂
-
-
- 😁
-
-
-
-
-
- 🚀
-
-
- 🤦
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`Storyshots NewMarkdown Hashtag 1`] = `
-
-
-
-
- #text_channel
-
-
- and
-
-
- #not_a_channel
-
-
-
-
-`;
-
-exports[`Storyshots NewMarkdown Headers 1`] = `
-
-
- # Header 1
-
-
- ## Header 2
-
-
- ### Header 3
-
-
- #### Header 4
-
-
- ##### Header 5
-
-
- ###### Header 6
-
-
-`;
-
-exports[`Storyshots NewMarkdown Links 1`] = `
-
-
-
-
- https://rocket.chat
-
-
-
-
-
-
- Markdown link
-
-
-
-
-`;
-
-exports[`Storyshots NewMarkdown Lists 1`] = `
-
-
-
-
- -
-
-
-
- Plain text
-
-
- 💡
-
-
-
- italic
-
-
-
-
- bold
-
-
-
-
- strike
-
-
-
- #general
-
-
- link
-
-
- rocket.cat
-
-
-
- inline code
-
-
-
-
-
-
- -
-
-
-
- Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries
-
-
-
-
-
-
-
- 1
- .
-
-
-
- Plain text
-
-
- 💡
-
-
-
- italic
-
-
-
-
- bold
-
-
-
-
- strike
-
-
-
- #general
-
-
- link
-
-
- rocket.cat
-
-
-
- inline code
-
-
-
-
-
-
- 2
- .
-
-
-
- Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries
-
-
-
-
-
-
-
- - [x]
-
-
-
- Plain text
-
-
- 💡
-
-
-
- italic
-
-
-
-
- bold
-
-
-
-
- strike
-
-
-
- #general
-
-
- link
-
-
- rocket.cat
-
-
-
- inline code
-
-
-
-
-
-
- - [ ]
-
-
-
- Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries
-
-
-
-
-
-`;
-
-exports[`Storyshots NewMarkdown Mentions 1`] = `
-
-
-
-
- rocket.cat
-
-
-
-
-
-
- name
-
-
-
-
-
- rocket.cat
-
-
-
-
-
- here
-
-
-
-
-
- all
-
-
-
-
-`;
-
-exports[`Storyshots NewMarkdown Text 1`] = `
-
-
-
-
- This is Rocket.Chat
-
-
-
-
-
-
- Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries
-
-
-
-
-
-
- a
-
-
- b
-
-
- c
-
-
-
-
-
- d
-
-
-
-
-
-
-
-
- e
-
-
-
-
-
-
- a b c
-
-
-
-
-
-
- This is Rocket.Chat
-
-
-
-
-
-
- Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries
-
-
-
-
-`;
-
-exports[`Storyshots Room Item Alerts 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- rocket.cat
-
-
-
- 10:00
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- unread
-
-
-
- 10:00
-
-
-
- 1
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- unread
-
-
-
- 10:00
-
-
-
- +999
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- user mentions
-
-
-
- 10:00
-
-
-
- 1
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- group mentions
-
-
-
- 10:00
-
-
-
- 1
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- thread unread
-
-
-
- 10:00
-
-
-
- 1
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- thread unread user
-
-
-
- 10:00
-
-
-
- 1
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- thread unread group
-
-
-
- 10:00
-
-
-
- 1
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- user mentions priority 1
-
-
-
- 10:00
-
-
-
- 1
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- group mentions priority 2
-
-
-
- 10:00
-
-
-
- 1
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- thread unread priority 3
-
-
-
- 10:00
-
-
-
- 1
-
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`Storyshots Room Item Basic 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- rocket.cat
-
-
-
- 10:00
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`Storyshots Room Item Condensed Room Item 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- rocket.cat
-
-
-
- 10:00
-
-
-
- 1
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- unread
-
-
-
- 10:00
-
-
-
- +999
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- rocket.cat
-
-
-
- Auto-join
-
-
-
-
- 10:00
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`Storyshots Room Item Condensed Room Item without Avatar 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- rocket.cat
-
-
-
- 10:00
-
-
-
- 1
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- rocket.cat
-
-
-
- 10:00
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries
-
-
-
- Auto-join
-
-
-
-
- 10:00
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`Storyshots Room Item Expanded Room Item without Avatar 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- rocket.cat
-
-
- 10:00
-
-
-
-
- Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries
-
-
-
- 1
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- rocket.cat
-
-
- 10:00
-
-
-
-
- Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries
-
-
-
- 1
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- rocket.cat
-
-
- 10:00
-
-
-
-
- Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`Storyshots Room Item Last Message 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- rocket.cat
-
-
- 10:00
-
-
-
-
- No Message
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- rocket.cat
-
-
- 10:00
-
-
-
-
- 2
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- rocket.cat
-
-
- 10:00
-
-
-
-
- You: 1
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- rocket.cat
-
-
- 10:00
-
-
-
-
- Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- rocket.cat
-
-
- 10:00
-
-
-
-
- Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries
-
-
-
- 1
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- rocket.cat
-
-
- 10:00
-
-
-
-
- Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries
-
-
-
- +999
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- rocket.cat
-
-
- 10:00
-
-
-
-
- Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries
-
-
-
- 1
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`Storyshots Room Item Tag 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- rocket.cat
-
-
-
- Auto-join
-
-
-
-
- 10:00
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- rocket.cat
-
-
-
- Auto-join
-
-
-
- 10:00
-
-
-
-
- No Message
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries
-
-
-
- Auto-join
-
-
-
-
- 10:00
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries
-
-
-
- Auto-join
-
-
-
- 10:00
-
-
-
-
- No Message
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`Storyshots Room Item Touch 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- rocket.cat
-
-
-
- 10:00
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`Storyshots Room Item Type 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- rocket.cat
-
-
-
- 10:00
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- rocket.cat
-
-
-
- 10:00
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- rocket.cat
-
-
-
- 10:00
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- rocket.cat
-
-
-
- 10:00
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- rocket.cat
-
-
-
- 10:00
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- rocket.cat
-
-
-
- 10:00
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- rocket.cat
-
-
-
- 10:00
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`Storyshots Room Item User 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- diego.mello
-
-
-
- 10:00
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries
-
-
-
- 10:00
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`Storyshots Room Item User status 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- rocket.cat
-
-
-
- 10:00
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- rocket.cat
-
-
-
- 10:00
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- rocket.cat
-
-
-
- 10:00
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- rocket.cat
-
-
-
- 10:00
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- rocket.cat
-
-
-
- 10:00
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- rocket.cat
-
-
-
- 10:00
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`Storyshots RoomHeader icons 1`] = `
-Array [
-
-
-
-
-
-
-
-
-
- private channel
-
-
-
-
-
- ,
-
-
-
-
-
-
-
-
-
- public channel
-
-
-
-
-
- ,
-
-
-
-
-
-
-
-
-
- discussion
-
-
-
-
-
- ,
-
-
-
-
-
-
-
-
-
- omnichannel
-
-
-
-
-
- ,
-
-
-
-
-
-
-
-
-
- private team
-
-
-
-
-
- ,
-
-
-
-
-
-
-
-
-
- public team
-
-
-
-
-
- ,
-
-
-
-
-
-
-
-
-
- group dm
-
-
-
-
-
- ,
-
-
-
-
-
-
-
-
-
- online dm
-
-
-
-
-
- ,
-
-
-
-
-
-
-
-
-
- away dm
-
-
-
-
-
- ,
-
-
-
-
-
-
-
-
-
- busy dm
-
-
-
-
-
- ,
-
-
-
-
-
-
-
-
-
- loading dm
-
-
-
-
-
- ,
-
-
-
-
-
-
-
-
-
- offline dm
-
-
-
-
-
- ,
-]
-`;
-
-exports[`Storyshots RoomHeader landscape 1`] = `
-Array [
-
-
-
-
-
-
-
-
-
- title
-
-
-
-
-
- ,
-
-
-
-
-
-
-
-
-
- title
-
-
-
- subtitle
-
-
-
-
- ,
-
-
-
-
-
-
-
-
-
- Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries
-
-
-
- Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries
-
-
-
-
- ,
-]
-`;
-
-exports[`Storyshots RoomHeader themes 1`] = `
-Array [
-
-
-
-
-
-
-
-
-
- title
-
-
-
- subtitle
-
-
-
-
- ,
-
-
-
-
-
-
-
-
-
- title
-
-
-
- subtitle
-
-
-
-
- ,
-
-
-
-
-
-
-
-
-
- title
-
-
-
- subtitle
-
-
-
-
- ,
-]
-`;
-
-exports[`Storyshots RoomHeader thread 1`] = `
-Array [
-
-
-
-
-
-
- title
-
-
-
-
-
-
-
- parent title
-
-
-
-
-
- ,
-
-
-
-
-
-
- markdown preview #3 4 5
-
-
-
-
-
-
-
- Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries
-
-
-
-
-
- ,
-]
-`;
-
-exports[`Storyshots RoomHeader title and subtitle 1`] = `
-Array [
-
-
-
-
-
-
-
-
-
- title
-
-
-
-
-
- ,
-
-
-
-
-
-
-
-
-
- Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries
-
-
-
-
-
- ,
-
-
-
-
-
-
-
-
-
- title
-
-
-
- subtitle
-
-
-
-
- ,
-
-
-
-
-
-
-
-
-
- title
-
-
-
- Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries
-
-
-
-
- ,
-
-
-
-
-
-
-
-
-
- Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries
-
-
-
- Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries
-
-
-
-
- ,
-]
-`;
-
-exports[`Storyshots RoomHeader typing 1`] = `
-Array [
-
-
-
-
-
-
-
-
-
- title
-
-
-
-
- user 1
-
-
- is typing
- ...
-
-
-
-
- ,
-
-
-
-
-
-
-
-
-
- title
-
-
-
-
- user 1 and user 2
-
-
- are typing
- ...
-
-
-
-
- ,
-
-
-
-
-
-
-
-
-
- title
-
-
-
-
- user 1, user 2, user 3, user 4, user 5
-
-
- are typing
- ...
-
-
-
-
- ,
-]
-`;
-
-exports[`Storyshots ServerItem content 1`] = `
-Array [
-
-
-
-
-
-
-
- Rocket.Chat
-
-
- https://open.rocket.chat/
-
-
-
-
-
-
- ,
-
-
-
-
-
-
-
- Super Long Server Name in Rocket.Chat
-
-
- https://superlongservername.tologintoasuperlongservername/
-
-
-
- ,
-
-
-
-
-
-
-
- https://stable.rocket.chat/
-
-
- https://stable.rocket.chat/
-
-
-
- ,
-]
-`;
-
-exports[`Storyshots ServerItem themes 1`] = `
-Array [
-
-
-
-
-
-
-
- Rocket.Chat
-
-
- https://open.rocket.chat/
-
-
-
-
-
-
- ,
-
-
-
-
-
-
-
- Rocket.Chat
-
-
- https://open.rocket.chat/
-
-
-
-
-
-
- ,
-
-
-
-
-
-
-
- Rocket.Chat
-
-
- https://open.rocket.chat/
-
-
-
-
-
-
- ,
-]
-`;
-
-exports[`Storyshots ServerItem touchable 1`] = `
-Array [
-
-
-
-
-
-
-
- Rocket.Chat
-
-
- https://open.rocket.chat/
-
-
-
- ,
-
-
-
-
-
-
-
- Rocket.Chat
-
-
- https://open.rocket.chat/
-
-
-
- ,
-
-
-
-
-
-
-
- Rocket.Chat
-
-
- https://open.rocket.chat/
-
-
-
- ,
-]
-`;
-
-exports[`Storyshots Text Input Short and Long Text 1`] = `
-
-
-
- Short Text
-
-
-
-
-
-
-
- Long Text
-
-
-
-
-
-
-`;
-
-exports[`Storyshots Thread Messages.Item badge 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
- rocket.cat
-
-
- November 10, 2020
-
-
-
-
- Message content
-
-
-
-
-
-
-
-
-
-
- 1
-
-
-
-
-
-
-
- 1
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- rocket.cat
-
-
- November 10, 2020
-
-
-
-
- Message content
-
-
-
-
-
-
-
-
-
-
- 1
-
-
-
-
-
-
-
- 1
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- rocket.cat
-
-
- November 10, 2020
-
-
-
-
- Message content
-
-
-
-
-
-
-
-
-
-
- 1
-
-
-
-
-
-
-
- 1
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- rocket.cat
-
-
- November 10, 2020
-
-
-
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
-
-
-
-
-
-
-
-
-
-
- 1
-
-
-
-
-
-
-
- 1
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`Storyshots Thread Messages.Item content 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
- rocket.cat
-
-
- November 10, 2020
-
-
-
-
- Message content
-
-
-
-
-
-
-
-
-
- 1
-
-
-
-
-
-
-
- 1
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- rocket.cat
-
-
- November 10, 2020
-
-
-
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
-
-
-
-
-
-
-
-
-
- 1
-
-
-
-
-
-
-
- 1
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- rocket.cat
-
-
- November 10, 2020
-
-
-
-
- Message content
-
-
-
-
-
-
-
-
-
- +999
-
-
-
-
-
-
-
- +999
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- rocket.cat
-
-
- November 10, 2020
-
-
-
-
- Attachment title
-
-
-
-
-
-
-
-
-
- 1
-
-
-
-
-
-
-
- 1
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Rocket Cat
-
-
- November 10, 2020
-
-
-
-
- Message content
-
-
-
-
-
-
-
-
-
- 1
-
-
-
-
-
-
-
- 1
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`Storyshots Thread Messages.Item themes 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
- rocket.cat
-
-
- November 10, 2020
-
-
-
-
- Message content
-
-
-
-
-
-
-
-
-
-
- 1
-
-
-
-
-
-
-
- 1
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- rocket.cat
-
-
- November 10, 2020
-
-
-
-
- Message content
-
-
-
-
-
-
-
-
-
-
- 1
-
-
-
-
-
-
-
- 1
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- rocket.cat
-
-
- November 10, 2020
-
-
-
-
- Message content
-
-
-
-
-
-
-
-
-
-
- 1
-
-
-
-
-
-
-
- 1
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-`;
-
-exports[`Storyshots UiKitMessage Action - Buttons 1`] = `
-
-
-
-
-
-`;
-
-exports[`Storyshots UiKitMessage Action - Select 1`] = `
-
-
-
-
-
-`;
-
-exports[`Storyshots UiKitMessage Context 1`] = `
-
-
-
-
-
-`;
-
-exports[`Storyshots UiKitMessage Fields 1`] = `
-
-
-
-
-
-`;
-
-exports[`Storyshots UiKitMessage Image 1`] = `
-
-
-
-
-
-`;
-
-exports[`Storyshots UiKitMessage Section + DatePicker 1`] = `
-
-
-
-
-
-`;
-
-exports[`Storyshots UiKitMessage Section + Markdown List 1`] = `
-
-
-
-
-
-`;
-
-exports[`Storyshots UiKitMessage Section + Multi Select 1`] = `
-
-
-
-
-
-`;
-
-exports[`Storyshots UiKitMessage Section + Overflow 1`] = `
-
-
-
-
-
-`;
-
-exports[`Storyshots UiKitMessage Section + Select 1`] = `
-
-
-
-
-
-`;
-
-exports[`Storyshots UiKitMessage Section + button 1`] = `
-
-
-
-
-
-`;
-
-exports[`Storyshots UiKitMessage Section + image 1`] = `
-
-
-
-
-
-`;
-
-exports[`Storyshots UiKitMessage Section 1`] = `
-
-
-
-
-
-`;
-
-exports[`Storyshots UiKitModal Modal - Actions 1`] = `
-
-
-
-
-
-`;
-
-exports[`Storyshots UiKitModal Modal - Contexts and Dividers 1`] = `
-
-
-
-
-
-`;
-
-exports[`Storyshots UiKitModal Modal - DatePicker with error 1`] = `
-
-
-
-
-
-`;
-
-exports[`Storyshots UiKitModal Modal - Form Input 1`] = `
-
-
-
-
-
-`;
-
-exports[`Storyshots UiKitModal Modal - Form TextArea 1`] = `
-
-
-
-
-
-`;
-
-exports[`Storyshots UiKitModal Modal - Images 1`] = `
-
-
-
-
-
-`;
-
-exports[`Storyshots UiKitModal Modal - Input with error 1`] = `
-
-
-
-
-
-`;
-
-exports[`Storyshots UiKitModal Modal - Multilne with error 1`] = `
-
-
-
-
-
-`;
-
-exports[`Storyshots UiKitModal Modal - Section Accessories 1`] = `
-
-
-
-
-
-`;
-
-exports[`Storyshots UiKitModal Modal - Section and Selects 1`] = `
-
-
-
-
-
-`;
-
-exports[`Storyshots Unread Badge all 1`] = `
-
-
-
- 9
-
-
-
-
- +99
-
-
-
-
- 9
-
-
-
-
- +999
-
-
-
-
- 9
-
-
-
-
- 9
-
-
-
-
- 9
-
-
-
-`;
-
-exports[`Storyshots Unread Badge different mention types 1`] = `
-
-
-
- 1
-
-
-
-
- 1
-
-
-
-
- 1
-
-
-
-
- 1
-
-
-
-
- 1
-
-
-
-
- 1
-
-
-
-
- 1
-
-
-
-`;
-
-exports[`Storyshots Unread Badge normal 1`] = `
-
-
-
- 9
-
-
-
-
- +999
-
-
-
-`;
-
-exports[`Storyshots Unread Badge small 1`] = `
-
-
-
- 9
-
-
-
-
- +99
-
-
-
-`;
-
-exports[`Storyshots Unread Badge themes 1`] = `
-Array [
-
-
-
- 1
-
-
-
-
- 1
-
-
-
-
- 1
-
-
-
-
- 1
-
-
- ,
-
-
-
- 1
-
-
-
-
- 1
-
-
-
-
- 1
-
-
-
-
- 1
-
-
- ,
-
-
-
- 1
-
-
-
-
- 1
-
-
-
-
- 1
-
-
-
-
- 1
-
-
- ,
-]
-`;
diff --git a/android/app/build.gradle b/android/app/build.gradle
index c95a2f79b..5e7aaee86 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -144,7 +144,7 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode VERSIONCODE as Integer
- versionName "4.24.0"
+ versionName "4.25.0"
vectorDrawables.useSupportLibrary = true
if (!isFoss) {
manifestPlaceholders = [BugsnagAPIKey: BugsnagAPIKey as String]
diff --git a/app/AppContainer.tsx b/app/AppContainer.tsx
index 441220fec..0ae892b7f 100644
--- a/app/AppContainer.tsx
+++ b/app/AppContainer.tsx
@@ -6,7 +6,7 @@ import { connect } from 'react-redux';
import { SetUsernameStackParamList, StackParamList } from './definitions/navigationTypes';
import Navigation from './lib/Navigation';
import { defaultHeader, getActiveRouteName, navigationTheme } from './utils/navigation';
-import { ROOT_INSIDE, ROOT_LOADING, ROOT_OUTSIDE, ROOT_SET_USERNAME } from './actions/app';
+import { RootEnum } from './definitions';
// Stacks
import AuthLoadingView from './views/AuthLoadingView';
// SetUsername Stack
@@ -56,13 +56,13 @@ const App = React.memo(({ root, isMasterDetail }: { root: string; isMasterDetail
}}>
<>
- {root === ROOT_LOADING ? : null}
- {root === ROOT_OUTSIDE ? : null}
- {root === ROOT_INSIDE && isMasterDetail ? (
+ {root === RootEnum.ROOT_LOADING ? : null}
+ {root === RootEnum.ROOT_OUTSIDE ? : null}
+ {root === RootEnum.ROOT_INSIDE && isMasterDetail ? (
) : null}
- {root === ROOT_INSIDE && !isMasterDetail ? : null}
- {root === ROOT_SET_USERNAME ? : null}
+ {root === RootEnum.ROOT_INSIDE && !isMasterDetail ? : null}
+ {root === RootEnum.ROOT_SET_USERNAME ? : null}
>
diff --git a/app/ReactotronConfig.js b/app/ReactotronConfig.js
index 80cb90beb..655923363 100644
--- a/app/ReactotronConfig.js
+++ b/app/ReactotronConfig.js
@@ -1,4 +1,5 @@
/* eslint-disable */
+import AsyncStorage from '@react-native-community/async-storage';
import { NativeModules } from 'react-native';
import Reactotron from 'reactotron-react-native';
import { reactotronRedux } from 'reactotron-redux';
@@ -7,7 +8,12 @@ import sagaPlugin from 'reactotron-redux-saga';
if (__DEV__) {
const scriptURL = NativeModules.SourceCode.scriptURL;
const scriptHostname = scriptURL.split('://')[1].split(':')[0];
- Reactotron.configure({ host: scriptHostname }).useReactNative().use(reactotronRedux()).use(sagaPlugin()).connect();
+ Reactotron.setAsyncStorageHandler(AsyncStorage)
+ .configure({ host: scriptHostname })
+ .useReactNative()
+ .use(reactotronRedux())
+ .use(sagaPlugin())
+ .connect();
// Running on android device
// $ adb reverse tcp:9090 tcp:9090
Reactotron.clear();
diff --git a/app/actions/actionsTypes.ts b/app/actions/actionsTypes.ts
index ad2d1718d..1367b2251 100644
--- a/app/actions/actionsTypes.ts
+++ b/app/actions/actionsTypes.ts
@@ -2,8 +2,8 @@ const REQUEST = 'REQUEST';
const SUCCESS = 'SUCCESS';
const FAILURE = 'FAILURE';
const defaultTypes = [REQUEST, SUCCESS, FAILURE];
-function createRequestTypes(base = {}, types = defaultTypes): Record {
- const res: Record = {};
+function createRequestTypes(base = {}, types = defaultTypes): Record {
+ const res: Record = {};
types.forEach(type => (res[type] = `${base}_${type}`));
return res;
}
@@ -58,7 +58,7 @@ export const SNIPPETED_MESSAGES = createRequestTypes('SNIPPETED_MESSAGES', ['OPE
export const DEEP_LINKING = createRequestTypes('DEEP_LINKING', ['OPEN']);
export const SORT_PREFERENCES = createRequestTypes('SORT_PREFERENCES', ['SET_ALL', 'SET']);
export const SET_CUSTOM_EMOJIS = 'SET_CUSTOM_EMOJIS';
-export const SET_ACTIVE_USERS = 'SET_ACTIVE_USERS';
+export const ACTIVE_USERS = createRequestTypes('ACTIVE_USERS', ['SET', 'CLEAR']);
export const USERS_TYPING = createRequestTypes('USERS_TYPING', ['ADD', 'REMOVE', 'CLEAR']);
export const INVITE_LINKS = createRequestTypes('INVITE_LINKS', [
'SET_TOKEN',
diff --git a/app/actions/activeUsers.ts b/app/actions/activeUsers.ts
index 737ae86b3..55d37d839 100644
--- a/app/actions/activeUsers.ts
+++ b/app/actions/activeUsers.ts
@@ -1,15 +1,19 @@
import { Action } from 'redux';
import { IActiveUsers } from '../reducers/activeUsers';
-import { SET_ACTIVE_USERS } from './actionsTypes';
+import { ACTIVE_USERS } from './actionsTypes';
-export interface ISetActiveUsers extends Action {
+interface ISetActiveUsers extends Action {
activeUsers: IActiveUsers;
}
export type TActionActiveUsers = ISetActiveUsers;
export const setActiveUsers = (activeUsers: IActiveUsers): ISetActiveUsers => ({
- type: SET_ACTIVE_USERS,
+ type: ACTIVE_USERS.SET,
activeUsers
});
+
+export const clearActiveUsers = (): Action => ({
+ type: ACTIVE_USERS.CLEAR
+});
diff --git a/app/actions/app.js b/app/actions/app.js
deleted file mode 100644
index fe6981d67..000000000
--- a/app/actions/app.js
+++ /dev/null
@@ -1,39 +0,0 @@
-import { APP } from './actionsTypes';
-
-export const ROOT_OUTSIDE = 'outside';
-export const ROOT_INSIDE = 'inside';
-export const ROOT_LOADING = 'loading';
-export const ROOT_SET_USERNAME = 'setUsername';
-
-export function appStart({ root, ...args }) {
- return {
- type: APP.START,
- root,
- ...args
- };
-}
-
-export function appReady() {
- return {
- type: APP.READY
- };
-}
-
-export function appInit() {
- return {
- type: APP.INIT
- };
-}
-
-export function appInitLocalSettings() {
- return {
- type: APP.INIT_LOCAL_SETTINGS
- };
-}
-
-export function setMasterDetail(isMasterDetail) {
- return {
- type: APP.SET_MASTER_DETAIL,
- isMasterDetail
- };
-}
diff --git a/app/actions/app.ts b/app/actions/app.ts
new file mode 100644
index 000000000..8285c527f
--- /dev/null
+++ b/app/actions/app.ts
@@ -0,0 +1,53 @@
+import { Action } from 'redux';
+
+import { RootEnum } from '../definitions';
+import { APP } from './actionsTypes';
+
+interface IAppStart extends Action {
+ root: RootEnum;
+ text?: string;
+}
+
+interface ISetMasterDetail extends Action {
+ isMasterDetail: boolean;
+}
+
+export type TActionApp = IAppStart & ISetMasterDetail;
+
+interface Params {
+ root: RootEnum;
+ [key: string]: any;
+}
+
+export function appStart({ root, ...args }: Params): IAppStart {
+ return {
+ type: APP.START,
+ root,
+ ...args
+ };
+}
+
+export function appReady(): Action {
+ return {
+ type: APP.READY
+ };
+}
+
+export function appInit(): Action {
+ return {
+ type: APP.INIT
+ };
+}
+
+export function appInitLocalSettings(): Action {
+ return {
+ type: APP.INIT_LOCAL_SETTINGS
+ };
+}
+
+export function setMasterDetail(isMasterDetail: boolean): ISetMasterDetail {
+ return {
+ type: APP.SET_MASTER_DETAIL,
+ isMasterDetail
+ };
+}
diff --git a/app/actions/connect.js b/app/actions/connect.js
deleted file mode 100644
index 57f46c71b..000000000
--- a/app/actions/connect.js
+++ /dev/null
@@ -1,20 +0,0 @@
-import * as types from './actionsTypes';
-
-export function connectRequest() {
- return {
- type: types.METEOR.REQUEST
- };
-}
-
-export function connectSuccess() {
- return {
- type: types.METEOR.SUCCESS
- };
-}
-
-export function disconnect(err) {
- return {
- type: types.METEOR.DISCONNECT,
- err
- };
-}
diff --git a/app/actions/connect.ts b/app/actions/connect.ts
new file mode 100644
index 000000000..be566253b
--- /dev/null
+++ b/app/actions/connect.ts
@@ -0,0 +1,21 @@
+import { Action } from 'redux';
+
+import * as types from './actionsTypes';
+
+export function connectRequest(): Action {
+ return {
+ type: types.METEOR.REQUEST
+ };
+}
+
+export function connectSuccess(): Action {
+ return {
+ type: types.METEOR.SUCCESS
+ };
+}
+
+export function disconnect(): Action {
+ return {
+ type: types.METEOR.DISCONNECT
+ };
+}
diff --git a/app/actions/createChannel.js b/app/actions/createChannel.js
deleted file mode 100644
index c93b47ef4..000000000
--- a/app/actions/createChannel.js
+++ /dev/null
@@ -1,23 +0,0 @@
-import * as types from './actionsTypes';
-
-export function createChannelRequest(data) {
- return {
- type: types.CREATE_CHANNEL.REQUEST,
- data
- };
-}
-
-export function createChannelSuccess(data) {
- return {
- type: types.CREATE_CHANNEL.SUCCESS,
- data
- };
-}
-
-export function createChannelFailure(err, isTeam) {
- return {
- type: types.CREATE_CHANNEL.FAILURE,
- err,
- isTeam
- };
-}
diff --git a/app/actions/createChannel.ts b/app/actions/createChannel.ts
new file mode 100644
index 000000000..32df47f6a
--- /dev/null
+++ b/app/actions/createChannel.ts
@@ -0,0 +1,41 @@
+import { Action } from 'redux';
+
+import { TCreateChannelResult } from '../reducers/createChannel';
+import { CREATE_CHANNEL } from './actionsTypes';
+
+interface ICreateChannelRequest extends Action {
+ data: TCreateChannelResult;
+}
+
+interface ICreateChannelSuccess extends Action {
+ data: TCreateChannelResult;
+}
+
+interface ICreateChannelFailure extends Action {
+ err: any;
+ isTeam: boolean;
+}
+
+export type TActionCreateChannel = ICreateChannelRequest & ICreateChannelSuccess & ICreateChannelFailure;
+
+export function createChannelRequest(data: TCreateChannelResult): ICreateChannelRequest {
+ return {
+ type: CREATE_CHANNEL.REQUEST,
+ data
+ };
+}
+
+export function createChannelSuccess(data: TCreateChannelResult): ICreateChannelSuccess {
+ return {
+ type: CREATE_CHANNEL.SUCCESS,
+ data
+ };
+}
+
+export function createChannelFailure(err: any, isTeam: boolean): ICreateChannelFailure {
+ return {
+ type: CREATE_CHANNEL.FAILURE,
+ err,
+ isTeam
+ };
+}
diff --git a/app/actions/createDiscussion.js b/app/actions/createDiscussion.js
deleted file mode 100644
index 5b6faa851..000000000
--- a/app/actions/createDiscussion.js
+++ /dev/null
@@ -1,22 +0,0 @@
-import * as types from './actionsTypes';
-
-export function createDiscussionRequest(data) {
- return {
- type: types.CREATE_DISCUSSION.REQUEST,
- data
- };
-}
-
-export function createDiscussionSuccess(data) {
- return {
- type: types.CREATE_DISCUSSION.SUCCESS,
- data
- };
-}
-
-export function createDiscussionFailure(err) {
- return {
- type: types.CREATE_DISCUSSION.FAILURE,
- err
- };
-}
diff --git a/app/actions/createDiscussion.ts b/app/actions/createDiscussion.ts
new file mode 100644
index 000000000..bf64baaa6
--- /dev/null
+++ b/app/actions/createDiscussion.ts
@@ -0,0 +1,38 @@
+import { Action } from 'redux';
+
+import { CREATE_DISCUSSION } from './actionsTypes';
+
+interface ICreateDiscussionRequest extends Action {
+ data: any;
+}
+
+interface ICreateDiscussionSuccess extends Action {
+ data: any;
+}
+
+interface ICreateDiscussionFailure extends Action {
+ err: any;
+}
+
+export type TActionCreateDiscussion = ICreateDiscussionRequest & ICreateDiscussionSuccess & ICreateDiscussionFailure;
+
+export function createDiscussionRequest(data: any): ICreateDiscussionRequest {
+ return {
+ type: CREATE_DISCUSSION.REQUEST,
+ data
+ };
+}
+
+export function createDiscussionSuccess(data: any): ICreateDiscussionSuccess {
+ return {
+ type: CREATE_DISCUSSION.SUCCESS,
+ data
+ };
+}
+
+export function createDiscussionFailure(err: any): ICreateDiscussionFailure {
+ return {
+ type: CREATE_DISCUSSION.FAILURE,
+ err
+ };
+}
diff --git a/app/actions/customEmojis.js b/app/actions/customEmojis.js
deleted file mode 100644
index 740a936ac..000000000
--- a/app/actions/customEmojis.js
+++ /dev/null
@@ -1,8 +0,0 @@
-import * as types from './actionsTypes';
-
-export function setCustomEmojis(emojis) {
- return {
- type: types.SET_CUSTOM_EMOJIS,
- emojis
- };
-}
diff --git a/app/actions/customEmojis.ts b/app/actions/customEmojis.ts
new file mode 100644
index 000000000..261fbd241
--- /dev/null
+++ b/app/actions/customEmojis.ts
@@ -0,0 +1,17 @@
+import { Action } from 'redux';
+
+import { ICustomEmojis } from '../reducers/customEmojis';
+import { SET_CUSTOM_EMOJIS } from './actionsTypes';
+
+export interface ISetCustomEmojis extends Action {
+ emojis: ICustomEmojis;
+}
+
+export type TActionCustomEmojis = ISetCustomEmojis;
+
+export function setCustomEmojis(emojis: ICustomEmojis): ISetCustomEmojis {
+ return {
+ type: SET_CUSTOM_EMOJIS,
+ emojis
+ };
+}
diff --git a/app/actions/deepLinking.js b/app/actions/deepLinking.js
deleted file mode 100644
index dfd540b6f..000000000
--- a/app/actions/deepLinking.js
+++ /dev/null
@@ -1,8 +0,0 @@
-import * as types from './actionsTypes';
-
-export function deepLinkingOpen(params) {
- return {
- type: types.DEEP_LINKING.OPEN,
- params
- };
-}
diff --git a/app/actions/deepLinking.ts b/app/actions/deepLinking.ts
new file mode 100644
index 000000000..78ea929bb
--- /dev/null
+++ b/app/actions/deepLinking.ts
@@ -0,0 +1,25 @@
+import { Action } from 'redux';
+
+import { DEEP_LINKING } from './actionsTypes';
+
+interface IParams {
+ path: string;
+ rid: string;
+ messageId: string;
+ host: string;
+ isCall: boolean;
+ fullURL: string;
+ type: string;
+ token: string;
+}
+
+interface IDeepLinkingOpen extends Action {
+ params: Partial;
+}
+
+export function deepLinkingOpen(params: Partial): IDeepLinkingOpen {
+ return {
+ type: DEEP_LINKING.OPEN,
+ params
+ };
+}
diff --git a/app/actions/encryption.js b/app/actions/encryption.js
deleted file mode 100644
index 390dfe903..000000000
--- a/app/actions/encryption.js
+++ /dev/null
@@ -1,35 +0,0 @@
-import * as types from './actionsTypes';
-
-export function encryptionInit() {
- return {
- type: types.ENCRYPTION.INIT
- };
-}
-
-export function encryptionStop() {
- return {
- type: types.ENCRYPTION.STOP
- };
-}
-
-export function encryptionSet(enabled = false, banner = null) {
- return {
- type: types.ENCRYPTION.SET,
- enabled,
- banner
- };
-}
-
-export function encryptionSetBanner(banner) {
- return {
- type: types.ENCRYPTION.SET_BANNER,
- banner
- };
-}
-
-export function encryptionDecodeKey(password) {
- return {
- type: types.ENCRYPTION.DECODE_KEY,
- password
- };
-}
diff --git a/app/actions/encryption.ts b/app/actions/encryption.ts
new file mode 100644
index 000000000..be3fb43fa
--- /dev/null
+++ b/app/actions/encryption.ts
@@ -0,0 +1,52 @@
+import { Action } from 'redux';
+
+import { IBanner } from '../reducers/encryption';
+import { ENCRYPTION } from './actionsTypes';
+
+export interface IEncryptionSet extends Action {
+ enabled: boolean;
+ banner: IBanner;
+}
+
+export interface IEncryptionSetBanner extends Action {
+ banner: IBanner;
+}
+export interface IEncryptionDecodeKey extends Action {
+ password: string;
+}
+
+export type TActionEncryption = IEncryptionSet & IEncryptionSetBanner & IEncryptionDecodeKey;
+
+export function encryptionInit(): Action {
+ return {
+ type: ENCRYPTION.INIT
+ };
+}
+
+export function encryptionStop(): Action {
+ return {
+ type: ENCRYPTION.STOP
+ };
+}
+
+export function encryptionSet(enabled = false, banner: IBanner = ''): IEncryptionSet {
+ return {
+ type: ENCRYPTION.SET,
+ enabled,
+ banner
+ };
+}
+
+export function encryptionSetBanner(banner: IBanner = ''): IEncryptionSetBanner {
+ return {
+ type: ENCRYPTION.SET_BANNER,
+ banner
+ };
+}
+
+export function encryptionDecodeKey(password: string): IEncryptionDecodeKey {
+ return {
+ type: ENCRYPTION.DECODE_KEY,
+ password
+ };
+}
diff --git a/app/actions/enterpriseModules.js b/app/actions/enterpriseModules.js
deleted file mode 100644
index 74b097873..000000000
--- a/app/actions/enterpriseModules.js
+++ /dev/null
@@ -1,14 +0,0 @@
-import { ENTERPRISE_MODULES } from './actionsTypes';
-
-export function setEnterpriseModules(modules) {
- return {
- type: ENTERPRISE_MODULES.SET,
- payload: modules
- };
-}
-
-export function clearEnterpriseModules() {
- return {
- type: ENTERPRISE_MODULES.CLEAR
- };
-}
diff --git a/app/actions/enterpriseModules.ts b/app/actions/enterpriseModules.ts
new file mode 100644
index 000000000..073aef49b
--- /dev/null
+++ b/app/actions/enterpriseModules.ts
@@ -0,0 +1,23 @@
+import { Action } from 'redux';
+
+import { IEnterpriseModules } from '../reducers/enterpriseModules';
+import { ENTERPRISE_MODULES } from './actionsTypes';
+
+interface ISetEnterpriseModules extends Action {
+ payload: IEnterpriseModules[];
+}
+
+export type TActionEnterpriseModules = ISetEnterpriseModules & Action;
+
+export function setEnterpriseModules(modules: IEnterpriseModules[]): ISetEnterpriseModules {
+ return {
+ type: ENTERPRISE_MODULES.SET,
+ payload: modules
+ };
+}
+
+export function clearEnterpriseModules(): Action {
+ return {
+ type: ENTERPRISE_MODULES.CLEAR
+ };
+}
diff --git a/app/actions/inviteLinks.js b/app/actions/inviteLinks.js
deleted file mode 100644
index cd2fd1639..000000000
--- a/app/actions/inviteLinks.js
+++ /dev/null
@@ -1,54 +0,0 @@
-import * as types from './actionsTypes';
-
-export function inviteLinksSetToken(token) {
- return {
- type: types.INVITE_LINKS.SET_TOKEN,
- token
- };
-}
-
-export function inviteLinksRequest(token) {
- return {
- type: types.INVITE_LINKS.REQUEST,
- token
- };
-}
-
-export function inviteLinksSuccess() {
- return {
- type: types.INVITE_LINKS.SUCCESS
- };
-}
-
-export function inviteLinksFailure() {
- return {
- type: types.INVITE_LINKS.FAILURE
- };
-}
-
-export function inviteLinksClear() {
- return {
- type: types.INVITE_LINKS.CLEAR
- };
-}
-
-export function inviteLinksCreate(rid) {
- return {
- type: types.INVITE_LINKS.CREATE,
- rid
- };
-}
-
-export function inviteLinksSetParams(params) {
- return {
- type: types.INVITE_LINKS.SET_PARAMS,
- params
- };
-}
-
-export function inviteLinksSetInvite(invite) {
- return {
- type: types.INVITE_LINKS.SET_INVITE,
- invite
- };
-}
diff --git a/app/actions/inviteLinks.ts b/app/actions/inviteLinks.ts
new file mode 100644
index 000000000..3f32865d9
--- /dev/null
+++ b/app/actions/inviteLinks.ts
@@ -0,0 +1,61 @@
+import { Action } from 'redux';
+
+import { TInvite } from '../reducers/inviteLinks';
+import { INVITE_LINKS } from './actionsTypes';
+
+interface IInviteLinksGeneric extends Action {
+ token: string;
+}
+
+interface IInviteLinksCreate extends Action {
+ rid: string;
+}
+
+interface IInviteLinksSetInvite extends Action {
+ invite: TInvite;
+}
+
+type TParams = Record;
+
+interface IInviteLinksSetParams extends Action {
+ params: TParams;
+}
+
+export type TActionInviteLinks = IInviteLinksGeneric & IInviteLinksCreate & IInviteLinksSetInvite & IInviteLinksSetParams;
+
+export const inviteLinksSetToken = (token: string): IInviteLinksGeneric => ({
+ type: INVITE_LINKS.SET_TOKEN,
+ token
+});
+
+export const inviteLinksRequest = (token: string): IInviteLinksGeneric => ({
+ type: INVITE_LINKS.REQUEST,
+ token
+});
+
+export const inviteLinksSuccess = (): Action => ({
+ type: INVITE_LINKS.SUCCESS
+});
+
+export const inviteLinksFailure = (): Action => ({
+ type: INVITE_LINKS.FAILURE
+});
+
+export const inviteLinksClear = (): Action => ({
+ type: INVITE_LINKS.CLEAR
+});
+
+export const inviteLinksCreate = (rid: string): IInviteLinksCreate => ({
+ type: INVITE_LINKS.CREATE,
+ rid
+});
+
+export const inviteLinksSetParams = (params: TParams): IInviteLinksSetParams => ({
+ type: INVITE_LINKS.SET_PARAMS,
+ params
+});
+
+export const inviteLinksSetInvite = (invite: TInvite): IInviteLinksSetInvite => ({
+ type: INVITE_LINKS.SET_INVITE,
+ invite
+});
diff --git a/app/actions/login.js b/app/actions/login.js
deleted file mode 100644
index e0f4c1e28..000000000
--- a/app/actions/login.js
+++ /dev/null
@@ -1,59 +0,0 @@
-import * as types from './actionsTypes';
-
-export function loginRequest(credentials, logoutOnError, isFromWebView) {
- return {
- type: types.LOGIN.REQUEST,
- credentials,
- logoutOnError,
- isFromWebView
- };
-}
-
-export function loginSuccess(user) {
- return {
- type: types.LOGIN.SUCCESS,
- user
- };
-}
-
-export function loginFailure(err) {
- return {
- type: types.LOGIN.FAILURE,
- err
- };
-}
-
-export function logout(forcedByServer = false) {
- return {
- type: types.LOGOUT,
- forcedByServer
- };
-}
-
-export function setUser(user) {
- return {
- type: types.USER.SET,
- user
- };
-}
-
-export function setLoginServices(data) {
- return {
- type: types.LOGIN.SET_SERVICES,
- data
- };
-}
-
-export function setPreference(preference) {
- return {
- type: types.LOGIN.SET_PREFERENCE,
- preference
- };
-}
-
-export function setLocalAuthenticated(isLocalAuthenticated) {
- return {
- type: types.LOGIN.SET_LOCAL_AUTHENTICATED,
- isLocalAuthenticated
- };
-}
diff --git a/app/actions/login.ts b/app/actions/login.ts
new file mode 100644
index 000000000..dead31433
--- /dev/null
+++ b/app/actions/login.ts
@@ -0,0 +1,115 @@
+import { Action } from 'redux';
+
+import { IUser } from '../definitions';
+import * as types from './actionsTypes';
+
+interface ICredentials {
+ resume: string;
+ user: string;
+ password: string;
+}
+
+interface ILoginRequest extends Action {
+ credentials: any;
+ logoutOnError?: boolean;
+ isFromWebView?: boolean;
+}
+
+interface ILoginSuccess extends Action {
+ user: Partial;
+}
+
+interface ILoginFailure extends Action {
+ err: Partial;
+}
+
+interface ILogout extends Action {
+ forcedByServer: boolean;
+}
+
+interface ISetUser extends Action {
+ user: Partial;
+}
+
+interface ISetServices extends Action {
+ data: Record;
+}
+
+interface ISetPreference extends Action {
+ preference: Record;
+}
+
+interface ISetLocalAuthenticated extends Action {
+ isLocalAuthenticated: boolean;
+}
+
+export type TActionsLogin = ILoginRequest &
+ ILoginSuccess &
+ ILoginFailure &
+ ILogout &
+ ISetUser &
+ ISetServices &
+ ISetPreference &
+ ISetLocalAuthenticated;
+
+export function loginRequest(
+ credentials: Partial,
+ logoutOnError?: boolean,
+ isFromWebView?: boolean
+): ILoginRequest {
+ return {
+ type: types.LOGIN.REQUEST,
+ credentials,
+ logoutOnError,
+ isFromWebView
+ };
+}
+
+export function loginSuccess(user: Partial): ILoginSuccess {
+ return {
+ type: types.LOGIN.SUCCESS,
+ user
+ };
+}
+
+export function loginFailure(err: Record): ILoginFailure {
+ return {
+ type: types.LOGIN.FAILURE,
+ err
+ };
+}
+
+export function logout(forcedByServer = false): ILogout {
+ return {
+ type: types.LOGOUT,
+ forcedByServer
+ };
+}
+
+export function setUser(user: Partial): ISetUser {
+ return {
+ type: types.USER.SET,
+ user
+ };
+}
+
+export function setLoginServices(data: Record): ISetServices {
+ return {
+ type: types.LOGIN.SET_SERVICES,
+ data
+ };
+}
+
+export function setPreference(preference: Record): ISetPreference {
+ return {
+ type: types.LOGIN.SET_PREFERENCE,
+ preference
+ };
+}
+
+export function setLocalAuthenticated(isLocalAuthenticated: boolean): ISetLocalAuthenticated {
+ return {
+ type: types.LOGIN.SET_LOCAL_AUTHENTICATED,
+ isLocalAuthenticated
+ };
+}
diff --git a/app/actions/messages.js b/app/actions/messages.js
deleted file mode 100644
index 9f40809c7..000000000
--- a/app/actions/messages.js
+++ /dev/null
@@ -1,8 +0,0 @@
-import * as types from './actionsTypes';
-
-export function replyBroadcast(message) {
- return {
- type: types.MESSAGES.REPLY_BROADCAST,
- message
- };
-}
diff --git a/app/actions/messages.ts b/app/actions/messages.ts
new file mode 100644
index 000000000..eb0383abf
--- /dev/null
+++ b/app/actions/messages.ts
@@ -0,0 +1,16 @@
+import { Action } from 'redux';
+
+import { MESSAGES } from './actionsTypes';
+
+type IMessage = Record;
+
+interface IReplyBroadcast extends Action {
+ message: IMessage;
+}
+
+export function replyBroadcast(message: IMessage): IReplyBroadcast {
+ return {
+ type: MESSAGES.REPLY_BROADCAST,
+ message
+ };
+}
diff --git a/app/actions/permissions.js b/app/actions/permissions.js
deleted file mode 100644
index 444d9f11c..000000000
--- a/app/actions/permissions.js
+++ /dev/null
@@ -1,15 +0,0 @@
-import * as types from './actionsTypes';
-
-export function setPermissions(permissions) {
- return {
- type: types.PERMISSIONS.SET,
- permissions
- };
-}
-
-export function updatePermission(id, roles) {
- return {
- type: types.PERMISSIONS.UPDATE,
- payload: { id, roles }
- };
-}
diff --git a/app/actions/permissions.ts b/app/actions/permissions.ts
new file mode 100644
index 000000000..0c79b2e85
--- /dev/null
+++ b/app/actions/permissions.ts
@@ -0,0 +1,28 @@
+import { Action } from 'redux';
+
+import { IPermissionsState, TSupportedPermissions } from '../reducers/permissions';
+import { PERMISSIONS } from './actionsTypes';
+
+interface ISetPermissions extends Action {
+ permissions: IPermissionsState;
+}
+
+interface IUpdatePermissions extends Action {
+ payload: { id: TSupportedPermissions; roles: string[] };
+}
+
+export type TActionPermissions = ISetPermissions & IUpdatePermissions;
+
+export function setPermissions(permissions: IPermissionsState): ISetPermissions {
+ return {
+ type: PERMISSIONS.SET,
+ permissions
+ };
+}
+
+export function updatePermission(id: TSupportedPermissions, roles: string[]): IUpdatePermissions {
+ return {
+ type: PERMISSIONS.UPDATE,
+ payload: { id, roles }
+ };
+}
diff --git a/app/actions/roles.js b/app/actions/roles.js
deleted file mode 100644
index 8ee30425b..000000000
--- a/app/actions/roles.js
+++ /dev/null
@@ -1,20 +0,0 @@
-import * as types from './actionsTypes';
-
-export function setRoles(roles) {
- return {
- type: types.ROLES.SET,
- roles
- };
-}
-export function updateRoles(id, desc) {
- return {
- type: types.ROLES.UPDATE,
- payload: { id, desc }
- };
-}
-export function removeRoles(id) {
- return {
- type: types.ROLES.REMOVE,
- payload: { id }
- };
-}
diff --git a/app/actions/roles.ts b/app/actions/roles.ts
new file mode 100644
index 000000000..3dbfa370d
--- /dev/null
+++ b/app/actions/roles.ts
@@ -0,0 +1,39 @@
+import { Action } from 'redux';
+
+import { IRoles } from '../reducers/roles';
+import { ROLES } from './actionsTypes';
+
+export interface ISetRoles extends Action {
+ roles: IRoles;
+}
+
+export interface IUpdateRoles extends Action {
+ payload: { id: string; desc: string };
+}
+
+export interface IRemoveRoles extends Action {
+ payload: { id: string };
+}
+
+export type IActionRoles = ISetRoles & IUpdateRoles & IRemoveRoles;
+
+export function setRoles(roles: IRoles): ISetRoles {
+ return {
+ type: ROLES.SET,
+ roles
+ };
+}
+
+export function updateRoles(id: string, desc: string): IUpdateRoles {
+ return {
+ type: ROLES.UPDATE,
+ payload: { id, desc }
+ };
+}
+
+export function removeRoles(id: string): IRemoveRoles {
+ return {
+ type: ROLES.REMOVE,
+ payload: { id }
+ };
+}
diff --git a/app/actions/room.js b/app/actions/room.js
deleted file mode 100644
index 2753d7b9a..000000000
--- a/app/actions/room.js
+++ /dev/null
@@ -1,62 +0,0 @@
-import * as types from './actionsTypes';
-
-export function subscribeRoom(rid) {
- return {
- type: types.ROOM.SUBSCRIBE,
- rid
- };
-}
-
-export function unsubscribeRoom(rid) {
- return {
- type: types.ROOM.UNSUBSCRIBE,
- rid
- };
-}
-
-export function leaveRoom(roomType, room, selected) {
- return {
- type: types.ROOM.LEAVE,
- room,
- roomType,
- selected
- };
-}
-
-export function deleteRoom(roomType, room, selected) {
- return {
- type: types.ROOM.DELETE,
- room,
- roomType,
- selected
- };
-}
-
-export function closeRoom(rid) {
- return {
- type: types.ROOM.CLOSE,
- rid
- };
-}
-
-export function forwardRoom(rid, transferData) {
- return {
- type: types.ROOM.FORWARD,
- transferData,
- rid
- };
-}
-
-export function removedRoom() {
- return {
- type: types.ROOM.REMOVED
- };
-}
-
-export function userTyping(rid, status = true) {
- return {
- type: types.ROOM.USER_TYPING,
- rid,
- status
- };
-}
diff --git a/app/actions/room.ts b/app/actions/room.ts
new file mode 100644
index 000000000..9c817f565
--- /dev/null
+++ b/app/actions/room.ts
@@ -0,0 +1,109 @@
+import { Action } from 'redux';
+
+import { ERoomType } from '../definitions/ERoomType';
+import { ROOM } from './actionsTypes';
+
+// TYPE RETURN RELATED
+type ISelected = Record;
+
+export interface ITransferData {
+ roomId: string;
+ userId?: string;
+ departmentId?: string;
+}
+
+// ACTION RETURN RELATED
+interface IBaseReturn extends Action {
+ rid: string;
+}
+
+type TSubscribeRoom = IBaseReturn;
+type TUnsubscribeRoom = IBaseReturn;
+type TCloseRoom = IBaseReturn;
+
+type TRoom = Record;
+
+interface ILeaveRoom extends Action {
+ roomType: ERoomType;
+ room: TRoom;
+ selected?: ISelected;
+}
+
+interface IDeleteRoom extends Action {
+ roomType: ERoomType;
+ room: TRoom;
+ selected?: ISelected;
+}
+
+interface IForwardRoom extends Action {
+ transferData: ITransferData;
+ rid: string;
+}
+
+interface IUserTyping extends Action {
+ rid: string;
+ status: boolean;
+}
+
+export type TActionsRoom = TSubscribeRoom & TUnsubscribeRoom & TCloseRoom & ILeaveRoom & IDeleteRoom & IForwardRoom & IUserTyping;
+
+export function subscribeRoom(rid: string): TSubscribeRoom {
+ return {
+ type: ROOM.SUBSCRIBE,
+ rid
+ };
+}
+
+export function unsubscribeRoom(rid: string): TUnsubscribeRoom {
+ return {
+ type: ROOM.UNSUBSCRIBE,
+ rid
+ };
+}
+
+export function leaveRoom(roomType: ERoomType, room: TRoom, selected?: ISelected): ILeaveRoom {
+ return {
+ type: ROOM.LEAVE,
+ room,
+ roomType,
+ selected
+ };
+}
+
+export function deleteRoom(roomType: ERoomType, room: TRoom, selected?: ISelected): IDeleteRoom {
+ return {
+ type: ROOM.DELETE,
+ room,
+ roomType,
+ selected
+ };
+}
+
+export function closeRoom(rid: string): TCloseRoom {
+ return {
+ type: ROOM.CLOSE,
+ rid
+ };
+}
+
+export function forwardRoom(rid: string, transferData: ITransferData): IForwardRoom {
+ return {
+ type: ROOM.FORWARD,
+ transferData,
+ rid
+ };
+}
+
+export function removedRoom(): Action {
+ return {
+ type: ROOM.REMOVED
+ };
+}
+
+export function userTyping(rid: string, status = true): IUserTyping {
+ return {
+ type: ROOM.USER_TYPING,
+ rid,
+ status
+ };
+}
diff --git a/app/actions/rooms.js b/app/actions/rooms.js
deleted file mode 100644
index 0f708f8cf..000000000
--- a/app/actions/rooms.js
+++ /dev/null
@@ -1,58 +0,0 @@
-import * as types from './actionsTypes';
-
-export function roomsRequest(params = { allData: false }) {
- return {
- type: types.ROOMS.REQUEST,
- params
- };
-}
-
-export function roomsSuccess() {
- return {
- type: types.ROOMS.SUCCESS
- };
-}
-
-export function roomsFailure(err) {
- return {
- type: types.ROOMS.FAILURE,
- err
- };
-}
-
-export function roomsRefresh() {
- return {
- type: types.ROOMS.REFRESH
- };
-}
-
-export function setSearch(searchText) {
- return {
- type: types.ROOMS.SET_SEARCH,
- searchText
- };
-}
-
-export function closeServerDropdown() {
- return {
- type: types.ROOMS.CLOSE_SERVER_DROPDOWN
- };
-}
-
-export function toggleServerDropdown() {
- return {
- type: types.ROOMS.TOGGLE_SERVER_DROPDOWN
- };
-}
-
-export function openSearchHeader() {
- return {
- type: types.ROOMS.OPEN_SEARCH_HEADER
- };
-}
-
-export function closeSearchHeader() {
- return {
- type: types.ROOMS.CLOSE_SEARCH_HEADER
- };
-}
diff --git a/app/actions/rooms.ts b/app/actions/rooms.ts
new file mode 100644
index 000000000..514582ff6
--- /dev/null
+++ b/app/actions/rooms.ts
@@ -0,0 +1,78 @@
+import { Action } from 'redux';
+
+import { ROOMS } from './actionsTypes';
+
+export interface IRoomsRequest extends Action {
+ params: any;
+}
+
+export interface ISetSearch extends Action {
+ searchText: string;
+}
+
+export interface IRoomsFailure extends Action {
+ err: Record | string;
+}
+
+export type IRoomsAction = IRoomsRequest & ISetSearch & IRoomsFailure;
+
+export function roomsRequest(
+ params: {
+ allData: boolean;
+ } = { allData: false }
+): IRoomsRequest {
+ return {
+ type: ROOMS.REQUEST,
+ params
+ };
+}
+
+export function roomsSuccess(): Action {
+ return {
+ type: ROOMS.SUCCESS
+ };
+}
+
+export function roomsFailure(err: string): IRoomsFailure {
+ return {
+ type: ROOMS.FAILURE,
+ err
+ };
+}
+
+export function roomsRefresh(): Action {
+ return {
+ type: ROOMS.REFRESH
+ };
+}
+
+export function setSearch(searchText: string): ISetSearch {
+ return {
+ type: ROOMS.SET_SEARCH,
+ searchText
+ };
+}
+
+export function closeServerDropdown(): Action {
+ return {
+ type: ROOMS.CLOSE_SERVER_DROPDOWN
+ };
+}
+
+export function toggleServerDropdown(): Action {
+ return {
+ type: ROOMS.TOGGLE_SERVER_DROPDOWN
+ };
+}
+
+export function openSearchHeader(): Action {
+ return {
+ type: ROOMS.OPEN_SEARCH_HEADER
+ };
+}
+
+export function closeSearchHeader(): Action {
+ return {
+ type: ROOMS.CLOSE_SEARCH_HEADER
+ };
+}
diff --git a/app/actions/server.js b/app/actions/server.js
deleted file mode 100644
index fea239450..000000000
--- a/app/actions/server.js
+++ /dev/null
@@ -1,60 +0,0 @@
-import { SERVER } from './actionsTypes';
-
-export function selectServerRequest(server, version, fetchVersion = true, changeServer = false) {
- return {
- type: SERVER.SELECT_REQUEST,
- server,
- version,
- fetchVersion,
- changeServer
- };
-}
-
-export function selectServerSuccess(server, version) {
- return {
- type: SERVER.SELECT_SUCCESS,
- server,
- version
- };
-}
-
-export function selectServerFailure() {
- return {
- type: SERVER.SELECT_FAILURE
- };
-}
-
-export function serverRequest(server, username = null, fromServerHistory = false) {
- return {
- type: SERVER.REQUEST,
- server,
- username,
- fromServerHistory
- };
-}
-
-export function serverSuccess() {
- return {
- type: SERVER.SUCCESS
- };
-}
-
-export function serverFailure(err) {
- return {
- type: SERVER.FAILURE,
- err
- };
-}
-
-export function serverInitAdd(previousServer) {
- return {
- type: SERVER.INIT_ADD,
- previousServer
- };
-}
-
-export function serverFinishAdd() {
- return {
- type: SERVER.FINISH_ADD
- };
-}
diff --git a/app/actions/server.ts b/app/actions/server.ts
new file mode 100644
index 000000000..da8ee6d4c
--- /dev/null
+++ b/app/actions/server.ts
@@ -0,0 +1,90 @@
+import { Action } from 'redux';
+
+import { SERVER } from './actionsTypes';
+
+interface ISelectServer extends Action {
+ server: string;
+ version?: string;
+ fetchVersion: boolean;
+ changeServer: boolean;
+}
+
+interface ISelectServerSuccess extends Action {
+ server: string;
+ version: string;
+}
+
+interface IServer extends Action {
+ server: string;
+ username: string | null;
+ fromServerHistory: boolean;
+}
+
+interface IServerInit extends Action {
+ previousServer: string;
+}
+
+interface IServerFailure extends Action {
+ err: any;
+}
+
+export type TActionServer = ISelectServer & ISelectServerSuccess & IServer & IServerInit & IServerFailure;
+
+export function selectServerRequest(server: string, version?: string, fetchVersion = true, changeServer = false): ISelectServer {
+ return {
+ type: SERVER.SELECT_REQUEST,
+ server,
+ version,
+ fetchVersion,
+ changeServer
+ };
+}
+
+export function selectServerSuccess(server: string, version: string): ISelectServerSuccess {
+ return {
+ type: SERVER.SELECT_SUCCESS,
+ server,
+ version
+ };
+}
+
+export function selectServerFailure(): Action {
+ return {
+ type: SERVER.SELECT_FAILURE
+ };
+}
+
+export function serverRequest(server: string, username: string | null = null, fromServerHistory = false): IServer {
+ return {
+ type: SERVER.REQUEST,
+ server,
+ username,
+ fromServerHistory
+ };
+}
+
+export function serverSuccess(): Action {
+ return {
+ type: SERVER.SUCCESS
+ };
+}
+
+export function serverFailure(err: any): IServerFailure {
+ return {
+ type: SERVER.FAILURE,
+ err
+ };
+}
+
+export function serverInitAdd(previousServer: string): IServerInit {
+ return {
+ type: SERVER.INIT_ADD,
+ previousServer
+ };
+}
+
+export function serverFinishAdd(): Action {
+ return {
+ type: SERVER.FINISH_ADD
+ };
+}
diff --git a/app/actions/settings.js b/app/actions/settings.js
deleted file mode 100644
index 6fae375bc..000000000
--- a/app/actions/settings.js
+++ /dev/null
@@ -1,21 +0,0 @@
-import { SETTINGS } from './actionsTypes';
-
-export function addSettings(settings) {
- return {
- type: SETTINGS.ADD,
- payload: settings
- };
-}
-
-export function updateSettings(id, value) {
- return {
- type: SETTINGS.UPDATE,
- payload: { id, value }
- };
-}
-
-export function clearSettings() {
- return {
- type: SETTINGS.CLEAR
- };
-}
diff --git a/app/actions/settings.ts b/app/actions/settings.ts
new file mode 100644
index 000000000..77b8dcc77
--- /dev/null
+++ b/app/actions/settings.ts
@@ -0,0 +1,34 @@
+import { Action } from 'redux';
+
+import { ISettings, TSettings } from '../reducers/settings';
+import { SETTINGS } from './actionsTypes';
+
+interface IAddSettings extends Action {
+ payload: ISettings;
+}
+
+interface IUpdateSettings extends Action {
+ payload: { id: string; value: TSettings };
+}
+
+export type IActionSettings = IAddSettings & IUpdateSettings;
+
+export function addSettings(settings: ISettings): IAddSettings {
+ return {
+ type: SETTINGS.ADD,
+ payload: settings
+ };
+}
+
+export function updateSettings(id: string, value: TSettings): IUpdateSettings {
+ return {
+ type: SETTINGS.UPDATE,
+ payload: { id, value }
+ };
+}
+
+export function clearSettings(): Action {
+ return {
+ type: SETTINGS.CLEAR
+ };
+}
diff --git a/app/actions/share.js b/app/actions/share.js
deleted file mode 100644
index ff4d5a592..000000000
--- a/app/actions/share.js
+++ /dev/null
@@ -1,22 +0,0 @@
-import { SHARE } from './actionsTypes';
-
-export function shareSelectServer(server) {
- return {
- type: SHARE.SELECT_SERVER,
- server
- };
-}
-
-export function shareSetSettings(settings) {
- return {
- type: SHARE.SET_SETTINGS,
- settings
- };
-}
-
-export function shareSetUser(user) {
- return {
- type: SHARE.SET_USER,
- user
- };
-}
diff --git a/app/actions/share.ts b/app/actions/share.ts
new file mode 100644
index 000000000..22e012680
--- /dev/null
+++ b/app/actions/share.ts
@@ -0,0 +1,39 @@
+import { Action } from 'redux';
+
+import { IShareServer, IShareUser, TShareSettings } from '../reducers/share';
+import { SHARE } from './actionsTypes';
+
+interface IShareSelectServer extends Action {
+ server: IShareServer;
+}
+
+interface IShareSetSettings extends Action {
+ settings: TShareSettings;
+}
+
+interface IShareSetUser extends Action {
+ user: IShareUser;
+}
+
+export type TActionsShare = IShareSelectServer & IShareSetSettings & IShareSetUser;
+
+export function shareSelectServer(server: IShareServer): IShareSelectServer {
+ return {
+ type: SHARE.SELECT_SERVER,
+ server
+ };
+}
+
+export function shareSetSettings(settings: TShareSettings): IShareSetSettings {
+ return {
+ type: SHARE.SET_SETTINGS,
+ settings
+ };
+}
+
+export function shareSetUser(user: IShareUser): IShareSetUser {
+ return {
+ type: SHARE.SET_USER,
+ user
+ };
+}
diff --git a/app/actions/sortPreferences.js b/app/actions/sortPreferences.js
deleted file mode 100644
index e452e74c0..000000000
--- a/app/actions/sortPreferences.js
+++ /dev/null
@@ -1,15 +0,0 @@
-import * as types from './actionsTypes';
-
-export function setAllPreferences(preferences) {
- return {
- type: types.SORT_PREFERENCES.SET_ALL,
- preferences
- };
-}
-
-export function setPreference(preference) {
- return {
- type: types.SORT_PREFERENCES.SET,
- preference
- };
-}
diff --git a/app/actions/sortPreferences.ts b/app/actions/sortPreferences.ts
new file mode 100644
index 000000000..6a3328616
--- /dev/null
+++ b/app/actions/sortPreferences.ts
@@ -0,0 +1,28 @@
+import { Action } from 'redux';
+
+import { IPreferences } from '../definitions';
+import { SORT_PREFERENCES } from './actionsTypes';
+
+interface ISetAllPreferences extends Action {
+ preferences: IPreferences;
+}
+
+interface ISetPreference extends Action {
+ preference: Partial;
+}
+
+export type TActionSortPreferences = ISetAllPreferences & ISetPreference;
+
+export function setAllPreferences(preferences: IPreferences): ISetAllPreferences {
+ return {
+ type: SORT_PREFERENCES.SET_ALL,
+ preferences
+ };
+}
+
+export function setPreference(preference: Partial): ISetPreference {
+ return {
+ type: SORT_PREFERENCES.SET,
+ preference
+ };
+}
diff --git a/app/actions/usersTyping.js b/app/actions/usersTyping.js
deleted file mode 100644
index fb8f5980b..000000000
--- a/app/actions/usersTyping.js
+++ /dev/null
@@ -1,21 +0,0 @@
-import { USERS_TYPING } from './actionsTypes';
-
-export function addUserTyping(username) {
- return {
- type: USERS_TYPING.ADD,
- username
- };
-}
-
-export function removeUserTyping(username) {
- return {
- type: USERS_TYPING.REMOVE,
- username
- };
-}
-
-export function clearUserTyping() {
- return {
- type: USERS_TYPING.CLEAR
- };
-}
diff --git a/app/actions/usersTyping.ts b/app/actions/usersTyping.ts
new file mode 100644
index 000000000..19077ce65
--- /dev/null
+++ b/app/actions/usersTyping.ts
@@ -0,0 +1,29 @@
+import { Action } from 'redux';
+
+import { USERS_TYPING } from './actionsTypes';
+
+export interface IUsersTypingGenericAction extends Action {
+ username: string;
+}
+
+export type TActionUserTyping = IUsersTypingGenericAction & Action;
+
+export function addUserTyping(username: string): IUsersTypingGenericAction {
+ return {
+ type: USERS_TYPING.ADD,
+ username
+ };
+}
+
+export function removeUserTyping(username: string): IUsersTypingGenericAction {
+ return {
+ type: USERS_TYPING.REMOVE,
+ username
+ };
+}
+
+export function clearUserTyping(): Action {
+ return {
+ type: USERS_TYPING.CLEAR
+ };
+}
diff --git a/app/commands.js b/app/commands.ts
similarity index 62%
rename from app/commands.js
rename to app/commands.ts
index 300473019..b1aee847c 100644
--- a/app/commands.js
+++ b/app/commands.ts
@@ -1,5 +1,6 @@
/* eslint-disable no-bitwise */
-import KeyCommands, { constants } from 'react-native-keycommands';
+import { NativeSyntheticEvent } from 'react-native';
+import KeyCommands, { constants, KeyCommand } from 'react-native-keycommands';
import I18n from './i18n';
@@ -136,13 +137,18 @@ const keyCommands = [
}))
];
-export const setKeyCommands = () => KeyCommands.setKeyCommands(keyCommands);
+export const setKeyCommands = (): void => KeyCommands.setKeyCommands(keyCommands);
-export const deleteKeyCommands = () => KeyCommands.deleteKeyCommands(keyCommands);
+export const deleteKeyCommands = (): void => KeyCommands.deleteKeyCommands(keyCommands);
export const KEY_COMMAND = 'KEY_COMMAND';
-export const commandHandle = (event, key, flags = []) => {
+interface IKeyCommandEvent extends NativeSyntheticEvent {
+ input: string;
+ modifierFlags: string | number;
+}
+
+export const commandHandle = (event: IKeyCommandEvent, key: string | string[], flags: string[] = []): boolean => {
const { input, modifierFlags } = event;
let _flags = 0;
if (flags.includes('command') && flags.includes('alternate')) {
@@ -155,35 +161,41 @@ export const commandHandle = (event, key, flags = []) => {
return key.includes(input) && modifierFlags === _flags;
};
-export const handleCommandTyping = event => commandHandle(event, KEY_TYPING);
+export const handleCommandTyping = (event: IKeyCommandEvent): boolean => commandHandle(event, KEY_TYPING);
-export const handleCommandSubmit = event => commandHandle(event, KEY_SEND_MESSAGE);
+export const handleCommandSubmit = (event: IKeyCommandEvent): boolean => commandHandle(event, KEY_SEND_MESSAGE);
-export const handleCommandShowUpload = event => commandHandle(event, KEY_UPLOAD, ['command']);
+export const handleCommandShowUpload = (event: IKeyCommandEvent): boolean => commandHandle(event, KEY_UPLOAD, ['command']);
-export const handleCommandScroll = event =>
+export const handleCommandScroll = (event: IKeyCommandEvent): boolean =>
commandHandle(event, [constants.keyInputUpArrow, constants.keyInputDownArrow], ['alternate']);
-export const handleCommandRoomActions = event => commandHandle(event, KEY_ROOM_ACTIONS, ['command']);
+export const handleCommandRoomActions = (event: IKeyCommandEvent): boolean => commandHandle(event, KEY_ROOM_ACTIONS, ['command']);
-export const handleCommandSearchMessages = event => commandHandle(event, KEY_SEARCH, ['command']);
+export const handleCommandSearchMessages = (event: IKeyCommandEvent): boolean => commandHandle(event, KEY_SEARCH, ['command']);
-export const handleCommandReplyLatest = event => commandHandle(event, KEY_REPLY, ['command']);
+export const handleCommandReplyLatest = (event: IKeyCommandEvent): boolean => commandHandle(event, KEY_REPLY, ['command']);
-export const handleCommandSelectServer = event => commandHandle(event, KEY_SELECT, ['command', 'alternate']);
+export const handleCommandSelectServer = (event: IKeyCommandEvent): boolean =>
+ commandHandle(event, KEY_SELECT, ['command', 'alternate']);
-export const handleCommandShowPreferences = event => commandHandle(event, KEY_PREFERENCES, ['command']);
+export const handleCommandShowPreferences = (event: IKeyCommandEvent): boolean =>
+ commandHandle(event, KEY_PREFERENCES, ['command']);
-export const handleCommandSearching = event => commandHandle(event, KEY_SEARCH, ['command', 'alternate']);
+export const handleCommandSearching = (event: IKeyCommandEvent): boolean =>
+ commandHandle(event, KEY_SEARCH, ['command', 'alternate']);
-export const handleCommandSelectRoom = event => commandHandle(event, KEY_SELECT, ['command']);
+export const handleCommandSelectRoom = (event: IKeyCommandEvent): boolean => commandHandle(event, KEY_SELECT, ['command']);
-export const handleCommandPreviousRoom = event => commandHandle(event, KEY_PREVIOUS_ROOM, ['command']);
+export const handleCommandPreviousRoom = (event: IKeyCommandEvent): boolean =>
+ commandHandle(event, KEY_PREVIOUS_ROOM, ['command']);
-export const handleCommandNextRoom = event => commandHandle(event, KEY_NEXT_ROOM, ['command']);
+export const handleCommandNextRoom = (event: IKeyCommandEvent): boolean => commandHandle(event, KEY_NEXT_ROOM, ['command']);
-export const handleCommandShowNewMessage = event => commandHandle(event, KEY_NEW_ROOM, ['command']);
+export const handleCommandShowNewMessage = (event: IKeyCommandEvent): boolean => commandHandle(event, KEY_NEW_ROOM, ['command']);
-export const handleCommandAddNewServer = event => commandHandle(event, KEY_ADD_SERVER, ['command', 'alternate']);
+export const handleCommandAddNewServer = (event: IKeyCommandEvent): boolean =>
+ commandHandle(event, KEY_ADD_SERVER, ['command', 'alternate']);
-export const handleCommandOpenServerDropdown = event => commandHandle(event, KEY_SERVER_SELECTION, ['command', 'alternate']);
+export const handleCommandOpenServerDropdown = (event: IKeyCommandEvent): boolean =>
+ commandHandle(event, KEY_SERVER_SELECTION, ['command', 'alternate']);
diff --git a/app/constants/localAuthentication.ts b/app/constants/localAuthentication.ts
index 1312e57aa..bd74997a3 100644
--- a/app/constants/localAuthentication.ts
+++ b/app/constants/localAuthentication.ts
@@ -1,6 +1,7 @@
export const PASSCODE_KEY = 'kPasscode';
export const LOCKED_OUT_TIMER_KEY = 'kLockedOutTimer';
export const ATTEMPTS_KEY = 'kAttempts';
+export const BIOMETRY_ENABLED_KEY = 'kBiometryEnabled';
export const LOCAL_AUTHENTICATE_EMITTER = 'LOCAL_AUTHENTICATE';
export const CHANGE_PASSCODE_EMITTER = 'CHANGE_PASSCODE';
diff --git a/app/containers/Avatar/Avatar.tsx b/app/containers/Avatar/Avatar.tsx
index 0ad2634f2..8abb87843 100644
--- a/app/containers/Avatar/Avatar.tsx
+++ b/app/containers/Avatar/Avatar.tsx
@@ -44,7 +44,7 @@ const Avatar = React.memo(
if (emoji) {
image = (
{
private mounted: boolean;
- private subscription: any;
+ private subscription?: Subscription;
static defaultProps = {
text: '',
@@ -59,15 +61,17 @@ class AvatarContainer extends React.Component {
record = user;
} else {
const { rid } = this.props;
- record = await subsCollection.find(rid);
+ if (rid) {
+ record = await subsCollection.find(rid);
+ }
}
} catch {
// Record not found
}
if (record) {
- const observable = record.observe();
- this.subscription = observable.subscribe((r: any) => {
+ const observable = record.observe() as Observable;
+ this.subscription = observable.subscribe(r => {
const { avatarETag } = r;
if (this.mounted) {
this.setState({ avatarETag });
diff --git a/app/containers/Avatar/interfaces.ts b/app/containers/Avatar/interfaces.ts
index 78152e522..ddec5b276 100644
--- a/app/containers/Avatar/interfaces.ts
+++ b/app/containers/Avatar/interfaces.ts
@@ -1,3 +1,5 @@
+import { TGetCustomEmoji } from '../../definitions/IEmoji';
+
export interface IAvatar {
server?: string;
style?: any;
@@ -14,10 +16,10 @@ export interface IAvatar {
};
theme?: string;
onPress?: () => void;
- getCustomEmoji?: () => any;
+ getCustomEmoji?: TGetCustomEmoji;
avatarETag?: string;
isStatic?: boolean | string;
rid?: string;
blockUnauthenticatedAccess?: boolean;
- serverVersion?: string;
+ serverVersion: string;
}
diff --git a/app/containers/BackgroundContainer/__snapshots__/index.stories.storyshot b/app/containers/BackgroundContainer/__snapshots__/index.stories.storyshot
new file mode 100644
index 000000000..8974b91a9
--- /dev/null
+++ b/app/containers/BackgroundContainer/__snapshots__/index.stories.storyshot
@@ -0,0 +1,17 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Storyshots BackgroundContainer basic 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessibilityIgnoresInvertColors\\":true,\\"style\\":{\\"width\\":\\"100%\\",\\"height\\":\\"100%\\",\\"position\\":\\"absolute\\"}},\\"children\\":[{\\"type\\":\\"Image\\",\\"props\\":{\\"source\\":{\\"uri\\":\\"message_empty_light\\"},\\"style\\":[{\\"position\\":\\"absolute\\",\\"left\\":0,\\"right\\":0,\\"top\\":0,\\"bottom\\":0},{\\"width\\":\\"100%\\",\\"height\\":\\"100%\\"},null]},\\"children\\":null}]}]}"`;
+
+exports[`Storyshots BackgroundContainer black theme - loading 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessibilityIgnoresInvertColors\\":true,\\"style\\":{\\"width\\":\\"100%\\",\\"height\\":\\"100%\\",\\"position\\":\\"absolute\\"}},\\"children\\":[{\\"type\\":\\"Image\\",\\"props\\":{\\"source\\":{\\"uri\\":\\"message_empty_black\\"},\\"style\\":[{\\"position\\":\\"absolute\\",\\"left\\":0,\\"right\\":0,\\"top\\":0,\\"bottom\\":0},{\\"width\\":\\"100%\\",\\"height\\":\\"100%\\"},null]},\\"children\\":null}]},{\\"type\\":\\"ActivityIndicator\\",\\"props\\":{\\"animating\\":true,\\"color\\":\\"#f9f9f9\\",\\"hidesWhenStopped\\":true,\\"size\\":\\"small\\",\\"style\\":{\\"position\\":\\"absolute\\",\\"top\\":60,\\"left\\":0,\\"right\\":0,\\"fontSize\\":16,\\"paddingHorizontal\\":24,\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\"}},\\"children\\":null}]}"`;
+
+exports[`Storyshots BackgroundContainer black theme - text 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessibilityIgnoresInvertColors\\":true,\\"style\\":{\\"width\\":\\"100%\\",\\"height\\":\\"100%\\",\\"position\\":\\"absolute\\"}},\\"children\\":[{\\"type\\":\\"Image\\",\\"props\\":{\\"source\\":{\\"uri\\":\\"message_empty_black\\"},\\"style\\":[{\\"position\\":\\"absolute\\",\\"left\\":0,\\"right\\":0,\\"top\\":0,\\"bottom\\":0},{\\"width\\":\\"100%\\",\\"height\\":\\"100%\\"},null]},\\"children\\":null}]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"position\\":\\"absolute\\",\\"top\\":60,\\"left\\":0,\\"right\\":0,\\"fontSize\\":16,\\"paddingHorizontal\\":24,\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\"},{\\"color\\":\\"#f9f9f9\\"}]},\\"children\\":[\\"Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries\\"]}]}"`;
+
+exports[`Storyshots BackgroundContainer dark theme - loading 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessibilityIgnoresInvertColors\\":true,\\"style\\":{\\"width\\":\\"100%\\",\\"height\\":\\"100%\\",\\"position\\":\\"absolute\\"}},\\"children\\":[{\\"type\\":\\"Image\\",\\"props\\":{\\"source\\":{\\"uri\\":\\"message_empty_dark\\"},\\"style\\":[{\\"position\\":\\"absolute\\",\\"left\\":0,\\"right\\":0,\\"top\\":0,\\"bottom\\":0},{\\"width\\":\\"100%\\",\\"height\\":\\"100%\\"},null]},\\"children\\":null}]},{\\"type\\":\\"ActivityIndicator\\",\\"props\\":{\\"animating\\":true,\\"color\\":\\"#f9f9f9\\",\\"hidesWhenStopped\\":true,\\"size\\":\\"small\\",\\"style\\":{\\"position\\":\\"absolute\\",\\"top\\":60,\\"left\\":0,\\"right\\":0,\\"fontSize\\":16,\\"paddingHorizontal\\":24,\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\"}},\\"children\\":null}]}"`;
+
+exports[`Storyshots BackgroundContainer dark theme - text 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessibilityIgnoresInvertColors\\":true,\\"style\\":{\\"width\\":\\"100%\\",\\"height\\":\\"100%\\",\\"position\\":\\"absolute\\"}},\\"children\\":[{\\"type\\":\\"Image\\",\\"props\\":{\\"source\\":{\\"uri\\":\\"message_empty_dark\\"},\\"style\\":[{\\"position\\":\\"absolute\\",\\"left\\":0,\\"right\\":0,\\"top\\":0,\\"bottom\\":0},{\\"width\\":\\"100%\\",\\"height\\":\\"100%\\"},null]},\\"children\\":null}]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"position\\":\\"absolute\\",\\"top\\":60,\\"left\\":0,\\"right\\":0,\\"fontSize\\":16,\\"paddingHorizontal\\":24,\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\"},{\\"color\\":\\"#f9f9f9\\"}]},\\"children\\":[\\"Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries\\"]}]}"`;
+
+exports[`Storyshots BackgroundContainer loading 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessibilityIgnoresInvertColors\\":true,\\"style\\":{\\"width\\":\\"100%\\",\\"height\\":\\"100%\\",\\"position\\":\\"absolute\\"}},\\"children\\":[{\\"type\\":\\"Image\\",\\"props\\":{\\"source\\":{\\"uri\\":\\"message_empty_light\\"},\\"style\\":[{\\"position\\":\\"absolute\\",\\"left\\":0,\\"right\\":0,\\"top\\":0,\\"bottom\\":0},{\\"width\\":\\"100%\\",\\"height\\":\\"100%\\"},null]},\\"children\\":null}]},{\\"type\\":\\"ActivityIndicator\\",\\"props\\":{\\"animating\\":true,\\"color\\":\\"#6C727A\\",\\"hidesWhenStopped\\":true,\\"size\\":\\"small\\",\\"style\\":{\\"position\\":\\"absolute\\",\\"top\\":60,\\"left\\":0,\\"right\\":0,\\"fontSize\\":16,\\"paddingHorizontal\\":24,\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\"}},\\"children\\":null}]}"`;
+
+exports[`Storyshots BackgroundContainer long text 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessibilityIgnoresInvertColors\\":true,\\"style\\":{\\"width\\":\\"100%\\",\\"height\\":\\"100%\\",\\"position\\":\\"absolute\\"}},\\"children\\":[{\\"type\\":\\"Image\\",\\"props\\":{\\"source\\":{\\"uri\\":\\"message_empty_light\\"},\\"style\\":[{\\"position\\":\\"absolute\\",\\"left\\":0,\\"right\\":0,\\"top\\":0,\\"bottom\\":0},{\\"width\\":\\"100%\\",\\"height\\":\\"100%\\"},null]},\\"children\\":null}]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"position\\":\\"absolute\\",\\"top\\":60,\\"left\\":0,\\"right\\":0,\\"fontSize\\":16,\\"paddingHorizontal\\":24,\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\"},{\\"color\\":\\"#6C727A\\"}]},\\"children\\":[\\"Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries\\"]}]}"`;
+
+exports[`Storyshots BackgroundContainer text 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessibilityIgnoresInvertColors\\":true,\\"style\\":{\\"width\\":\\"100%\\",\\"height\\":\\"100%\\",\\"position\\":\\"absolute\\"}},\\"children\\":[{\\"type\\":\\"Image\\",\\"props\\":{\\"source\\":{\\"uri\\":\\"message_empty_light\\"},\\"style\\":[{\\"position\\":\\"absolute\\",\\"left\\":0,\\"right\\":0,\\"top\\":0,\\"bottom\\":0},{\\"width\\":\\"100%\\",\\"height\\":\\"100%\\"},null]},\\"children\\":null}]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"position\\":\\"absolute\\",\\"top\\":60,\\"left\\":0,\\"right\\":0,\\"fontSize\\":16,\\"paddingHorizontal\\":24,\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\"},{\\"color\\":\\"#6C727A\\"}]},\\"children\\":[\\"Text here\\"]}]}"`;
diff --git a/app/containers/BackgroundContainer/index.tsx b/app/containers/BackgroundContainer/index.tsx
index fc26fe0ab..a485611c0 100644
--- a/app/containers/BackgroundContainer/index.tsx
+++ b/app/containers/BackgroundContainer/index.tsx
@@ -35,7 +35,7 @@ const styles = StyleSheet.create({
const BackgroundContainer = ({ theme, text, loading }: IBackgroundContainer) => (
- {text ? {text} : null}
+ {text && !loading ? {text} : null}
{loading ? : null}
);
diff --git a/app/containers/EmojiPicker/CustomEmoji.tsx b/app/containers/EmojiPicker/CustomEmoji.tsx
index f64fa2ae5..c96ee7ae5 100644
--- a/app/containers/EmojiPicker/CustomEmoji.tsx
+++ b/app/containers/EmojiPicker/CustomEmoji.tsx
@@ -1,7 +1,7 @@
import React from 'react';
import FastImage from '@rocket.chat/react-native-fast-image';
-import { ICustomEmoji } from './interfaces';
+import { ICustomEmoji } from '../../definitions/IEmoji';
const CustomEmoji = React.memo(
({ baseUrl, emoji, style }: ICustomEmoji) => (
diff --git a/app/containers/EmojiPicker/EmojiCategory.tsx b/app/containers/EmojiPicker/EmojiCategory.tsx
index 62285fe87..6fd4439f0 100644
--- a/app/containers/EmojiPicker/EmojiCategory.tsx
+++ b/app/containers/EmojiPicker/EmojiCategory.tsx
@@ -5,7 +5,7 @@ import shortnameToUnicode from '../../utils/shortnameToUnicode';
import styles from './styles';
import CustomEmoji from './CustomEmoji';
import scrollPersistTaps from '../../utils/scrollPersistTaps';
-import { IEmoji, IEmojiCategory } from './interfaces';
+import { IEmoji, IEmojiCategory } from '../../definitions/IEmoji';
const EMOJI_SIZE = 50;
diff --git a/app/containers/EmojiPicker/index.tsx b/app/containers/EmojiPicker/index.tsx
index 12217cf95..5577fd190 100644
--- a/app/containers/EmojiPicker/index.tsx
+++ b/app/containers/EmojiPicker/index.tsx
@@ -17,7 +17,7 @@ import shortnameToUnicode from '../../utils/shortnameToUnicode';
import log from '../../utils/log';
import { themes } from '../../constants/colors';
import { withTheme } from '../../theme';
-import { IEmoji } from './interfaces';
+import { IEmoji } from '../../definitions/IEmoji';
const scrollProps = {
keyboardShouldPersistTaps: 'always',
@@ -36,7 +36,7 @@ interface IEmojiPickerProps {
}
interface IEmojiPickerState {
- frequentlyUsed: [];
+ frequentlyUsed: (string | { content?: string; extension?: string; isCustom: boolean })[];
customEmojis: any;
show: boolean;
width: number | null;
@@ -114,7 +114,7 @@ class EmojiPicker extends Component {
// Do nothing
}
- await db.action(async () => {
+ await db.write(async () => {
if (freqEmojiRecord) {
await freqEmojiRecord.update((f: any) => {
f.count += 1;
@@ -132,8 +132,8 @@ class EmojiPicker extends Component {
updateFrequentlyUsed = async () => {
const db = database.active;
const frequentlyUsedRecords = await db.get('frequently_used_emojis').query().fetch();
- let frequentlyUsed: any = orderBy(frequentlyUsedRecords, ['count'], ['desc']);
- frequentlyUsed = frequentlyUsed.map((item: IEmoji) => {
+ const frequentlyUsedOrdered = orderBy(frequentlyUsedRecords, ['count'], ['desc']);
+ const frequentlyUsed = frequentlyUsedOrdered.map(item => {
if (item.isCustom) {
return { content: item.content, extension: item.extension, isCustom: item.isCustom };
}
diff --git a/app/containers/MessageActions/Header.tsx b/app/containers/MessageActions/Header.tsx
index ace8d56b7..52165b8b7 100644
--- a/app/containers/MessageActions/Header.tsx
+++ b/app/containers/MessageActions/Header.tsx
@@ -10,7 +10,8 @@ import database from '../../lib/database';
import { Button } from '../ActionSheet';
import { useDimensions } from '../../dimensions';
import sharedStyles from '../../views/Styles';
-import { IEmoji } from '../EmojiPicker/interfaces';
+import { IEmoji } from '../../definitions/IEmoji';
+import { TFrequentlyUsedEmojiModel } from '../../definitions/IFrequentlyUsedEmoji';
interface IHeader {
handleReaction: Function;
@@ -90,14 +91,14 @@ const HeaderFooter = React.memo(({ onReaction, theme }: THeaderFooter) => (
));
const Header = React.memo(({ handleReaction, server, message, isMasterDetail, theme }: IHeader) => {
- const [items, setItems] = useState([]);
+ const [items, setItems] = useState<(TFrequentlyUsedEmojiModel | string)[]>([]);
const { width, height }: any = useDimensions();
const setEmojis = async () => {
try {
const db = database.active;
const freqEmojiCollection = db.get('frequently_used_emojis');
- let freqEmojis = await freqEmojiCollection.query().fetch();
+ let freqEmojis: (TFrequentlyUsedEmojiModel | string)[] = await freqEmojiCollection.query().fetch();
const isLandscape = width > height;
const size = (isLandscape || isMasterDetail ? width / 2 : width) - CONTAINER_MARGIN * 2;
diff --git a/app/containers/MessageActions/index.tsx b/app/containers/MessageActions/index.tsx
index 4237e2c1e..13a1e369d 100644
--- a/app/containers/MessageActions/index.tsx
+++ b/app/containers/MessageActions/index.tsx
@@ -15,6 +15,7 @@ import { showConfirmationAlert } from '../../utils/info';
import { useActionSheet } from '../ActionSheet';
import Header, { HEADER_HEIGHT } from './Header';
import events from '../../utils/log/events';
+import { TMessageModel } from '../../definitions/IMessage';
interface IMessageActions {
room: {
@@ -182,9 +183,9 @@ const MessageActions = React.memo(
if (result.success) {
const subCollection = db.get('subscriptions');
const subRecord = await subCollection.find(rid);
- await db.action(async () => {
+ await db.write(async () => {
try {
- await subRecord.update((sub: any) => (sub.lastOpen = ts));
+ await subRecord.update(sub => (sub.lastOpen = ts));
} catch {
// do nothing
}
@@ -269,11 +270,11 @@ const MessageActions = React.memo(
}
};
- const handleToggleTranslation = async (message: any) => {
+ const handleToggleTranslation = async (message: TMessageModel) => {
try {
const db = database.active;
- await db.action(async () => {
- await message.update((m: any) => {
+ await db.write(async () => {
+ await message.update(m => {
m.autoTranslate = !m.autoTranslate;
m._updatedAt = new Date();
});
@@ -282,7 +283,7 @@ const MessageActions = React.memo(
if (!translatedMessage) {
const m = {
_id: message.id,
- rid: message.subscription.id,
+ rid: message.subscription ? message.subscription.id : '',
u: message.u,
msg: message.msg
};
@@ -320,7 +321,7 @@ const MessageActions = React.memo(
});
};
- const getOptions = (message: any) => {
+ const getOptions = (message: TMessageModel) => {
let options: any = [];
// Reply
@@ -446,7 +447,7 @@ const MessageActions = React.memo(
return options;
};
- const showMessageActions = async (message: any) => {
+ const showMessageActions = async (message: TMessageModel) => {
logEvent(events.ROOM_SHOW_MSG_ACTIONS);
await getPermissions();
showActionSheet({
diff --git a/app/containers/MessageBox/EmojiKeyboard.tsx b/app/containers/MessageBox/EmojiKeyboard.tsx
index 91acc45d1..e5bc65a6e 100644
--- a/app/containers/MessageBox/EmojiKeyboard.tsx
+++ b/app/containers/MessageBox/EmojiKeyboard.tsx
@@ -2,7 +2,7 @@ import React from 'react';
import { View } from 'react-native';
import { KeyboardRegistry } from 'react-native-ui-lib/keyboard';
-import store from '../../lib/createStore';
+import { store } from '../../lib/auxStore';
import EmojiPicker from '../EmojiPicker';
import styles from './styles';
import { themes } from '../../constants/colors';
diff --git a/app/containers/MessageBox/Mentions/MentionEmoji.tsx b/app/containers/MessageBox/Mentions/MentionEmoji.tsx
index 5e012c136..851b857b3 100644
--- a/app/containers/MessageBox/Mentions/MentionEmoji.tsx
+++ b/app/containers/MessageBox/Mentions/MentionEmoji.tsx
@@ -6,7 +6,7 @@ import shortnameToUnicode from '../../../utils/shortnameToUnicode';
import styles from '../styles';
import MessageboxContext from '../Context';
import CustomEmoji from '../../EmojiPicker/CustomEmoji';
-import { IEmoji } from '../../EmojiPicker/interfaces';
+import { IEmoji } from '../../../definitions/IEmoji';
interface IMessageBoxMentionEmoji {
item: IEmoji;
diff --git a/app/containers/MessageBox/Mentions/MentionItem.tsx b/app/containers/MessageBox/Mentions/MentionItem.tsx
index c315b9250..6c78bfe9f 100644
--- a/app/containers/MessageBox/Mentions/MentionItem.tsx
+++ b/app/containers/MessageBox/Mentions/MentionItem.tsx
@@ -8,7 +8,7 @@ import FixedMentionItem from './FixedMentionItem';
import MentionEmoji from './MentionEmoji';
import { MENTIONS_TRACKING_TYPE_EMOJIS, MENTIONS_TRACKING_TYPE_COMMANDS, MENTIONS_TRACKING_TYPE_CANNED } from '../constants';
import { themes } from '../../../constants/colors';
-import { IEmoji } from '../../EmojiPicker/interfaces';
+import { IEmoji } from '../../../definitions/IEmoji';
interface IMessageBoxMentionItem {
item: {
diff --git a/app/containers/MessageBox/ReplyPreview.tsx b/app/containers/MessageBox/ReplyPreview.tsx
index c6681af0a..4029580dc 100644
--- a/app/containers/MessageBox/ReplyPreview.tsx
+++ b/app/containers/MessageBox/ReplyPreview.tsx
@@ -3,10 +3,11 @@ import { StyleSheet, Text, View } from 'react-native';
import moment from 'moment';
import { connect } from 'react-redux';
-import Markdown from '../markdown';
+import { MarkdownPreview } from '../markdown';
import { CustomIcon } from '../../lib/Icons';
import sharedStyles from '../../views/Styles';
import { themes } from '../../constants/colors';
+import { IMessage } from '../../definitions/IMessage';
const styles = StyleSheet.create({
container: {
@@ -42,11 +43,7 @@ const styles = StyleSheet.create({
interface IMessageBoxReplyPreview {
replying: boolean;
- message: {
- ts: Date;
- msg: string;
- u: any;
- };
+ message: IMessage;
Message_TimeFormat: string;
close(): void;
baseUrl: string;
@@ -57,17 +54,7 @@ interface IMessageBoxReplyPreview {
}
const ReplyPreview = React.memo(
- ({
- message,
- Message_TimeFormat,
- baseUrl,
- username,
- replying,
- getCustomEmoji,
- close,
- theme,
- useRealName
- }: IMessageBoxReplyPreview) => {
+ ({ message, Message_TimeFormat, replying, close, theme, useRealName }: IMessageBoxReplyPreview) => {
if (!replying) {
return null;
}
@@ -82,16 +69,7 @@ const ReplyPreview = React.memo(
{time}
- {/* @ts-ignore*/}
-
+
diff --git a/app/containers/MessageBox/forceJpgExtension.test.ts b/app/containers/MessageBox/forceJpgExtension.test.ts
new file mode 100644
index 000000000..b1031c21c
--- /dev/null
+++ b/app/containers/MessageBox/forceJpgExtension.test.ts
@@ -0,0 +1,107 @@
+import { Image } from 'react-native-image-crop-picker';
+
+import { forceJpgExtension } from './forceJpgExtension';
+
+const attachment: Image = {
+ exif: null,
+ filename: 'IMG_0040.PNG',
+ path: 'tmp/temp',
+ height: 534,
+ width: 223,
+ data: null,
+ modificationDate: '1643984790',
+ localIdentifier: 'device/L0/001',
+ size: 16623,
+ sourceURL: '',
+ mime: 'image/jpeg',
+ cropRect: null,
+ creationDate: '1641490665'
+};
+
+describe('forceJpgExtension for iOS', () => {
+ jest.mock('react-native', () => ({ Platform: { OS: 'ios' } }));
+ describe('with mime as image/jpeg', () => {
+ test('filename.jpg should be filename.jpg', () => {
+ const newAttachment = attachment;
+ newAttachment.filename = 'filename.jpg';
+ const file = forceJpgExtension(newAttachment);
+
+ expect(file.filename).toBe('filename.jpg');
+ });
+ test('filename.png should be filename.jpg', () => {
+ const newAttachment = attachment;
+ newAttachment.filename = 'filename.png';
+ const file = forceJpgExtension(newAttachment);
+
+ expect(file.filename).toBe('filename.jpg');
+ });
+ test('filename.jpeg should be filename.jpg', () => {
+ const newAttachment = attachment;
+ newAttachment.filename = 'filename.jpeg';
+ const file = forceJpgExtension(newAttachment);
+
+ expect(file.filename).toBe('filename.jpg');
+ });
+ test('filename.heic should be filename.jpg', () => {
+ const newAttachment = attachment;
+ newAttachment.filename = 'filename.heic';
+ const file = forceJpgExtension(newAttachment);
+
+ expect(file.filename).toBe('filename.jpg');
+ });
+ });
+ describe('with mime different', () => {
+ test('filename.jpg should be filename.jpg', () => {
+ const newAttachment = attachment;
+ newAttachment.filename = 'filename.png';
+ newAttachment.mime = 'image/png';
+ const file = forceJpgExtension(newAttachment);
+
+ expect(file.filename).toBe('filename.png');
+ });
+ });
+});
+
+describe('forceJpgExtension for android', () => {
+ jest.mock('react-native', () => ({ Platform: { OS: 'android' } }));
+ describe('with mime as image/jpeg', () => {
+ test('filename.jpg should be filename.jpg', () => {
+ const newAttachment = attachment;
+ newAttachment.filename = 'filename.jpg';
+ const file = forceJpgExtension(newAttachment);
+
+ expect(file.filename).toBe('filename.jpg');
+ });
+ test('filename.png should be filename.png', () => {
+ const newAttachment = attachment;
+ newAttachment.filename = 'filename.png';
+ const file = forceJpgExtension(newAttachment);
+
+ expect(file.filename).toBe('filename.png');
+ });
+ test('filename.jpeg should be filename.jpeg', () => {
+ const newAttachment = attachment;
+ newAttachment.filename = 'filename.jpeg';
+ const file = forceJpgExtension(newAttachment);
+
+ expect(file.filename).toBe('filename.jpeg');
+ });
+ test('filename.heic should be filename.heic', () => {
+ const newAttachment = attachment;
+ newAttachment.filename = 'filename.heic';
+ const file = forceJpgExtension(newAttachment);
+
+ expect(file.filename).toBe('filename.heic');
+ });
+ });
+ describe('with mime different', () => {
+ test('filename.jpg should be filename.jpg', () => {
+ const newAttachment = attachment;
+ newAttachment.filename = 'filename.png';
+ newAttachment.mime = 'image/png';
+ const file = forceJpgExtension(newAttachment);
+
+ expect(file.filename).toBe('filename.png');
+ });
+ });
+});
diff --git a/app/containers/MessageBox/forceJpgExtension.ts b/app/containers/MessageBox/forceJpgExtension.ts
new file mode 100644
index 000000000..68e49c876
--- /dev/null
+++ b/app/containers/MessageBox/forceJpgExtension.ts
@@ -0,0 +1,13 @@
+import { ImageOrVideo } from 'react-native-image-crop-picker';
+
+import { isIOS } from '../../utils/deviceInfo';
+
+const regex = new RegExp(/\.[^/.]+$/); // Check from last '.' of the string
+
+export const forceJpgExtension = (attachment: ImageOrVideo): ImageOrVideo => {
+ if (isIOS && attachment.mime === 'image/jpeg' && attachment.filename) {
+ // Replace files extension that mime type is 'image/jpeg' to .jpg;
+ attachment.filename = attachment.filename.replace(regex, '.jpg');
+ }
+ return attachment;
+};
diff --git a/app/containers/MessageBox/index.tsx b/app/containers/MessageBox/index.tsx
index fbcf24cd2..3c924df6f 100644
--- a/app/containers/MessageBox/index.tsx
+++ b/app/containers/MessageBox/index.tsx
@@ -27,7 +27,7 @@ import LeftButtons from './LeftButtons';
// @ts-ignore
// eslint-disable-next-line import/extensions,import/no-unresolved
import RightButtons from './RightButtons';
-import { isAndroid, isIOS, isTablet } from '../../utils/deviceInfo';
+import { isAndroid, isTablet } from '../../utils/deviceInfo';
import { canUploadFile } from '../../utils/media';
import EventEmiter from '../../utils/events';
import { KEY_COMMAND, handleCommandShowUpload, handleCommandSubmit, handleCommandTyping } from '../../commands';
@@ -47,6 +47,8 @@ import Navigation from '../../lib/Navigation';
import { withActionSheet } from '../ActionSheet';
import { sanitizeLikeString } from '../../lib/database/utils';
import { CustomIcon } from '../../lib/Icons';
+import { IMessage } from '../../definitions/IMessage';
+import { forceJpgExtension } from './forceJpgExtension';
if (isAndroid) {
require('./EmojiKeyboard');
@@ -73,18 +75,14 @@ const videoPickerConfig = {
interface IMessageBoxProps {
rid: string;
baseUrl: string;
- message: {
- u: {
- username: string;
- };
- id: any;
- };
+ message: IMessage;
replying: boolean;
editing: boolean;
threadsEnabled: boolean;
isFocused(): boolean;
user: {
id: string;
+ _id: string;
username: string;
token: string;
};
@@ -130,18 +128,6 @@ interface IMessageBoxState {
permissionToUpload: boolean;
}
-const forceJpgExtension = (attachment: ImageOrVideo) => {
- if (isIOS && attachment.mime === 'image/jpeg' && attachment.filename) {
- const regex = new RegExp(/.heic$/i);
- if (attachment.filename.match(regex)) {
- attachment.filename = attachment.filename.replace(regex, '.jpg');
- } else {
- attachment.filename += '.jpg';
- }
- }
- return attachment;
-};
-
class MessageBox extends Component {
private text: string;
@@ -1083,7 +1069,6 @@ class MessageBox extends Component {
const replyPreview = !recording ? (
({
const dispatchToProps = {
typing: (rid: any, status: any) => userTypingAction(rid, status)
};
-// @ts-ignore
+
export default connect(mapStateToProps, dispatchToProps, null, { forwardRef: true })(withActionSheet(MessageBox)) as any;
diff --git a/app/containers/MessageErrorActions.tsx b/app/containers/MessageErrorActions.tsx
index 6f56e54db..40b57536e 100644
--- a/app/containers/MessageErrorActions.tsx
+++ b/app/containers/MessageErrorActions.tsx
@@ -36,7 +36,7 @@ const MessageErrorActions = forwardRef(({ tmid }: any, ref): any => {
try {
// Find the thread header and update it
const msg = await msgCollection.find(tmid);
- if (msg.tcount <= 1) {
+ if (msg?.tcount && msg.tcount <= 1) {
deleteBatch.push(
msg.prepareUpdate((m: any) => {
m.tcount = null;
@@ -62,7 +62,7 @@ const MessageErrorActions = forwardRef(({ tmid }: any, ref): any => {
// Do nothing: message not found
}
}
- await db.action(async () => {
+ await db.write(async () => {
await db.batch(...deleteBatch);
});
} catch (e) {
diff --git a/app/containers/ReactionsModal.tsx b/app/containers/ReactionsModal.tsx
index 9cd2ff0ef..e55f418d5 100644
--- a/app/containers/ReactionsModal.tsx
+++ b/app/containers/ReactionsModal.tsx
@@ -9,6 +9,7 @@ import { CustomIcon } from '../lib/Icons';
import sharedStyles from '../views/Styles';
import { themes } from '../constants/colors';
import { withTheme } from '../theme';
+import { TGetCustomEmoji } from '../definitions/IEmoji';
import SafeAreaView from './SafeAreaView';
const styles = StyleSheet.create({
@@ -66,7 +67,7 @@ interface IItem {
};
user?: { username: any };
baseUrl?: string;
- getCustomEmoji?: Function;
+ getCustomEmoji?: TGetCustomEmoji;
theme?: string;
}
diff --git a/app/containers/RoomHeader/RoomHeader.tsx b/app/containers/RoomHeader/RoomHeader.tsx
index 719725fec..bb0952b38 100644
--- a/app/containers/RoomHeader/RoomHeader.tsx
+++ b/app/containers/RoomHeader/RoomHeader.tsx
@@ -4,7 +4,7 @@ import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import I18n from '../../i18n';
import sharedStyles from '../../views/Styles';
import { themes } from '../../constants/colors';
-import Markdown from '../markdown';
+import { MarkdownPreview } from '../markdown';
import RoomTypeIcon from '../RoomTypeIcon';
import { withTheme } from '../../theme';
@@ -101,16 +101,7 @@ const SubTitle = React.memo(({ usersTyping, subtitle, renderFunc, theme, scale }
// subtitle
if (subtitle) {
- return (
- // @ts-ignore
-
- );
+ return ;
}
return null;
@@ -126,10 +117,7 @@ const HeaderTitle = React.memo(({ title, tmid, prid, scale, theme, testID }: TRo
);
}
- return (
- // @ts-ignore
-
- );
+ return ;
});
const Header = React.memo(
diff --git a/app/containers/RoomHeader/__snapshots__/RoomHeader.stories.storyshot b/app/containers/RoomHeader/__snapshots__/RoomHeader.stories.storyshot
new file mode 100644
index 000000000..ad19e0a0a
--- /dev/null
+++ b/app/containers/RoomHeader/__snapshots__/RoomHeader.stories.storyshot
@@ -0,0 +1,13 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Storyshots RoomHeader icons 1`] = `"[{\\"type\\":\\"RNCSafeAreaView\\",\\"props\\":{\\"style\\":{\\"backgroundColor\\":\\"#EEEFF1\\"},\\"edges\\":[\\"top\\",\\"left\\",\\"right\\"]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":44,\\"flexDirection\\":\\"row\\",\\"justifyContent\\":\\"center\\",\\"elevation\\":4},{\\"borderBottomWidth\\":0.5,\\"borderBottomColor\\":\\"#B2B2B2\\",\\"elevation\\":0,\\"backgroundColor\\":\\"#EEEFF1\\"}]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1,\\"paddingHorizontal\\":12}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"private channel\\",\\"testID\\":\\"room-header\\",\\"hitSlop\\":{\\"top\\":5,\\"right\\":5,\\"bottom\\":5,\\"left\\":5},\\"focusable\\":true,\\"style\\":{\\"flex\\":1,\\"justifyContent\\":\\"center\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"alignItems\\":\\"center\\",\\"flexDirection\\":\\"row\\"}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":16},[{\\"marginRight\\":4},{\\"color\\":\\"#0d0e12\\"},null],{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"flexShrink\\":1,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"600\\"},{\\"fontSize\\":16,\\"color\\":\\"#0C0D0F\\"}],\\"numberOfLines\\":1,\\"testID\\":\\"private channel\\"},\\"children\\":[\\"private channel\\"]}]}]}]}]}]},{\\"type\\":\\"RNCSafeAreaView\\",\\"props\\":{\\"style\\":{\\"backgroundColor\\":\\"#EEEFF1\\"},\\"edges\\":[\\"top\\",\\"left\\",\\"right\\"]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":44,\\"flexDirection\\":\\"row\\",\\"justifyContent\\":\\"center\\",\\"elevation\\":4},{\\"borderBottomWidth\\":0.5,\\"borderBottomColor\\":\\"#B2B2B2\\",\\"elevation\\":0,\\"backgroundColor\\":\\"#EEEFF1\\"}]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1,\\"paddingHorizontal\\":12}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"public channel\\",\\"testID\\":\\"room-header\\",\\"hitSlop\\":{\\"top\\":5,\\"right\\":5,\\"bottom\\":5,\\"left\\":5},\\"focusable\\":true,\\"style\\":{\\"flex\\":1,\\"justifyContent\\":\\"center\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"alignItems\\":\\"center\\",\\"flexDirection\\":\\"row\\"}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":16},[{\\"marginRight\\":4},{\\"color\\":\\"#0d0e12\\"},null],{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"flexShrink\\":1,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"600\\"},{\\"fontSize\\":16,\\"color\\":\\"#0C0D0F\\"}],\\"numberOfLines\\":1,\\"testID\\":\\"public channel\\"},\\"children\\":[\\"public channel\\"]}]}]}]}]}]},{\\"type\\":\\"RNCSafeAreaView\\",\\"props\\":{\\"style\\":{\\"backgroundColor\\":\\"#EEEFF1\\"},\\"edges\\":[\\"top\\",\\"left\\",\\"right\\"]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":44,\\"flexDirection\\":\\"row\\",\\"justifyContent\\":\\"center\\",\\"elevation\\":4},{\\"borderBottomWidth\\":0.5,\\"borderBottomColor\\":\\"#B2B2B2\\",\\"elevation\\":0,\\"backgroundColor\\":\\"#EEEFF1\\"}]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1,\\"paddingHorizontal\\":12}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"discussion\\",\\"testID\\":\\"room-header\\",\\"hitSlop\\":{\\"top\\":5,\\"right\\":5,\\"bottom\\":5,\\"left\\":5},\\"focusable\\":true,\\"style\\":{\\"flex\\":1,\\"justifyContent\\":\\"center\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"alignItems\\":\\"center\\",\\"flexDirection\\":\\"row\\"}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":16},[{\\"marginRight\\":4},{\\"color\\":\\"#0d0e12\\"},null],{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"accessibilityLabel\\":\\"discussion\\",\\"style\\":[{\\"fontSize\\":16,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\"},{\\"color\\":\\"#2f343d\\"},{\\"flexShrink\\":1,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"600\\"},{\\"fontSize\\":16,\\"color\\":\\"#0C0D0F\\"}],\\"numberOfLines\\":1,\\"testID\\":\\"discussion\\"},\\"children\\":[\\"discussion\\"]}]}]}]}]}]},{\\"type\\":\\"RNCSafeAreaView\\",\\"props\\":{\\"style\\":{\\"backgroundColor\\":\\"#EEEFF1\\"},\\"edges\\":[\\"top\\",\\"left\\",\\"right\\"]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":44,\\"flexDirection\\":\\"row\\",\\"justifyContent\\":\\"center\\",\\"elevation\\":4},{\\"borderBottomWidth\\":0.5,\\"borderBottomColor\\":\\"#B2B2B2\\",\\"elevation\\":0,\\"backgroundColor\\":\\"#EEEFF1\\"}]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1,\\"paddingHorizontal\\":12}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"omnichannel\\",\\"testID\\":\\"room-header\\",\\"hitSlop\\":{\\"top\\":5,\\"right\\":5,\\"bottom\\":5,\\"left\\":5},\\"focusable\\":true,\\"style\\":{\\"flex\\":1,\\"justifyContent\\":\\"center\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"alignItems\\":\\"center\\",\\"flexDirection\\":\\"row\\"}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":16},[{\\"marginRight\\":4},{\\"color\\":\\"#0d0e12\\"},null],{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"flexShrink\\":1,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"600\\"},{\\"fontSize\\":16,\\"color\\":\\"#0C0D0F\\"}],\\"numberOfLines\\":1,\\"testID\\":\\"omnichannel\\"},\\"children\\":[\\"omnichannel\\"]}]}]}]}]}]},{\\"type\\":\\"RNCSafeAreaView\\",\\"props\\":{\\"style\\":{\\"backgroundColor\\":\\"#EEEFF1\\"},\\"edges\\":[\\"top\\",\\"left\\",\\"right\\"]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":44,\\"flexDirection\\":\\"row\\",\\"justifyContent\\":\\"center\\",\\"elevation\\":4},{\\"borderBottomWidth\\":0.5,\\"borderBottomColor\\":\\"#B2B2B2\\",\\"elevation\\":0,\\"backgroundColor\\":\\"#EEEFF1\\"}]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1,\\"paddingHorizontal\\":12}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"private team\\",\\"testID\\":\\"room-header\\",\\"hitSlop\\":{\\"top\\":5,\\"right\\":5,\\"bottom\\":5,\\"left\\":5},\\"focusable\\":true,\\"style\\":{\\"flex\\":1,\\"justifyContent\\":\\"center\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"alignItems\\":\\"center\\",\\"flexDirection\\":\\"row\\"}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":16},[{\\"marginRight\\":4},{\\"color\\":\\"#0d0e12\\"},null],{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"flexShrink\\":1,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"600\\"},{\\"fontSize\\":16,\\"color\\":\\"#0C0D0F\\"}],\\"numberOfLines\\":1,\\"testID\\":\\"private team\\"},\\"children\\":[\\"private team\\"]}]}]}]}]}]},{\\"type\\":\\"RNCSafeAreaView\\",\\"props\\":{\\"style\\":{\\"backgroundColor\\":\\"#EEEFF1\\"},\\"edges\\":[\\"top\\",\\"left\\",\\"right\\"]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":44,\\"flexDirection\\":\\"row\\",\\"justifyContent\\":\\"center\\",\\"elevation\\":4},{\\"borderBottomWidth\\":0.5,\\"borderBottomColor\\":\\"#B2B2B2\\",\\"elevation\\":0,\\"backgroundColor\\":\\"#EEEFF1\\"}]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1,\\"paddingHorizontal\\":12}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"public team\\",\\"testID\\":\\"room-header\\",\\"hitSlop\\":{\\"top\\":5,\\"right\\":5,\\"bottom\\":5,\\"left\\":5},\\"focusable\\":true,\\"style\\":{\\"flex\\":1,\\"justifyContent\\":\\"center\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"alignItems\\":\\"center\\",\\"flexDirection\\":\\"row\\"}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":16},[{\\"marginRight\\":4},{\\"color\\":\\"#0d0e12\\"},null],{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"flexShrink\\":1,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"600\\"},{\\"fontSize\\":16,\\"color\\":\\"#0C0D0F\\"}],\\"numberOfLines\\":1,\\"testID\\":\\"public team\\"},\\"children\\":[\\"public team\\"]}]}]}]}]}]},{\\"type\\":\\"RNCSafeAreaView\\",\\"props\\":{\\"style\\":{\\"backgroundColor\\":\\"#EEEFF1\\"},\\"edges\\":[\\"top\\",\\"left\\",\\"right\\"]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":44,\\"flexDirection\\":\\"row\\",\\"justifyContent\\":\\"center\\",\\"elevation\\":4},{\\"borderBottomWidth\\":0.5,\\"borderBottomColor\\":\\"#B2B2B2\\",\\"elevation\\":0,\\"backgroundColor\\":\\"#EEEFF1\\"}]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1,\\"paddingHorizontal\\":12}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"group dm\\",\\"testID\\":\\"room-header\\",\\"hitSlop\\":{\\"top\\":5,\\"right\\":5,\\"bottom\\":5,\\"left\\":5},\\"focusable\\":true,\\"style\\":{\\"flex\\":1,\\"justifyContent\\":\\"center\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"alignItems\\":\\"center\\",\\"flexDirection\\":\\"row\\"}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":16},[{\\"marginRight\\":4},{\\"color\\":\\"#0d0e12\\"},null],{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"flexShrink\\":1,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"600\\"},{\\"fontSize\\":16,\\"color\\":\\"#0C0D0F\\"}],\\"numberOfLines\\":1,\\"testID\\":\\"group dm\\"},\\"children\\":[\\"group dm\\"]}]}]}]}]}]},{\\"type\\":\\"RNCSafeAreaView\\",\\"props\\":{\\"style\\":{\\"backgroundColor\\":\\"#EEEFF1\\"},\\"edges\\":[\\"top\\",\\"left\\",\\"right\\"]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":44,\\"flexDirection\\":\\"row\\",\\"justifyContent\\":\\"center\\",\\"elevation\\":4},{\\"borderBottomWidth\\":0.5,\\"borderBottomColor\\":\\"#B2B2B2\\",\\"elevation\\":0,\\"backgroundColor\\":\\"#EEEFF1\\"}]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1,\\"paddingHorizontal\\":12}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"online dm\\",\\"testID\\":\\"room-header\\",\\"hitSlop\\":{\\"top\\":5,\\"right\\":5,\\"bottom\\":5,\\"left\\":5},\\"focusable\\":true,\\"style\\":{\\"flex\\":1,\\"justifyContent\\":\\"center\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"alignItems\\":\\"center\\",\\"flexDirection\\":\\"row\\"}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":16,\\"color\\":\\"#2de0a5\\"},[{\\"width\\":16,\\"height\\":16,\\"textAlignVertical\\":\\"center\\"},[[{\\"marginRight\\":4},{\\"color\\":\\"#0d0e12\\"},null],{\\"color\\":\\"#2de0a5\\"}]],{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"flexShrink\\":1,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"600\\"},{\\"fontSize\\":16,\\"color\\":\\"#0C0D0F\\"}],\\"numberOfLines\\":1,\\"testID\\":\\"online dm\\"},\\"children\\":[\\"online dm\\"]}]}]}]}]}]},{\\"type\\":\\"RNCSafeAreaView\\",\\"props\\":{\\"style\\":{\\"backgroundColor\\":\\"#EEEFF1\\"},\\"edges\\":[\\"top\\",\\"left\\",\\"right\\"]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":44,\\"flexDirection\\":\\"row\\",\\"justifyContent\\":\\"center\\",\\"elevation\\":4},{\\"borderBottomWidth\\":0.5,\\"borderBottomColor\\":\\"#B2B2B2\\",\\"elevation\\":0,\\"backgroundColor\\":\\"#EEEFF1\\"}]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1,\\"paddingHorizontal\\":12}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"away dm\\",\\"testID\\":\\"room-header\\",\\"hitSlop\\":{\\"top\\":5,\\"right\\":5,\\"bottom\\":5,\\"left\\":5},\\"focusable\\":true,\\"style\\":{\\"flex\\":1,\\"justifyContent\\":\\"center\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"alignItems\\":\\"center\\",\\"flexDirection\\":\\"row\\"}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":16,\\"color\\":\\"#ffd21f\\"},[{\\"width\\":16,\\"height\\":16,\\"textAlignVertical\\":\\"center\\"},[[{\\"marginRight\\":4},{\\"color\\":\\"#0d0e12\\"},null],{\\"color\\":\\"#ffd21f\\"}]],{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"flexShrink\\":1,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"600\\"},{\\"fontSize\\":16,\\"color\\":\\"#0C0D0F\\"}],\\"numberOfLines\\":1,\\"testID\\":\\"away dm\\"},\\"children\\":[\\"away dm\\"]}]}]}]}]}]},{\\"type\\":\\"RNCSafeAreaView\\",\\"props\\":{\\"style\\":{\\"backgroundColor\\":\\"#EEEFF1\\"},\\"edges\\":[\\"top\\",\\"left\\",\\"right\\"]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":44,\\"flexDirection\\":\\"row\\",\\"justifyContent\\":\\"center\\",\\"elevation\\":4},{\\"borderBottomWidth\\":0.5,\\"borderBottomColor\\":\\"#B2B2B2\\",\\"elevation\\":0,\\"backgroundColor\\":\\"#EEEFF1\\"}]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1,\\"paddingHorizontal\\":12}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"busy dm\\",\\"testID\\":\\"room-header\\",\\"hitSlop\\":{\\"top\\":5,\\"right\\":5,\\"bottom\\":5,\\"left\\":5},\\"focusable\\":true,\\"style\\":{\\"flex\\":1,\\"justifyContent\\":\\"center\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"alignItems\\":\\"center\\",\\"flexDirection\\":\\"row\\"}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":16,\\"color\\":\\"#f5455c\\"},[{\\"width\\":16,\\"height\\":16,\\"textAlignVertical\\":\\"center\\"},[[{\\"marginRight\\":4},{\\"color\\":\\"#0d0e12\\"},null],{\\"color\\":\\"#f5455c\\"}]],{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"flexShrink\\":1,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"600\\"},{\\"fontSize\\":16,\\"color\\":\\"#0C0D0F\\"}],\\"numberOfLines\\":1,\\"testID\\":\\"busy dm\\"},\\"children\\":[\\"busy dm\\"]}]}]}]}]}]},{\\"type\\":\\"RNCSafeAreaView\\",\\"props\\":{\\"style\\":{\\"backgroundColor\\":\\"#EEEFF1\\"},\\"edges\\":[\\"top\\",\\"left\\",\\"right\\"]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":44,\\"flexDirection\\":\\"row\\",\\"justifyContent\\":\\"center\\",\\"elevation\\":4},{\\"borderBottomWidth\\":0.5,\\"borderBottomColor\\":\\"#B2B2B2\\",\\"elevation\\":0,\\"backgroundColor\\":\\"#EEEFF1\\"}]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1,\\"paddingHorizontal\\":12}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"loading dm\\",\\"testID\\":\\"room-header\\",\\"hitSlop\\":{\\"top\\":5,\\"right\\":5,\\"bottom\\":5,\\"left\\":5},\\"focusable\\":true,\\"style\\":{\\"flex\\":1,\\"justifyContent\\":\\"center\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"alignItems\\":\\"center\\",\\"flexDirection\\":\\"row\\"}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":16,\\"color\\":\\"#9ea2a8\\"},[{\\"width\\":16,\\"height\\":16,\\"textAlignVertical\\":\\"center\\"},[[{\\"marginRight\\":4},{\\"color\\":\\"#0d0e12\\"},null],{\\"color\\":\\"#9ea2a8\\"}]],{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"flexShrink\\":1,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"600\\"},{\\"fontSize\\":16,\\"color\\":\\"#0C0D0F\\"}],\\"numberOfLines\\":1,\\"testID\\":\\"loading dm\\"},\\"children\\":[\\"loading dm\\"]}]}]}]}]}]},{\\"type\\":\\"RNCSafeAreaView\\",\\"props\\":{\\"style\\":{\\"backgroundColor\\":\\"#EEEFF1\\"},\\"edges\\":[\\"top\\",\\"left\\",\\"right\\"]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":44,\\"flexDirection\\":\\"row\\",\\"justifyContent\\":\\"center\\",\\"elevation\\":4},{\\"borderBottomWidth\\":0.5,\\"borderBottomColor\\":\\"#B2B2B2\\",\\"elevation\\":0,\\"backgroundColor\\":\\"#EEEFF1\\"}]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1,\\"paddingHorizontal\\":12}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"offline dm\\",\\"testID\\":\\"room-header\\",\\"hitSlop\\":{\\"top\\":5,\\"right\\":5,\\"bottom\\":5,\\"left\\":5},\\"focusable\\":true,\\"style\\":{\\"flex\\":1,\\"justifyContent\\":\\"center\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"alignItems\\":\\"center\\",\\"flexDirection\\":\\"row\\"}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":16,\\"color\\":\\"#cbced1\\"},[{\\"width\\":16,\\"height\\":16,\\"textAlignVertical\\":\\"center\\"},[[{\\"marginRight\\":4},{\\"color\\":\\"#0d0e12\\"},null],{\\"color\\":\\"#cbced1\\"}]],{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"flexShrink\\":1,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"600\\"},{\\"fontSize\\":16,\\"color\\":\\"#0C0D0F\\"}],\\"numberOfLines\\":1,\\"testID\\":\\"offline dm\\"},\\"children\\":[\\"offline dm\\"]}]}]}]}]}]}]"`;
+
+exports[`Storyshots RoomHeader landscape 1`] = `"[{\\"type\\":\\"RNCSafeAreaView\\",\\"props\\":{\\"style\\":{\\"backgroundColor\\":\\"#EEEFF1\\"},\\"edges\\":[\\"top\\",\\"left\\",\\"right\\"]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":44,\\"flexDirection\\":\\"row\\",\\"justifyContent\\":\\"center\\",\\"elevation\\":4},{\\"borderBottomWidth\\":0.5,\\"borderBottomColor\\":\\"#B2B2B2\\",\\"elevation\\":0,\\"backgroundColor\\":\\"#EEEFF1\\"}]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1,\\"paddingHorizontal\\":12}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"title\\",\\"testID\\":\\"room-header\\",\\"hitSlop\\":{\\"top\\":5,\\"right\\":5,\\"bottom\\":5,\\"left\\":5},\\"focusable\\":true,\\"style\\":{\\"flex\\":1,\\"justifyContent\\":\\"center\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"alignItems\\":\\"center\\",\\"flexDirection\\":\\"row\\"}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":16},[{\\"marginRight\\":4},{\\"color\\":\\"#0d0e12\\"},null],{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"flexShrink\\":1,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"600\\"},{\\"fontSize\\":16,\\"color\\":\\"#0C0D0F\\"}],\\"numberOfLines\\":1},\\"children\\":[\\"title\\"]}]}]}]}]}]},{\\"type\\":\\"RNCSafeAreaView\\",\\"props\\":{\\"style\\":{\\"backgroundColor\\":\\"#EEEFF1\\"},\\"edges\\":[\\"top\\",\\"left\\",\\"right\\"]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":44,\\"flexDirection\\":\\"row\\",\\"justifyContent\\":\\"center\\",\\"elevation\\":4},{\\"borderBottomWidth\\":0.5,\\"borderBottomColor\\":\\"#B2B2B2\\",\\"elevation\\":0,\\"backgroundColor\\":\\"#EEEFF1\\"}]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1,\\"paddingHorizontal\\":12}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"title\\",\\"testID\\":\\"room-header\\",\\"hitSlop\\":{\\"top\\":5,\\"right\\":5,\\"bottom\\":5,\\"left\\":5},\\"focusable\\":true,\\"style\\":{\\"flex\\":1,\\"justifyContent\\":\\"center\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"alignItems\\":\\"center\\",\\"flexDirection\\":\\"row\\"}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":16},[{\\"marginRight\\":4},{\\"color\\":\\"#0d0e12\\"},null],{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"flexShrink\\":1,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"600\\"},{\\"fontSize\\":12.8,\\"color\\":\\"#0C0D0F\\"}],\\"numberOfLines\\":1},\\"children\\":[\\"title\\"]}]},{\\"type\\":\\"Text\\",\\"props\\":{\\"accessibilityLabel\\":\\"subtitle\\",\\"style\\":[{\\"fontSize\\":16,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\"},{\\"color\\":\\"#2f343d\\"},{\\"flexShrink\\":1,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\"},{\\"fontSize\\":9.600000000000001,\\"color\\":\\"#9ca2a8\\"}],\\"numberOfLines\\":1},\\"children\\":[\\"subtitle\\"]}]}]}]}]},{\\"type\\":\\"RNCSafeAreaView\\",\\"props\\":{\\"style\\":{\\"backgroundColor\\":\\"#EEEFF1\\"},\\"edges\\":[\\"top\\",\\"left\\",\\"right\\"]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":44,\\"flexDirection\\":\\"row\\",\\"justifyContent\\":\\"center\\",\\"elevation\\":4},{\\"borderBottomWidth\\":0.5,\\"borderBottomColor\\":\\"#B2B2B2\\",\\"elevation\\":0,\\"backgroundColor\\":\\"#EEEFF1\\"}]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1,\\"paddingHorizontal\\":12}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries\\",\\"testID\\":\\"room-header\\",\\"hitSlop\\":{\\"top\\":5,\\"right\\":5,\\"bottom\\":5,\\"left\\":5},\\"focusable\\":true,\\"style\\":{\\"flex\\":1,\\"justifyContent\\":\\"center\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"alignItems\\":\\"center\\",\\"flexDirection\\":\\"row\\"}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":16},[{\\"marginRight\\":4},{\\"color\\":\\"#0d0e12\\"},null],{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"flexShrink\\":1,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"600\\"},{\\"fontSize\\":12.8,\\"color\\":\\"#0C0D0F\\"}],\\"numberOfLines\\":1,\\"testID\\":\\"Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries\\"},\\"children\\":[\\"Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries\\"]}]},{\\"type\\":\\"Text\\",\\"props\\":{\\"accessibilityLabel\\":\\"Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries\\",\\"style\\":[{\\"fontSize\\":16,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\"},{\\"color\\":\\"#2f343d\\"},{\\"flexShrink\\":1,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\"},{\\"fontSize\\":9.600000000000001,\\"color\\":\\"#9ca2a8\\"}],\\"numberOfLines\\":1},\\"children\\":[\\"Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries\\"]}]}]}]}]}]"`;
+
+exports[`Storyshots RoomHeader themes 1`] = `"[{\\"type\\":\\"RNCSafeAreaView\\",\\"props\\":{\\"style\\":{\\"backgroundColor\\":\\"#EEEFF1\\"},\\"edges\\":[\\"top\\",\\"left\\",\\"right\\"]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":44,\\"flexDirection\\":\\"row\\",\\"justifyContent\\":\\"center\\",\\"elevation\\":4},{\\"borderBottomWidth\\":0.5,\\"borderBottomColor\\":\\"#B2B2B2\\",\\"elevation\\":0,\\"backgroundColor\\":\\"#EEEFF1\\"}]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1,\\"paddingHorizontal\\":12}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"title\\",\\"testID\\":\\"room-header\\",\\"hitSlop\\":{\\"top\\":5,\\"right\\":5,\\"bottom\\":5,\\"left\\":5},\\"focusable\\":true,\\"style\\":{\\"flex\\":1,\\"justifyContent\\":\\"center\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"alignItems\\":\\"center\\",\\"flexDirection\\":\\"row\\"}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":16},[{\\"marginRight\\":4},{\\"color\\":\\"#0d0e12\\"},null],{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"flexShrink\\":1,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"600\\"},{\\"fontSize\\":16,\\"color\\":\\"#0C0D0F\\"}],\\"numberOfLines\\":1},\\"children\\":[\\"title\\"]}]},{\\"type\\":\\"Text\\",\\"props\\":{\\"accessibilityLabel\\":\\"subtitle\\",\\"style\\":[{\\"fontSize\\":16,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\"},{\\"color\\":\\"#2f343d\\"},{\\"flexShrink\\":1,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\"},{\\"fontSize\\":12,\\"color\\":\\"#9ca2a8\\"}],\\"numberOfLines\\":1},\\"children\\":[\\"subtitle\\"]}]}]}]}]},{\\"type\\":\\"RNCSafeAreaView\\",\\"props\\":{\\"style\\":{\\"backgroundColor\\":\\"#0b182c\\"},\\"edges\\":[\\"top\\",\\"left\\",\\"right\\"]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":44,\\"flexDirection\\":\\"row\\",\\"justifyContent\\":\\"center\\",\\"elevation\\":4},{\\"borderBottomWidth\\":0.5,\\"borderBottomColor\\":\\"#2F3A4B\\",\\"elevation\\":0,\\"backgroundColor\\":\\"#0b182c\\"}]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1,\\"paddingHorizontal\\":12}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"title\\",\\"testID\\":\\"room-header\\",\\"hitSlop\\":{\\"top\\":5,\\"right\\":5,\\"bottom\\":5,\\"left\\":5},\\"focusable\\":true,\\"style\\":{\\"flex\\":1,\\"justifyContent\\":\\"center\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"alignItems\\":\\"center\\",\\"flexDirection\\":\\"row\\"}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":16},[{\\"marginRight\\":4},{\\"color\\":\\"#f9f9f9\\"},null],{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"flexShrink\\":1,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"600\\"},{\\"fontSize\\":16,\\"color\\":\\"#f9f9f9\\"}],\\"numberOfLines\\":1},\\"children\\":[\\"title\\"]}]},{\\"type\\":\\"Text\\",\\"props\\":{\\"accessibilityLabel\\":\\"subtitle\\",\\"style\\":[{\\"fontSize\\":16,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\"},{\\"color\\":\\"#cbced1\\"},{\\"flexShrink\\":1,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\"},{\\"fontSize\\":12,\\"color\\":\\"#9297a2\\"}],\\"numberOfLines\\":1},\\"children\\":[\\"subtitle\\"]}]}]}]}]},{\\"type\\":\\"RNCSafeAreaView\\",\\"props\\":{\\"style\\":{\\"backgroundColor\\":\\"#0d0d0d\\"},\\"edges\\":[\\"top\\",\\"left\\",\\"right\\"]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":44,\\"flexDirection\\":\\"row\\",\\"justifyContent\\":\\"center\\",\\"elevation\\":4},{\\"borderBottomWidth\\":0.5,\\"borderBottomColor\\":\\"#323232\\",\\"elevation\\":0,\\"backgroundColor\\":\\"#0d0d0d\\"}]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1,\\"paddingHorizontal\\":12}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"title\\",\\"testID\\":\\"room-header\\",\\"hitSlop\\":{\\"top\\":5,\\"right\\":5,\\"bottom\\":5,\\"left\\":5},\\"focusable\\":true,\\"style\\":{\\"flex\\":1,\\"justifyContent\\":\\"center\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"alignItems\\":\\"center\\",\\"flexDirection\\":\\"row\\"}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":16},[{\\"marginRight\\":4},{\\"color\\":\\"#f9f9f9\\"},null],{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"flexShrink\\":1,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"600\\"},{\\"fontSize\\":16,\\"color\\":\\"#f9f9f9\\"}],\\"numberOfLines\\":1},\\"children\\":[\\"title\\"]}]},{\\"type\\":\\"Text\\",\\"props\\":{\\"accessibilityLabel\\":\\"subtitle\\",\\"style\\":[{\\"fontSize\\":16,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\"},{\\"color\\":\\"#cbced1\\"},{\\"flexShrink\\":1,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\"},{\\"fontSize\\":12,\\"color\\":\\"#b2b8c6\\"}],\\"numberOfLines\\":1},\\"children\\":[\\"subtitle\\"]}]}]}]}]}]"`;
+
+exports[`Storyshots RoomHeader thread 1`] = `"[{\\"type\\":\\"RNCSafeAreaView\\",\\"props\\":{\\"style\\":{\\"backgroundColor\\":\\"#EEEFF1\\"},\\"edges\\":[\\"top\\",\\"left\\",\\"right\\"]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":44,\\"flexDirection\\":\\"row\\",\\"justifyContent\\":\\"center\\",\\"elevation\\":4},{\\"borderBottomWidth\\":0.5,\\"borderBottomColor\\":\\"#B2B2B2\\",\\"elevation\\":0,\\"backgroundColor\\":\\"#EEEFF1\\"}]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1,\\"paddingHorizontal\\":12}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"title\\",\\"testID\\":\\"room-header\\",\\"hitSlop\\":{\\"top\\":5,\\"right\\":5,\\"bottom\\":5,\\"left\\":5},\\"focusable\\":true,\\"style\\":{\\"flex\\":1,\\"justifyContent\\":\\"center\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"alignItems\\":\\"center\\",\\"flexDirection\\":\\"row\\"}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"accessibilityLabel\\":\\"title\\",\\"style\\":[{\\"fontSize\\":16,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\"},{\\"color\\":\\"#2f343d\\"},{\\"flexShrink\\":1,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"600\\"},{\\"fontSize\\":16,\\"color\\":\\"#0C0D0F\\"}],\\"numberOfLines\\":1},\\"children\\":[\\"title\\"]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"alignItems\\":\\"center\\",\\"flexDirection\\":\\"row\\"}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":16},[{\\"marginRight\\":4},{\\"color\\":\\"#0d0e12\\"},null],{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"flexShrink\\":1,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\"},{\\"color\\":\\"#9ca2a8\\"}],\\"numberOfLines\\":1},\\"children\\":[\\"parent title\\"]}]}]}]}]}]},{\\"type\\":\\"RNCSafeAreaView\\",\\"props\\":{\\"style\\":{\\"backgroundColor\\":\\"#EEEFF1\\"},\\"edges\\":[\\"top\\",\\"left\\",\\"right\\"]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":44,\\"flexDirection\\":\\"row\\",\\"justifyContent\\":\\"center\\",\\"elevation\\":4},{\\"borderBottomWidth\\":0.5,\\"borderBottomColor\\":\\"#B2B2B2\\",\\"elevation\\":0,\\"backgroundColor\\":\\"#EEEFF1\\"}]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1,\\"paddingHorizontal\\":12}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"markdown\\\\npreview\\\\n#3\\\\n4\\\\n5\\",\\"testID\\":\\"room-header\\",\\"hitSlop\\":{\\"top\\":5,\\"right\\":5,\\"bottom\\":5,\\"left\\":5},\\"focusable\\":true,\\"style\\":{\\"flex\\":1,\\"justifyContent\\":\\"center\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"alignItems\\":\\"center\\",\\"flexDirection\\":\\"row\\"}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"accessibilityLabel\\":\\"markdown preview #3 4 5\\",\\"style\\":[{\\"fontSize\\":16,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\"},{\\"color\\":\\"#2f343d\\"},{\\"flexShrink\\":1,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"600\\"},{\\"fontSize\\":16,\\"color\\":\\"#0C0D0F\\"}],\\"numberOfLines\\":1,\\"testID\\":\\"markdown\\\\npreview\\\\n#3\\\\n4\\\\n5\\"},\\"children\\":[\\"markdown preview #3 4 5\\"]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"alignItems\\":\\"center\\",\\"flexDirection\\":\\"row\\"}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":16},[{\\"marginRight\\":4},{\\"color\\":\\"#0d0e12\\"},null],{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"flexShrink\\":1,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\"},{\\"color\\":\\"#9ca2a8\\"}],\\"numberOfLines\\":1},\\"children\\":[\\"Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries\\"]}]}]}]}]}]}]"`;
+
+exports[`Storyshots RoomHeader title and subtitle 1`] = `"[{\\"type\\":\\"RNCSafeAreaView\\",\\"props\\":{\\"style\\":{\\"backgroundColor\\":\\"#EEEFF1\\"},\\"edges\\":[\\"top\\",\\"left\\",\\"right\\"]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":44,\\"flexDirection\\":\\"row\\",\\"justifyContent\\":\\"center\\",\\"elevation\\":4},{\\"borderBottomWidth\\":0.5,\\"borderBottomColor\\":\\"#B2B2B2\\",\\"elevation\\":0,\\"backgroundColor\\":\\"#EEEFF1\\"}]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1,\\"paddingHorizontal\\":12}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"title\\",\\"testID\\":\\"room-header\\",\\"hitSlop\\":{\\"top\\":5,\\"right\\":5,\\"bottom\\":5,\\"left\\":5},\\"focusable\\":true,\\"style\\":{\\"flex\\":1,\\"justifyContent\\":\\"center\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"alignItems\\":\\"center\\",\\"flexDirection\\":\\"row\\"}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":16},[{\\"marginRight\\":4},{\\"color\\":\\"#0d0e12\\"},null],{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"flexShrink\\":1,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"600\\"},{\\"fontSize\\":16,\\"color\\":\\"#0C0D0F\\"}],\\"numberOfLines\\":1,\\"testID\\":\\"title\\"},\\"children\\":[\\"title\\"]}]}]}]}]}]},{\\"type\\":\\"RNCSafeAreaView\\",\\"props\\":{\\"style\\":{\\"backgroundColor\\":\\"#EEEFF1\\"},\\"edges\\":[\\"top\\",\\"left\\",\\"right\\"]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":44,\\"flexDirection\\":\\"row\\",\\"justifyContent\\":\\"center\\",\\"elevation\\":4},{\\"borderBottomWidth\\":0.5,\\"borderBottomColor\\":\\"#B2B2B2\\",\\"elevation\\":0,\\"backgroundColor\\":\\"#EEEFF1\\"}]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1,\\"paddingHorizontal\\":12}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries\\",\\"testID\\":\\"room-header\\",\\"hitSlop\\":{\\"top\\":5,\\"right\\":5,\\"bottom\\":5,\\"left\\":5},\\"focusable\\":true,\\"style\\":{\\"flex\\":1,\\"justifyContent\\":\\"center\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"alignItems\\":\\"center\\",\\"flexDirection\\":\\"row\\"}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":16},[{\\"marginRight\\":4},{\\"color\\":\\"#0d0e12\\"},null],{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"flexShrink\\":1,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"600\\"},{\\"fontSize\\":16,\\"color\\":\\"#0C0D0F\\"}],\\"numberOfLines\\":1,\\"testID\\":\\"Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries\\"},\\"children\\":[\\"Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries\\"]}]}]}]}]}]},{\\"type\\":\\"RNCSafeAreaView\\",\\"props\\":{\\"style\\":{\\"backgroundColor\\":\\"#EEEFF1\\"},\\"edges\\":[\\"top\\",\\"left\\",\\"right\\"]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":44,\\"flexDirection\\":\\"row\\",\\"justifyContent\\":\\"center\\",\\"elevation\\":4},{\\"borderBottomWidth\\":0.5,\\"borderBottomColor\\":\\"#B2B2B2\\",\\"elevation\\":0,\\"backgroundColor\\":\\"#EEEFF1\\"}]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1,\\"paddingHorizontal\\":12}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"title\\",\\"testID\\":\\"room-header\\",\\"hitSlop\\":{\\"top\\":5,\\"right\\":5,\\"bottom\\":5,\\"left\\":5},\\"focusable\\":true,\\"style\\":{\\"flex\\":1,\\"justifyContent\\":\\"center\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"alignItems\\":\\"center\\",\\"flexDirection\\":\\"row\\"}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":16},[{\\"marginRight\\":4},{\\"color\\":\\"#0d0e12\\"},null],{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"flexShrink\\":1,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"600\\"},{\\"fontSize\\":16,\\"color\\":\\"#0C0D0F\\"}],\\"numberOfLines\\":1},\\"children\\":[\\"title\\"]}]},{\\"type\\":\\"Text\\",\\"props\\":{\\"accessibilityLabel\\":\\"subtitle\\",\\"style\\":[{\\"fontSize\\":16,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\"},{\\"color\\":\\"#2f343d\\"},{\\"flexShrink\\":1,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\"},{\\"fontSize\\":12,\\"color\\":\\"#9ca2a8\\"}],\\"numberOfLines\\":1},\\"children\\":[\\"subtitle\\"]}]}]}]}]},{\\"type\\":\\"RNCSafeAreaView\\",\\"props\\":{\\"style\\":{\\"backgroundColor\\":\\"#EEEFF1\\"},\\"edges\\":[\\"top\\",\\"left\\",\\"right\\"]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":44,\\"flexDirection\\":\\"row\\",\\"justifyContent\\":\\"center\\",\\"elevation\\":4},{\\"borderBottomWidth\\":0.5,\\"borderBottomColor\\":\\"#B2B2B2\\",\\"elevation\\":0,\\"backgroundColor\\":\\"#EEEFF1\\"}]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1,\\"paddingHorizontal\\":12}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"title\\",\\"testID\\":\\"room-header\\",\\"hitSlop\\":{\\"top\\":5,\\"right\\":5,\\"bottom\\":5,\\"left\\":5},\\"focusable\\":true,\\"style\\":{\\"flex\\":1,\\"justifyContent\\":\\"center\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"alignItems\\":\\"center\\",\\"flexDirection\\":\\"row\\"}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":16},[{\\"marginRight\\":4},{\\"color\\":\\"#0d0e12\\"},null],{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"flexShrink\\":1,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"600\\"},{\\"fontSize\\":16,\\"color\\":\\"#0C0D0F\\"}],\\"numberOfLines\\":1},\\"children\\":[\\"title\\"]}]},{\\"type\\":\\"Text\\",\\"props\\":{\\"accessibilityLabel\\":\\"Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries\\",\\"style\\":[{\\"fontSize\\":16,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\"},{\\"color\\":\\"#2f343d\\"},{\\"flexShrink\\":1,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\"},{\\"fontSize\\":12,\\"color\\":\\"#9ca2a8\\"}],\\"numberOfLines\\":1},\\"children\\":[\\"Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries\\"]}]}]}]}]},{\\"type\\":\\"RNCSafeAreaView\\",\\"props\\":{\\"style\\":{\\"backgroundColor\\":\\"#EEEFF1\\"},\\"edges\\":[\\"top\\",\\"left\\",\\"right\\"]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":44,\\"flexDirection\\":\\"row\\",\\"justifyContent\\":\\"center\\",\\"elevation\\":4},{\\"borderBottomWidth\\":0.5,\\"borderBottomColor\\":\\"#B2B2B2\\",\\"elevation\\":0,\\"backgroundColor\\":\\"#EEEFF1\\"}]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1,\\"paddingHorizontal\\":12}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries\\",\\"testID\\":\\"room-header\\",\\"hitSlop\\":{\\"top\\":5,\\"right\\":5,\\"bottom\\":5,\\"left\\":5},\\"focusable\\":true,\\"style\\":{\\"flex\\":1,\\"justifyContent\\":\\"center\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"alignItems\\":\\"center\\",\\"flexDirection\\":\\"row\\"}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":16},[{\\"marginRight\\":4},{\\"color\\":\\"#0d0e12\\"},null],{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"flexShrink\\":1,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"600\\"},{\\"fontSize\\":16,\\"color\\":\\"#0C0D0F\\"}],\\"numberOfLines\\":1,\\"testID\\":\\"Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries\\"},\\"children\\":[\\"Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries\\"]}]},{\\"type\\":\\"Text\\",\\"props\\":{\\"accessibilityLabel\\":\\"Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries\\",\\"style\\":[{\\"fontSize\\":16,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\"},{\\"color\\":\\"#2f343d\\"},{\\"flexShrink\\":1,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\"},{\\"fontSize\\":12,\\"color\\":\\"#9ca2a8\\"}],\\"numberOfLines\\":1},\\"children\\":[\\"Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries\\"]}]}]}]}]}]"`;
+
+exports[`Storyshots RoomHeader typing 1`] = `"[{\\"type\\":\\"RNCSafeAreaView\\",\\"props\\":{\\"style\\":{\\"backgroundColor\\":\\"#EEEFF1\\"},\\"edges\\":[\\"top\\",\\"left\\",\\"right\\"]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":44,\\"flexDirection\\":\\"row\\",\\"justifyContent\\":\\"center\\",\\"elevation\\":4},{\\"borderBottomWidth\\":0.5,\\"borderBottomColor\\":\\"#B2B2B2\\",\\"elevation\\":0,\\"backgroundColor\\":\\"#EEEFF1\\"}]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1,\\"paddingHorizontal\\":12}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"title\\",\\"testID\\":\\"room-header\\",\\"hitSlop\\":{\\"top\\":5,\\"right\\":5,\\"bottom\\":5,\\"left\\":5},\\"focusable\\":true,\\"style\\":{\\"flex\\":1,\\"justifyContent\\":\\"center\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"alignItems\\":\\"center\\",\\"flexDirection\\":\\"row\\"}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":16},[{\\"marginRight\\":4},{\\"color\\":\\"#0d0e12\\"},null],{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"flexShrink\\":1,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"600\\"},{\\"fontSize\\":16,\\"color\\":\\"#0C0D0F\\"}],\\"numberOfLines\\":1},\\"children\\":[\\"title\\"]}]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"flexShrink\\":1,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\"},{\\"fontSize\\":12,\\"color\\":\\"#9ca2a8\\"}],\\"numberOfLines\\":1},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"600\\"}},\\"children\\":[\\"user 1\\",\\" \\"]},\\"is typing\\",\\"...\\"]}]}]}]}]},{\\"type\\":\\"RNCSafeAreaView\\",\\"props\\":{\\"style\\":{\\"backgroundColor\\":\\"#EEEFF1\\"},\\"edges\\":[\\"top\\",\\"left\\",\\"right\\"]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":44,\\"flexDirection\\":\\"row\\",\\"justifyContent\\":\\"center\\",\\"elevation\\":4},{\\"borderBottomWidth\\":0.5,\\"borderBottomColor\\":\\"#B2B2B2\\",\\"elevation\\":0,\\"backgroundColor\\":\\"#EEEFF1\\"}]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1,\\"paddingHorizontal\\":12}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"title\\",\\"testID\\":\\"room-header\\",\\"hitSlop\\":{\\"top\\":5,\\"right\\":5,\\"bottom\\":5,\\"left\\":5},\\"focusable\\":true,\\"style\\":{\\"flex\\":1,\\"justifyContent\\":\\"center\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"alignItems\\":\\"center\\",\\"flexDirection\\":\\"row\\"}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":16},[{\\"marginRight\\":4},{\\"color\\":\\"#0d0e12\\"},null],{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"flexShrink\\":1,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"600\\"},{\\"fontSize\\":16,\\"color\\":\\"#0C0D0F\\"}],\\"numberOfLines\\":1},\\"children\\":[\\"title\\"]}]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"flexShrink\\":1,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\"},{\\"fontSize\\":12,\\"color\\":\\"#9ca2a8\\"}],\\"numberOfLines\\":1},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"600\\"}},\\"children\\":[\\"user 1 and user 2\\",\\" \\"]},\\"are typing\\",\\"...\\"]}]}]}]}]},{\\"type\\":\\"RNCSafeAreaView\\",\\"props\\":{\\"style\\":{\\"backgroundColor\\":\\"#EEEFF1\\"},\\"edges\\":[\\"top\\",\\"left\\",\\"right\\"]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":44,\\"flexDirection\\":\\"row\\",\\"justifyContent\\":\\"center\\",\\"elevation\\":4},{\\"borderBottomWidth\\":0.5,\\"borderBottomColor\\":\\"#B2B2B2\\",\\"elevation\\":0,\\"backgroundColor\\":\\"#EEEFF1\\"}]},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1,\\"paddingHorizontal\\":12}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"title\\",\\"testID\\":\\"room-header\\",\\"hitSlop\\":{\\"top\\":5,\\"right\\":5,\\"bottom\\":5,\\"left\\":5},\\"focusable\\":true,\\"style\\":{\\"flex\\":1,\\"justifyContent\\":\\"center\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"alignItems\\":\\"center\\",\\"flexDirection\\":\\"row\\"}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":16},[{\\"marginRight\\":4},{\\"color\\":\\"#0d0e12\\"},null],{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"flexShrink\\":1,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"600\\"},{\\"fontSize\\":16,\\"color\\":\\"#0C0D0F\\"}],\\"numberOfLines\\":1},\\"children\\":[\\"title\\"]}]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"flexShrink\\":1,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\"},{\\"fontSize\\":12,\\"color\\":\\"#9ca2a8\\"}],\\"numberOfLines\\":1},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"600\\"}},\\"children\\":[\\"user 1, user 2, user 3, user 4, user 5\\",\\" \\"]},\\"are typing\\",\\"...\\"]}]}]}]}]}]"`;
diff --git a/app/containers/RoomHeader/index.tsx b/app/containers/RoomHeader/index.tsx
index fb00bc10c..4c21ecba2 100644
--- a/app/containers/RoomHeader/index.tsx
+++ b/app/containers/RoomHeader/index.tsx
@@ -1,10 +1,11 @@
+import { dequal } from 'dequal';
import React, { Component } from 'react';
import { connect } from 'react-redux';
-import { dequal } from 'dequal';
-import RoomHeader from './RoomHeader';
+import { IApplicationState } from '../../definitions';
import { withDimensions } from '../../dimensions';
import I18n from '../../i18n';
+import RoomHeader from './RoomHeader';
interface IRoomHeaderContainerProps {
title: string;
@@ -122,8 +123,8 @@ class RoomHeaderContainer extends Component {
}
}
-const mapStateToProps = (state: any, ownProps: any) => {
- let statusText;
+const mapStateToProps = (state: IApplicationState, ownProps: any) => {
+ let statusText = '';
let status = 'offline';
const { roomUserId, type, visitor = {}, tmid } = ownProps;
diff --git a/app/containers/SearchBox.tsx b/app/containers/SearchBox.tsx
index 9ab236eb0..d30923e82 100644
--- a/app/containers/SearchBox.tsx
+++ b/app/containers/SearchBox.tsx
@@ -1,5 +1,13 @@
import React from 'react';
-import { NativeSyntheticEvent, StyleSheet, Text, TextInputFocusEventData, TextInputProps, View } from 'react-native';
+import {
+ NativeSyntheticEvent,
+ StyleSheet,
+ TextInput as RNTextInput,
+ Text,
+ TextInputFocusEventData,
+ TextInputProps,
+ View
+} from 'react-native';
import Touchable from 'react-native-platform-touchable';
import TextInput from '../presentation/TextInput';
@@ -51,7 +59,7 @@ interface ISearchBox {
hasCancel?: boolean;
onCancelPress?: Function;
theme?: string;
- inputRef?: React.Ref;
+ inputRef?: React.Ref;
testID?: string;
onFocus?: (e: NativeSyntheticEvent) => void;
}
diff --git a/app/containers/SearchHeader.tsx b/app/containers/SearchHeader.tsx
index 4b0a49176..8d3c81416 100644
--- a/app/containers/SearchHeader.tsx
+++ b/app/containers/SearchHeader.tsx
@@ -1,7 +1,8 @@
import React from 'react';
import { StyleSheet, View } from 'react-native';
-import { withTheme } from '../theme';
+import I18n from '../i18n';
+import { useTheme } from '../theme';
import sharedStyles from '../views/Styles';
import { themes } from '../constants/colors';
import TextInput from '../presentation/TextInput';
@@ -19,14 +20,13 @@ const styles = StyleSheet.create({
}
});
-interface ISearchHeader {
- theme?: string;
+interface ISearchHeaderProps {
onSearchChangeText?: (text: string) => void;
+ testID?: string;
}
-// TODO: it might be useful to refactor this component for reusage
-const SearchHeader = ({ theme, onSearchChangeText }: ISearchHeader) => {
- const titleColorStyle = { color: themes[theme!].headerTitleColor };
+const SearchHeader = ({ onSearchChangeText, testID }: ISearchHeaderProps): JSX.Element => {
+ const { theme } = useTheme();
const isLight = theme === 'light';
const { isLandscape } = useOrientation();
const scale = isIOS && isLandscape && !isTablet ? 0.8 : 1;
@@ -36,14 +36,14 @@ const SearchHeader = ({ theme, onSearchChangeText }: ISearchHeader) => {
);
};
-export default withTheme(SearchHeader);
+export default SearchHeader;
diff --git a/app/containers/TextInput.tsx b/app/containers/TextInput.tsx
index 76cb1765c..f829bb909 100644
--- a/app/containers/TextInput.tsx
+++ b/app/containers/TextInput.tsx
@@ -1,5 +1,5 @@
import React from 'react';
-import { StyleProp, StyleSheet, Text, TextInputProps, TextStyle, View, ViewStyle } from 'react-native';
+import { StyleProp, StyleSheet, Text, TextInputProps, TextInput as RNTextInput, TextStyle, View, ViewStyle } from 'react-native';
import Touchable from 'react-native-platform-touchable';
import sharedStyles from '../views/Styles';
@@ -59,7 +59,7 @@ export interface IRCTextInputProps extends TextInputProps {
loading?: boolean;
containerStyle?: StyleProp;
inputStyle?: StyleProp;
- inputRef?: React.Ref;
+ inputRef?: React.Ref;
testID?: string;
iconLeft?: string;
iconRight?: string;
diff --git a/app/containers/ThreadDetails.tsx b/app/containers/ThreadDetails.tsx
index 90f3faa4b..5dc9fb954 100644
--- a/app/containers/ThreadDetails.tsx
+++ b/app/containers/ThreadDetails.tsx
@@ -5,7 +5,7 @@ import Touchable from 'react-native-platform-touchable';
import { CustomIcon } from '../lib/Icons';
import { themes } from '../constants/colors';
import sharedStyles from '../views/Styles';
-import { withTheme } from '../theme';
+import { useTheme } from '../theme';
import { TThreadModel } from '../definitions/IThread';
const styles = StyleSheet.create({
@@ -48,12 +48,12 @@ interface IThreadDetails {
badgeColor?: string;
toggleFollowThread: Function;
style: ViewStyle;
- theme?: string;
}
-const ThreadDetails = ({ item, user, badgeColor, toggleFollowThread, style, theme }: IThreadDetails) => {
+const ThreadDetails = ({ item, user, badgeColor, toggleFollowThread, style }: IThreadDetails): JSX.Element => {
+ const { theme } = useTheme();
let { tcount } = item;
- if (tcount! >= 1000) {
+ if (tcount && tcount >= 1000) {
tcount = '+999';
}
@@ -81,7 +81,6 @@ const ThreadDetails = ({ item, user, badgeColor, toggleFollowThread, style, them
-
{badgeColor ? : null}
toggleFollowThread?.(isFollowing, item.id)}>
@@ -96,4 +95,4 @@ const ThreadDetails = ({ item, user, badgeColor, toggleFollowThread, style, them
);
};
-export default withTheme(ThreadDetails);
+export default ThreadDetails;
diff --git a/app/containers/UIKit/MultiSelect/index.tsx b/app/containers/UIKit/MultiSelect/index.tsx
index d50dede31..bedac3257 100644
--- a/app/containers/UIKit/MultiSelect/index.tsx
+++ b/app/containers/UIKit/MultiSelect/index.tsx
@@ -22,8 +22,8 @@ interface IMultiSelect {
context?: number;
loading?: boolean;
multiselect?: boolean;
- onSearch: Function;
- onClose: Function;
+ onSearch?: () => void;
+ onClose?: () => void;
inputStyle: object;
value?: any[];
disabled?: boolean | object;
diff --git a/app/containers/UIKit/index.tsx b/app/containers/UIKit/index.tsx
index 3f3415509..a5850441a 100644
--- a/app/containers/UIKit/index.tsx
+++ b/app/containers/UIKit/index.tsx
@@ -3,7 +3,7 @@ import React, { useContext } from 'react';
import { StyleSheet, Text } from 'react-native';
import { BLOCK_CONTEXT, UiKitParserMessage, UiKitParserModal, uiKitMessage, uiKitModal } from '@rocket.chat/ui-kit';
-import Markdown from '../markdown';
+import Markdown, { MarkdownPreview } from '../markdown';
import Button from '../Button';
import TextInput from '../TextInput';
import { textParser, useBlockContext } from './utils';
@@ -49,10 +49,10 @@ class MessageParser extends UiKitParserMessage {
}
const isContext = context === BLOCK_CONTEXT.CONTEXT;
- return (
- // @ts-ignore
-
- );
+ if (isContext) {
+ return ;
+ }
+ return ;
}
button(element: any, context: any) {
diff --git a/app/containers/__snapshots__/TextInput.stories.storyshot b/app/containers/__snapshots__/TextInput.stories.storyshot
new file mode 100644
index 000000000..6d9c32d90
--- /dev/null
+++ b/app/containers/__snapshots__/TextInput.stories.storyshot
@@ -0,0 +1,3 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Storyshots Text Input Short and Long Text 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"paddingHorizontal\\":14}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"marginBottom\\":10},null]},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"marginBottom\\":10,\\"fontSize\\":14,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"600\\"},{\\"color\\":\\"#0d0e12\\"},null]},\\"children\\":[\\"Short Text\\"]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"position\\":\\"relative\\"}},\\"children\\":[{\\"type\\":\\"TextInput\\",\\"props\\":{\\"allowFontScaling\\":true,\\"rejectResponderTermination\\":true,\\"underlineColorAndroid\\":\\"transparent\\",\\"style\\":[{\\"color\\":\\"#0d0e12\\"},[{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\",\\"height\\":48,\\"fontSize\\":16,\\"padding\\":14,\\"borderWidth\\":0.5,\\"borderRadius\\":2},null,null,{\\"backgroundColor\\":\\"#ffffff\\",\\"borderColor\\":\\"#cbcbcc\\",\\"color\\":\\"#0d0e12\\"},null,null],{\\"textAlign\\":\\"auto\\"}],\\"placeholderTextColor\\":\\"#9ca2a8\\",\\"keyboardAppearance\\":\\"light\\",\\"autoCorrect\\":false,\\"autoCapitalize\\":\\"none\\",\\"accessibilityLabel\\":\\"placeholder\\",\\"placeholder\\":\\"placeholder\\",\\"value\\":\\"Rocket.Chat\\"},\\"children\\":null}]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"marginBottom\\":10},null]},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"marginBottom\\":10,\\"fontSize\\":14,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"600\\"},{\\"color\\":\\"#0d0e12\\"},null]},\\"children\\":[\\"Long Text\\"]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"position\\":\\"relative\\"}},\\"children\\":[{\\"type\\":\\"TextInput\\",\\"props\\":{\\"allowFontScaling\\":true,\\"rejectResponderTermination\\":true,\\"underlineColorAndroid\\":\\"transparent\\",\\"style\\":[{\\"color\\":\\"#0d0e12\\"},[{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\",\\"height\\":48,\\"fontSize\\":16,\\"padding\\":14,\\"borderWidth\\":0.5,\\"borderRadius\\":2},null,null,{\\"backgroundColor\\":\\"#ffffff\\",\\"borderColor\\":\\"#cbcbcc\\",\\"color\\":\\"#0d0e12\\"},null,null],{\\"textAlign\\":\\"auto\\"}],\\"placeholderTextColor\\":\\"#9ca2a8\\",\\"keyboardAppearance\\":\\"light\\",\\"autoCorrect\\":false,\\"autoCapitalize\\":\\"none\\",\\"accessibilityLabel\\":\\"placeholder\\",\\"placeholder\\":\\"placeholder\\",\\"value\\":\\"https://open.rocket.chat/images/logo/android-chrome-512x512.png\\"},\\"children\\":null}]}]}]}"`;
diff --git a/app/containers/markdown/AtMention.tsx b/app/containers/markdown/AtMention.tsx
index 689313257..7c0f258a0 100644
--- a/app/containers/markdown/AtMention.tsx
+++ b/app/containers/markdown/AtMention.tsx
@@ -8,10 +8,10 @@ import { events, logEvent } from '../../utils/log';
interface IAtMention {
mention: string;
- username: string;
- navToRoomInfo: Function;
+ username?: string;
+ navToRoomInfo?: Function;
style?: any;
- useRealName: boolean;
+ useRealName?: boolean;
mentions: any;
}
@@ -51,7 +51,9 @@ const AtMention = React.memo(({ mention, mentions, username, navToRoomInfo, styl
t: 'd',
rid: user && user._id
};
- navToRoomInfo(navParam);
+ if (navToRoomInfo) {
+ navToRoomInfo(navParam);
+ }
};
if (user) {
diff --git a/app/containers/markdown/Emoji.tsx b/app/containers/markdown/Emoji.tsx
index bb348268a..f5672a599 100644
--- a/app/containers/markdown/Emoji.tsx
+++ b/app/containers/markdown/Emoji.tsx
@@ -12,8 +12,8 @@ interface IEmoji {
getCustomEmoji?: Function;
baseUrl: string;
customEmojis?: any;
- style: object;
- theme?: string;
+ style?: object;
+ theme: string;
onEmojiSelected?: Function;
tabEmojiStyle?: object;
}
@@ -32,7 +32,7 @@ const Emoji = React.memo(
);
}
return (
-
+
{emojiUnicode}
);
diff --git a/app/containers/markdown/Hashtag.tsx b/app/containers/markdown/Hashtag.tsx
index b20794de5..29ba8ddcc 100644
--- a/app/containers/markdown/Hashtag.tsx
+++ b/app/containers/markdown/Hashtag.tsx
@@ -1,19 +1,16 @@
import React from 'react';
-import { Text, TextStyle } from 'react-native';
+import { Text, TextStyle, StyleProp } from 'react-native';
import { themes } from '../../constants/colors';
import { useTheme } from '../../theme';
+import { IUserChannel } from './interfaces';
import styles from './styles';
interface IHashtag {
hashtag: string;
- navToRoomInfo: Function;
- style?: TextStyle[];
- channels: {
- [index: number]: string | number;
- name: string;
- _id: number;
- }[];
+ navToRoomInfo?: Function;
+ style?: StyleProp[];
+ channels?: IUserChannel[];
}
const Hashtag = React.memo(({ hashtag, channels, navToRoomInfo, style = [] }: IHashtag) => {
@@ -21,11 +18,13 @@ const Hashtag = React.memo(({ hashtag, channels, navToRoomInfo, style = [] }: IH
const handlePress = () => {
const index = channels?.findIndex(channel => channel.name === hashtag);
- const navParam = {
- t: 'c',
- rid: channels[index]._id
- };
- navToRoomInfo(navParam);
+ if (index && navToRoomInfo) {
+ const navParam = {
+ t: 'c',
+ rid: channels?.[index]._id
+ };
+ navToRoomInfo(navParam);
+ }
};
if (channels && channels.length && channels.findIndex(channel => channel.name === hashtag) !== -1) {
diff --git a/app/containers/markdown/Link.tsx b/app/containers/markdown/Link.tsx
index d2aca85f6..414b73a52 100644
--- a/app/containers/markdown/Link.tsx
+++ b/app/containers/markdown/Link.tsx
@@ -7,12 +7,13 @@ import { LISTENER } from '../Toast';
import EventEmitter from '../../utils/events';
import I18n from '../../i18n';
import openLink from '../../utils/openLink';
+import { TOnLinkPress } from './interfaces';
interface ILink {
children: JSX.Element;
link: string;
theme: string;
- onLinkPress: Function;
+ onLinkPress?: TOnLinkPress;
}
const Link = React.memo(({ children, link, theme, onLinkPress }: ILink) => {
diff --git a/app/containers/markdown/List.tsx b/app/containers/markdown/List.tsx
index 70a79418b..b032a6a0f 100644
--- a/app/containers/markdown/List.tsx
+++ b/app/containers/markdown/List.tsx
@@ -5,7 +5,7 @@ interface IList {
ordered: boolean;
start: number;
tight: boolean;
- numberOfLines: number;
+ numberOfLines?: number;
}
const List = React.memo(({ children, ordered, tight, start = 1, numberOfLines = 0 }: IList) => {
diff --git a/app/containers/markdown/Preview.tsx b/app/containers/markdown/Preview.tsx
new file mode 100644
index 000000000..d75eb3df8
--- /dev/null
+++ b/app/containers/markdown/Preview.tsx
@@ -0,0 +1,44 @@
+import React from 'react';
+import { StyleProp, Text, TextStyle } from 'react-native';
+import removeMarkdown from 'remove-markdown';
+
+import shortnameToUnicode from '../../utils/shortnameToUnicode';
+import { themes } from '../../constants/colors';
+import { formatText } from './formatText';
+import { useTheme } from '../../theme';
+import styles from './styles';
+import { formatHyperlink } from './formatHyperlink';
+
+interface IMarkdownPreview {
+ msg?: string;
+ numberOfLines?: number;
+ testID?: string;
+ style?: StyleProp[];
+}
+
+const MarkdownPreview = ({ msg, numberOfLines = 1, testID, style = [] }: IMarkdownPreview): React.ReactElement | null => {
+ if (!msg) {
+ return null;
+ }
+
+ const { theme } = useTheme();
+
+ let m = formatText(msg);
+ m = formatHyperlink(m);
+ m = shortnameToUnicode(m);
+ // Removes sequential empty spaces
+ m = m.replace(/\s+/g, ' ');
+ m = removeMarkdown(m);
+ m = m.replace(/\n+/g, ' ');
+ return (
+
+ {m}
+
+ );
+};
+
+export default MarkdownPreview;
diff --git a/app/containers/markdown/formatHyperlink.test.ts b/app/containers/markdown/formatHyperlink.test.ts
new file mode 100644
index 000000000..f6bf567ee
--- /dev/null
+++ b/app/containers/markdown/formatHyperlink.test.ts
@@ -0,0 +1,26 @@
+import { formatHyperlink } from './formatHyperlink';
+
+describe('FormatText', () => {
+ test('empty to be empty', () => {
+ expect(formatHyperlink('')).toBe('');
+ });
+ test('A123 to be A123', () => {
+ expect(formatHyperlink('A123')).toBe('A123');
+ });
+ test('Format to be ', () => {
+ expect(formatHyperlink('')).toBe('');
+ });
+ test('Format "[ ](https://open.rocket.chat/) Test" to be Test', () => {
+ expect(formatHyperlink('[ ](https://open.rocket.chat/) Test')).toBe('Test');
+ });
+ test('Format "[Open](https://open.rocket.chat/) Test" to be Test', () => {
+ expect(formatHyperlink('[Open](https://open.rocket.chat/) Test')).toBe('[Open](https://open.rocket.chat/) Test');
+ });
+ test('render test (arabic)', () => {
+ expect(formatHyperlink('[ ](https://open.rocket.chat/) اختبا')).toBe('اختبا');
+ });
+
+ test('render test (russian)', () => {
+ expect(formatHyperlink('[ ](https://open.rocket.chat/) тест123')).toBe('тест123');
+ });
+});
diff --git a/app/containers/markdown/formatHyperlink.ts b/app/containers/markdown/formatHyperlink.ts
new file mode 100644
index 000000000..47ed71fd3
--- /dev/null
+++ b/app/containers/markdown/formatHyperlink.ts
@@ -0,0 +1,3 @@
+// Ex: '[ ](https://open.rocket.chat/group/test?msg=abcdef) Test'
+// Return: 'Test'
+export const formatHyperlink = (text: string): string => text.replace(/^\[([\s]*)\]\(([^)]*)\)\s/, '').trim();
diff --git a/app/containers/markdown/formatText.test.ts b/app/containers/markdown/formatText.test.ts
new file mode 100644
index 000000000..5d2bc6787
--- /dev/null
+++ b/app/containers/markdown/formatText.test.ts
@@ -0,0 +1,20 @@
+import { formatText } from './formatText';
+
+describe('FormatText', () => {
+ test('empty to be empty', () => {
+ expect(formatText('')).toBe('');
+ });
+ test('A123 to be A123', () => {
+ expect(formatText('A123')).toBe('A123');
+ });
+ test('Format to be [Text](http://link)', () => {
+ expect(formatText('')).toBe('[Text](http://link)');
+ });
+ test('render test (arabic)', () => {
+ expect(formatText('اختبا ')).toBe('اختبا [ر123](http://link)');
+ });
+
+ test('render test (russian)', () => {
+ expect(formatText('')).toBe('[тест123](http://link)');
+ });
+});
diff --git a/app/containers/markdown/formatText.ts b/app/containers/markdown/formatText.ts
new file mode 100644
index 000000000..2273134c8
--- /dev/null
+++ b/app/containers/markdown/formatText.ts
@@ -0,0 +1,6 @@
+// Support
+export const formatText = (text: string): string =>
+ text.replace(
+ new RegExp('(?:<|<)((?:https|http):\\/\\/[^\\|]+)\\|(.+?)(?=>|>)(?:>|>)', 'gm'),
+ (match, url, title) => `[${title}](${url})`
+ );
diff --git a/app/containers/markdown/index.tsx b/app/containers/markdown/index.tsx
index e8e2ea06b..73e74e8f3 100644
--- a/app/containers/markdown/index.tsx
+++ b/app/containers/markdown/index.tsx
@@ -1,12 +1,9 @@
import React, { PureComponent } from 'react';
-import { Image, Text } from 'react-native';
+import { Image, StyleProp, Text, TextStyle } from 'react-native';
import { Node, Parser } from 'commonmark';
import Renderer from 'commonmark-react-renderer';
-import removeMarkdown from 'remove-markdown';
import { MarkdownAST } from '@rocket.chat/message-parser';
-import { UserMention } from '../message/interfaces';
-import shortnameToUnicode from '../../utils/shortnameToUnicode';
import I18n from '../../i18n';
import { themes } from '../../constants/colors';
import MarkdownLink from './Link';
@@ -23,43 +20,39 @@ import mergeTextNodes from './mergeTextNodes';
import styles from './styles';
import { isValidURL } from '../../utils/url';
import NewMarkdown from './new';
+import { formatText } from './formatText';
+import { IUserMention, IUserChannel, TOnLinkPress } from './interfaces';
+import { TGetCustomEmoji } from '../../definitions/IEmoji';
+import { formatHyperlink } from './formatHyperlink';
+
+export { default as MarkdownPreview } from './Preview';
interface IMarkdownProps {
- msg: string;
- md: MarkdownAST;
- mentions: UserMention[];
- getCustomEmoji: Function;
- baseUrl: string;
- username: string;
- tmid: string;
- isEdited: boolean;
- numberOfLines: number;
- customEmojis: boolean;
- useRealName: boolean;
- channels: {
- name: string;
- _id: number;
- }[];
- enableMessageParser: boolean;
- navToRoomInfo: Function;
- preview: boolean;
+ msg?: string;
theme: string;
- testID: string;
- style: any;
- onLinkPress: Function;
+ md?: MarkdownAST;
+ mentions?: IUserMention[];
+ getCustomEmoji?: TGetCustomEmoji;
+ baseUrl?: string;
+ username?: string;
+ tmid?: string;
+ isEdited?: boolean;
+ numberOfLines?: number;
+ customEmojis?: boolean;
+ useRealName?: boolean;
+ channels?: IUserChannel[];
+ enableMessageParser?: boolean;
+ // TODO: Refactor when migrate Room
+ navToRoomInfo?: Function;
+ testID?: string;
+ style?: StyleProp[];
+ onLinkPress?: TOnLinkPress;
}
type TLiteral = {
literal: string;
};
-// Support
-const formatText = (text: string) =>
- text.replace(
- new RegExp('(?:<|<)((?:https|http):\\/\\/[^\\|]+)\\|(.+?)(?=>|>)(?:>|>)', 'gm'),
- (match, url, title) => `[${title}](${url})`
- );
-
const emojiRanges = [
'\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff]', // unicode emoji from https://www.regextester.com/106421
':.{1,40}:', // custom emoji
@@ -148,7 +141,7 @@ class Markdown extends PureComponent {
get isNewMarkdown(): boolean {
const { md, enableMessageParser } = this.props;
- return enableMessageParser && !!md;
+ return !!enableMessageParser && !!md;
}
editedMessage = (ast: any) => {
@@ -244,7 +237,7 @@ class Markdown extends PureComponent {
};
renderAtMention = ({ mentionName }: { mentionName: string }) => {
- const { username, mentions, navToRoomInfo, useRealName, style } = this.props;
+ const { username = '', mentions, navToRoomInfo, useRealName, style } = this.props;
return (
{
};
renderEmoji = ({ literal }: TLiteral) => {
- const { getCustomEmoji, baseUrl, customEmojis, style, theme } = this.props;
+ const { getCustomEmoji, baseUrl = '', customEmojis, style, theme } = this.props;
return (
{
const {
msg,
md,
- numberOfLines,
- preview = false,
- theme,
- style = [],
- testID,
mentions,
channels,
navToRoomInfo,
useRealName,
- username,
+ username = '',
getCustomEmoji,
- baseUrl,
+ baseUrl = '',
onLinkPress
} = this.props;
@@ -362,7 +350,7 @@ class Markdown extends PureComponent {
return null;
}
- if (this.isNewMarkdown && !preview) {
+ if (this.isNewMarkdown) {
return (
{
}
let m = formatText(msg);
-
- // Ex: '[ ](https://open.rocket.chat/group/test?msg=abcdef) Test'
- // Return: 'Test'
- m = m.replace(/^\[([\s]*)\]\(([^)]*)\)\s/, '').trim();
-
- if (preview) {
- m = shortnameToUnicode(m);
- // Removes sequential empty spaces
- m = m.replace(/\s+/g, ' ');
- m = removeMarkdown(m);
- m = m.replace(/\n+/g, ' ');
- return (
-
- {m}
-
- );
- }
-
+ m = formatHyperlink(m);
let ast = parser.parse(m);
ast = mergeTextNodes(ast);
this.isMessageContainsOnlyEmoji = isOnlyEmoji(m) && emojiCount(m) <= 3;
diff --git a/app/containers/markdown/interfaces.ts b/app/containers/markdown/interfaces.ts
new file mode 100644
index 000000000..8764b84fa
--- /dev/null
+++ b/app/containers/markdown/interfaces.ts
@@ -0,0 +1,12 @@
+export interface IUserMention {
+ _id: string;
+ username: string;
+ name?: string;
+}
+
+export interface IUserChannel {
+ name: string;
+ _id: string;
+}
+
+export type TOnLinkPress = (link: string) => void;
diff --git a/app/containers/markdown/new/MarkdownContext.ts b/app/containers/markdown/new/MarkdownContext.ts
index b22f15614..03fe0e93e 100644
--- a/app/containers/markdown/new/MarkdownContext.ts
+++ b/app/containers/markdown/new/MarkdownContext.ts
@@ -1,17 +1,14 @@
import React from 'react';
-import { UserMention } from '../../message/interfaces';
+import { IUserMention, IUserChannel } from '../interfaces';
interface IMarkdownContext {
- mentions: UserMention[];
- channels: {
- name: string;
- _id: number;
- }[];
- useRealName: boolean;
- username: string;
- baseUrl: string;
- navToRoomInfo: Function;
+ mentions?: IUserMention[];
+ channels?: IUserChannel[];
+ useRealName?: boolean;
+ username?: string;
+ baseUrl?: string;
+ navToRoomInfo?: Function;
getCustomEmoji?: Function;
onLinkPress?: Function;
}
diff --git a/app/containers/markdown/new/index.tsx b/app/containers/markdown/new/index.tsx
index a56b66f54..a02faffca 100644
--- a/app/containers/markdown/new/index.tsx
+++ b/app/containers/markdown/new/index.tsx
@@ -1,5 +1,6 @@
import React from 'react';
import { MarkdownAST } from '@rocket.chat/message-parser';
+import isEmpty from 'lodash/isEmpty';
import Quote from './Quote';
import Paragraph from './Paragraph';
@@ -8,21 +9,18 @@ import Code from './Code';
import BigEmoji from './BigEmoji';
import OrderedList from './OrderedList';
import UnorderedList from './UnorderedList';
-import { UserMention } from '../../message/interfaces';
+import { IUserMention, IUserChannel, TOnLinkPress } from '../interfaces';
import TaskList from './TaskList';
import MarkdownContext from './MarkdownContext';
interface IBodyProps {
- tokens: MarkdownAST;
- mentions: UserMention[];
- channels: {
- name: string;
- _id: number;
- }[];
+ tokens?: MarkdownAST;
+ mentions?: IUserMention[];
+ channels?: IUserChannel[];
getCustomEmoji?: Function;
- onLinkPress?: Function;
- navToRoomInfo: Function;
- useRealName: boolean;
+ onLinkPress?: TOnLinkPress;
+ navToRoomInfo?: Function;
+ useRealName?: boolean;
username: string;
baseUrl: string;
}
@@ -37,41 +35,47 @@ const Body = ({
getCustomEmoji,
baseUrl,
onLinkPress
-}: IBodyProps): JSX.Element => (
-
- {tokens.map(block => {
- switch (block.type) {
- case 'BIG_EMOJI':
- return ;
- case 'UNORDERED_LIST':
- return ;
- case 'ORDERED_LIST':
- return ;
- case 'TASKS':
- return ;
- case 'QUOTE':
- return
;
- case 'PARAGRAPH':
- return ;
- case 'CODE':
- return
;
- case 'HEADING':
- return ;
- default:
- return null;
- }
- })}
-
-);
+}: IBodyProps): React.ReactElement | null => {
+ if (isEmpty(tokens)) {
+ return null;
+ }
+
+ return (
+
+ {tokens?.map(block => {
+ switch (block.type) {
+ case 'BIG_EMOJI':
+ return ;
+ case 'UNORDERED_LIST':
+ return ;
+ case 'ORDERED_LIST':
+ return ;
+ case 'TASKS':
+ return ;
+ case 'QUOTE':
+ return
;
+ case 'PARAGRAPH':
+ return ;
+ case 'CODE':
+ return
;
+ case 'HEADING':
+ return ;
+ default:
+ return null;
+ }
+ })}
+
+ );
+};
export default Body;
diff --git a/app/containers/message/Audio.tsx b/app/containers/message/Audio.tsx
index 8a93cab14..65500adc9 100644
--- a/app/containers/message/Audio.tsx
+++ b/app/containers/message/Audio.tsx
@@ -15,6 +15,7 @@ import { isAndroid, isIOS } from '../../utils/deviceInfo';
import MessageContext from './Context';
import ActivityIndicator from '../ActivityIndicator';
import { withDimensions } from '../../dimensions';
+import { TGetCustomEmoji } from '../../definitions/IEmoji';
interface IButton {
loading: boolean;
@@ -29,7 +30,7 @@ interface IMessageAudioProps {
description: string;
};
theme: string;
- getCustomEmoji: Function;
+ getCustomEmoji: TGetCustomEmoji;
scale?: number;
}
@@ -280,7 +281,6 @@ class MessageAudio extends React.Component
{this.duration}
- {/* @ts-ignore*/}
>
);
diff --git a/app/containers/message/Content.tsx b/app/containers/message/Content.tsx
index 9d4d005e0..684d4c6bf 100644
--- a/app/containers/message/Content.tsx
+++ b/app/containers/message/Content.tsx
@@ -4,7 +4,7 @@ import { dequal } from 'dequal';
import I18n from '../../i18n';
import styles from './styles';
-import Markdown from '../markdown';
+import Markdown, { MarkdownPreview } from '../markdown';
import User from './User';
import { SYSTEM_MESSAGE_TYPES_WITH_AUTHOR_NAME, getInfoMessage } from './utils';
import { themes } from '../../constants/colors';
@@ -49,10 +49,11 @@ const Content = React.memo(
{I18n.t('Encrypted_message')}
);
+ } else if (isPreview) {
+ content = ;
} else {
const { baseUrl, user, onLinkPress } = useContext(MessageContext);
content = (
- // @ts-ignore
- {/* @ts-ignore */}
{
diff --git a/app/containers/message/Reactions.tsx b/app/containers/message/Reactions.tsx
index 58c82486e..2a681a8d3 100644
--- a/app/containers/message/Reactions.tsx
+++ b/app/containers/message/Reactions.tsx
@@ -9,6 +9,7 @@ import { BUTTON_HIT_SLOP } from './utils';
import { themes } from '../../constants/colors';
import { withTheme } from '../../theme';
import MessageContext from './Context';
+import { TGetCustomEmoji } from '../../definitions/IEmoji';
interface IMessageAddReaction {
theme: string;
@@ -19,13 +20,13 @@ interface IMessageReaction {
usernames: [];
emoji: object;
};
- getCustomEmoji: Function;
+ getCustomEmoji: TGetCustomEmoji;
theme: string;
}
interface IMessageReactions {
reactions?: object[];
- getCustomEmoji: Function;
+ getCustomEmoji: TGetCustomEmoji;
theme: string;
}
diff --git a/app/containers/message/RepliedThread.tsx b/app/containers/message/RepliedThread.tsx
index 454ed852b..9f1e5639a 100644
--- a/app/containers/message/RepliedThread.tsx
+++ b/app/containers/message/RepliedThread.tsx
@@ -5,7 +5,7 @@ import { CustomIcon } from '../../lib/Icons';
import styles from './styles';
import { themes } from '../../constants/colors';
import I18n from '../../i18n';
-import Markdown from '../markdown';
+import { MarkdownPreview } from '../markdown';
import { IMessageRepliedThread } from './interfaces';
const RepliedThread = memo(({ tmid, tmsg, isHeader, fetchThreadName, id, isEncrypted, theme }: IMessageRepliedThread) => {
@@ -32,14 +32,7 @@ const RepliedThread = memo(({ tmid, tmsg, isHeader, fetchThreadName, id, isEncry
return (
- {/* @ts-ignore*/}
-
+
diff --git a/app/containers/message/Reply.tsx b/app/containers/message/Reply.tsx
index 8d8126050..0da83d6fd 100644
--- a/app/containers/message/Reply.tsx
+++ b/app/containers/message/Reply.tsx
@@ -14,6 +14,7 @@ import MessageContext from './Context';
import { fileDownloadAndPreview } from '../../utils/fileDownload';
import { formatAttachmentUrl } from '../../lib/utils';
import { IAttachment } from '../../definitions/IAttachment';
+import { TGetCustomEmoji } from '../../definitions/IEmoji';
import RCActivityIndicator from '../ActivityIndicator';
const styles = StyleSheet.create({
@@ -99,14 +100,14 @@ interface IMessageTitle {
interface IMessageDescription {
attachment: IAttachment;
- getCustomEmoji: Function;
+ getCustomEmoji: TGetCustomEmoji;
theme: string;
}
interface IMessageFields {
attachment: IAttachment;
theme: string;
- getCustomEmoji: Function;
+ getCustomEmoji: TGetCustomEmoji;
}
interface IMessageReply {
@@ -114,7 +115,7 @@ interface IMessageReply {
timeFormat: string;
index: number;
theme: string;
- getCustomEmoji: Function;
+ getCustomEmoji: TGetCustomEmoji;
}
const Title = React.memo(({ attachment, timeFormat, theme }: IMessageTitle) => {
@@ -137,10 +138,7 @@ const Description = React.memo(
return null;
}
const { baseUrl, user } = useContext(MessageContext);
- return (
- // @ts-ignore
-
- );
+ return ;
},
(prevProps, nextProps) => {
if (prevProps.attachment.text !== nextProps.attachment.text) {
@@ -180,9 +178,8 @@ const Fields = React.memo(
{attachment.fields.map(field => (
{field.title}
- {/* @ts-ignore*/}
- {/* @ts-ignore*/}
SUPPORTED_TYPES.indexOf(type) !== -1;
@@ -33,7 +34,7 @@ const styles = StyleSheet.create({
interface IMessageVideo {
file: IAttachment;
showAttachment: Function;
- getCustomEmoji: Function;
+ getCustomEmoji: TGetCustomEmoji;
theme: string;
}
@@ -50,7 +51,7 @@ const Video = React.memo(
return showAttachment(file);
}
- if (!isIOS) {
+ if (!isIOS && file.video_url) {
const uri = formatAttachmentUrl(file.video_url, user.id, user.token, baseUrl);
await downloadVideo(uri);
return;
@@ -82,7 +83,6 @@ const Video = React.memo(
)}
- {/* @ts-ignore*/}
{
static defaultProps = {
- getCustomEmoji: () => {},
+ getCustomEmoji: () => null,
onLongPress: () => {},
onReactionPress: () => {},
onEncryptedPress: () => {},
@@ -147,6 +148,12 @@ class MessageContainer extends React.Component {
if ((item.tlm || item.tmid) && !isThreadRoom) {
this.onThreadPress();
}
+
+ const { onDiscussionPress } = this.props;
+
+ if (item.dlm && onDiscussionPress) {
+ onDiscussionPress(item);
+ }
},
300,
true
@@ -297,7 +304,7 @@ class MessageContainer extends React.Component {
}
};
- onLinkPress = (link: any) => {
+ onLinkPress = (link: string): void => {
const { item, theme, jumpToMessage } = this.props;
const isMessageLink = item?.attachments?.findIndex((att: any) => att?.message_link === link) !== -1;
if (isMessageLink) {
diff --git a/app/containers/message/interfaces.ts b/app/containers/message/interfaces.ts
index 7d3e14950..a068cd9cd 100644
--- a/app/containers/message/interfaces.ts
+++ b/app/containers/message/interfaces.ts
@@ -1,10 +1,15 @@
import { MarkdownAST } from '@rocket.chat/message-parser';
+import { IUserChannel, IUserMention } from '../markdown/interfaces';
+import { TGetCustomEmoji } from '../../definitions/IEmoji';
+
+export type TMessageType = 'discussion-created' | 'jitsi_call_started';
+
export interface IMessageAttachments {
attachments: any;
timeFormat: string;
showAttachment: Function;
- getCustomEmoji: Function;
+ getCustomEmoji: TGetCustomEmoji;
theme: string;
}
@@ -26,7 +31,7 @@ export interface IMessageAvatar {
};
small?: boolean;
navToRoomInfo: Function;
- getCustomEmoji(): void;
+ getCustomEmoji: TGetCustomEmoji;
theme: string;
}
@@ -57,8 +62,6 @@ export interface IUser {
name: string;
}
-export type UserMention = Pick;
-
export interface IMessageContent {
_id: string;
isTemp: boolean;
@@ -70,12 +73,9 @@ export interface IMessageContent {
theme: string;
isEdited: boolean;
isEncrypted: boolean;
- getCustomEmoji: Function;
- channels: {
- name: string;
- _id: number;
- }[];
- mentions: UserMention[];
+ getCustomEmoji: TGetCustomEmoji;
+ channels: IUserChannel[];
+ mentions: IUserMention[];
navToRoomInfo: Function;
useRealName: boolean;
isIgnored: boolean;
@@ -94,14 +94,14 @@ export interface IMessageEmoji {
baseUrl: string;
standardEmojiStyle: object;
customEmojiStyle: object;
- getCustomEmoji: Function;
+ getCustomEmoji: TGetCustomEmoji;
}
export interface IMessageThread {
msg: string;
tcount: number;
theme: string;
- tlm: string;
+ tlm: Date;
isThreadRoom: boolean;
id: string;
}
@@ -140,7 +140,7 @@ export interface IMessageInner
IMessageThread,
IMessageAttachments,
IMessageBroadcast {
- type: string;
+ type: TMessageType;
blocks: [];
}
diff --git a/app/containers/message/utils.ts b/app/containers/message/utils.ts
index 27d904a5d..b10f6f741 100644
--- a/app/containers/message/utils.ts
+++ b/app/containers/message/utils.ts
@@ -1,3 +1,4 @@
+import { TMessageModel } from '../../definitions/IMessage';
import I18n from '../../i18n';
import { DISCUSSION } from './constants';
@@ -44,7 +45,14 @@ export const SYSTEM_MESSAGES = [
'message_snippeted',
'thread-created',
'room_e2e_enabled',
- 'room_e2e_disabled'
+ 'room_e2e_disabled',
+ 'removed-user-from-team',
+ 'added-user-to-team',
+ 'user-added-room-to-team',
+ 'user-converted-to-team',
+ 'user-converted-to-channel',
+ 'user-deleted-room-from-team',
+ 'user-removed-room-from-team'
];
export const SYSTEM_MESSAGE_TYPES = {
@@ -55,7 +63,14 @@ export const SYSTEM_MESSAGE_TYPES = {
USER_JOINED_TEAM: 'ujt',
USER_JOINED_DISCUSSION: 'ut',
USER_LEFT_CHANNEL: 'ul',
- USER_LEFT_TEAM: 'ult'
+ USER_LEFT_TEAM: 'ult',
+ REMOVED_USER_FROM_TEAM: 'removed-user-from-team',
+ ADDED_USER_TO_TEAM: 'added-user-to-team',
+ ADDED_ROOM_TO_TEAM: 'user-added-room-to-team',
+ CONVERTED_TO_TEAM: 'user-converted-to-team',
+ CONVERTED_TO_CHANNEL: 'user-converted-to-channel',
+ DELETED_ROOM_FROM_TEAM: 'user-deleted-room-from-team',
+ REMOVED_ROOM_FROM_TEAM: 'user-removed-room-from-team'
};
export const SYSTEM_MESSAGE_TYPES_WITH_AUTHOR_NAME = [
@@ -66,7 +81,14 @@ export const SYSTEM_MESSAGE_TYPES_WITH_AUTHOR_NAME = [
SYSTEM_MESSAGE_TYPES.USER_JOINED_TEAM,
SYSTEM_MESSAGE_TYPES.USER_JOINED_DISCUSSION,
SYSTEM_MESSAGE_TYPES.USER_LEFT_CHANNEL,
- SYSTEM_MESSAGE_TYPES.USER_LEFT_TEAM
+ SYSTEM_MESSAGE_TYPES.USER_LEFT_TEAM,
+ SYSTEM_MESSAGE_TYPES.REMOVED_USER_FROM_TEAM,
+ SYSTEM_MESSAGE_TYPES.ADDED_USER_TO_TEAM,
+ SYSTEM_MESSAGE_TYPES.ADDED_ROOM_TO_TEAM,
+ SYSTEM_MESSAGE_TYPES.CONVERTED_TO_TEAM,
+ SYSTEM_MESSAGE_TYPES.CONVERTED_TO_CHANNEL,
+ SYSTEM_MESSAGE_TYPES.DELETED_ROOM_FROM_TEAM,
+ SYSTEM_MESSAGE_TYPES.REMOVED_ROOM_FROM_TEAM
];
type TInfoMessage = {
@@ -75,7 +97,8 @@ type TInfoMessage = {
msg: string;
author: { username: string };
};
-export const getInfoMessage = ({ type, role, msg, author }: TInfoMessage) => {
+
+export const getInfoMessage = ({ type, role, msg, author }: TInfoMessage): string => {
const { username } = author;
if (type === 'rm') {
return I18n.t('Message_removed');
@@ -146,10 +169,31 @@ export const getInfoMessage = ({ type, role, msg, author }: TInfoMessage) => {
if (type === 'room_e2e_enabled') {
return I18n.t('This_room_encryption_has_been_enabled_by__username_', { username });
}
+ if (type === 'removed-user-from-team') {
+ return I18n.t('Removed__username__from_team', { user_removed: username });
+ }
+ if (type === 'added-user-to-team') {
+ return I18n.t('Added__username__to_team', { user_added: username });
+ }
+ if (type === 'user-added-room-to-team') {
+ return I18n.t('added__roomName__to_team', { roomName: msg });
+ }
+ if (type === 'user-converted-to-team') {
+ return I18n.t('Converted__roomName__to_team', { roomName: msg });
+ }
+ if (type === 'user-converted-to-channel') {
+ return I18n.t('Converted__roomName__to_channel', { roomName: msg });
+ }
+ if (type === 'user-deleted-room-from-team') {
+ return I18n.t('Deleted__roomName__', { roomName: msg });
+ }
+ if (type === 'user-removed-room-from-team') {
+ return I18n.t('Removed__roomName__from_this_team', { roomName: msg });
+ }
return '';
};
-export const getMessageTranslation = (message: { translations: any }, autoTranslateLanguage: string) => {
+export const getMessageTranslation = (message: TMessageModel, autoTranslateLanguage: string) => {
if (!autoTranslateLanguage) {
return null;
}
diff --git a/app/definitions/ERoomType.ts b/app/definitions/ERoomType.ts
new file mode 100644
index 000000000..010fa78f0
--- /dev/null
+++ b/app/definitions/ERoomType.ts
@@ -0,0 +1,7 @@
+export enum ERoomType {
+ p = 'group',
+ c = 'channel',
+ d = 'direct',
+ t = 'team',
+ l = 'omnichannel'
+}
diff --git a/app/definitions/ICannedResponse.ts b/app/definitions/ICannedResponse.ts
new file mode 100644
index 000000000..fcf531c0c
--- /dev/null
+++ b/app/definitions/ICannedResponse.ts
@@ -0,0 +1,31 @@
+export interface IDepartment {
+ _id: string;
+ enabled: boolean;
+ name: string;
+ description: string;
+ showOnRegistration: boolean;
+ showOnOfflineForm: boolean;
+ requestTagBeforeClosingChat: boolean;
+ email: string;
+ chatClosingTags: string[];
+ offlineMessageChannelName: string;
+ maxNumberSimultaneousChat: number;
+ abandonedRoomsCloseCustomMessage: string;
+ waitingQueueMessage: string;
+ departmentsAllowedToForward: string;
+ _updatedAt: Date;
+ numAgents: number;
+ ancestors: string[];
+}
+
+export interface ICannedResponse {
+ _id: string;
+ shortcut: string;
+ text: string;
+ scope: string;
+ tags: string[];
+ createdBy: { _id: string; username: string };
+ userId: string;
+ scopeName: string;
+ departmentId?: string;
+}
diff --git a/app/definitions/ICertificate.ts b/app/definitions/ICertificate.ts
new file mode 100644
index 000000000..025b6a96f
--- /dev/null
+++ b/app/definitions/ICertificate.ts
@@ -0,0 +1,4 @@
+export interface ICertificate {
+ path: string;
+ password: string;
+}
diff --git a/app/definitions/ICustomEmoji.ts b/app/definitions/ICustomEmoji.ts
index 6a6c131a7..348e66098 100644
--- a/app/definitions/ICustomEmoji.ts
+++ b/app/definitions/ICustomEmoji.ts
@@ -1,8 +1,9 @@
import Model from '@nozbe/watermelondb/Model';
export interface ICustomEmoji {
+ _id: string;
name?: string;
- aliases?: string;
+ aliases?: string[];
extension: string;
_updatedAt: Date;
}
diff --git a/app/definitions/ICustomEmojiDescriptor.ts b/app/definitions/ICustomEmojiDescriptor.ts
new file mode 100644
index 000000000..faf263c19
--- /dev/null
+++ b/app/definitions/ICustomEmojiDescriptor.ts
@@ -0,0 +1,7 @@
+import { IRocketChatRecord } from './IRocketChatRecord';
+
+export interface ICustomEmojiDescriptor extends IRocketChatRecord {
+ name: string;
+ aliases: string[];
+ extension: string;
+}
diff --git a/app/containers/EmojiPicker/interfaces.ts b/app/definitions/IEmoji.ts
similarity index 80%
rename from app/containers/EmojiPicker/interfaces.ts
rename to app/definitions/IEmoji.ts
index 288497f9a..58be90a24 100644
--- a/app/containers/EmojiPicker/interfaces.ts
+++ b/app/definitions/IEmoji.ts
@@ -6,7 +6,7 @@ export interface IEmoji {
}
export interface ICustomEmoji {
- baseUrl: string;
+ baseUrl?: string;
emoji: IEmoji;
style: any;
}
@@ -20,3 +20,5 @@ export interface IEmojiCategory {
style: any;
tabLabel: string;
}
+
+export type TGetCustomEmoji = (name: string) => IEmoji | null;
diff --git a/app/definitions/IFrequentlyUsedEmoji.ts b/app/definitions/IFrequentlyUsedEmoji.ts
index a659e2abd..87e46cf33 100644
--- a/app/definitions/IFrequentlyUsedEmoji.ts
+++ b/app/definitions/IFrequentlyUsedEmoji.ts
@@ -7,4 +7,4 @@ export interface IFrequentlyUsedEmoji {
count: number;
}
-export type TFrequentlyUsedEmoji = IFrequentlyUsedEmoji & Model;
+export type TFrequentlyUsedEmojiModel = IFrequentlyUsedEmoji & Model;
diff --git a/app/definitions/IInvite.ts b/app/definitions/IInvite.ts
new file mode 100644
index 000000000..e9604f6e2
--- /dev/null
+++ b/app/definitions/IInvite.ts
@@ -0,0 +1,11 @@
+import { IRocketChatRecord } from './IRocketChatRecord';
+
+export interface IInvite extends IRocketChatRecord {
+ days: number;
+ maxUses: number;
+ rid: string;
+ userId: string;
+ createdAt: Date;
+ expires: Date | null;
+ uses: number;
+}
diff --git a/app/definitions/ILivechatAgent.ts b/app/definitions/ILivechatAgent.ts
new file mode 100644
index 000000000..01d97d082
--- /dev/null
+++ b/app/definitions/ILivechatAgent.ts
@@ -0,0 +1,16 @@
+import { IUser } from './IUser';
+
+export enum ILivechatAgentStatus {
+ AVAILABLE = 'available',
+ UNAVAILABLE = 'unavailable'
+}
+
+export interface ILivechatAgent extends IUser {
+ statusLivechat: ILivechatAgentStatus;
+ livechat: {
+ maxNumberSimultaneousChat: number;
+ };
+ livechatCount: number;
+ lastRoutingTime: Date;
+ livechatStatusSystemModified?: boolean;
+}
diff --git a/app/definitions/ILivechatDepartment.ts b/app/definitions/ILivechatDepartment.ts
new file mode 100644
index 000000000..a3e67e9f2
--- /dev/null
+++ b/app/definitions/ILivechatDepartment.ts
@@ -0,0 +1,18 @@
+export interface ILivechatDepartment {
+ _id: string;
+ name: string;
+ enabled: boolean;
+ description?: string;
+ showOnRegistration: boolean;
+ showOnOfflineForm: boolean;
+ requestTagBeforeClosingChat?: boolean;
+ email: string;
+ chatClosingTags?: string[];
+ offlineMessageChannelName: string;
+ numAgents: number;
+ _updatedAt?: Date;
+ businessHourId?: string;
+ fallbackForwardDepartment?: string;
+ // extra optional fields
+ [k: string]: any;
+}
diff --git a/app/definitions/ILivechatDepartmentAgents.ts b/app/definitions/ILivechatDepartmentAgents.ts
new file mode 100644
index 000000000..e33c80ff9
--- /dev/null
+++ b/app/definitions/ILivechatDepartmentAgents.ts
@@ -0,0 +1,9 @@
+export interface ILivechatDepartmentAgents {
+ _id: string;
+ departmentId: string;
+ departmentEnabled: boolean;
+ agentId: string;
+ username: string;
+ count: number;
+ order: number;
+}
diff --git a/app/definitions/ILivechatMonitor.ts b/app/definitions/ILivechatMonitor.ts
new file mode 100644
index 000000000..231e4c855
--- /dev/null
+++ b/app/definitions/ILivechatMonitor.ts
@@ -0,0 +1,8 @@
+export interface ILivechatMonitor {
+ _id: string;
+ name: string;
+ enabled: boolean;
+ numMonitors: number;
+ type: string;
+ visibility: string;
+}
diff --git a/app/definitions/ILivechatTag.ts b/app/definitions/ILivechatTag.ts
new file mode 100644
index 000000000..196c74c8d
--- /dev/null
+++ b/app/definitions/ILivechatTag.ts
@@ -0,0 +1,7 @@
+export interface ILivechatTag {
+ _id: string;
+ name: string;
+ description: string;
+ numDepartments: number;
+ departments: Array;
+}
diff --git a/app/definitions/ILivechatVisitor.ts b/app/definitions/ILivechatVisitor.ts
new file mode 100644
index 000000000..847d3b26c
--- /dev/null
+++ b/app/definitions/ILivechatVisitor.ts
@@ -0,0 +1,53 @@
+import { IRocketChatRecord } from './IRocketChatRecord';
+
+export interface IVisitorPhone {
+ phoneNumber: string;
+}
+
+export interface IVisitorLastChat {
+ _id: string;
+ ts: string;
+}
+
+export interface ILivechatVisitorConnectionData {
+ httpHeaders: {
+ [k: string]: string;
+ };
+ clientAddress: string;
+}
+
+export interface IVisitorEmail {
+ address: string;
+}
+
+export interface ILivechatVisitor extends IRocketChatRecord {
+ username: string;
+ ts: Date;
+ token: string;
+ department?: string;
+ name?: string;
+ phone?: IVisitorPhone[] | null;
+ lastChat?: IVisitorLastChat;
+ userAgent?: string;
+ ip?: string;
+ host?: string;
+ visitorEmails?: IVisitorEmail[];
+}
+
+export interface ILivechatVisitorDTO {
+ id: string;
+ token: string;
+ name: string;
+ email: string;
+ department: string;
+ phone: string | { number: string };
+ username: string;
+ customFields: {
+ key: string;
+ value: string;
+ overwrite: boolean;
+ }[];
+ connectionData: {
+ httpHeaders: Record;
+ };
+}
diff --git a/app/definitions/ILoggedUser.ts b/app/definitions/ILoggedUser.ts
index 487d29c18..ecd9e2674 100644
--- a/app/definitions/ILoggedUser.ts
+++ b/app/definitions/ILoggedUser.ts
@@ -8,11 +8,11 @@ export interface ILoggedUser {
language?: string;
status: string;
statusText?: string;
- roles: string[];
+ roles?: string[];
avatarETag?: string;
- showMessageInMainThread: boolean;
- isFromWebView: boolean;
+ showMessageInMainThread?: boolean;
+ isFromWebView?: boolean;
enableMessageParserEarlyAdoption?: boolean;
}
-export type TLoggedUser = ILoggedUser & Model;
+export type TLoggedUserModel = ILoggedUser & Model;
diff --git a/app/definitions/IMessage.ts b/app/definitions/IMessage.ts
index d4c9d3266..15f046c8b 100644
--- a/app/definitions/IMessage.ts
+++ b/app/definitions/IMessage.ts
@@ -3,7 +3,9 @@ import { MarkdownAST } from '@rocket.chat/message-parser';
import { IAttachment } from './IAttachment';
import { IReaction } from './IReaction';
-import { SubscriptionType } from './ISubscription';
+import { IUrl } from './IUrl';
+
+export type MessageType = 'jitsi_call_started' | 'discussion-created' | 'e2e' | 'load_more' | 'rm' | 'uj';
export interface IUserMessage {
_id: string;
@@ -34,12 +36,16 @@ export interface ITranslations {
value: string;
}
+export type E2EType = 'pending' | 'done';
+
export interface ILastMessage {
_id: string;
rid: string;
tshow: boolean;
+ t: MessageType;
tmid: string;
msg: string;
+ e2e: E2EType;
ts: Date;
u: IUserMessage;
_updatedAt: Date;
@@ -54,17 +60,20 @@ export interface ILastMessage {
}
export interface IMessage {
+ _id: string;
+ rid: string;
msg?: string;
- t?: SubscriptionType;
- ts: Date;
+ id?: string;
+ t?: MessageType;
+ ts: string | Date;
u: IUserMessage;
- alias: string;
- parseUrls: boolean;
+ alias?: string;
+ parseUrls?: boolean;
groupable?: boolean;
avatar?: string;
emoji?: string;
attachments?: IAttachment[];
- urls?: string[];
+ urls?: IUrl[];
_updatedAt: Date;
status?: number;
pinned?: boolean;
@@ -89,7 +98,7 @@ export interface IMessage {
e2e?: string;
tshow?: boolean;
md?: MarkdownAST;
- subscription: { id: string };
+ subscription?: { id: string };
}
export type TMessageModel = IMessage & Model;
diff --git a/app/definitions/IPermission.ts b/app/definitions/IPermission.ts
index 0ccc13465..5583f3edd 100644
--- a/app/definitions/IPermission.ts
+++ b/app/definitions/IPermission.ts
@@ -1,9 +1,9 @@
import Model from '@nozbe/watermelondb/Model';
export interface IPermission {
- id: string;
+ _id: string;
roles: string[];
- _updatedAt: Date;
+ _updatedAt: Date | string;
}
export type TPermissionModel = IPermission & Model;
diff --git a/app/definitions/IPreferences.ts b/app/definitions/IPreferences.ts
new file mode 100644
index 000000000..d444dd7e4
--- /dev/null
+++ b/app/definitions/IPreferences.ts
@@ -0,0 +1,10 @@
+import { SortBy, DisplayMode } from '../constants/constantDisplayMode';
+
+export interface IPreferences {
+ sortBy: SortBy;
+ groupByType: boolean;
+ showFavorites: boolean;
+ showUnread: boolean;
+ showAvatar: boolean;
+ displayMode: DisplayMode;
+}
diff --git a/app/definitions/IRocketChat.ts b/app/definitions/IRocketChat.ts
new file mode 100644
index 000000000..654e2c92c
--- /dev/null
+++ b/app/definitions/IRocketChat.ts
@@ -0,0 +1,9 @@
+import rocketchat from '../lib/rocketchat';
+
+type TRocketChat = typeof rocketchat;
+
+export interface IRocketChat extends TRocketChat {
+ sdk: any;
+ activeUsersSubTimeout: any;
+ roomsSub: any;
+}
diff --git a/app/definitions/IRocketChatRecord.ts b/app/definitions/IRocketChatRecord.ts
new file mode 100644
index 000000000..8a933059a
--- /dev/null
+++ b/app/definitions/IRocketChatRecord.ts
@@ -0,0 +1,10 @@
+export interface IRocketChatRecord {
+ _id?: string;
+ _updatedAt?: Date;
+}
+
+export type RocketChatRecordDeleted = T &
+ IRocketChatRecord & {
+ _deletedAt: Date;
+ __collection__: string;
+ };
diff --git a/app/definitions/IRole.ts b/app/definitions/IRole.ts
index 1fec42150..332dab65d 100644
--- a/app/definitions/IRole.ts
+++ b/app/definitions/IRole.ts
@@ -3,6 +3,12 @@ import Model from '@nozbe/watermelondb/Model';
export interface IRole {
id: string;
description?: string;
+ mandatory2fa?: boolean;
+ name: string;
+ protected: boolean;
+ // scope?: string;
+ scope: 'Users' | 'Subscriptions';
+ _id: string;
}
export type TRoleModel = IRole & Model;
diff --git a/app/definitions/IRoom.ts b/app/definitions/IRoom.ts
index 503ea4dd7..2ea2f1824 100644
--- a/app/definitions/IRoom.ts
+++ b/app/definitions/IRoom.ts
@@ -1,9 +1,20 @@
import Model from '@nozbe/watermelondb/Model';
+import { IMessage } from './IMessage';
import { IServedBy } from './IServedBy';
import { SubscriptionType } from './ISubscription';
+import { IUser } from './IUser';
+
+interface IRequestTranscript {
+ email: string;
+ requestedAt: Date;
+ requestedBy: IUser;
+ subject: string;
+}
export interface IRoom {
+ _id?: string;
+ fname?: string;
id: string;
rid: string;
prid: string;
@@ -15,13 +26,78 @@ export interface IRoom {
broadcast: boolean;
encrypted: boolean;
ro: boolean;
- v?: string[];
+ v?: {
+ _id?: string;
+ token?: string;
+ status: 'online' | 'busy' | 'away' | 'offline';
+ };
servedBy?: IServedBy;
departmentId?: string;
livechatData?: any;
tags?: string[];
e2eKeyId?: string;
avatarETag?: string;
+ latest?: string;
+ default?: true;
+ featured?: true;
+}
+
+export enum OmnichannelSourceType {
+ WIDGET = 'widget',
+ EMAIL = 'email',
+ SMS = 'sms',
+ APP = 'app',
+ API = 'api',
+ OTHER = 'other' // catch-all source type
+}
+export interface IOmnichannelRoom extends Omit {
+ t: SubscriptionType.OMNICHANNEL;
+ v: {
+ _id?: string;
+ token?: string;
+ status: 'online' | 'busy' | 'away' | 'offline';
+ };
+ email?: {
+ // Data used when the room is created from an email, via email Integration.
+ inbox: string;
+ thread: string;
+ replyTo: string;
+ subject: string;
+ };
+ source: {
+ // TODO: looks like this is not so required as the definition suggests
+ // The source, or client, which created the Omnichannel room
+ type: OmnichannelSourceType;
+ // An optional identification of external sources, such as an App
+ id?: string;
+ // A human readable alias that goes with the ID, for post analytical purposes
+ alias?: string;
+ // A label to be shown in the room info
+ label?: string;
+ // The sidebar icon
+ sidebarIcon?: string;
+ // The default sidebar icon
+ defaultIcon?: string;
+ };
+ transcriptRequest?: IRequestTranscript;
+ servedBy?: IServedBy;
+ onHold?: boolean;
+ departmentId?: string;
+
+ lastMessage?: IMessage & { token?: string };
+
+ tags: any;
+ closedAt: any;
+ metrics: any;
+ waitingResponse: any;
+ responseBy: any;
+ priorityId: any;
+ livechatData: any;
+ queuedAt?: Date;
+
+ ts: Date;
+ label?: string;
+ crmData?: unknown;
}
export type TRoomModel = IRoom & Model;
diff --git a/app/definitions/IServer.ts b/app/definitions/IServer.ts
index 0c3bf57d3..3f36d7feb 100644
--- a/app/definitions/IServer.ts
+++ b/app/definitions/IServer.ts
@@ -6,7 +6,7 @@ export interface IServer {
useRealName: boolean;
FileUpload_MediaTypeWhiteList: string;
FileUpload_MaxFileSize: number;
- roomsUpdatedAt: Date;
+ roomsUpdatedAt: Date | null;
version: string;
lastLocalAuthenticatedSession: Date;
autoLock: boolean;
diff --git a/app/definitions/IServerHistory.ts b/app/definitions/IServerHistory.ts
index 296cba4ed..68d25df4f 100644
--- a/app/definitions/IServerHistory.ts
+++ b/app/definitions/IServerHistory.ts
@@ -7,4 +7,4 @@ export interface IServerHistory {
updatedAt: Date;
}
-export type TServerHistory = IServerHistory & Model;
+export type TServerHistoryModel = IServerHistory & Model;
diff --git a/app/definitions/ISetting.ts b/app/definitions/ISetting.ts
new file mode 100644
index 000000000..a51494db5
--- /dev/null
+++ b/app/definitions/ISetting.ts
@@ -0,0 +1,237 @@
+export type SettingId = string;
+export type GroupId = SettingId;
+export type TabId = SettingId;
+export type SectionName = string;
+
+export enum SettingEditor {
+ COLOR = 'color',
+ EXPRESSION = 'expression'
+}
+type AssetValue = { defaultUrl?: string };
+export type SettingValueMultiSelect = (string | number)[];
+export type SettingValueRoomPick = Array<{ _id: string; name: string }> | string;
+export type SettingValue = string | boolean | number | SettingValueMultiSelect | Date | AssetValue | undefined;
+
+export interface ISettingSelectOption {
+ key: string | number;
+ i18nLabel: string;
+}
+
+export type ISetting = ISettingBase | ISettingEnterprise | ISettingColor | ISettingCode | ISettingAction;
+
+export interface ISettingBase {
+ _id: SettingId;
+ type:
+ | 'boolean'
+ | 'timezone'
+ | 'string'
+ | 'relativeUrl'
+ | 'password'
+ | 'int'
+ | 'select'
+ | 'multiSelect'
+ | 'language'
+ | 'color'
+ | 'font'
+ | 'code'
+ | 'action'
+ | 'asset'
+ | 'roomPick'
+ | 'group'
+ | 'date';
+ public: boolean;
+ env: boolean;
+ group?: GroupId;
+ section?: SectionName;
+ tab?: TabId;
+ i18nLabel: string;
+ value: SettingValue;
+ packageValue: SettingValue;
+ blocked: boolean;
+ // enableQuery?: string | FilterQuery | FilterQuery[];
+ // displayQuery?: string | FilterQuery | FilterQuery[];
+ sorter: number;
+ properties?: unknown;
+ enterprise?: boolean;
+ requiredOnWizard?: boolean;
+ hidden?: boolean;
+ modules?: Array;
+ invalidValue?: SettingValue;
+ valueSource?: string;
+ secret?: boolean;
+ i18nDescription?: string;
+ autocomplete?: boolean;
+ processEnvValue?: SettingValue;
+ meteorSettingsValue?: SettingValue;
+ ts: Date;
+ createdAt: Date;
+ _updatedAt?: Date;
+ multiline?: boolean;
+ values?: Array;
+ placeholder?: string;
+ wizard?: {
+ step: number;
+ order: number;
+ } | null;
+ persistent?: boolean; // todo: remove
+ readonly?: boolean; // todo: remove
+ alert?: string; // todo: check if this is still used
+ private?: boolean; // todo: remove
+}
+
+export interface ISettingGroup {
+ _id: string;
+ hidden: boolean;
+ blocked: boolean;
+ ts?: Date;
+ sorter: number;
+ i18nLabel: string;
+ // displayQuery?: string | FilterQuery | FilterQuery[];
+ i18nDescription: string;
+ value?: undefined;
+ type: 'group';
+
+ alert?: string; // todo: check if this is needed
+}
+
+export interface ISettingEnterprise extends ISettingBase {
+ enterprise: true;
+ invalidValue: SettingValue;
+}
+
+export interface ISettingColor extends ISettingBase {
+ type: 'color';
+ editor: SettingEditor;
+ packageEditor?: SettingEditor;
+}
+export interface ISettingCode extends ISettingBase {
+ type: 'code';
+ code?: string;
+}
+
+export interface ISettingAction extends ISettingBase {
+ type: 'action';
+ value: string;
+ actionText?: string;
+}
+export interface ISettingAsset extends ISettingBase {
+ type: 'asset';
+ value: AssetValue;
+}
+
+export interface ISettingDate extends ISettingBase {
+ type: 'date';
+ value: Date;
+}
+
+export const isDateSetting = (setting: ISetting): setting is ISettingDate => setting.type === 'date';
+
+export const isSettingEnterprise = (setting: ISettingBase): setting is ISettingEnterprise => setting.enterprise === true;
+
+export const isSettingColor = (setting: ISettingBase): setting is ISettingColor => setting.type === 'color';
+
+export const isSettingCode = (setting: ISettingBase): setting is ISettingCode => setting.type === 'code';
+
+export const isSettingAction = (setting: ISettingBase): setting is ISettingAction => setting.type === 'action';
+
+export const isSettingAsset = (setting: ISettingBase): setting is ISettingAsset => setting.type === 'asset';
+
+export interface ISettingStatistics {
+ account2fa?: boolean;
+ cannedResponsesEnabled?: boolean;
+ e2e?: boolean;
+ e2eDefaultDirectRoom?: boolean;
+ e2eDefaultPrivateRoom?: boolean;
+ smtpHost?: string;
+ smtpPort?: string;
+ fromEmail?: string;
+ frameworkDevMode?: boolean;
+ frameworkEnable?: boolean;
+ surveyEnabled?: boolean;
+ updateChecker?: boolean;
+ liveStream?: boolean;
+ broadcasting?: boolean;
+ allowEditing?: boolean;
+ allowDeleting?: boolean;
+ allowUnrecognizedSlashCommand?: boolean;
+ allowBadWordsFilter?: boolean;
+ readReceiptEnabled?: boolean;
+ readReceiptStoreUsers?: boolean;
+ otrEnable?: boolean;
+ pushEnable?: boolean;
+ globalSearchEnabled?: boolean;
+ threadsEnabled?: boolean;
+ bigBlueButton?: boolean;
+ jitsiEnabled?: boolean;
+ webRTCEnableChannel?: boolean;
+ webRTCEnablePrivate?: boolean;
+ webRTCEnableDirect?: boolean;
+}
+
+export interface ISettingStatisticsObject {
+ accounts?: {
+ account2fa?: boolean;
+ };
+ cannedResponses?: {
+ cannedResponsesEnabled?: boolean;
+ };
+ e2ee?: {
+ e2e?: boolean;
+ e2eDefaultDirectRoom?: boolean;
+ e2eDefaultPrivateRoom?: boolean;
+ };
+ email?: {
+ smtp?: {
+ smtpHost?: string;
+ smtpPort?: string;
+ fromEmail?: string;
+ };
+ };
+ general?: {
+ apps?: {
+ frameworkDevMode?: boolean;
+ frameworkEnable?: boolean;
+ };
+ nps?: {
+ surveyEnabled?: boolean;
+ };
+ update?: {
+ updateChecker?: boolean;
+ };
+ };
+ liveStreamAndBroadcasting?: {
+ liveStream?: boolean;
+ broadcasting?: boolean;
+ };
+ message?: {
+ allowEditing?: boolean;
+ allowDeleting?: boolean;
+ allowUnrecognizedSlashCommand?: boolean;
+ allowBadWordsFilter?: boolean;
+ readReceiptEnabled?: boolean;
+ readReceiptStoreUsers?: boolean;
+ };
+ otr?: {
+ otrEnable?: boolean;
+ };
+ push?: {
+ pushEnable?: boolean;
+ };
+ search?: {
+ defaultProvider?: {
+ globalSearchEnabled?: boolean;
+ };
+ };
+ threads?: {
+ threadsEnabled?: boolean;
+ };
+ videoConference?: {
+ bigBlueButton?: boolean;
+ jitsiEnabled?: boolean;
+ };
+ webRTC?: {
+ webRTCEnableChannel?: boolean;
+ webRTCEnablePrivate?: boolean;
+ webRTCEnableDirect?: boolean;
+ };
+}
diff --git a/app/definitions/ISettings.ts b/app/definitions/ISettings.ts
index 1fbb63ac8..61dd0a0dd 100644
--- a/app/definitions/ISettings.ts
+++ b/app/definitions/ISettings.ts
@@ -9,4 +9,22 @@ export interface ISettings {
_updatedAt?: Date;
}
+export interface IPreparedSettings {
+ _id: string;
+ value: string;
+ enterprise: boolean;
+ valueAsString?: string;
+ valueAsBoolean?: boolean;
+ valueAsNumber?: number;
+}
+
+export interface ISettingsIcon {
+ _id: string;
+ value: {
+ defaultUrl: string;
+ url?: string;
+ };
+ enterprise: boolean;
+}
+
export type TSettingsModel = ISettings & Model;
diff --git a/app/definitions/ISlashCommand.ts b/app/definitions/ISlashCommand.ts
index a859448df..966392733 100644
--- a/app/definitions/ISlashCommand.ts
+++ b/app/definitions/ISlashCommand.ts
@@ -9,4 +9,8 @@ export interface ISlashCommand {
appId?: string;
}
+export interface ISlashCommandResult extends ISlashCommand {
+ command: string;
+}
+
export type TSlashCommandModel = ISlashCommand & Model;
diff --git a/app/definitions/ISubscription.ts b/app/definitions/ISubscription.ts
index 1f241599a..3e523656b 100644
--- a/app/definitions/ISubscription.ts
+++ b/app/definitions/ISubscription.ts
@@ -12,7 +12,8 @@ export enum SubscriptionType {
DIRECT = 'd',
CHANNEL = 'c',
OMNICHANNEL = 'l',
- THREAD = 'thread'
+ E2E = 'e2e',
+ THREAD = 'thread' // FIXME: this is not a type of subscription
}
export interface IVisitor {
@@ -79,8 +80,6 @@ export interface ISubscription {
avatarETag?: string;
teamId?: string;
teamMain?: boolean;
- search?: boolean;
- username?: string;
// https://nozbe.github.io/WatermelonDB/Relation.html#relation-api
messages: Relation;
threads: Relation;
diff --git a/app/definitions/ITagsOmnichannel.ts b/app/definitions/ITagsOmnichannel.ts
new file mode 100644
index 000000000..d0bb2c9b5
--- /dev/null
+++ b/app/definitions/ITagsOmnichannel.ts
@@ -0,0 +1,5 @@
+export interface ITagsOmnichannel {
+ _id: string;
+ name: string;
+ departments: string[];
+}
diff --git a/app/definitions/ITeam.ts b/app/definitions/ITeam.ts
index 8cf8bddce..3b1774054 100644
--- a/app/definitions/ITeam.ts
+++ b/app/definitions/ITeam.ts
@@ -1,5 +1,48 @@
-// https://github.com/RocketChat/Rocket.Chat/blob/develop/definition/ITeam.ts
-exports.TEAM_TYPE = {
- PUBLIC: 0,
- PRIVATE: 1
-};
+import { IRocketChatRecord } from './IRocketChatRecord';
+import { IUser } from './IUser';
+
+export enum TEAM_TYPE {
+ PUBLIC = 0,
+ PRIVATE = 1
+}
+
+export type SortType = -1 | 1;
+
+export interface ITeam extends IRocketChatRecord {
+ name: string;
+ type: TEAM_TYPE;
+ roomId: string;
+ createdBy: Pick;
+ createdAt: Date;
+}
+
+export interface ITeamMember extends IRocketChatRecord {
+ teamId: string;
+ userId: string;
+ roles?: Array;
+ createdBy: Pick;
+ createdAt: Date;
+}
+export interface IPaginationOptions {
+ offset: number;
+ count: number;
+}
+export interface IRecordsWithTotal {
+ records: Array;
+ total: number;
+}
+
+export interface ITeamStatData {
+ teamId: string;
+ mainRoom: string;
+ totalRooms: number;
+ totalMessages: number;
+ totalPublicRooms: number;
+ totalPrivateRooms: number;
+ totalDefaultRooms: number;
+ totalMembers: number;
+}
+export interface ITeamStats {
+ totalTeams: number;
+ teamStats: Array;
+}
diff --git a/app/definitions/IThread.ts b/app/definitions/IThread.ts
index e97b2348c..670e10d52 100644
--- a/app/definitions/IThread.ts
+++ b/app/definitions/IThread.ts
@@ -2,16 +2,9 @@ import Model from '@nozbe/watermelondb/Model';
import { MarkdownAST } from '@rocket.chat/message-parser';
import { IAttachment } from './IAttachment';
-import { IEditedBy, IUserChannel, IUserMention, IUserMessage } from './IMessage';
+import { IEditedBy, IUserChannel, IUserMention, IUserMessage, MessageType } from './IMessage';
import { IReaction } from './IReaction';
-import { SubscriptionType } from './ISubscription';
-
-export interface IUrl {
- title: string;
- description: string;
- image: string;
- url: string;
-}
+import { IUrl } from './IUrl';
interface IFileThread {
_id: string;
@@ -22,28 +15,29 @@ interface IFileThread {
export interface IThreadResult {
_id: string;
rid: string;
- ts: string;
- msg: string;
+ ts: string | Date;
+ msg?: string;
file?: IFileThread;
files?: IFileThread[];
groupable?: boolean;
attachments?: IAttachment[];
md?: MarkdownAST;
u: IUserMessage;
- _updatedAt: string;
- urls: IUrl[];
- mentions: IUserMention[];
- channels: IUserChannel[];
- replies: string[];
- tcount: number;
- tlm: string;
+ _updatedAt: Date;
+ urls?: IUrl[];
+ mentions?: IUserMention[];
+ channels?: IUserChannel[];
+ replies?: string[];
+ tcount?: number;
+ tlm?: Date;
}
export interface IThread {
id: string;
+ tmsg?: string;
msg?: string;
- t?: SubscriptionType;
- rid?: string;
+ t?: MessageType;
+ rid: string;
_updatedAt?: Date;
ts?: Date;
u?: IUserMessage;
@@ -61,10 +55,10 @@ export interface IThread {
reactions?: IReaction[];
role?: string;
drid?: string;
- dcount?: number;
+ dcount?: number | string;
dlm?: number;
tmid?: string;
- tcount: number | string;
+ tcount?: number | string;
tlm?: string;
replies?: string[];
mentions?: IUserMention[];
@@ -73,6 +67,7 @@ export interface IThread {
autoTranslate?: boolean;
translations?: any;
e2e?: string;
+ subscription: { id: string };
}
export type TThreadModel = IThread & Model;
diff --git a/app/definitions/IThreadMessage.ts b/app/definitions/IThreadMessage.ts
index c773e4dc5..574d4a200 100644
--- a/app/definitions/IThreadMessage.ts
+++ b/app/definitions/IThreadMessage.ts
@@ -1,15 +1,17 @@
import Model from '@nozbe/watermelondb/Model';
import { IAttachment } from './IAttachment';
-import { IEditedBy, ITranslations, IUserChannel, IUserMention, IUserMessage } from './IMessage';
+import { IEditedBy, ITranslations, IUserChannel, IUserMention, IUserMessage, MessageType } from './IMessage';
import { IReaction } from './IReaction';
-import { SubscriptionType } from './ISubscription';
+import { IUrl } from './IUrl';
export interface IThreadMessage {
+ _id: string;
+ tmsg?: string;
msg?: string;
- t?: SubscriptionType;
+ t?: MessageType;
rid: string;
- ts: Date;
+ ts: string | Date;
u: IUserMessage;
alias?: string;
parseUrls?: boolean;
@@ -17,7 +19,7 @@ export interface IThreadMessage {
avatar?: string;
emoji?: string;
attachments?: IAttachment[];
- urls?: string[];
+ urls?: IUrl[];
_updatedAt?: Date;
status?: number;
pinned?: boolean;
diff --git a/app/definitions/IUpload.ts b/app/definitions/IUpload.ts
index 6ff03c519..059555130 100644
--- a/app/definitions/IUpload.ts
+++ b/app/definitions/IUpload.ts
@@ -1,16 +1,17 @@
import Model from '@nozbe/watermelondb/Model';
export interface IUpload {
- id: string;
- path?: string;
+ id?: string;
+ rid?: string;
+ path: string;
name?: string;
description?: string;
size: number;
type?: string;
store?: string;
- progress: number;
- error: boolean;
- subscription: { id: string };
+ progress?: number;
+ error?: boolean;
+ subscription?: { id: string };
}
export type TUploadModel = IUpload & Model;
diff --git a/app/definitions/IUrl.ts b/app/definitions/IUrl.ts
index 9b72fda26..3e613bab3 100644
--- a/app/definitions/IUrl.ts
+++ b/app/definitions/IUrl.ts
@@ -1,6 +1,49 @@
export interface IUrl {
+ _id: number;
title: string;
description: string;
image: string;
url: string;
}
+
+export interface IUrlFromServer {
+ url: string;
+ meta: {
+ pageTitle: string;
+ description: string;
+ fbAppId: string;
+ twitterImageSrc: string;
+ twitterSite: string;
+ twitterCard: string;
+ twitterTitle: string;
+ twitterDescription: string;
+ ogImage: string;
+ ogImageAlt: string;
+ ogImageWidth: string;
+ ogImageHeight: string;
+ ogSiteName: string;
+ ogType: string;
+ ogTitle: string;
+ ogUrl: string;
+ ogDescription: string;
+ title: string;
+ oembedTitle: string;
+ oembedAuthorName: string;
+ twitterImage: string;
+ oembedThumbnailUrl: string;
+ };
+ headers: {
+ contentType: string;
+ };
+ parsedUrl: {
+ host: string;
+ hash: any;
+ pathname: string;
+ protocol: string;
+ port: any;
+ query: any;
+ search: any;
+ hostname: string;
+ };
+ ignoreParse: boolean;
+}
diff --git a/app/definitions/IUser.ts b/app/definitions/IUser.ts
index 012ef8087..461b52fb4 100644
--- a/app/definitions/IUser.ts
+++ b/app/definitions/IUser.ts
@@ -1,10 +1,157 @@
import Model from '@nozbe/watermelondb/Model';
-export interface IUser {
- _id: string;
- name?: string;
- username: string;
- avatarETag?: string;
+import { UserStatus } from './UserStatus';
+import { IRocketChatRecord } from './IRocketChatRecord';
+import { ILoggedUser } from './ILoggedUser';
+
+export interface ILoginToken {
+ hashedToken: string;
+ twoFactorAuthorizedUntil?: Date;
+ twoFactorAuthorizedHash?: string;
}
+export interface IMeteorLoginToken extends ILoginToken {
+ when: Date;
+}
+
+export interface IPersonalAccessToken extends ILoginToken {
+ type: 'personalAccessToken';
+ createdAt: Date;
+ lastTokenPart: string;
+ name?: string;
+ bypassTwoFactor?: boolean;
+}
+
+export interface IUserEmailVerificationToken {
+ token: string;
+ address: string;
+ when: Date;
+}
+
+export interface IUserEmailCode {
+ code: string;
+ expire: Date;
+}
+
+type LoginToken = IMeteorLoginToken & IPersonalAccessToken;
+export type Username = string;
+
+export type ILoginUsername =
+ | {
+ username: string;
+ }
+ | {
+ email: string;
+ };
+export type LoginUsername = string | ILoginUsername;
+
+export interface IUserServices {
+ password?: {
+ bcrypt: string;
+ };
+ passwordHistory?: string[];
+ email?: {
+ verificationTokens?: IUserEmailVerificationToken[];
+ };
+ resume?: {
+ loginTokens?: LoginToken[];
+ };
+ google?: any;
+ facebook?: any;
+ github?: any;
+ totp?: {
+ enabled: boolean;
+ hashedBackup: string[];
+ secret: string;
+ };
+ email2fa?: {
+ enabled: boolean;
+ changedAt: Date;
+ };
+ emailCode: IUserEmailCode[];
+ saml?: {
+ inResponseTo?: string;
+ provider?: string;
+ idp?: string;
+ idpSession?: string;
+ nameID?: string;
+ };
+ ldap?: {
+ id: string;
+ idAttribute?: string;
+ };
+}
+
+export interface IUserEmail {
+ address: string;
+ verified: boolean;
+}
+
+export interface IUserSettings {
+ profile: any;
+ preferences: {
+ [key: string]: any;
+ };
+}
+
+export interface IUser extends IRocketChatRecord, Omit {
+ _id: string;
+ id: string;
+ token: string;
+ createdAt?: Date;
+ roles?: string[];
+ type?: string;
+ active?: boolean;
+ username: string;
+ name?: string;
+ services?: IUserServices;
+ emails?: IUserEmail[];
+ status?: UserStatus;
+ statusConnection?: string;
+ lastLogin?: Date;
+ avatarOrigin?: string;
+ avatarETag?: string;
+ utcOffset?: number;
+ language?: string;
+ statusDefault?: UserStatus;
+ statusText?: string;
+ oauth?: {
+ authorizedClients: string[];
+ };
+ statusLivechat?: string;
+ e2e?: {
+ private_key: string;
+ public_key: string;
+ };
+ requirePasswordChange?: boolean;
+ customFields?: {
+ [key: string]: any;
+ };
+ settings?: IUserSettings;
+ defaultRoom?: string;
+ ldap?: boolean;
+}
+
+export interface IRegisterUser extends IUser {
+ username: string;
+ name: string;
+}
+export const isRegisterUser = (user: IUser): user is IRegisterUser => user.username !== undefined && user.name !== undefined;
+
+export type IUserDataEvent = {
+ id: unknown;
+} & (
+ | ({
+ type: 'inserted';
+ } & IUser)
+ | {
+ type: 'removed';
+ }
+ | {
+ type: 'updated';
+ diff: Partial;
+ unset: Record;
+ }
+);
+
export type TUserModel = IUser & Model;
diff --git a/app/definitions/IVisitor.ts b/app/definitions/IVisitor.ts
new file mode 100644
index 000000000..637513567
--- /dev/null
+++ b/app/definitions/IVisitor.ts
@@ -0,0 +1,24 @@
+export interface IVisitorEmail {
+ address: string;
+}
+
+export interface IVisitorPhone {
+ phoneNumber: string;
+}
+
+export interface IVisitor {
+ _id?: string;
+ token: string;
+ username: string;
+ updatedAt?: Date;
+ name: string;
+ department?: string;
+ phone?: IVisitorPhone[];
+ visitorEmails?: IVisitorEmail[];
+ customFields?: {
+ [key: string]: any;
+ };
+ livechatData: {
+ [key: string]: any;
+ };
+}
diff --git a/app/definitions/UserStatus.ts b/app/definitions/UserStatus.ts
new file mode 100644
index 000000000..fafb9ccaa
--- /dev/null
+++ b/app/definitions/UserStatus.ts
@@ -0,0 +1,6 @@
+export enum UserStatus {
+ ONLINE = 'online',
+ AWAY = 'away',
+ OFFLINE = 'offline',
+ BUSY = 'busy'
+}
diff --git a/app/definitions/index.ts b/app/definitions/index.ts
index 80eeb88ca..c11e91b1a 100644
--- a/app/definitions/index.ts
+++ b/app/definitions/index.ts
@@ -3,11 +3,27 @@ import { StackNavigationProp } from '@react-navigation/stack';
import { Dispatch } from 'redux';
export * from './IAttachment';
-export * from './IMessage';
export * from './INotification';
-export * from './IRoom';
-export * from './IServer';
+export * from './IPreferences';
export * from './ISubscription';
+export * from './IRoom';
+export * from './IMessage';
+export * from './IThread';
+export * from './IThreadMessage';
+export * from './ICustomEmoji';
+export * from './IFrequentlyUsedEmoji';
+export * from './IUpload';
+export * from './ISettings';
+export * from './IRole';
+export * from './IPermission';
+export * from './ISlashCommand';
+export * from './IUser';
+export * from './IServer';
+export * from './ILoggedUser';
+export * from './IServerHistory';
+export * from './IRocketChat';
+export * from './ICertificate';
+export * from './IUrl';
export interface IBaseScreen, S extends string> {
navigation: StackNavigationProp;
@@ -17,3 +33,4 @@ export interface IBaseScreen, S ext
}
export * from './redux';
+export * from './redux/TRootEnum';
diff --git a/app/definitions/redux/TRootEnum.ts b/app/definitions/redux/TRootEnum.ts
new file mode 100644
index 000000000..8b0faca42
--- /dev/null
+++ b/app/definitions/redux/TRootEnum.ts
@@ -0,0 +1,6 @@
+export enum RootEnum {
+ ROOT_OUTSIDE = 'outside',
+ ROOT_INSIDE = 'inside',
+ ROOT_LOADING = 'loading',
+ ROOT_SET_USERNAME = 'setUsername'
+}
diff --git a/app/definitions/redux/index.ts b/app/definitions/redux/index.ts
index e95763e29..7b4e52db0 100644
--- a/app/definitions/redux/index.ts
+++ b/app/definitions/redux/index.ts
@@ -1,31 +1,75 @@
-import { TActionSelectedUsers } from '../../actions/selectedUsers';
+// ACTIONS
import { TActionActiveUsers } from '../../actions/activeUsers';
+import { TActionApp } from '../../actions/app';
+import { TActionCreateChannel } from '../../actions/createChannel';
+import { TActionCreateDiscussion } from '../../actions/createDiscussion';
+import { TActionCustomEmojis } from '../../actions/customEmojis';
+import { TActionEncryption } from '../../actions/encryption';
+import { TActionInviteLinks } from '../../actions/inviteLinks';
+import { IActionRoles } from '../../actions/roles';
+import { TActionSelectedUsers } from '../../actions/selectedUsers';
+import { TActionServer } from '../../actions/server';
+import { IActionSettings } from '../../actions/settings';
+import { TActionsShare } from '../../actions/share';
+import { TActionSortPreferences } from '../../actions/sortPreferences';
+import { TActionUserTyping } from '../../actions/usersTyping';
+import { TActionPermissions } from '../../actions/permissions';
+import { TActionEnterpriseModules } from '../../actions/enterpriseModules';
// REDUCERS
import { IActiveUsers } from '../../reducers/activeUsers';
+import { IApp } from '../../reducers/app';
+import { IConnect } from '../../reducers/connect';
+import { ICreateChannel } from '../../reducers/createChannel';
+import { ICreateDiscussion } from '../../reducers/createDiscussion';
+import { IEncryption } from '../../reducers/encryption';
+import { IInviteLinks } from '../../reducers/inviteLinks';
+import { ILogin } from '../../reducers/login';
+import { IRoles } from '../../reducers/roles';
+import { IRoom } from '../../reducers/room';
import { ISelectedUsers } from '../../reducers/selectedUsers';
+import { IServer } from '../../reducers/server';
+import { ISettings } from '../../reducers/settings';
+import { IShare } from '../../reducers/share';
+import { IPermissionsState } from '../../reducers/permissions';
+import { IEnterpriseModules } from '../../reducers/enterpriseModules';
export interface IApplicationState {
- settings: any;
- login: any;
- meteor: any;
- server: any;
+ settings: ISettings;
+ meteor: IConnect;
+ login: ILogin;
+ server: IServer;
selectedUsers: ISelectedUsers;
- createChannel: any;
- app: any;
- room: any;
+ app: IApp;
+ createChannel: ICreateChannel;
+ room: IRoom;
rooms: any;
sortPreferences: any;
- share: any;
+ share: IShare;
customEmojis: any;
activeUsers: IActiveUsers;
usersTyping: any;
- inviteLinks: any;
- createDiscussion: any;
+ inviteLinks: IInviteLinks;
+ createDiscussion: ICreateDiscussion;
inquiry: any;
- enterpriseModules: any;
- encryption: any;
- permissions: any;
- roles: any;
+ enterpriseModules: IEnterpriseModules;
+ encryption: IEncryption;
+ permissions: IPermissionsState;
+ roles: IRoles;
}
-export type TApplicationActions = TActionActiveUsers & TActionSelectedUsers;
+export type TApplicationActions = TActionActiveUsers &
+ TActionSelectedUsers &
+ TActionCustomEmojis &
+ TActionInviteLinks &
+ IActionRoles &
+ IActionSettings &
+ TActionEncryption &
+ TActionSortPreferences &
+ TActionUserTyping &
+ TActionCreateDiscussion &
+ TActionCreateChannel &
+ TActionsShare &
+ TActionServer &
+ TActionApp &
+ TActionPermissions &
+ TActionEnterpriseModules;
diff --git a/app/definitions/rest/helpers/PaginatedRequest.ts b/app/definitions/rest/helpers/PaginatedRequest.ts
new file mode 100644
index 000000000..3543675e3
--- /dev/null
+++ b/app/definitions/rest/helpers/PaginatedRequest.ts
@@ -0,0 +1,5 @@
+export type PaginatedRequest = {
+ count?: number;
+ offset?: number;
+ sort?: `{ "${S}": ${1 | -1} }` | string;
+} & T;
diff --git a/app/definitions/rest/helpers/PaginatedResult.ts b/app/definitions/rest/helpers/PaginatedResult.ts
new file mode 100644
index 000000000..ea153093c
--- /dev/null
+++ b/app/definitions/rest/helpers/PaginatedResult.ts
@@ -0,0 +1,5 @@
+export type PaginatedResult = {
+ count: number;
+ offset: number;
+ total: number;
+} & T;
diff --git a/app/definitions/rest/helpers/index.ts b/app/definitions/rest/helpers/index.ts
new file mode 100644
index 000000000..00a73d1ff
--- /dev/null
+++ b/app/definitions/rest/helpers/index.ts
@@ -0,0 +1,93 @@
+/* eslint-disable @typescript-eslint/no-unused-vars */
+import { Endpoints } from '../v1';
+
+type ReplacePlaceholders = string extends TPath
+ ? TPath
+ : TPath extends `${infer Start}:${infer _Param}/${infer Rest}`
+ ? `${Start}${string}/${ReplacePlaceholders}`
+ : TPath extends `${infer Start}:${infer _Param}`
+ ? `${Start}${string}`
+ : TPath;
+
+type KeyOfEach = T extends any ? keyof T : never;
+
+type GetParams = TOperation extends (...args: any) => any
+ ? Parameters[0] extends void
+ ? void
+ : Parameters[0]
+ : never;
+
+type GetResult = TOperation extends (...args: any) => any ? ReturnType : never;
+
+type OperationsByPathPatternAndMethod<
+ TPathPattern extends keyof Endpoints,
+ TMethod extends KeyOfEach = KeyOfEach
+> = TMethod extends any
+ ? {
+ pathPattern: TPathPattern;
+ method: TMethod;
+ path: ReplacePlaceholders;
+ params: GetParams;
+ result: GetResult;
+ }
+ : never;
+
+type OperationsByPathPattern = TPathPattern extends any
+ ? OperationsByPathPatternAndMethod
+ : never;
+
+type Operations = OperationsByPathPattern;
+
+type Method = Operations['method'];
+
+export type PathPattern = Operations['pathPattern'];
+
+type Path = Operations['path'];
+
+//
+
+export type Serialized = T extends Date
+ ? Exclude | string
+ : T extends boolean | number | string | null | undefined
+ ? T
+ : T extends {}
+ ? {
+ [K in keyof T]: Serialized;
+ }
+ : null;
+
+export type MatchPathPattern = TPath extends any
+ ? Extract['pathPattern']
+ : never;
+
+export type OperationResult<
+ TMethod extends Method,
+ TPathPattern extends PathPattern
+> = TMethod extends keyof Endpoints[TPathPattern] ? GetResult : never;
+
+export type PathFor = TMethod extends any ? Extract['path'] : never;
+
+export type OperationParams<
+ TMethod extends Method,
+ TPathPattern extends PathPattern
+> = TMethod extends keyof Endpoints[TPathPattern] ? GetParams : never;
+
+type SuccessResult = T & { success: true };
+
+type FailureResult = {
+ success: false;
+ error: T;
+ stack: TStack;
+ errorType: TErrorType;
+ details: TErrorDetails;
+};
+
+type UnauthorizedResult = {
+ success: false;
+ error: T | 'unauthorized';
+};
+
+export type ResultFor =
+ | SuccessResult>
+ | FailureResult
+ | UnauthorizedResult;
diff --git a/app/definitions/rest/v1/channels.ts b/app/definitions/rest/v1/channels.ts
new file mode 100644
index 000000000..d16798774
--- /dev/null
+++ b/app/definitions/rest/v1/channels.ts
@@ -0,0 +1,20 @@
+import type { IMessage } from '../../IMessage';
+import type { IRoom } from '../../IRoom';
+import type { IUser } from '../../IUser';
+
+export type ChannelsEndpoints = {
+ 'channels.files': {
+ GET: (params: { roomId: IRoom['_id']; offset: number; count: number; sort: string; query: string }) => {
+ files: IMessage[];
+ total: number;
+ };
+ };
+ 'channels.members': {
+ GET: (params: { roomId: IRoom['_id']; offset?: number; count?: number; filter?: string; status?: string[] }) => {
+ count: number;
+ offset: number;
+ members: IUser[];
+ total: number;
+ };
+ };
+};
diff --git a/app/definitions/rest/v1/chat.ts b/app/definitions/rest/v1/chat.ts
new file mode 100644
index 000000000..4a224c475
--- /dev/null
+++ b/app/definitions/rest/v1/chat.ts
@@ -0,0 +1,35 @@
+import type { IMessage } from '../../IMessage';
+import type { IRoom } from '../../IRoom';
+import { PaginatedResult } from '../helpers/PaginatedResult';
+
+export type ChatEndpoints = {
+ 'chat.getMessage': {
+ GET: (params: { msgId: IMessage['_id'] }) => {
+ message: IMessage;
+ };
+ };
+ 'chat.followMessage': {
+ POST: (params: { mid: IMessage['_id'] }) => void;
+ };
+ 'chat.unfollowMessage': {
+ POST: (params: { mid: IMessage['_id'] }) => void;
+ };
+ 'chat.getDiscussions': {
+ GET: (params: { roomId: IRoom['_id']; text?: string; offset: number; count: number }) => {
+ messages: IMessage[];
+ total: number;
+ };
+ };
+ 'chat.getThreadsList': {
+ GET: (params: {
+ rid: IRoom['_id'];
+ type: 'unread' | 'following' | 'all';
+ text?: string;
+ offset: number;
+ count: number;
+ }) => PaginatedResult<{
+ threads: IMessage[];
+ total: number;
+ }>;
+ };
+};
diff --git a/app/definitions/rest/v1/customUserStatus.ts b/app/definitions/rest/v1/customUserStatus.ts
new file mode 100644
index 000000000..db5e76ee4
--- /dev/null
+++ b/app/definitions/rest/v1/customUserStatus.ts
@@ -0,0 +1,7 @@
+export type CustomUserStatusEndpoints = {
+ 'custom-user-status.list': {
+ GET: (params: { query: string }) => {
+ statuses: unknown[];
+ };
+ };
+};
diff --git a/app/definitions/rest/v1/dm.ts b/app/definitions/rest/v1/dm.ts
new file mode 100644
index 000000000..6783cbc07
--- /dev/null
+++ b/app/definitions/rest/v1/dm.ts
@@ -0,0 +1,21 @@
+import type { IRoom } from '../../IRoom';
+import type { IUser } from '../../IUser';
+
+export type DmEndpoints = {
+ 'dm.create': {
+ POST: (
+ params: (
+ | {
+ username: Exclude;
+ }
+ | {
+ usernames: string;
+ }
+ ) & {
+ excludeSelf?: boolean;
+ }
+ ) => {
+ room: IRoom & { rid: IRoom['_id'] };
+ };
+ };
+};
diff --git a/app/definitions/rest/v1/emojiCustom.ts b/app/definitions/rest/v1/emojiCustom.ts
new file mode 100644
index 000000000..086589f2d
--- /dev/null
+++ b/app/definitions/rest/v1/emojiCustom.ts
@@ -0,0 +1,21 @@
+import type { ICustomEmojiDescriptor } from '../../ICustomEmojiDescriptor';
+import { PaginatedRequest } from '../helpers/PaginatedRequest';
+import { PaginatedResult } from '../helpers/PaginatedResult';
+
+export type EmojiCustomEndpoints = {
+ 'emoji-custom.all': {
+ GET: (params: PaginatedRequest<{ query: string }, 'name'>) => {
+ emojis: ICustomEmojiDescriptor[];
+ } & PaginatedResult;
+ };
+ 'emoji-custom.list': {
+ GET: (params: { updatedSince: string }) => {
+ emojis?: {
+ update: ICustomEmojiDescriptor[];
+ };
+ };
+ };
+ 'emoji-custom.delete': {
+ POST: (params: { emojiId: ICustomEmojiDescriptor['_id'] }) => void;
+ };
+};
diff --git a/app/definitions/rest/v1/groups.ts b/app/definitions/rest/v1/groups.ts
new file mode 100644
index 000000000..ca6454734
--- /dev/null
+++ b/app/definitions/rest/v1/groups.ts
@@ -0,0 +1,20 @@
+import type { IMessage } from '../../IMessage';
+import type { IRoom } from '../../IRoom';
+import type { IUser } from '../../IUser';
+
+export type GroupsEndpoints = {
+ 'groups.files': {
+ GET: (params: { roomId: IRoom['_id']; count: number; sort: string; query: string }) => {
+ files: IMessage[];
+ total: number;
+ };
+ };
+ 'groups.members': {
+ GET: (params: { roomId: IRoom['_id']; offset?: number; count?: number; filter?: string; status?: string[] }) => {
+ count: number;
+ offset: number;
+ members: IUser[];
+ total: number;
+ };
+ };
+};
diff --git a/app/definitions/rest/v1/im.ts b/app/definitions/rest/v1/im.ts
new file mode 100644
index 000000000..a9502ab2d
--- /dev/null
+++ b/app/definitions/rest/v1/im.ts
@@ -0,0 +1,36 @@
+import type { IMessage } from '../../IMessage';
+import type { IRoom } from '../../IRoom';
+import type { IUser } from '../../IUser';
+
+export type ImEndpoints = {
+ 'im.create': {
+ POST: (
+ params: (
+ | {
+ username: Exclude;
+ }
+ | {
+ usernames: string;
+ }
+ ) & {
+ excludeSelf?: boolean;
+ }
+ ) => {
+ room: IRoom;
+ };
+ };
+ 'im.files': {
+ GET: (params: { roomId: IRoom['_id']; count: number; sort: string; query: string }) => {
+ files: IMessage[];
+ total: number;
+ };
+ };
+ 'im.members': {
+ GET: (params: { roomId: IRoom['_id']; offset?: number; count?: number; filter?: string; status?: string[] }) => {
+ count: number;
+ offset: number;
+ members: IUser[];
+ total: number;
+ };
+ };
+};
diff --git a/app/definitions/rest/v1/index.ts b/app/definitions/rest/v1/index.ts
new file mode 100644
index 000000000..ae5f29262
--- /dev/null
+++ b/app/definitions/rest/v1/index.ts
@@ -0,0 +1,33 @@
+import { ChannelsEndpoints } from './channels';
+import { ChatEndpoints } from './chat';
+import { CustomUserStatusEndpoints } from './customUserStatus';
+import { DmEndpoints } from './dm';
+import { EmojiCustomEndpoints } from './emojiCustom';
+import { GroupsEndpoints } from './groups';
+import { ImEndpoints } from './im';
+import { InvitesEndpoints } from './invites';
+import { OmnichannelEndpoints } from './omnichannel';
+import { PermissionsEndpoints } from './permissions';
+import { RolesEndpoints } from './roles';
+import { RoomsEndpoints } from './rooms';
+import { OauthCustomConfiguration } from './settings';
+import { UserEndpoints } from './user';
+import { UsersEndpoints } from './users';
+import { TeamsEndpoints } from './teams';
+
+export type Endpoints = ChannelsEndpoints &
+ ChatEndpoints &
+ CustomUserStatusEndpoints &
+ DmEndpoints &
+ EmojiCustomEndpoints &
+ GroupsEndpoints &
+ ImEndpoints &
+ InvitesEndpoints &
+ OmnichannelEndpoints &
+ PermissionsEndpoints &
+ RolesEndpoints &
+ RoomsEndpoints &
+ OauthCustomConfiguration &
+ UserEndpoints &
+ UsersEndpoints &
+ TeamsEndpoints;
diff --git a/app/definitions/rest/v1/invites.ts b/app/definitions/rest/v1/invites.ts
new file mode 100644
index 000000000..6682c9e32
--- /dev/null
+++ b/app/definitions/rest/v1/invites.ts
@@ -0,0 +1,25 @@
+import type { IInvite } from '../../IInvite';
+import type { IRoom } from '../../IRoom';
+
+export type InvitesEndpoints = {
+ listInvites: {
+ GET: () => Array;
+ };
+ 'removeInvite/:_id': {
+ DELETE: () => void;
+ };
+ '/v1/useInviteToken': {
+ POST: (params: { token: string }) => {
+ room: {
+ rid: IRoom['_id'];
+ prid: IRoom['prid'];
+ fname: IRoom['fname'];
+ name: IRoom['name'];
+ t: IRoom['t'];
+ };
+ };
+ };
+ '/v1/validateInviteToken': {
+ POST: (params: { token: string }) => { valid: boolean };
+ };
+};
diff --git a/app/definitions/rest/v1/omnichannel.ts b/app/definitions/rest/v1/omnichannel.ts
new file mode 100644
index 000000000..784415a53
--- /dev/null
+++ b/app/definitions/rest/v1/omnichannel.ts
@@ -0,0 +1,194 @@
+import { ILivechatAgent } from '../../ILivechatAgent';
+import { ILivechatDepartment } from '../../ILivechatDepartment';
+import { ILivechatDepartmentAgents } from '../../ILivechatDepartmentAgents';
+import { ILivechatMonitor } from '../../ILivechatMonitor';
+import { ILivechatTag } from '../../ILivechatTag';
+import { ILivechatVisitor, ILivechatVisitorDTO } from '../../ILivechatVisitor';
+import { IMessage } from '../../IMessage';
+import { IOmnichannelRoom, IRoom } from '../../IRoom';
+import { ISetting } from '../../ISetting';
+import { PaginatedRequest } from '../helpers/PaginatedRequest';
+import { PaginatedResult } from '../helpers/PaginatedResult';
+
+type booleanString = 'true' | 'false';
+
+export type OmnichannelEndpoints = {
+ 'livechat/appearance': {
+ GET: () => {
+ appearance: ISetting[];
+ };
+ };
+ 'livechat/visitors.info': {
+ GET: (params: { visitorId: string }) => {
+ visitor: {
+ visitorEmails: Array<{
+ address: string;
+ }>;
+ };
+ };
+ };
+ 'livechat/room.onHold': {
+ POST: (params: { roomId: IRoom['_id'] }) => void;
+ };
+ 'livechat/monitors.list': {
+ GET: (params: PaginatedRequest<{ text: string }>) => PaginatedResult<{
+ monitors: ILivechatMonitor[];
+ }>;
+ };
+ 'livechat/tags.list': {
+ GET: (params: PaginatedRequest<{ text: string }, 'name'>) => PaginatedResult<{
+ tags: ILivechatTag[];
+ }>;
+ };
+ 'livechat/department': {
+ GET: (
+ params: PaginatedRequest<{
+ text: string;
+ onlyMyDepartments?: booleanString;
+ enabled?: boolean;
+ excludeDepartmentId?: string;
+ }>
+ ) => PaginatedResult<{
+ departments: ILivechatDepartment[];
+ }>;
+ POST: (params: { department: Partial; agents: string[] }) => {
+ department: ILivechatDepartment;
+ agents: any[];
+ };
+ };
+ 'livechat/department/:_id': {
+ GET: (params: { onlyMyDepartments?: booleanString; includeAgents?: booleanString }) => {
+ department: ILivechatDepartment | null;
+ agents?: any[];
+ };
+ PUT: (params: { department: Partial[]; agents: any[] }) => {
+ department: ILivechatDepartment;
+ agents: any[];
+ };
+ DELETE: () => void;
+ };
+ 'livechat/department.autocomplete': {
+ GET: (params: { selector: string; onlyMyDepartments: booleanString }) => {
+ items: ILivechatDepartment[];
+ };
+ };
+ 'livechat/department/:departmentId/agents': {
+ GET: (params: { sort: string }) => PaginatedResult<{ agents: ILivechatDepartmentAgents[] }>;
+ POST: (params: { upsert: string[]; remove: string[] }) => void;
+ };
+ 'livechat/departments.available-by-unit/:id': {
+ GET: (params: PaginatedRequest<{ text: string }>) => PaginatedResult<{
+ departments: ILivechatDepartment[];
+ }>;
+ };
+ 'livechat/departments.by-unit/': {
+ GET: (params: PaginatedRequest<{ text: string }>) => PaginatedResult<{
+ departments: ILivechatDepartment[];
+ }>;
+ };
+
+ 'livechat/departments.by-unit/:id': {
+ GET: (params: PaginatedRequest<{ text: string }>) => PaginatedResult<{
+ departments: ILivechatDepartment[];
+ }>;
+ };
+
+ 'livechat/department.listByIds': {
+ GET: (params: { ids: string[]; fields?: Record }) => { departments: ILivechatDepartment[] };
+ };
+
+ 'livechat/custom-fields': {
+ GET: (params: PaginatedRequest<{ text: string }>) => PaginatedResult<{
+ customFields: [
+ {
+ _id: string;
+ label: string;
+ }
+ ];
+ }>;
+ };
+ 'livechat/rooms': {
+ GET: (params: {
+ guest: string;
+ fname: string;
+ servedBy: string[];
+ status: string;
+ department: string;
+ from: string;
+ to: string;
+ customFields: any;
+ current: number;
+ itemsPerPage: number;
+ tags: string[];
+ }) => PaginatedResult<{
+ rooms: IOmnichannelRoom[];
+ }>;
+ };
+ 'livechat/:rid/messages': {
+ GET: (params: PaginatedRequest<{ query: string }>) => PaginatedResult<{
+ messages: IMessage[];
+ }>;
+ };
+ 'livechat/users/agent': {
+ GET: (params: PaginatedRequest<{ text?: string }>) => PaginatedResult<{
+ users: {
+ _id: string;
+ emails: {
+ address: string;
+ verified: boolean;
+ }[];
+ status: string;
+ name: string;
+ username: string;
+ statusLivechat: string;
+ livechat: {
+ maxNumberSimultaneousChat: number;
+ };
+ }[];
+ }>;
+ };
+
+ 'livechat/visitor': {
+ POST: (params: { visitor: ILivechatVisitorDTO }) => { visitor: ILivechatVisitor };
+ };
+
+ 'livechat/visitor/:token': {
+ GET: (params: { token: string }) => { visitor: ILivechatVisitor };
+ DELETE: (params: { token: string }) => { visitor: { _id: string; ts: string } };
+ };
+
+ 'livechat/visitor/:token/room': {
+ GET: (params: { token: string }) => { rooms: IOmnichannelRoom[] };
+ };
+
+ 'livechat/visitor.callStatus': {
+ POST: (params: { token: string; callStatus: string; rid: string; callId: string }) => {
+ token: string;
+ callStatus: string;
+ };
+ };
+
+ 'livechat/visitor.status': {
+ POST: (params: { token: string; status: string }) => { token: string; status: string };
+ };
+
+ 'livechat/queue': {
+ GET: (params: {
+ agentId?: ILivechatAgent['_id'];
+ includeOfflineAgents?: boolean;
+ departmentId?: ILivechatAgent['_id'];
+ offset: number;
+ count: number;
+ sort: string;
+ }) => {
+ queue: {
+ chats: number;
+ department: { _id: string; name: string };
+ user: { _id: string; username: string; status: string };
+ }[];
+ count: number;
+ offset: number;
+ total: number;
+ };
+ };
+};
diff --git a/app/definitions/rest/v1/permissions.ts b/app/definitions/rest/v1/permissions.ts
new file mode 100644
index 000000000..58369aa07
--- /dev/null
+++ b/app/definitions/rest/v1/permissions.ts
@@ -0,0 +1,17 @@
+import { IPermission } from '../../IPermission';
+
+type PermissionsUpdateProps = { permissions: { _id: string; roles: string[] }[] };
+
+export type PermissionsEndpoints = {
+ 'permissions.listAll': {
+ GET: (params: { updatedSince?: string }) => {
+ update: IPermission[];
+ remove: IPermission[];
+ };
+ };
+ 'permissions.update': {
+ POST: (params: PermissionsUpdateProps) => {
+ permissions: IPermission[];
+ };
+ };
+};
diff --git a/app/definitions/rest/v1/roles.ts b/app/definitions/rest/v1/roles.ts
new file mode 100644
index 000000000..15cb596db
--- /dev/null
+++ b/app/definitions/rest/v1/roles.ts
@@ -0,0 +1,76 @@
+import { IRole } from '../../IRole';
+import { RocketChatRecordDeleted } from '../../IRocketChatRecord';
+import { IUser } from '../../IUser';
+
+type RoleCreateProps = Pick & Partial>;
+
+type RoleUpdateProps = { roleId: IRole['_id']; name: IRole['name'] } & Partial;
+
+type RoleDeleteProps = { roleId: IRole['_id'] };
+
+type RoleAddUserToRoleProps = {
+ username: string;
+ roleName: string;
+ roomId?: string;
+};
+
+type RoleRemoveUserFromRoleProps = {
+ username: string;
+ roleName: string;
+ roomId?: string;
+ scope?: string;
+};
+
+type RoleSyncProps = {
+ updatedSince?: string;
+};
+
+export type RolesEndpoints = {
+ 'roles.list': {
+ GET: () => {
+ roles: IRole[];
+ };
+ };
+ 'roles.sync': {
+ GET: (params: RoleSyncProps) => {
+ roles: {
+ update: IRole[];
+ remove: RocketChatRecordDeleted[];
+ };
+ };
+ };
+ 'roles.create': {
+ POST: (params: RoleCreateProps) => {
+ role: IRole;
+ };
+ };
+
+ 'roles.addUserToRole': {
+ POST: (params: RoleAddUserToRoleProps) => {
+ role: IRole;
+ };
+ };
+
+ 'roles.getUsersInRole': {
+ GET: (params: { roomId: string; role: string; offset: number; count: number }) => {
+ users: IUser[];
+ total: number;
+ };
+ };
+
+ 'roles.update': {
+ POST: (role: RoleUpdateProps) => {
+ role: IRole;
+ };
+ };
+
+ 'roles.delete': {
+ POST: (prop: RoleDeleteProps) => void;
+ };
+
+ 'roles.removeUserFromRole': {
+ POST: (props: RoleRemoveUserFromRoleProps) => {
+ role: IRole;
+ };
+ };
+};
diff --git a/app/definitions/rest/v1/rooms.ts b/app/definitions/rest/v1/rooms.ts
new file mode 100644
index 000000000..8306cd68f
--- /dev/null
+++ b/app/definitions/rest/v1/rooms.ts
@@ -0,0 +1,44 @@
+import type { IMessage } from '../../IMessage';
+import type { IRoom } from '../../IRoom';
+import type { IUser } from '../../IUser';
+
+export type RoomsEndpoints = {
+ 'rooms.autocomplete.channelAndPrivate': {
+ GET: (params: { selector: string }) => {
+ items: IRoom[];
+ };
+ };
+ 'rooms.autocomplete.channelAndPrivate.withPagination': {
+ GET: (params: { selector: string; offset?: number; count?: number; sort?: string }) => {
+ items: IRoom[];
+ count: number;
+ offset: number;
+ total: number;
+ };
+ };
+ 'rooms.autocomplete.availableForTeams': {
+ GET: (params: { name: string }) => {
+ items: IRoom[];
+ };
+ };
+ 'rooms.info': {
+ GET: (params: { roomId: string } | { roomName: string }) => {
+ room: IRoom;
+ };
+ };
+ 'rooms.createDiscussion': {
+ POST: (params: {
+ prid: IRoom['_id'];
+ pmid?: IMessage['_id'];
+ t_name: IRoom['fname'];
+ users?: IUser['username'][];
+ encrypted?: boolean;
+ reply?: string;
+ }) => {
+ discussion: IRoom;
+ };
+ };
+ 'rooms.favorite': {
+ POST: (params: { roomId: string; favorite: boolean }) => {};
+ };
+};
diff --git a/app/definitions/rest/v1/settings.ts b/app/definitions/rest/v1/settings.ts
new file mode 100644
index 000000000..1e2256e23
--- /dev/null
+++ b/app/definitions/rest/v1/settings.ts
@@ -0,0 +1,103 @@
+import { ISetting, ISettingColor } from '../../ISetting';
+import { PaginatedResult } from '../helpers/PaginatedResult';
+
+type SettingsUpdateProps = SettingsUpdatePropDefault | SettingsUpdatePropsActions | SettingsUpdatePropsColor;
+
+type SettingsUpdatePropsActions = {
+ execute: boolean;
+};
+
+export type OauthCustomConfiguration = {
+ _id: string;
+ clientId?: string;
+ custom: unknown;
+ service?: string;
+ serverURL: unknown;
+ tokenPath: unknown;
+ identityPath: unknown;
+ authorizePath: unknown;
+ scope: unknown;
+ loginStyle: 'popup' | 'redirect';
+ tokenSentVia: unknown;
+ identityTokenSentVia: unknown;
+ keyField: unknown;
+ usernameField: unknown;
+ emailField: unknown;
+ nameField: unknown;
+ avatarField: unknown;
+ rolesClaim: unknown;
+ groupsClaim: unknown;
+ mapChannels: unknown;
+ channelsMap: unknown;
+ channelsAdmin: unknown;
+ mergeUsers: unknown;
+ mergeRoles: unknown;
+ accessTokenParam: unknown;
+ showButton: unknown;
+
+ appId: unknown;
+ consumerKey?: string;
+
+ clientConfig: unknown;
+ buttonLabelText: unknown;
+ buttonLabelColor: unknown;
+ buttonColor: unknown;
+};
+
+export const isOauthCustomConfiguration = (config: any): config is OauthCustomConfiguration => Boolean(config);
+
+export const isSettingsUpdatePropsActions = (props: Partial): props is SettingsUpdatePropsActions =>
+ 'execute' in props;
+
+type SettingsUpdatePropsColor = {
+ editor: ISettingColor['editor'];
+ value: ISetting['value'];
+};
+
+export const isSettingsUpdatePropsColor = (props: Partial): props is SettingsUpdatePropsColor =>
+ 'editor' in props && 'value' in props;
+
+type SettingsUpdatePropDefault = {
+ value: ISetting['value'];
+};
+
+export const isSettingsUpdatePropDefault = (props: Partial): props is SettingsUpdatePropDefault =>
+ 'value' in props;
+
+export type SettingsEndpoints = {
+ 'settings.public': {
+ GET: () => PaginatedResult & {
+ settings: Array;
+ };
+ };
+
+ 'settings.oauth': {
+ GET: () => {
+ services: Partial[];
+ };
+ };
+
+ 'settings.addCustomOAuth': {
+ POST: (params: { name: string }) => void;
+ };
+
+ settings: {
+ GET: () => {
+ settings: ISetting[];
+ };
+ };
+
+ 'settings/:_id': {
+ GET: () => Pick;
+ POST: (params: SettingsUpdateProps) => void;
+ };
+
+ 'service.configurations': {
+ GET: () => {
+ configurations: Array<{
+ appId: string;
+ secret: string;
+ }>;
+ };
+ };
+};
diff --git a/app/definitions/rest/v1/teams.ts b/app/definitions/rest/v1/teams.ts
new file mode 100644
index 000000000..29047149d
--- /dev/null
+++ b/app/definitions/rest/v1/teams.ts
@@ -0,0 +1,7 @@
+import { IRoom } from '../../IRoom';
+
+export type TeamsEndpoints = {
+ 'teams.removeRoom': {
+ POST: (params: { roomId: string; teamId: string }) => { room: IRoom };
+ };
+};
diff --git a/app/definitions/rest/v1/user.ts b/app/definitions/rest/v1/user.ts
new file mode 100644
index 000000000..7de12aabf
--- /dev/null
+++ b/app/definitions/rest/v1/user.ts
@@ -0,0 +1,14 @@
+import { IUser } from '../../IUser';
+
+export type UserEndpoints = {
+ 'users.info': {
+ GET: (params: { userId: IUser['_id'] }) => {
+ user: IUser;
+ success: boolean;
+ };
+ POST: (params: { userId: IUser['_id'] }) => {
+ user: IUser;
+ success: boolean;
+ };
+ };
+};
diff --git a/app/definitions/rest/v1/users.ts b/app/definitions/rest/v1/users.ts
new file mode 100644
index 000000000..337a2182f
--- /dev/null
+++ b/app/definitions/rest/v1/users.ts
@@ -0,0 +1,14 @@
+import type { ITeam } from '../../ITeam';
+import type { IUser } from '../../IUser';
+
+export type UsersEndpoints = {
+ 'users.2fa.sendEmailCode': {
+ POST: (params: { emailOrUsername: string }) => void;
+ };
+ 'users.autocomplete': {
+ GET: (params: { selector: string }) => { items: IUser[] };
+ };
+ 'users.listTeams': {
+ GET: (params: { userId: IUser['_id'] }) => { teams: Array };
+ };
+};
diff --git a/app/ee/omnichannel/lib/subscriptions/inquiry.js b/app/ee/omnichannel/lib/subscriptions/inquiry.js
index d10d5c892..4e6cda682 100644
--- a/app/ee/omnichannel/lib/subscriptions/inquiry.js
+++ b/app/ee/omnichannel/lib/subscriptions/inquiry.js
@@ -1,5 +1,5 @@
import log from '../../../../utils/log';
-import store from '../../../../lib/createStore';
+import { store } from '../../../../lib/auxStore';
import RocketChat from '../../../../lib/rocketchat';
import { inquiryQueueAdd, inquiryQueueRemove, inquiryQueueUpdate, inquiryRequest } from '../../actions/inquiry';
diff --git a/app/i18n/locales/en.json b/app/i18n/locales/en.json
index 499a50e7a..4cae91c18 100644
--- a/app/i18n/locales/en.json
+++ b/app/i18n/locales/en.json
@@ -775,6 +775,7 @@
"creating_discussion": "creating discussion",
"Canned_Responses": "Canned Responses",
"No_match_found": "No match found.",
+ "No_discussions": "No discussions",
"Check_canned_responses": "Check on canned responses.",
"Searching": "Searching",
"Use": "Use",
@@ -787,5 +788,24 @@
"Enable_Message_Parser": "Enable Message Parser",
"Unsupported_format": "Unsupported format",
"Downloaded_file": "Downloaded file",
- "Error_Download_file": "Error while downloading file"
+ "Error_Download_file": "Error while downloading file",
+ "added__roomName__to_team": "added #{{roomName}} to this Team",
+ "Added__username__to_team": "added @{{user_added}} to this Team",
+ "Converted__roomName__to_team": "converted #{{roomName}} to a Team",
+ "Converted__roomName__to_channel": "converted #{{roomName}} to a Channel",
+ "Converting_team_to_channel": "Converting Team to Channel",
+ "Deleted__roomName__": "deleted #{{roomName}}",
+ "Message_HideType_added_user_to_team": "Hide \"User Added to Team\" messages",
+ "Message_HideType_removed_user_from_team": "Hide \"User Removed from Team\" messages",
+ "Message_HideType_ujt": "Hide \"User Joined Team\" messages",
+ "Message_HideType_ult": "Hide \"User Left Team\" messages",
+ "Message_HideType_user_added_room_to_team": "Hide \"User Added Room to Team\" messages",
+ "Message_HideType_user_converted_to_channel": "Hide \"User converted team to a Channel\" messages",
+ "Message_HideType_user_converted_to_team": "Hide \"User converted channel to a Team\" messages",
+ "Message_HideType_user_deleted_room_from_team": "Hide \"User deleted room from Team\" messages",
+ "Message_HideType_user_removed_room_from_team": "Hide \"User removed room from Team\" messages",
+ "Removed__roomName__from_this_team": "removed #{{roomName}} from this Team",
+ "Removed__username__from_team": "removed @{{user_removed}} from this Team",
+ "User_joined_team": "joined this Team",
+ "User_left_team": "left this Team"
}
\ No newline at end of file
diff --git a/app/i18n/locales/fr.json b/app/i18n/locales/fr.json
index 81e8e21b3..977679fed 100644
--- a/app/i18n/locales/fr.json
+++ b/app/i18n/locales/fr.json
@@ -21,6 +21,7 @@
"error-save-video": "Erreur en sauvegardant la vidéo",
"error-field-unavailable": "{{field}} est déjà utilisé: (",
"error-file-too-large": "Le fichier est trop grand",
+ "error-not-permission-to-upload-file": "Vous n'êtes pas autorisé à envoyer des fichiers",
"error-importer-not-defined": "L'importateur n'a pas été défini correctement, il manque la classe Import.",
"error-input-is-not-a-valid-field": "{{input}} n'est pas un {{field}} valide",
"error-invalid-actionlink": "Lien d'action non valide",
@@ -248,8 +249,10 @@
"Full_table": "Cliquez pour voir le tableau complet",
"Generate_New_Link": "Générer un nouveau lien",
"Has_joined_the_channel": "a rejoint le canal",
+ "Has_joined_the_team": "a rejoint l'équipe",
"Has_joined_the_conversation": "a rejoint la conversation",
"Has_left_the_channel": "a quitté le canal",
+ "Has_left_the_team": "a quitté l'équipe",
"Hide_System_Messages": "Masquer les messages système",
"Hide_type_messages": "Masquer les messages \"{{type}}\"",
"How_It_Works": "Comment cela fonctionne",
@@ -772,6 +775,7 @@
"creating_discussion": "créer une discussion",
"Canned_Responses": "Modèles de réponse",
"No_match_found": "Pas de résultat trouvé.",
+ "No_discussions": "Pas de discussions",
"Check_canned_responses": "Vérifiez les réponses standardisées.",
"Searching": "Recherche",
"Use": "Utiliser",
diff --git a/app/i18n/locales/nl.json b/app/i18n/locales/nl.json
index aa63fdb74..08bfc7fff 100644
--- a/app/i18n/locales/nl.json
+++ b/app/i18n/locales/nl.json
@@ -21,6 +21,7 @@
"error-save-video": "Fout bij het opslaan van video",
"error-field-unavailable": "{{field}} is al in gebruik :(",
"error-file-too-large": "Bestand is te groot",
+ "error-not-permission-to-upload-file": "Je hebt geen toestemming om bestanden up te loaden",
"error-importer-not-defined": "De importeur is niet correct gedefinieerd, de klasse Import ontbreekt.",
"error-input-is-not-a-valid-field": "{{input}} is geen geldig {{field}}",
"error-invalid-actionlink": "Ongeldige actielink",
@@ -248,8 +249,10 @@
"Full_table": "Klik om de volledige tabel te zien",
"Generate_New_Link": "Nieuwe link genereren",
"Has_joined_the_channel": "is bij het kanaal gekomen",
+ "Has_joined_the_team": "is lid geworden van het team",
"Has_joined_the_conversation": "heeft zich bij het gesprek aangesloten",
"Has_left_the_channel": "heeft het kanaal verlaten",
+ "Has_left_the_team": "heeft het team verlaten",
"Hide_System_Messages": "Verberg systeemberichten",
"Hide_type_messages": "Verberg \"{{type}}\" berichten",
"How_It_Works": "Hoe het werkt",
@@ -772,6 +775,7 @@
"creating_discussion": "discussie aanmaken",
"Canned_Responses": "Standaardantwoorden",
"No_match_found": "Geen overeenkomst gevonden.",
+ "No_discussions": "Geen discussies",
"Check_canned_responses": "Controleer op standaardantwoorden.",
"Searching": "Zoeken",
"Use": "Gebruiken",
diff --git a/app/i18n/locales/pt-BR.json b/app/i18n/locales/pt-BR.json
index 8aa2f5ae0..a77dd415a 100644
--- a/app/i18n/locales/pt-BR.json
+++ b/app/i18n/locales/pt-BR.json
@@ -19,6 +19,7 @@
"error-save-image": "Erro ao salvar imagem",
"error-field-unavailable": "{{field}} já está sendo usado :(",
"error-file-too-large": "Arquivo é muito grande",
+ "error-not-permission-to-upload-file": "Você não tem permissão para enviar arquivos",
"error-importer-not-defined": "O importador não foi definido corretamente; está faltando a classe Import.",
"error-input-is-not-a-valid-field": "{{input}} não é válido um {{field}}",
"error-invalid-actionlink": "Link de ação inválido",
@@ -414,6 +415,7 @@
"Roles": "Papéis",
"Room_actions": "Ações",
"Room_changed_announcement": "O anúncio da sala foi alterado para: {{announcement}} por {{userBy}}",
+ "Room_changed_avatar": "Avatar da sala alterado por {{userBy}}",
"Room_changed_description": "A descrição da sala foi alterada para: {{description}} por {{userBy}}",
"Room_changed_privacy": "Tipo da sala mudou para: {{type}} por {{userBy}}",
"Room_changed_topic": "Tópico da sala mudou para: {{topic}} por {{userBy}}",
@@ -723,7 +725,7 @@
"Condensed": "Condensado",
"creating_discussion": "criando discussão",
"Canned_Responses": "Respostas Predefinidas",
- "No_match_found": "Nenhum resultado encontrado",
+ "No_match_found": "Nenhum resultado encontrado.",
"Check_canned_responses": "Verifique nas respostas predefinidas",
"Searching": "Buscando",
"Use": "Use",
diff --git a/app/i18n/locales/ru.json b/app/i18n/locales/ru.json
index bdf2c161a..235ba0205 100644
--- a/app/i18n/locales/ru.json
+++ b/app/i18n/locales/ru.json
@@ -21,6 +21,7 @@
"error-save-video": "Ошибка при попытке сохранить видео",
"error-field-unavailable": "{{field}} уже используется :(",
"error-file-too-large": "Файл слишком большой",
+ "error-not-permission-to-upload-file": "У вас нет разрешения на загрузку файлов",
"error-importer-not-defined": "Импортер не был определен правильно, ему не хватает класса Import.",
"error-input-is-not-a-valid-field": "{{input}} недействительно {{field}}",
"error-invalid-actionlink": "Недействительная ссылка действия",
@@ -248,8 +249,10 @@
"Full_table": "Нажмите, чтобы увидеть полную таблицу",
"Generate_New_Link": "Сгенерировать Новую Ссылку",
"Has_joined_the_channel": "присоединился к каналу",
+ "Has_joined_the_team": "присоединился к команде",
"Has_joined_the_conversation": "присоединился к беседе",
"Has_left_the_channel": "покинул канал",
+ "Has_left_the_team": "покинул команду",
"Hide_System_Messages": "Скрыть Системные Сообщения",
"Hide_type_messages": "Скрыть \"{{type}}\" сообщения",
"How_It_Works": "Как Это Работает",
@@ -772,6 +775,7 @@
"creating_discussion": "создание обсуждения",
"Canned_Responses": "Заготовленные ответы",
"No_match_found": "Совпадений не найдено.",
+ "No_discussions": "Нет обсуждений",
"Check_canned_responses": "Проверить заготовленные ответы",
"Searching": "Поиск",
"Use": "Использовать",
diff --git a/app/index.tsx b/app/index.tsx
index cc0a06a26..89e6c5270 100644
--- a/app/index.tsx
+++ b/app/index.tsx
@@ -32,8 +32,10 @@ import debounce from './utils/debounce';
import { isFDroidBuild } from './constants/environment';
import { IThemePreference } from './definitions/ITheme';
import { ICommand } from './definitions/ICommand';
+import { initStore } from './lib/auxStore';
RNScreens.enableScreens();
+initStore(store);
interface IDimensions {
width: number;
diff --git a/app/lib/auxStore.ts b/app/lib/auxStore.ts
new file mode 100644
index 000000000..2de3f72aa
--- /dev/null
+++ b/app/lib/auxStore.ts
@@ -0,0 +1,10 @@
+import { Store } from 'redux';
+
+import { IApplicationState } from '../definitions';
+
+// https://redux.js.org/faq/code-structure#how-can-i-use-the-redux-store-in-non-component-files
+export let store: Store = null as any;
+
+export const initStore = (_store: Store): void => {
+ store = _store;
+};
diff --git a/app/lib/database/index.js b/app/lib/database/index.ts
similarity index 81%
rename from app/lib/database/index.js
rename to app/lib/database/index.ts
index ee02e6212..1b8df955b 100644
--- a/app/lib/database/index.js
+++ b/app/lib/database/index.ts
@@ -25,6 +25,7 @@ import serversSchema from './schema/servers';
import appSchema from './schema/app';
import migrations from './model/migrations';
import serversMigrations from './model/servers/migrations';
+import { TAppDatabase, TServerDatabase } from './interfaces';
const appGroupPath = isIOS ? appGroup.path : '';
@@ -32,9 +33,9 @@ if (__DEV__ && isIOS) {
console.log(appGroupPath);
}
-const getDatabasePath = name => `${appGroupPath}${name}${isOfficial ? '' : '-experimental'}.db`;
+const getDatabasePath = (name: string) => `${appGroupPath}${name}${isOfficial ? '' : '-experimental'}.db`;
-export const getDatabase = (database = '') => {
+export const getDatabase = (database = ''): Database => {
const path = database.replace(/(^\w+:|^)\/\//, '').replace(/\//g, '.');
const dbName = getDatabasePath(path);
@@ -64,8 +65,14 @@ export const getDatabase = (database = '') => {
});
};
+interface IDatabases {
+ shareDB?: TAppDatabase;
+ serversDB: TServerDatabase;
+ activeDB?: TAppDatabase;
+}
+
class DB {
- databases = {
+ databases: IDatabases = {
serversDB: new Database({
adapter: new SQLiteAdapter({
dbName: getDatabasePath('default'),
@@ -73,11 +80,12 @@ class DB {
migrations: serversMigrations
}),
modelClasses: [Server, LoggedUser, ServersHistory]
- })
+ }) as TServerDatabase
};
- get active() {
- return this.databases.shareDB || this.databases.activeDB;
+ // Expected at least one database
+ get active(): TAppDatabase {
+ return this.databases.shareDB || this.databases.activeDB!;
}
get share() {
@@ -116,11 +124,11 @@ class DB {
Setting,
User
]
- });
+ }) as TAppDatabase;
}
- setActiveDB(database) {
- this.databases.activeDB = getDatabase(database);
+ setActiveDB(database: string) {
+ this.databases.activeDB = getDatabase(database) as TAppDatabase;
}
}
diff --git a/app/lib/database/interfaces.ts b/app/lib/database/interfaces.ts
new file mode 100644
index 000000000..49c30e068
--- /dev/null
+++ b/app/lib/database/interfaces.ts
@@ -0,0 +1,72 @@
+import { Database, Collection } from '@nozbe/watermelondb';
+
+import * as models from './model';
+import * as definitions from '../../definitions';
+
+export type TAppDatabaseNames =
+ | typeof models.SUBSCRIPTIONS_TABLE
+ | typeof models.ROOMS_TABLE
+ | typeof models.MESSAGES_TABLE
+ | typeof models.THREADS_TABLE
+ | typeof models.THREAD_MESSAGES_TABLE
+ | typeof models.CUSTOM_EMOJIS_TABLE
+ | typeof models.FREQUENTLY_USED_EMOJIS_TABLE
+ | typeof models.UPLOADS_TABLE
+ | typeof models.SETTINGS_TABLE
+ | typeof models.ROLES_TABLE
+ | typeof models.PERMISSIONS_TABLE
+ | typeof models.SLASH_COMMANDS_TABLE
+ | typeof models.USERS_TABLE;
+
+// Verify if T extends one type from TAppDatabaseNames, and if is truly,
+// returns the specific model type.
+// https://stackoverflow.com/a/54166010 TypeScript function return type based on input parameter
+type ObjectType = T extends typeof models.SUBSCRIPTIONS_TABLE
+ ? definitions.TSubscriptionModel
+ : T extends typeof models.ROOMS_TABLE
+ ? definitions.TRoomModel
+ : T extends typeof models.MESSAGES_TABLE
+ ? definitions.TMessageModel
+ : T extends typeof models.THREADS_TABLE
+ ? definitions.TThreadModel
+ : T extends typeof models.THREAD_MESSAGES_TABLE
+ ? definitions.TThreadMessageModel
+ : T extends typeof models.CUSTOM_EMOJIS_TABLE
+ ? definitions.TCustomEmojiModel
+ : T extends typeof models.FREQUENTLY_USED_EMOJIS_TABLE
+ ? definitions.TFrequentlyUsedEmojiModel
+ : T extends typeof models.UPLOADS_TABLE
+ ? definitions.TUploadModel
+ : T extends typeof models.SETTINGS_TABLE
+ ? definitions.TSettingsModel
+ : T extends typeof models.ROLES_TABLE
+ ? definitions.TRoleModel
+ : T extends typeof models.PERMISSIONS_TABLE
+ ? definitions.TPermissionModel
+ : T extends typeof models.SLASH_COMMANDS_TABLE
+ ? definitions.TSlashCommandModel
+ : T extends typeof models.USERS_TABLE
+ ? definitions.TUserModel
+ : never;
+
+export type TAppDatabase = {
+ get: (db: T) => Collection>;
+} & Database;
+
+// Migration to server database
+export type TServerDatabaseNames =
+ | typeof models.SERVERS_TABLE
+ | typeof models.LOGGED_USERS_TABLE
+ | typeof models.SERVERS_HISTORY_TABLE;
+
+type ObjectServerType = T extends typeof models.SERVERS_TABLE
+ ? definitions.TServerModel
+ : T extends typeof models.LOGGED_USERS_TABLE
+ ? definitions.TLoggedUserModel
+ : T extends typeof models.SERVERS_HISTORY_TABLE
+ ? definitions.TServerHistoryModel
+ : never;
+
+export type TServerDatabase = {
+ get: (db: T) => Collection>;
+} & Database;
diff --git a/app/lib/database/model/CustomEmoji.js b/app/lib/database/model/CustomEmoji.js
index 9f3e1b6a8..c8fdedd05 100644
--- a/app/lib/database/model/CustomEmoji.js
+++ b/app/lib/database/model/CustomEmoji.js
@@ -3,8 +3,10 @@ import { date, field, json } from '@nozbe/watermelondb/decorators';
import { sanitizer } from '../utils';
+export const CUSTOM_EMOJIS_TABLE = 'custom_emojis';
+
export default class CustomEmoji extends Model {
- static table = 'custom_emojis';
+ static table = CUSTOM_EMOJIS_TABLE;
@field('name') name;
diff --git a/app/lib/database/model/FrequentlyUsedEmoji.js b/app/lib/database/model/FrequentlyUsedEmoji.js
index 9629d3a99..2c6c815ed 100644
--- a/app/lib/database/model/FrequentlyUsedEmoji.js
+++ b/app/lib/database/model/FrequentlyUsedEmoji.js
@@ -1,8 +1,9 @@
import { Model } from '@nozbe/watermelondb';
import { field } from '@nozbe/watermelondb/decorators';
+export const FREQUENTLY_USED_EMOJIS_TABLE = 'frequently_used_emojis';
export default class FrequentlyUsedEmoji extends Model {
- static table = 'frequently_used_emojis';
+ static table = FREQUENTLY_USED_EMOJIS_TABLE;
@field('content') content;
diff --git a/app/lib/database/model/Message.js b/app/lib/database/model/Message.js
index 20134733b..392283a63 100644
--- a/app/lib/database/model/Message.js
+++ b/app/lib/database/model/Message.js
@@ -3,10 +3,10 @@ import { date, field, json, relation } from '@nozbe/watermelondb/decorators';
import { sanitizer } from '../utils';
-export const TABLE_NAME = 'messages';
+export const MESSAGES_TABLE = 'messages';
export default class Message extends Model {
- static table = TABLE_NAME;
+ static table = MESSAGES_TABLE;
static associations = {
subscriptions: { type: 'belongs_to', key: 'rid' }
diff --git a/app/lib/database/model/Permission.js b/app/lib/database/model/Permission.js
index 5923f83dc..c397f079a 100644
--- a/app/lib/database/model/Permission.js
+++ b/app/lib/database/model/Permission.js
@@ -3,8 +3,10 @@ import { date, json } from '@nozbe/watermelondb/decorators';
import { sanitizer } from '../utils';
+export const PERMISSIONS_TABLE = 'permissions';
+
export default class Permission extends Model {
- static table = 'permissions';
+ static table = PERMISSIONS_TABLE;
@json('roles', sanitizer) roles;
diff --git a/app/lib/database/model/Role.js b/app/lib/database/model/Role.js
index ad9256d58..e0633b045 100644
--- a/app/lib/database/model/Role.js
+++ b/app/lib/database/model/Role.js
@@ -1,8 +1,10 @@
import { Model } from '@nozbe/watermelondb';
import { field } from '@nozbe/watermelondb/decorators';
+export const ROLES_TABLE = 'roles';
+
export default class Role extends Model {
- static table = 'roles';
+ static table = ROLES_TABLE;
@field('description') description;
}
diff --git a/app/lib/database/model/Room.js b/app/lib/database/model/Room.js
index 131fbaf0e..e2a1127bf 100644
--- a/app/lib/database/model/Room.js
+++ b/app/lib/database/model/Room.js
@@ -3,8 +3,10 @@ import { field, json } from '@nozbe/watermelondb/decorators';
import { sanitizer } from '../utils';
+export const ROOMS_TABLE = 'rooms';
+
export default class Room extends Model {
- static table = 'rooms';
+ static table = ROOMS_TABLE;
@json('custom_fields', sanitizer) customFields;
diff --git a/app/lib/database/model/ServersHistory.js b/app/lib/database/model/ServersHistory.js
index 4bbef3e98..168403782 100644
--- a/app/lib/database/model/ServersHistory.js
+++ b/app/lib/database/model/ServersHistory.js
@@ -1,8 +1,10 @@
import { Model } from '@nozbe/watermelondb';
import { date, field, readonly } from '@nozbe/watermelondb/decorators';
+export const SERVERS_HISTORY_TABLE = 'servers_history';
+
export default class ServersHistory extends Model {
- static table = 'servers_history';
+ static table = SERVERS_HISTORY_TABLE;
@field('url') url;
diff --git a/app/lib/database/model/Setting.js b/app/lib/database/model/Setting.js
index 753d965ba..1597272bd 100644
--- a/app/lib/database/model/Setting.js
+++ b/app/lib/database/model/Setting.js
@@ -3,8 +3,10 @@ import { date, field, json } from '@nozbe/watermelondb/decorators';
import { sanitizer } from '../utils';
+export const SETTINGS_TABLE = 'settings';
+
export default class Setting extends Model {
- static table = 'settings';
+ static table = SETTINGS_TABLE;
@field('value_as_string') valueAsString;
diff --git a/app/lib/database/model/SlashCommand.js b/app/lib/database/model/SlashCommand.js
index 418e72144..8bcba65f7 100644
--- a/app/lib/database/model/SlashCommand.js
+++ b/app/lib/database/model/SlashCommand.js
@@ -1,8 +1,10 @@
import { Model } from '@nozbe/watermelondb';
import { field } from '@nozbe/watermelondb/decorators';
+export const SLASH_COMMANDS_TABLE = 'slash_commands';
+
export default class SlashCommand extends Model {
- static table = 'slash_commands';
+ static table = SLASH_COMMANDS_TABLE;
@field('params') params;
diff --git a/app/lib/database/model/Subscription.js b/app/lib/database/model/Subscription.js
index aab0e0bbb..7b177349a 100644
--- a/app/lib/database/model/Subscription.js
+++ b/app/lib/database/model/Subscription.js
@@ -3,10 +3,10 @@ import { children, date, field, json } from '@nozbe/watermelondb/decorators';
import { sanitizer } from '../utils';
-export const TABLE_NAME = 'subscriptions';
+export const SUBSCRIPTIONS_TABLE = 'subscriptions';
export default class Subscription extends Model {
- static table = TABLE_NAME;
+ static table = SUBSCRIPTIONS_TABLE;
static associations = {
messages: { type: 'has_many', foreignKey: 'rid' },
diff --git a/app/lib/database/model/Thread.js b/app/lib/database/model/Thread.js
index 5d1246af8..1224554b6 100644
--- a/app/lib/database/model/Thread.js
+++ b/app/lib/database/model/Thread.js
@@ -3,10 +3,10 @@ import { date, field, json, relation } from '@nozbe/watermelondb/decorators';
import { sanitizer } from '../utils';
-export const TABLE_NAME = 'threads';
+export const THREADS_TABLE = 'threads';
export default class Thread extends Model {
- static table = TABLE_NAME;
+ static table = THREADS_TABLE;
static associations = {
subscriptions: { type: 'belongs_to', key: 'rid' }
diff --git a/app/lib/database/model/ThreadMessage.js b/app/lib/database/model/ThreadMessage.js
index 27ea0e850..bc5502fd2 100644
--- a/app/lib/database/model/ThreadMessage.js
+++ b/app/lib/database/model/ThreadMessage.js
@@ -3,10 +3,10 @@ import { date, field, json, relation } from '@nozbe/watermelondb/decorators';
import { sanitizer } from '../utils';
-export const TABLE_NAME = 'thread_messages';
+export const THREAD_MESSAGES_TABLE = 'thread_messages';
export default class ThreadMessage extends Model {
- static table = TABLE_NAME;
+ static table = THREAD_MESSAGES_TABLE;
static associations = {
subscriptions: { type: 'belongs_to', key: 'subscription_id' }
diff --git a/app/lib/database/model/Upload.js b/app/lib/database/model/Upload.js
index 810fcd5b7..d03a87729 100644
--- a/app/lib/database/model/Upload.js
+++ b/app/lib/database/model/Upload.js
@@ -1,8 +1,10 @@
import { Model } from '@nozbe/watermelondb';
import { field, relation } from '@nozbe/watermelondb/decorators';
+export const UPLOADS_TABLE = 'uploads';
+
export default class Upload extends Model {
- static table = 'uploads';
+ static table = UPLOADS_TABLE;
static associations = {
subscriptions: { type: 'belongs_to', key: 'rid' }
diff --git a/app/lib/database/model/User.js b/app/lib/database/model/User.js
index 9bc1f2008..23978d1ca 100644
--- a/app/lib/database/model/User.js
+++ b/app/lib/database/model/User.js
@@ -3,8 +3,10 @@ import { field, json } from '@nozbe/watermelondb/decorators';
import { sanitizer } from '../utils';
+export const USERS_TABLE = 'users';
+
export default class User extends Model {
- static table = 'users';
+ static table = USERS_TABLE;
@field('_id') _id;
diff --git a/app/lib/database/model/index.ts b/app/lib/database/model/index.ts
new file mode 100644
index 000000000..064204244
--- /dev/null
+++ b/app/lib/database/model/index.ts
@@ -0,0 +1,16 @@
+export * from './CustomEmoji';
+export * from './FrequentlyUsedEmoji';
+export * from './Message';
+export * from './Permission';
+export * from './Role';
+export * from './Room';
+export * from './Setting';
+export * from './SlashCommand';
+export * from './Subscription';
+export * from './Thread';
+export * from './ThreadMessage';
+export * from './Upload';
+export * from './User';
+export * from './ServersHistory';
+export * from './servers/Server';
+export * from './servers/User';
diff --git a/app/lib/database/model/servers/Server.js b/app/lib/database/model/servers/Server.js
index 0bff69ffe..e0098075c 100644
--- a/app/lib/database/model/servers/Server.js
+++ b/app/lib/database/model/servers/Server.js
@@ -1,8 +1,10 @@
import { Model } from '@nozbe/watermelondb';
import { date, field } from '@nozbe/watermelondb/decorators';
+export const SERVERS_TABLE = 'servers';
+
export default class Server extends Model {
- static table = 'servers';
+ static table = SERVERS_TABLE;
@field('name') name;
diff --git a/app/lib/database/model/servers/User.js b/app/lib/database/model/servers/User.js
index 30bd5f57d..5d3438237 100644
--- a/app/lib/database/model/servers/User.js
+++ b/app/lib/database/model/servers/User.js
@@ -3,8 +3,10 @@ import { field, json } from '@nozbe/watermelondb/decorators';
import { sanitizer } from '../../utils';
+export const LOGGED_USERS_TABLE = 'users';
+
export default class User extends Model {
- static table = 'users';
+ static table = LOGGED_USERS_TABLE;
@field('token') token;
diff --git a/app/lib/database/schema/servers.js b/app/lib/database/schema/servers.js
index 1d849b874..68eddb036 100644
--- a/app/lib/database/schema/servers.js
+++ b/app/lib/database/schema/servers.js
@@ -33,7 +33,7 @@ export default appSchema({
{ name: 'last_local_authenticated_session', type: 'number', isOptional: true },
{ name: 'auto_lock', type: 'boolean', isOptional: true },
{ name: 'auto_lock_time', type: 'number', isOptional: true },
- { name: 'biometry', type: 'boolean', isOptional: true },
+ { name: 'biometry', type: 'boolean', isOptional: true }, // deprecated
{ name: 'unique_id', type: 'string', isOptional: true },
{ name: 'enterprise_modules', type: 'string', isOptional: true },
{ name: 'e2e_enable', type: 'boolean', isOptional: true }
diff --git a/app/lib/database/services/Message.js b/app/lib/database/services/Message.ts
similarity index 50%
rename from app/lib/database/services/Message.js
rename to app/lib/database/services/Message.ts
index 594513dec..60295b371 100644
--- a/app/lib/database/services/Message.js
+++ b/app/lib/database/services/Message.ts
@@ -1,9 +1,10 @@
import database from '..';
-import { TABLE_NAME } from '../model/Message';
+import { TAppDatabase } from '../interfaces';
+import { MESSAGES_TABLE } from '../model/Message';
-const getCollection = db => db.get(TABLE_NAME);
+const getCollection = (db: TAppDatabase) => db.get(MESSAGES_TABLE);
-export const getMessageById = async messageId => {
+export const getMessageById = async (messageId: string) => {
const db = database.active;
const messageCollection = getCollection(db);
try {
diff --git a/app/lib/database/services/Role.ts b/app/lib/database/services/Role.ts
new file mode 100644
index 000000000..e033afb72
--- /dev/null
+++ b/app/lib/database/services/Role.ts
@@ -0,0 +1,17 @@
+import database from '..';
+import { TAppDatabase } from '../interfaces';
+import { ROLES_TABLE } from '../model';
+import { TRoleModel } from '../../../definitions';
+
+const getCollection = (db: TAppDatabase) => db.get(ROLES_TABLE);
+
+export const getRoleById = async (id: string): Promise => {
+ const db = database.active;
+ const roleCollection = getCollection(db);
+ try {
+ const result = await roleCollection.find(id);
+ return result;
+ } catch (error) {
+ return null;
+ }
+};
diff --git a/app/lib/database/services/Subscription.js b/app/lib/database/services/Subscription.js
deleted file mode 100644
index 68bf8ac96..000000000
--- a/app/lib/database/services/Subscription.js
+++ /dev/null
@@ -1,15 +0,0 @@
-import database from '..';
-import { TABLE_NAME } from '../model/Subscription';
-
-const getCollection = db => db.get(TABLE_NAME);
-
-export const getSubscriptionByRoomId = async rid => {
- const db = database.active;
- const subCollection = getCollection(db);
- try {
- const result = await subCollection.find(rid);
- return result;
- } catch (error) {
- return null;
- }
-};
diff --git a/app/lib/database/services/Subscription.ts b/app/lib/database/services/Subscription.ts
new file mode 100644
index 000000000..4f68f4320
--- /dev/null
+++ b/app/lib/database/services/Subscription.ts
@@ -0,0 +1,16 @@
+import database from '..';
+import { TAppDatabase } from '../interfaces';
+import { SUBSCRIPTIONS_TABLE } from '../model/Subscription';
+
+const getCollection = (db: TAppDatabase) => db.get(SUBSCRIPTIONS_TABLE);
+
+export const getSubscriptionByRoomId = async (rid: string) => {
+ const db = database.active;
+ const subCollection = getCollection(db);
+ try {
+ const result = await subCollection.find(rid);
+ return result;
+ } catch (error) {
+ return null;
+ }
+};
diff --git a/app/lib/database/services/Thread.js b/app/lib/database/services/Thread.ts
similarity index 50%
rename from app/lib/database/services/Thread.js
rename to app/lib/database/services/Thread.ts
index 9b362dcb1..8dc61b8a4 100644
--- a/app/lib/database/services/Thread.js
+++ b/app/lib/database/services/Thread.ts
@@ -1,9 +1,10 @@
import database from '..';
-import { TABLE_NAME } from '../model/Thread';
+import { TAppDatabase } from '../interfaces';
+import { THREADS_TABLE } from '../model/Thread';
-const getCollection = db => db.get(TABLE_NAME);
+const getCollection = (db: TAppDatabase) => db.get(THREADS_TABLE);
-export const getThreadById = async tmid => {
+export const getThreadById = async (tmid: string) => {
const db = database.active;
const threadCollection = getCollection(db);
try {
diff --git a/app/lib/database/services/ThreadMessage.js b/app/lib/database/services/ThreadMessage.js
deleted file mode 100644
index e9509774e..000000000
--- a/app/lib/database/services/ThreadMessage.js
+++ /dev/null
@@ -1,15 +0,0 @@
-import database from '..';
-import { TABLE_NAME } from '../model/ThreadMessage';
-
-const getCollection = db => db.get(TABLE_NAME);
-
-export const getThreadMessageById = async messageId => {
- const db = database.active;
- const threadMessageCollection = getCollection(db);
- try {
- const result = await threadMessageCollection.find(messageId);
- return result;
- } catch (error) {
- return null;
- }
-};
diff --git a/app/lib/database/services/ThreadMessage.ts b/app/lib/database/services/ThreadMessage.ts
new file mode 100644
index 000000000..899693f84
--- /dev/null
+++ b/app/lib/database/services/ThreadMessage.ts
@@ -0,0 +1,16 @@
+import database from '..';
+import { TAppDatabase } from '../interfaces';
+import { THREAD_MESSAGES_TABLE } from '../model/ThreadMessage';
+
+const getCollection = (db: TAppDatabase) => db.get(THREAD_MESSAGES_TABLE);
+
+export const getThreadMessageById = async (messageId: string) => {
+ const db = database.active;
+ const threadMessageCollection = getCollection(db);
+ try {
+ const result = await threadMessageCollection.find(messageId);
+ return result;
+ } catch (error) {
+ return null;
+ }
+};
diff --git a/app/lib/database/utils.js b/app/lib/database/utils.js
deleted file mode 100644
index 472af733b..000000000
--- a/app/lib/database/utils.js
+++ /dev/null
@@ -1,7 +0,0 @@
-import XRegExp from 'xregexp';
-
-// Matches letters from any alphabet and numbers
-const likeStringRegex = new XRegExp('[^\\p{L}\\p{Nd}]', 'g');
-export const sanitizeLikeString = str => str?.replace(likeStringRegex, '_');
-
-export const sanitizer = r => r;
diff --git a/app/lib/database/utils.test.js b/app/lib/database/utils.test.ts
similarity index 89%
rename from app/lib/database/utils.test.js
rename to app/lib/database/utils.test.ts
index 8c1d054e1..54ecbab6a 100644
--- a/app/lib/database/utils.test.js
+++ b/app/lib/database/utils.test.ts
@@ -1,14 +1,12 @@
-/* eslint-disable no-undef */
import * as utils from './utils';
describe('sanitizeLikeStringTester', () => {
// example chars that shouldn't return
const disallowedChars = ',./;[]!@#$%^&*()_-=+~';
- const sanitizeLikeStringTester = str =>
+ const sanitizeLikeStringTester = (str: string) =>
expect(utils.sanitizeLikeString(`${str}${disallowedChars}`)).toBe(`${str}${'_'.repeat(disallowedChars.length)}`);
test('render empty', () => {
- expect(utils.sanitizeLikeString(null)).toBe(undefined);
expect(utils.sanitizeLikeString('')).toBe('');
expect(utils.sanitizeLikeString(undefined)).toBe(undefined);
});
diff --git a/app/lib/database/utils.ts b/app/lib/database/utils.ts
new file mode 100644
index 000000000..a5b9d8e8d
--- /dev/null
+++ b/app/lib/database/utils.ts
@@ -0,0 +1,7 @@
+import XRegExp from 'xregexp';
+
+// Matches letters from any alphabet and numbers
+const likeStringRegex = XRegExp('[^\\p{L}\\p{Nd}]', 'g');
+export const sanitizeLikeString = (str?: string): string | undefined => str?.replace(likeStringRegex, '_');
+
+export const sanitizer = (r: object): object => r;
diff --git a/app/lib/encryption/constants.js b/app/lib/encryption/constants.ts
similarity index 100%
rename from app/lib/encryption/constants.js
rename to app/lib/encryption/constants.ts
diff --git a/app/lib/encryption/encryption.js b/app/lib/encryption/encryption.ts
similarity index 77%
rename from app/lib/encryption/encryption.js
rename to app/lib/encryption/encryption.ts
index 99a12bd8c..8d17ebd46 100644
--- a/app/lib/encryption/encryption.js
+++ b/app/lib/encryption/encryption.ts
@@ -1,7 +1,7 @@
import EJSON from 'ejson';
import SimpleCrypto from 'react-native-simple-crypto';
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
-import { Q } from '@nozbe/watermelondb';
+import { Q, Model } from '@nozbe/watermelondb';
import RocketChat from '../rocketchat';
import UserPreferences from '../userPreferences';
@@ -9,7 +9,7 @@ import database from '../database';
import protectedFunction from '../methods/helpers/protectedFunction';
import Deferred from '../../utils/deferred';
import log from '../../utils/log';
-import store from '../createStore';
+import { store } from '../auxStore';
import {
E2E_BANNER_TYPE,
E2E_MESSAGE_TYPE,
@@ -20,9 +20,25 @@ import {
} from './constants';
import { joinVectorData, randomPassword, splitVectorData, toString, utf8ToBuffer } from './utils';
import { EncryptionRoom } from './index';
+import { IMessage, ISubscription, TMessageModel, TSubscriptionModel, TThreadMessageModel, TThreadModel } from '../../definitions';
class Encryption {
+ ready: boolean;
+ privateKey: string | null;
+ readyPromise: Deferred;
+ userId: string | null;
+ roomInstances: {
+ [rid: string]: {
+ ready: boolean;
+ provideKeyToUser: Function;
+ handshake: Function;
+ decrypt: Function;
+ encrypt: Function;
+ };
+ };
+
constructor() {
+ this.userId = '';
this.ready = false;
this.privateKey = null;
this.roomInstances = {};
@@ -37,7 +53,7 @@ class Encryption {
}
// Initialize Encryption client
- initialize = userId => {
+ initialize = (userId: string) => {
this.userId = userId;
this.roomInstances = {};
@@ -82,7 +98,7 @@ class Encryption {
};
// When a new participant join and request a new room encryption key
- provideRoomKeyToUser = async (keyId, rid) => {
+ provideRoomKeyToUser = async (keyId: string, rid: string) => {
// If the client is not ready
if (!this.ready) {
try {
@@ -100,14 +116,14 @@ class Encryption {
};
// Persist keys on UserPreferences
- persistKeys = async (server, publicKey, privateKey) => {
+ persistKeys = async (server: string, publicKey: string, privateKey: string) => {
this.privateKey = await SimpleCrypto.RSA.importKey(EJSON.parse(privateKey));
await UserPreferences.setStringAsync(`${server}-${E2E_PUBLIC_KEY}`, EJSON.stringify(publicKey));
await UserPreferences.setStringAsync(`${server}-${E2E_PRIVATE_KEY}`, privateKey);
};
// Could not obtain public-private keypair from server.
- createKeys = async (userId, server) => {
+ createKeys = async (userId: string, server: string) => {
// Generate new keys
const key = await SimpleCrypto.RSA.generateKeys(2048);
@@ -132,7 +148,7 @@ class Encryption {
};
// Encode a private key before send it to the server
- encodePrivateKey = async (privateKey, password, userId) => {
+ encodePrivateKey = async (privateKey: string, password: string, userId: string) => {
const masterKey = await this.generateMasterKey(password, userId);
const vector = await SimpleCrypto.utils.randomBytes(16);
@@ -142,7 +158,7 @@ class Encryption {
};
// Decode a private key fetched from server
- decodePrivateKey = async (privateKey, password, userId) => {
+ decodePrivateKey = async (privateKey: string, password: string, userId: string) => {
const masterKey = await this.generateMasterKey(password, userId);
const [vector, cipherText] = splitVectorData(EJSON.parse(privateKey));
@@ -152,7 +168,7 @@ class Encryption {
};
// Generate a user master key, this is based on userId and a password
- generateMasterKey = async (password, userId) => {
+ generateMasterKey = async (password: string, userId: string) => {
const iterations = 1000;
const hash = 'SHA256';
const keyLen = 32;
@@ -166,18 +182,18 @@ class Encryption {
};
// Create a random password to local created keys
- createRandomPassword = async server => {
+ createRandomPassword = async (server: string) => {
const password = randomPassword();
await UserPreferences.setStringAsync(`${server}-${E2E_RANDOM_PASSWORD_KEY}`, password);
return password;
};
- changePassword = async (server, password) => {
+ changePassword = async (server: string, password: string) => {
// Cast key to the format server is expecting
- const privateKey = await SimpleCrypto.RSA.exportKey(this.privateKey);
+ const privateKey = await SimpleCrypto.RSA.exportKey(this.privateKey as string);
// Encode the private key
- const encodedPrivateKey = await this.encodePrivateKey(EJSON.stringify(privateKey), password, this.userId);
+ const encodedPrivateKey = await this.encodePrivateKey(EJSON.stringify(privateKey), password, this.userId as string);
const publicKey = await UserPreferences.getStringAsync(`${server}-${E2E_PUBLIC_KEY}`);
// Send the new keys to the server
@@ -185,7 +201,7 @@ class Encryption {
};
// get a encryption room instance
- getRoomInstance = async rid => {
+ getRoomInstance = async (rid: string) => {
// Prevent handshake again
if (this.roomInstances[rid]?.ready) {
return this.roomInstances[rid];
@@ -193,7 +209,7 @@ class Encryption {
// If doesn't have a instance of this room
if (!this.roomInstances[rid]) {
- this.roomInstances[rid] = new EncryptionRoom(rid, this.userId);
+ this.roomInstances[rid] = new EncryptionRoom(rid, this.userId as string);
}
const roomE2E = this.roomInstances[rid];
@@ -206,7 +222,7 @@ class Encryption {
// Logic to decrypt all pending messages/threads/threadMessages
// after initialize the encryption client
- decryptPendingMessages = async roomId => {
+ decryptPendingMessages = async (roomId?: string) => {
const db = database.active;
const messagesCollection = db.get('messages');
@@ -228,31 +244,39 @@ class Encryption {
const threadMessagesToDecrypt = await threadMessagesCollection.query(...whereClause).fetch();
// Concat messages/threads/threadMessages
- let toDecrypt = [...messagesToDecrypt, ...threadsToDecrypt, ...threadMessagesToDecrypt];
- toDecrypt = await Promise.all(
+ let toDecrypt: (TThreadModel | TThreadMessageModel)[] = [
+ ...messagesToDecrypt,
+ ...threadsToDecrypt,
+ ...threadMessagesToDecrypt
+ ];
+ toDecrypt = (await Promise.all(
toDecrypt.map(async message => {
const { t, msg, tmsg } = message;
- const { id: rid } = message.subscription;
- // WM Object -> Plain Object
- const newMessage = await this.decryptMessage({
- t,
- rid,
- msg,
- tmsg
- });
- if (message._hasPendingUpdate) {
- console.log(message);
- return;
+ let newMessage: TMessageModel = {} as TMessageModel;
+ if (message.subscription) {
+ const { id: rid } = message.subscription;
+ // WM Object -> Plain Object
+ newMessage = await this.decryptMessage({
+ t,
+ rid,
+ msg,
+ tmsg
+ });
}
- return message.prepareUpdate(
- protectedFunction(m => {
- Object.assign(m, newMessage);
- })
- );
- })
- );
- await db.action(async () => {
+ try {
+ return message.prepareUpdate(
+ protectedFunction((m: TMessageModel) => {
+ Object.assign(m, newMessage);
+ })
+ );
+ } catch {
+ return null;
+ }
+ })
+ )) as (TThreadModel | TThreadMessageModel)[];
+
+ await db.write(async () => {
await db.batch(...toDecrypt);
});
} catch (e) {
@@ -271,29 +295,29 @@ class Encryption {
const subsEncrypted = await subCollection.query(Q.where('e2e_key_id', Q.notEq(null))).fetch();
// We can't do this on database level since lastMessage is not a database object
const subsToDecrypt = subsEncrypted.filter(
- sub =>
+ (sub: ISubscription) =>
// Encrypted message
sub?.lastMessage?.t === E2E_MESSAGE_TYPE &&
// Message pending decrypt
sub?.lastMessage?.e2e === E2E_STATUS.PENDING
);
await Promise.all(
- subsToDecrypt.map(async sub => {
+ subsToDecrypt.map(async (sub: TSubscriptionModel) => {
const { rid, lastMessage } = sub;
const newSub = await this.decryptSubscription({ rid, lastMessage });
- if (sub._hasPendingUpdate) {
- console.log(sub);
- return;
+ try {
+ return sub.prepareUpdate(
+ protectedFunction((m: TSubscriptionModel) => {
+ Object.assign(m, newSub);
+ })
+ );
+ } catch {
+ return null;
}
- return sub.prepareUpdate(
- protectedFunction(m => {
- Object.assign(m, newSub);
- })
- );
})
);
- await db.action(async () => {
+ await db.write(async () => {
await db.batch(...subsToDecrypt);
});
} catch (e) {
@@ -302,7 +326,7 @@ class Encryption {
};
// Decrypt a subscription lastMessage
- decryptSubscription = async subscription => {
+ decryptSubscription = async (subscription: Partial) => {
// If the subscription doesn't have a lastMessage just return
if (!subscription?.lastMessage) {
return subscription;
@@ -334,37 +358,39 @@ class Encryption {
let subRecord;
try {
- subRecord = await subCollection.find(rid);
+ subRecord = await subCollection.find(rid as string);
} catch {
// Do nothing
}
try {
- const batch = [];
+ const batch: (Model | null | void | false | Promise)[] = [];
// If the subscription doesn't exists yet
if (!subRecord) {
// Let's create the subscription with the data received
batch.push(
- subCollection.prepareCreate(s => {
+ subCollection.prepareCreate((s: TSubscriptionModel) => {
s._raw = sanitizedRaw({ id: rid }, subCollection.schema);
Object.assign(s, subscription);
})
);
// If the subscription already exists but doesn't have the E2EKey yet
} else if (!subRecord.E2EKey && subscription.E2EKey) {
- if (!subRecord._hasPendingUpdate) {
+ try {
// Let's update the subscription with the received E2EKey
batch.push(
- subRecord.prepareUpdate(s => {
+ subRecord.prepareUpdate((s: TSubscriptionModel) => {
s.E2EKey = subscription.E2EKey;
})
);
+ } catch (e) {
+ log(e);
}
}
// If batch has some operation
if (batch.length) {
- await db.action(async () => {
+ await db.write(async () => {
await db.batch(...batch);
});
}
@@ -375,7 +401,7 @@ class Encryption {
}
// Get a instance using the subscription
- const roomE2E = await this.getRoomInstance(rid);
+ const roomE2E = await this.getRoomInstance(rid as string);
const decryptedMessage = await roomE2E.decrypt(lastMessage);
return {
...subscription,
@@ -384,7 +410,7 @@ class Encryption {
};
// Encrypt a message
- encryptMessage = async message => {
+ encryptMessage = async (message: IMessage) => {
const { rid } = message;
const db = database.active;
const subCollection = db.get('subscriptions');
@@ -417,7 +443,7 @@ class Encryption {
};
// Decrypt a message
- decryptMessage = async message => {
+ decryptMessage = async (message: Partial) => {
const { t, e2e } = message;
// Prevent create a new instance if this room was encrypted sometime ago
@@ -438,15 +464,15 @@ class Encryption {
}
const { rid } = message;
- const roomE2E = await this.getRoomInstance(rid);
+ const roomE2E = await this.getRoomInstance(rid as string);
return roomE2E.decrypt(message);
};
// Decrypt multiple messages
- decryptMessages = messages => Promise.all(messages.map(m => this.decryptMessage(m)));
+ decryptMessages = (messages: IMessage[]) => Promise.all(messages.map((m: IMessage) => this.decryptMessage(m)));
// Decrypt multiple subscriptions
- decryptSubscriptions = subscriptions => Promise.all(subscriptions.map(s => this.decryptSubscription(s)));
+ decryptSubscriptions = (subscriptions: ISubscription[]) => Promise.all(subscriptions.map(s => this.decryptSubscription(s)));
}
const encryption = new Encryption();
diff --git a/app/lib/encryption/index.js b/app/lib/encryption/index.ts
similarity index 100%
rename from app/lib/encryption/index.js
rename to app/lib/encryption/index.ts
diff --git a/app/lib/encryption/room.js b/app/lib/encryption/room.ts
similarity index 80%
rename from app/lib/encryption/room.js
rename to app/lib/encryption/room.ts
index d61c7d75a..a68ccc49f 100644
--- a/app/lib/encryption/room.js
+++ b/app/lib/encryption/room.ts
@@ -1,7 +1,9 @@
import EJSON from 'ejson';
import { Base64 } from 'js-base64';
import SimpleCrypto from 'react-native-simple-crypto';
+import ByteBuffer from 'bytebuffer';
+import { IMessage } from '../../definitions';
import RocketChat from '../rocketchat';
import Deferred from '../../utils/deferred';
import debounce from '../../utils/debounce';
@@ -19,13 +21,26 @@ import {
utf8ToBuffer
} from './utils';
import { Encryption } from './index';
+import { IUser } from '../../definitions/IUser';
export default class EncryptionRoom {
- constructor(roomId, userId) {
+ ready: boolean;
+ roomId: string;
+ userId: string;
+ establishing: boolean;
+ readyPromise: Deferred;
+ sessionKeyExportedString: string | ByteBuffer;
+ keyID: string;
+ roomKey: ArrayBuffer;
+
+ constructor(roomId: string, userId: string) {
this.ready = false;
this.roomId = roomId;
this.userId = userId;
this.establishing = false;
+ this.keyID = '';
+ this.sessionKeyExportedString = '';
+ this.roomKey = new ArrayBuffer(0);
this.readyPromise = new Deferred();
this.readyPromise.then(() => {
// Mark as ready
@@ -57,7 +72,7 @@ export default class EncryptionRoom {
const { E2EKey, e2eKeyId } = subscription;
// If this room has a E2EKey, we import it
- if (E2EKey) {
+ if (E2EKey && Encryption.privateKey) {
// We're establishing a new room encryption client
this.establishing = true;
await this.importRoomKey(E2EKey, Encryption.privateKey);
@@ -82,25 +97,25 @@ export default class EncryptionRoom {
};
// Import roomKey as an AES Decrypt key
- importRoomKey = async (E2EKey, privateKey) => {
+ importRoomKey = async (E2EKey: string, privateKey: string) => {
const roomE2EKey = E2EKey.slice(12);
const decryptedKey = await SimpleCrypto.RSA.decrypt(roomE2EKey, privateKey);
this.sessionKeyExportedString = toString(decryptedKey);
- this.keyID = Base64.encode(this.sessionKeyExportedString).slice(0, 12);
+ this.keyID = Base64.encode(this.sessionKeyExportedString as string).slice(0, 12);
// Extract K from Web Crypto Secret Key
// K is a base64URL encoded array of bytes
// Web Crypto API uses this as a private key to decrypt/encrypt things
// Reference: https://www.javadoc.io/doc/com.nimbusds/nimbus-jose-jwt/5.1/com/nimbusds/jose/jwk/OctetSequenceKey.html
- const { k } = EJSON.parse(this.sessionKeyExportedString);
+ const { k } = EJSON.parse(this.sessionKeyExportedString as string);
this.roomKey = b64ToBuffer(k);
};
// Create a key to a room
createRoomKey = async () => {
- const key = await SimpleCrypto.utils.randomBytes(16);
+ const key = (await SimpleCrypto.utils.randomBytes(16)) as Uint8Array;
this.roomKey = key;
// Web Crypto format of a Secret Key
@@ -131,7 +146,7 @@ export default class EncryptionRoom {
// Each time you see a encrypted message of a room that you don't have a key
// this will be called again and run once in 5 seconds
requestRoomKey = debounce(
- async e2eKeyId => {
+ async (e2eKeyId: string) => {
await RocketChat.e2eRequestRoomKey(this.roomId, e2eKeyId);
},
5000,
@@ -143,22 +158,22 @@ export default class EncryptionRoom {
const result = await RocketChat.e2eGetUsersOfRoomWithoutKey(this.roomId);
if (result.success) {
const { users } = result;
- await Promise.all(users.map(user => this.encryptRoomKeyForUser(user)));
+ await Promise.all(users.map((user: IUser) => this.encryptRoomKeyForUser(user)));
}
};
// Encrypt the room key to each user in
- encryptRoomKeyForUser = async user => {
+ encryptRoomKeyForUser = async (user: IUser) => {
if (user?.e2e?.public_key) {
const { public_key: publicKey } = user.e2e;
const userKey = await SimpleCrypto.RSA.importKey(EJSON.parse(publicKey));
- const encryptedUserKey = await SimpleCrypto.RSA.encrypt(this.sessionKeyExportedString, userKey);
+ const encryptedUserKey = await SimpleCrypto.RSA.encrypt(this.sessionKeyExportedString as string, userKey);
await RocketChat.e2eUpdateGroupKey(user?._id, this.roomId, this.keyID + encryptedUserKey);
}
};
// Provide this room key to a user
- provideKeyToUser = async keyId => {
+ provideKeyToUser = async (keyId: string) => {
// Don't provide a key if the keyId received
// is different than the current one
if (this.keyID !== keyId) {
@@ -169,16 +184,16 @@ export default class EncryptionRoom {
};
// Encrypt text
- encryptText = async text => {
- text = utf8ToBuffer(text);
+ encryptText = async (text: string | ArrayBuffer) => {
+ text = utf8ToBuffer(text as string);
const vector = await SimpleCrypto.utils.randomBytes(16);
- const data = await SimpleCrypto.AES.encrypt(text, this.roomKey, vector);
+ const data = await SimpleCrypto.AES.encrypt(text, this.roomKey as ArrayBuffer, vector);
return this.keyID + bufferToB64(joinVectorData(vector, data));
};
// Encrypt messages
- encrypt = async message => {
+ encrypt = async (message: IMessage) => {
if (!this.ready) {
return message;
}
@@ -207,8 +222,8 @@ export default class EncryptionRoom {
};
// Decrypt text
- decryptText = async msg => {
- msg = b64ToBuffer(msg.slice(12));
+ decryptText = async (msg: string | ArrayBuffer) => {
+ msg = b64ToBuffer(msg.slice(12) as string);
const [vector, cipherText] = splitVectorData(msg);
const decrypted = await SimpleCrypto.AES.decrypt(cipherText, this.roomKey, vector);
@@ -219,7 +234,7 @@ export default class EncryptionRoom {
};
// Decrypt messages
- decrypt = async message => {
+ decrypt = async (message: IMessage) => {
if (!this.ready) {
return message;
}
@@ -231,7 +246,7 @@ export default class EncryptionRoom {
if (t === E2E_MESSAGE_TYPE && e2e !== E2E_STATUS.DONE) {
let { msg, tmsg } = message;
// Decrypt msg
- msg = await this.decryptText(msg);
+ msg = await this.decryptText(msg as string);
// Decrypt tmsg
if (tmsg) {
diff --git a/app/lib/encryption/utils.js b/app/lib/encryption/utils.ts
similarity index 65%
rename from app/lib/encryption/utils.js
rename to app/lib/encryption/utils.ts
index e82e11cf3..97ba23f07 100644
--- a/app/lib/encryption/utils.js
+++ b/app/lib/encryption/utils.ts
@@ -1,4 +1,3 @@
-/* eslint-disable no-bitwise */
import ByteBuffer from 'bytebuffer';
import SimpleCrypto from 'react-native-simple-crypto';
@@ -7,12 +6,13 @@ import { fromByteArray, toByteArray } from '../../utils/base64-js';
const BASE64URI = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
-export const b64ToBuffer = base64 => toByteArray(base64).buffer;
+// @ts-ignore
+export const b64ToBuffer = (base64: string): ArrayBuffer => toByteArray(base64).buffer;
export const utf8ToBuffer = SimpleCrypto.utils.convertUtf8ToArrayBuffer;
-export const bufferToB64 = arrayBuffer => fromByteArray(new Uint8Array(arrayBuffer));
+export const bufferToB64 = (arrayBuffer: ArrayBuffer): string => fromByteArray(new Uint8Array(arrayBuffer));
// ArrayBuffer -> Base64 URI Safe
// https://github.com/herrjemand/Base64URL-ArrayBuffer/blob/master/lib/base64url-arraybuffer.js
-export const bufferToB64URI = buffer => {
+export const bufferToB64URI = (buffer: ArrayBuffer): string => {
const uintArray = new Uint8Array(buffer);
const len = uintArray.length;
let base64 = '';
@@ -33,28 +33,28 @@ export const bufferToB64URI = buffer => {
return base64;
};
// SimpleCrypto.utils.convertArrayBufferToUtf8 is not working with unicode emoji
-export const bufferToUtf8 = buffer => {
- const uintArray = new Uint8Array(buffer);
+export const bufferToUtf8 = (buffer: ArrayBuffer): string => {
+ const uintArray = new Uint8Array(buffer) as number[] & Uint8Array;
const encodedString = String.fromCharCode.apply(null, uintArray);
- const decodedString = decodeURIComponent(escape(encodedString));
- return decodedString;
+ return decodeURIComponent(escape(encodedString));
};
-export const splitVectorData = text => {
+export const splitVectorData = (text: ArrayBuffer): ArrayBuffer[] => {
const vector = text.slice(0, 16);
const data = text.slice(16);
return [vector, data];
};
-export const joinVectorData = (vector, data) => {
+
+export const joinVectorData = (vector: ArrayBuffer, data: ArrayBuffer): ArrayBufferLike => {
const output = new Uint8Array(vector.byteLength + data.byteLength);
output.set(new Uint8Array(vector), 0);
output.set(new Uint8Array(data), vector.byteLength);
return output.buffer;
};
-export const toString = thing => {
+export const toString = (thing: string | ByteBuffer | Buffer | ArrayBuffer | Uint8Array): string | ByteBuffer => {
if (typeof thing === 'string') {
return thing;
}
- // eslint-disable-next-line new-cap
+ // @ts-ignore
return new ByteBuffer.wrap(thing).toString('binary');
};
-export const randomPassword = () => `${random(3)}-${random(3)}-${random(3)}`.toLowerCase();
+export const randomPassword = (): string => `${random(3)}-${random(3)}-${random(3)}`.toLowerCase();
diff --git a/app/lib/methods/callJitsi.js b/app/lib/methods/callJitsi.ts
similarity index 62%
rename from app/lib/methods/callJitsi.js
rename to app/lib/methods/callJitsi.ts
index 8d2fa7db4..b04fbb19d 100644
--- a/app/lib/methods/callJitsi.js
+++ b/app/lib/methods/callJitsi.ts
@@ -1,9 +1,11 @@
-import reduxStore from '../createStore';
-import Navigation from '../Navigation';
+import { ISubscription } from '../../definitions';
import { events, logEvent } from '../../utils/log';
+import { store } from '../auxStore';
+import Navigation from '../Navigation';
+import sdk from '../rocketchat';
-async function jitsiURL({ room }) {
- const { settings } = reduxStore.getState();
+async function jitsiURL({ room }: { room: ISubscription }) {
+ const { settings } = store.getState();
const { Jitsi_Enabled } = settings;
if (!Jitsi_Enabled) {
@@ -19,7 +21,7 @@ async function jitsiURL({ room }) {
let queryString = '';
if (Jitsi_Enabled_TokenAuth) {
try {
- const accessToken = await this.methodCallWrapper('jitsi:generateAccessToken', room?.rid);
+ const accessToken = await sdk.methodCallWrapper('jitsi:generateAccessToken', room?.rid);
queryString = `?jwt=${accessToken}`;
} catch {
logEvent(events.RA_JITSI_F);
@@ -30,23 +32,23 @@ async function jitsiURL({ room }) {
if (Jitsi_URL_Room_Hash) {
rname = uniqueID + room?.rid;
} else {
- rname = encodeURIComponent(room.t === 'd' ? room?.usernames?.join?.(' x ') : room?.name);
+ rname = encodeURIComponent(room.t === 'd' ? (room?.usernames?.join?.(' x ') as string) : room?.name);
}
return `${protocol}${domain}${prefix}${rname}${queryString}`;
}
-export function callJitsiWithoutServer(path) {
+export function callJitsiWithoutServer(path: string): void {
logEvent(events.RA_JITSI_VIDEO);
- const { Jitsi_SSL } = reduxStore.getState().settings;
+ const { Jitsi_SSL } = store.getState().settings;
const protocol = Jitsi_SSL ? 'https://' : 'http://';
const url = `${protocol}${path}`;
Navigation.navigate('JitsiMeetView', { url, onlyAudio: false });
}
-async function callJitsi(room, onlyAudio = false) {
+async function callJitsi(room: ISubscription, onlyAudio = false): Promise {
logEvent(onlyAudio ? events.RA_JITSI_AUDIO : events.RA_JITSI_VIDEO);
- const url = await jitsiURL.call(this, { room });
+ const url = await jitsiURL({ room });
Navigation.navigate('JitsiMeetView', { url, onlyAudio, rid: room?.rid });
}
diff --git a/app/lib/methods/canOpenRoom.js b/app/lib/methods/canOpenRoom.js
index ca8835b9c..183b32a36 100644
--- a/app/lib/methods/canOpenRoom.js
+++ b/app/lib/methods/canOpenRoom.js
@@ -1,5 +1,5 @@
import database from '../database';
-import store from '../createStore';
+import { store } from '../auxStore';
const restTypes = {
channel: 'channels',
diff --git a/app/lib/methods/enterpriseModules.js b/app/lib/methods/enterpriseModules.js
index 312a99252..34313074d 100644
--- a/app/lib/methods/enterpriseModules.js
+++ b/app/lib/methods/enterpriseModules.js
@@ -1,5 +1,5 @@
-import { compareServerVersion, methods } from '../utils';
-import reduxStore from '../createStore';
+import { compareServerVersion } from '../utils';
+import { store as reduxStore } from '../auxStore';
import database from '../database';
import log from '../../utils/log';
import { clearEnterpriseModules, setEnterpriseModules as setEnterpriseModulesAction } from '../../actions/enterpriseModules';
@@ -32,7 +32,7 @@ export function getEnterpriseModules() {
return new Promise(async resolve => {
try {
const { version: serverVersion, server: serverId } = reduxStore.getState().server;
- if (compareServerVersion(serverVersion, '3.1.0', methods.greaterThanOrEqualTo)) {
+ if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '3.1.0')) {
// RC 3.1.0
const enterpriseModules = await this.methodCallWrapper('license:getModules');
if (enterpriseModules) {
diff --git a/app/lib/methods/getCustomEmojis.js b/app/lib/methods/getCustomEmojis.js
deleted file mode 100644
index 5e8a3e745..000000000
--- a/app/lib/methods/getCustomEmojis.js
+++ /dev/null
@@ -1,137 +0,0 @@
-import orderBy from 'lodash/orderBy';
-import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
-
-import { compareServerVersion, methods } from '../utils';
-import reduxStore from '../createStore';
-import database from '../database';
-import log from '../../utils/log';
-import { setCustomEmojis as setCustomEmojisAction } from '../../actions/customEmojis';
-
-const getUpdatedSince = allEmojis => {
- if (!allEmojis.length) {
- return null;
- }
- const ordered = orderBy(
- allEmojis.filter(item => item._updatedAt !== null),
- ['_updatedAt'],
- ['desc']
- );
- return ordered && ordered[0]._updatedAt.toISOString();
-};
-
-const updateEmojis = async ({ update = [], remove = [], allRecords }) => {
- if (!((update && update.length) || (remove && remove.length))) {
- return;
- }
- const db = database.active;
- const emojisCollection = db.get('custom_emojis');
- let emojisToCreate = [];
- let emojisToUpdate = [];
- let emojisToDelete = [];
-
- // Create or update
- if (update && update.length) {
- emojisToCreate = update.filter(i1 => !allRecords.find(i2 => i1._id === i2.id));
- emojisToUpdate = allRecords.filter(i1 => update.find(i2 => i1.id === i2._id));
- emojisToCreate = emojisToCreate.map(emoji =>
- emojisCollection.prepareCreate(e => {
- e._raw = sanitizedRaw({ id: emoji._id }, emojisCollection.schema);
- Object.assign(e, emoji);
- })
- );
- emojisToUpdate = emojisToUpdate.map(emoji => {
- const newEmoji = update.find(e => e._id === emoji.id);
- return emoji.prepareUpdate(e => {
- Object.assign(e, newEmoji);
- });
- });
- }
-
- if (remove && remove.length) {
- emojisToDelete = allRecords.filter(i1 => remove.find(i2 => i1.id === i2._id));
- emojisToDelete = emojisToDelete.map(emoji => emoji.prepareDestroyPermanently());
- }
-
- try {
- await db.action(async () => {
- await db.batch(...emojisToCreate, ...emojisToUpdate, ...emojisToDelete);
- });
- return true;
- } catch (e) {
- log(e);
- }
-};
-
-export async function setCustomEmojis() {
- const db = database.active;
- const emojisCollection = db.get('custom_emojis');
- const allEmojis = await emojisCollection.query().fetch();
- const parsed = allEmojis.reduce((ret, item) => {
- ret[item.name] = {
- name: item.name,
- extension: item.extension
- };
- item.aliases.forEach(alias => {
- ret[alias] = {
- name: item.name,
- extension: item.extension
- };
- });
- return ret;
- }, {});
- reduxStore.dispatch(setCustomEmojisAction(parsed));
-}
-
-export function getCustomEmojis() {
- return new Promise(async resolve => {
- try {
- const serverVersion = reduxStore.getState().server.version;
- const db = database.active;
- const emojisCollection = db.get('custom_emojis');
- const allRecords = await emojisCollection.query().fetch();
- const updatedSince = await getUpdatedSince(allRecords);
-
- // if server version is lower than 0.75.0, fetches from old api
- if (compareServerVersion(serverVersion, '0.75.0', methods.lowerThan)) {
- // RC 0.61.0
- const result = await this.sdk.get('emoji-custom');
-
- let { emojis } = result;
- emojis = emojis.filter(emoji => !updatedSince || emoji._updatedAt > updatedSince);
- const changedEmojis = await updateEmojis({ update: emojis, allRecords });
-
- // `setCustomEmojis` is fired on selectServer
- // We run it again only if emojis were changed
- if (changedEmojis) {
- setCustomEmojis();
- }
- return resolve();
- } else {
- const params = {};
- if (updatedSince) {
- params.updatedSince = updatedSince;
- }
-
- // RC 0.75.0
- const result = await this.sdk.get('emoji-custom.list', params);
-
- if (!result.success) {
- return resolve();
- }
-
- const { emojis } = result;
- const { update, remove } = emojis;
- const changedEmojis = await updateEmojis({ update, remove, allRecords });
-
- // `setCustomEmojis` is fired on selectServer
- // We run it again only if emojis were changed
- if (changedEmojis) {
- setCustomEmojis();
- }
- }
- } catch (e) {
- log(e);
- return resolve();
- }
- });
-}
diff --git a/app/lib/methods/getCustomEmojis.ts b/app/lib/methods/getCustomEmojis.ts
new file mode 100644
index 000000000..87a549d18
--- /dev/null
+++ b/app/lib/methods/getCustomEmojis.ts
@@ -0,0 +1,155 @@
+import orderBy from 'lodash/orderBy';
+import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
+
+import { ICustomEmojis } from '../../reducers/customEmojis';
+import { compareServerVersion } from '../utils';
+import { store as reduxStore } from '../auxStore';
+import database from '../database';
+import log from '../../utils/log';
+import { setCustomEmojis as setCustomEmojisAction } from '../../actions/customEmojis';
+import { ICustomEmoji, TCustomEmojiModel } from '../../definitions';
+import sdk from '../rocketchat/services/sdk';
+
+interface IUpdateEmojis {
+ update: TCustomEmojiModel[];
+ remove?: TCustomEmojiModel[];
+ allRecords: TCustomEmojiModel[];
+}
+
+const getUpdatedSince = (allEmojis: ICustomEmoji[]) => {
+ if (!allEmojis.length) {
+ return null;
+ }
+ const ordered = orderBy(
+ allEmojis.filter(item => item._updatedAt !== null),
+ ['_updatedAt'],
+ ['desc']
+ );
+ return ordered && ordered[0]._updatedAt.toISOString();
+};
+
+const updateEmojis = async ({ update = [], remove = [], allRecords }: IUpdateEmojis) => {
+ if (!((update && update.length) || (remove && remove.length))) {
+ return;
+ }
+ const db = database.active;
+ const emojisCollection = db.get('custom_emojis');
+ let emojisToCreate: TCustomEmojiModel[] = [];
+ let emojisToUpdate: TCustomEmojiModel[] = [];
+ let emojisToDelete: TCustomEmojiModel[] = [];
+
+ // Create or update
+ if (update && update.length) {
+ const filterEmojisToCreate = update.filter(i1 => !allRecords.find(i2 => i1._id === i2.id));
+ const filterEmojisToUpdate = allRecords.filter(i1 => update.find(i2 => i1.id === i2._id));
+ emojisToCreate = filterEmojisToCreate.map(emoji =>
+ emojisCollection.prepareCreate(e => {
+ e._raw = sanitizedRaw({ id: emoji._id }, emojisCollection.schema);
+ Object.assign(e, emoji);
+ })
+ );
+ emojisToUpdate = filterEmojisToUpdate.map(emoji => {
+ const newEmoji = update.find(e => e._id === emoji.id);
+ return emoji.prepareUpdate(e => {
+ Object.assign(e, newEmoji);
+ });
+ });
+ }
+
+ if (remove && remove.length) {
+ const filterEmojisToDelete = allRecords.filter(i1 => remove.find(i2 => i1.id === i2._id));
+ emojisToDelete = filterEmojisToDelete.map(emoji => emoji.prepareDestroyPermanently());
+ }
+
+ try {
+ await db.write(async () => {
+ await db.batch(...emojisToCreate, ...emojisToUpdate, ...emojisToDelete);
+ });
+ return true;
+ } catch (e) {
+ log(e);
+ }
+};
+
+export async function setCustomEmojis() {
+ const db = database.active;
+ const emojisCollection = db.get('custom_emojis');
+ const allEmojis = await emojisCollection.query().fetch();
+ const parsed = allEmojis.reduce((ret: ICustomEmojis, item) => {
+ if (item.name) {
+ ret[item.name] = {
+ name: item.name,
+ extension: item.extension
+ };
+ }
+
+ if (item.aliases) {
+ item.aliases.forEach(alias => {
+ if (item.name) {
+ ret[alias] = {
+ name: item.name,
+ extension: item.extension
+ };
+ }
+ });
+ }
+
+ return ret;
+ }, {});
+ reduxStore.dispatch(setCustomEmojisAction(parsed));
+}
+
+export function getCustomEmojis() {
+ return new Promise(async resolve => {
+ try {
+ const serverVersion = reduxStore.getState().server.version as string;
+ const db = database.active;
+ const emojisCollection = db.get('custom_emojis');
+ const allRecords = await emojisCollection.query().fetch();
+ const updatedSince = await getUpdatedSince(allRecords);
+
+ // if server version is lower than 0.75.0, fetches from old api
+ if (compareServerVersion(serverVersion, 'lowerThan', '0.75.0')) {
+ // RC 0.61.0
+ // @ts-ignore
+ const result = await sdk.get('emoji-custom');
+ // @ts-ignore
+ let { emojis } = result;
+ emojis = emojis.filter((emoji: TCustomEmojiModel) => !updatedSince || emoji._updatedAt.toISOString() > updatedSince);
+ const changedEmojis = await updateEmojis({ update: emojis, allRecords });
+
+ // `setCustomEmojis` is fired on selectServer
+ // We run it again only if emojis were changed
+ if (changedEmojis) {
+ setCustomEmojis();
+ }
+ return resolve();
+ }
+ const params: { updatedSince: string } = { updatedSince: '' };
+ if (updatedSince) {
+ params.updatedSince = updatedSince;
+ }
+
+ // RC 0.75.0
+ const result = await sdk.get('emoji-custom.list', params);
+
+ if (!result.success) {
+ return resolve();
+ }
+
+ const { emojis } = result;
+ // @ts-ignore
+ const { update, remove } = emojis;
+ const changedEmojis = await updateEmojis({ update, remove, allRecords });
+
+ // `setCustomEmojis` is fired on selectServer
+ // We run it again only if emojis were changed
+ if (changedEmojis) {
+ setCustomEmojis();
+ }
+ } catch (e) {
+ log(e);
+ return resolve();
+ }
+ });
+}
diff --git a/app/lib/methods/getPermissions.js b/app/lib/methods/getPermissions.ts
similarity index 55%
rename from app/lib/methods/getPermissions.js
rename to app/lib/methods/getPermissions.ts
index b680a9196..fa807d7fa 100644
--- a/app/lib/methods/getPermissions.js
+++ b/app/lib/methods/getPermissions.ts
@@ -2,15 +2,17 @@ import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
import { Q } from '@nozbe/watermelondb';
import orderBy from 'lodash/orderBy';
-import { compareServerVersion, methods } from '../utils';
+import { compareServerVersion } from '../utils';
import database from '../database';
import log from '../../utils/log';
-import reduxStore from '../createStore';
+import { store as reduxStore } from '../auxStore';
import RocketChat from '../rocketchat';
+import sdk from '../rocketchat/services/sdk';
import { setPermissions as setPermissionsAction } from '../../actions/permissions';
import protectedFunction from './helpers/protectedFunction';
+import { TPermissionModel, IPermission } from '../../definitions';
-const PERMISSIONS = [
+export const SUPPORTED_PERMISSIONS = [
'add-user-to-any-c-room',
'add-user-to-any-p-room',
'add-user-to-joined-room',
@@ -57,18 +59,20 @@ const PERMISSIONS = [
'edit-livechat-room-customfields',
'view-canned-responses',
'mobile-upload-file'
-];
+] as const;
-export async function setPermissions() {
+export async function setPermissions(): Promise {
const db = database.active;
- const permissionsCollection = db.collections.get('permissions');
- const allPermissions = await permissionsCollection.query(Q.where('id', Q.oneOf(PERMISSIONS))).fetch();
+ const permissionsCollection = db.get('permissions');
+ const allPermissions = await permissionsCollection
+ .query(Q.where('id', Q.oneOf(SUPPORTED_PERMISSIONS as unknown as string[])))
+ .fetch();
const parsed = allPermissions.reduce((acc, item) => ({ ...acc, [item.id]: item.roles }), {});
reduxStore.dispatch(setPermissionsAction(parsed));
}
-const getUpdatedSince = allRecords => {
+const getUpdatedSince = (allRecords: TPermissionModel[]) => {
try {
if (!allRecords.length) {
return null;
@@ -78,57 +82,63 @@ const getUpdatedSince = allRecords => {
['_updatedAt'],
['desc']
);
- return ordered && ordered[0]._updatedAt.toISOString();
+ return new Date(ordered[0]._updatedAt).toISOString();
} catch (e) {
log(e);
}
return null;
};
-const updatePermissions = async ({ update = [], remove = [], allRecords }) => {
+const updatePermissions = async ({
+ update = [],
+ remove = [],
+ allRecords
+}: {
+ update?: IPermission[];
+ remove?: IPermission[];
+ allRecords: TPermissionModel[];
+}) => {
if (!((update && update.length) || (remove && remove.length))) {
return;
}
const db = database.active;
const permissionsCollection = db.get('permissions');
- // filter permissions
- let permissionsToCreate = [];
- let permissionsToUpdate = [];
- let permissionsToDelete = [];
+ const batch: TPermissionModel[] = [];
+
+ // Delete
+ if (remove?.length) {
+ const filteredPermissionsToDelete = allRecords.filter(i1 => remove.find(i2 => i1.id === i2._id));
+ const permissionsToDelete = filteredPermissionsToDelete.map(permission => permission.prepareDestroyPermanently());
+ batch.push(...permissionsToDelete);
+ }
// Create or update
- if (update && update.length) {
- permissionsToCreate = update.filter(i1 => !allRecords.find(i2 => i1._id === i2.id));
- permissionsToUpdate = allRecords.filter(i1 => update.find(i2 => i1.id === i2._id));
- permissionsToCreate = permissionsToCreate.map(permission =>
+ if (update?.length) {
+ const filteredPermissionsToCreate = update.filter(i1 => !allRecords.find(i2 => i1._id === i2.id));
+ const filteredPermissionsToUpdate = allRecords.filter(i1 => update.find(i2 => i1.id === i2._id));
+ const permissionsToCreate = filteredPermissionsToCreate.map(permission =>
permissionsCollection.prepareCreate(
- protectedFunction(p => {
+ protectedFunction((p: TPermissionModel) => {
p._raw = sanitizedRaw({ id: permission._id }, permissionsCollection.schema);
Object.assign(p, permission);
})
)
);
- permissionsToUpdate = permissionsToUpdate.map(permission => {
+ const permissionsToUpdate = filteredPermissionsToUpdate.map(permission => {
const newPermission = update.find(p => p._id === permission.id);
return permission.prepareUpdate(
- protectedFunction(p => {
+ protectedFunction((p: TPermissionModel) => {
Object.assign(p, newPermission);
})
);
});
- }
- // Delete
- if (remove && remove.length) {
- permissionsToDelete = allRecords.filter(i1 => remove.find(i2 => i1.id === i2._id));
- permissionsToDelete = permissionsToDelete.map(permission => permission.prepareDestroyPermanently());
+ batch.push(...permissionsToCreate, ...permissionsToUpdate);
}
- const batch = [...permissionsToCreate, ...permissionsToUpdate, ...permissionsToDelete];
-
try {
- await db.action(async () => {
+ await db.write(async () => {
await db.batch(...batch);
});
return true;
@@ -137,18 +147,19 @@ const updatePermissions = async ({ update = [], remove = [], allRecords }) => {
}
};
-export function getPermissions() {
+export function getPermissions(): Promise {
return new Promise(async resolve => {
try {
- const serverVersion = reduxStore.getState().server.version;
+ const serverVersion: string | null = reduxStore.getState().server.version;
const db = database.active;
const permissionsCollection = db.get('permissions');
const allRecords = await permissionsCollection.query().fetch();
RocketChat.subscribe('stream-notify-logged', 'permissions-changed');
// if server version is lower than 0.73.0, fetches from old api
- if (compareServerVersion(serverVersion, '0.73.0', methods.lowerThan)) {
+ if (serverVersion && compareServerVersion(serverVersion, 'lowerThan', '0.73.0')) {
// RC 0.66.0
- const result = await this.sdk.get('permissions.list');
+ // @ts-ignore
+ const result: any = await sdk.get('permissions.list');
if (!result.success) {
return resolve();
}
@@ -157,25 +168,25 @@ export function getPermissions() {
setPermissions();
}
return resolve();
- } else {
- const params = {};
- const updatedSince = getUpdatedSince(allRecords);
- if (updatedSince) {
- params.updatedSince = updatedSince;
- }
- // RC 0.73.0
- const result = await this.sdk.get('permissions.listAll', params);
+ }
- if (!result.success) {
- return resolve();
- }
+ const params: { updatedSince?: string } = {};
+ const updatedSince = getUpdatedSince(allRecords);
+ if (updatedSince) {
+ params.updatedSince = updatedSince;
+ }
+ // RC 0.73.0
+ const result = await sdk.get('permissions.listAll', params);
- const changePermissions = await updatePermissions({ update: result.update, remove: result.delete, allRecords });
- if (changePermissions) {
- setPermissions();
- }
+ if (!result.success) {
return resolve();
}
+
+ const changePermissions = await updatePermissions({ update: result.update, remove: result.remove, allRecords });
+ if (changePermissions) {
+ setPermissions();
+ }
+ return resolve();
} catch (e) {
log(e);
return resolve();
diff --git a/app/lib/methods/getRoles.js b/app/lib/methods/getRoles.ts
similarity index 55%
rename from app/lib/methods/getRoles.js
rename to app/lib/methods/getRoles.ts
index d8beb5785..6add9e897 100644
--- a/app/lib/methods/getRoles.js
+++ b/app/lib/methods/getRoles.ts
@@ -1,70 +1,80 @@
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
+import Model from '@nozbe/watermelondb/Model';
import database from '../database';
+import { getRoleById } from '../database/services/Role';
import log from '../../utils/log';
-import reduxStore from '../createStore';
+import { store as reduxStore } from '../auxStore';
import { removeRoles, setRoles as setRolesAction, updateRoles } from '../../actions/roles';
import protectedFunction from './helpers/protectedFunction';
+import { TRoleModel } from '../../definitions';
+import sdk from '../rocketchat/services/sdk';
-export async function setRoles() {
+export async function setRoles(): Promise {
const db = database.active;
- const rolesCollection = db.collections.get('roles');
+ const rolesCollection = db.get('roles');
const allRoles = await rolesCollection.query().fetch();
const parsed = allRoles.reduce((acc, item) => ({ ...acc, [item.id]: item.description || item.id }), {});
reduxStore.dispatch(setRolesAction(parsed));
}
-export async function onRolesChanged(ddpMessage) {
+interface IRolesChanged {
+ fields: {
+ args: {
+ type: string;
+ _id: string;
+ description: string;
+ }[];
+ };
+}
+
+export async function onRolesChanged(ddpMessage: IRolesChanged): Promise {
const { type, _id, description } = ddpMessage.fields.args[0];
if (/changed/.test(type)) {
const db = database.active;
const rolesCollection = db.get('roles');
try {
- const rolesRecord = await rolesCollection.find(_id);
- try {
- await db.action(async () => {
- await rolesRecord.update(u => {
+ const roleRecord = await getRoleById(_id);
+ if (roleRecord) {
+ await db.write(async () => {
+ await roleRecord.update(u => {
u.description = description;
});
});
- } catch (e) {
- log(e);
- }
- reduxStore.dispatch(updateRoles(_id, description));
- } catch (err) {
- try {
- await db.action(async () => {
+ } else {
+ await db.write(async () => {
await rolesCollection.create(post => {
post._raw = sanitizedRaw({ id: _id, description }, rolesCollection.schema);
});
});
- } catch (e) {
- log(e);
}
reduxStore.dispatch(updateRoles(_id, description || _id));
+ } catch (e) {
+ log(e);
}
}
if (/removed/.test(type)) {
const db = database.active;
- const rolesCollection = db.get('roles');
try {
- const rolesRecord = await rolesCollection.find(_id);
- await db.action(async () => {
- await rolesRecord.destroyPermanently();
- });
- reduxStore.dispatch(removeRoles(_id));
- } catch (err) {
- console.log(err);
+ const roleRecord = await getRoleById(_id);
+ if (roleRecord) {
+ await db.write(async () => {
+ await roleRecord.destroyPermanently();
+ });
+ reduxStore.dispatch(removeRoles(_id));
+ }
+ } catch (e) {
+ log(e);
}
}
}
-export function getRoles() {
+export function getRoles(): Promise {
const db = database.active;
return new Promise(async resolve => {
try {
// RC 0.70.0
- const result = await this.sdk.get('roles.list');
+ const result = await sdk.get('roles.list');
if (!result.success) {
return resolve();
@@ -73,18 +83,18 @@ export function getRoles() {
const { roles } = result;
if (roles && roles.length) {
- await db.action(async () => {
+ await db.write(async () => {
const rolesCollections = db.get('roles');
const allRolesRecords = await rolesCollections.query().fetch();
// filter roles
- let rolesToCreate = roles.filter(i1 => !allRolesRecords.find(i2 => i1._id === i2.id));
- let rolesToUpdate = allRolesRecords.filter(i1 => roles.find(i2 => i1.id === i2._id));
+ const filteredRolesToCreate = roles.filter(i1 => !allRolesRecords.find(i2 => i1._id === i2.id));
+ const filteredRolesToUpdate = allRolesRecords.filter(i1 => roles.find(i2 => i1.id === i2._id));
// Create
- rolesToCreate = rolesToCreate.map(role =>
+ const rolesToCreate = filteredRolesToCreate.map(role =>
rolesCollections.prepareCreate(
- protectedFunction(r => {
+ protectedFunction((r: TRoleModel) => {
r._raw = sanitizedRaw({ id: role._id }, rolesCollections.schema);
Object.assign(r, role);
})
@@ -92,16 +102,16 @@ export function getRoles() {
);
// Update
- rolesToUpdate = rolesToUpdate.map(role => {
+ const rolesToUpdate = filteredRolesToUpdate.map(role => {
const newRole = roles.find(r => r._id === role.id);
return role.prepareUpdate(
- protectedFunction(r => {
+ protectedFunction((r: TRoleModel) => {
Object.assign(r, newRole);
})
);
});
- const allRecords = [...rolesToCreate, ...rolesToUpdate];
+ const allRecords: Model[] = [...rolesToCreate, ...rolesToUpdate];
try {
await db.batch(...allRecords);
diff --git a/app/lib/methods/getRoomInfo.js b/app/lib/methods/getRoomInfo.ts
similarity index 77%
rename from app/lib/methods/getRoomInfo.js
rename to app/lib/methods/getRoomInfo.ts
index 24c061aa0..b74dbaca1 100644
--- a/app/lib/methods/getRoomInfo.js
+++ b/app/lib/methods/getRoomInfo.ts
@@ -1,7 +1,8 @@
+import { IRoom } from '../../definitions';
import { getSubscriptionByRoomId } from '../database/services/Subscription';
import RocketChat from '../rocketchat';
-const getRoomInfo = async rid => {
+const getRoomInfo = async (rid: string): Promise | null> => {
let result;
result = await getSubscriptionByRoomId(rid);
if (result) {
diff --git a/app/lib/methods/getRooms.js b/app/lib/methods/getRooms.js
deleted file mode 100644
index 5004beda0..000000000
--- a/app/lib/methods/getRooms.js
+++ /dev/null
@@ -1,9 +0,0 @@
-export default function (updatedSince) {
- // subscriptions.get: Since RC 0.60.0
- // rooms.get: Since RC 0.62.0
- if (updatedSince) {
- updatedSince = updatedSince.toISOString();
- return Promise.all([this.sdk.get('subscriptions.get', { updatedSince }), this.sdk.get('rooms.get', { updatedSince })]);
- }
- return Promise.all([this.sdk.get('subscriptions.get'), this.sdk.get('rooms.get')]);
-}
diff --git a/app/lib/methods/getRooms.ts b/app/lib/methods/getRooms.ts
new file mode 100644
index 000000000..8d7f89397
--- /dev/null
+++ b/app/lib/methods/getRooms.ts
@@ -0,0 +1,19 @@
+import sdk from '../rocketchat/services/sdk';
+
+export default function (updatedSince: Date) {
+ // subscriptions.get: Since RC 0.60.0
+ // rooms.get: Since RC 0.62.0
+ if (updatedSince) {
+ const updatedDate = updatedSince.toISOString();
+ // TODO: missing definitions from server
+ return Promise.all([
+ // @ts-ignore
+ sdk.get('subscriptions.get', { updatedSince: updatedDate }),
+ // @ts-ignore
+ sdk.get('rooms.get', { updatedSince: updatedDate })
+ ]);
+ }
+ // TODO: missing definitions from server
+ // @ts-ignore
+ return Promise.all([sdk.get('subscriptions.get'), sdk.get('rooms.get')]);
+}
diff --git a/app/lib/methods/getSettings.js b/app/lib/methods/getSettings.ts
similarity index 75%
rename from app/lib/methods/getSettings.js
rename to app/lib/methods/getSettings.ts
index 9e1082938..2ef89b603 100644
--- a/app/lib/methods/getSettings.js
+++ b/app/lib/methods/getSettings.ts
@@ -1,14 +1,16 @@
-import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
import { Q } from '@nozbe/watermelondb';
+import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
import { addSettings, clearSettings } from '../../actions/settings';
-import RocketChat from '../rocketchat';
-import reduxStore from '../createStore';
-import settings from '../../constants/settings';
-import log from '../../utils/log';
-import database from '../database';
-import fetch from '../../utils/fetch';
import { DEFAULT_AUTO_LOCK } from '../../constants/localAuthentication';
+import settings from '../../constants/settings';
+import { IPreparedSettings, ISettingsIcon } from '../../definitions';
+import fetch from '../../utils/fetch';
+import log from '../../utils/log';
+import { store as reduxStore } from '../auxStore';
+import database from '../database';
+import RocketChat from '../rocketchat';
+import sdk from '../rocketchat/services/sdk';
import protectedFunction from './helpers/protectedFunction';
const serverInfoKeys = [
@@ -41,7 +43,7 @@ const loginSettings = [
'Accounts_Iframe_api_method'
];
-const serverInfoUpdate = async (serverInfo, iconSetting) => {
+const serverInfoUpdate = async (serverInfo: IPreparedSettings[], iconSetting: ISettingsIcon) => {
const serversDB = database.servers;
const serverId = reduxStore.getState().server.server;
const serversCollection = serversDB.get('servers');
@@ -73,7 +75,7 @@ const serverInfoUpdate = async (serverInfo, iconSetting) => {
return { ...allSettings, autoLockTime: DEFAULT_AUTO_LOCK };
}
// if Force_Screen_Lock_After > 0 and forceScreenLock is enabled, use it
- if (setting.valueAsNumber > 0 && forceScreenLock) {
+ if (setting.valueAsNumber && setting.valueAsNumber > 0 && forceScreenLock) {
return { ...allSettings, autoLockTime: setting.valueAsNumber };
}
}
@@ -91,7 +93,7 @@ const serverInfoUpdate = async (serverInfo, iconSetting) => {
info = { ...info, iconURL };
}
- await serversDB.action(async () => {
+ await serversDB.write(async () => {
try {
await server.update(record => {
Object.assign(record, info);
@@ -102,7 +104,7 @@ const serverInfoUpdate = async (serverInfo, iconSetting) => {
});
};
-export async function getLoginSettings({ server }) {
+export async function getLoginSettings({ server }: { server: string }): Promise {
try {
const settingsParams = JSON.stringify(loginSettings);
const result = await fetch(`${server}/api/v1/settings.public?query={"_id":{"$in":${settingsParams}}}`).then(response =>
@@ -111,14 +113,14 @@ export async function getLoginSettings({ server }) {
if (result.success && result.settings.length) {
reduxStore.dispatch(clearSettings());
- reduxStore.dispatch(addSettings(this.parseSettings(this._prepareSettings(result.settings))));
+ reduxStore.dispatch(addSettings(RocketChat.parseSettings(RocketChat._prepareSettings(result.settings))));
}
} catch (e) {
log(e);
}
}
-export async function setSettings() {
+export async function setSettings(): Promise {
const db = database.active;
const settingsCollection = db.get('settings');
const settingsRecords = await settingsCollection.query().fetch();
@@ -133,17 +135,19 @@ export async function setSettings() {
reduxStore.dispatch(addSettings(RocketChat.parseSettings(parsed.slice(0, parsed.length))));
}
-export function subscribeSettings() {
+export function subscribeSettings(): void {
return RocketChat.subscribe('stream-notify-all', 'public-settings-changed');
}
-export default async function () {
+type IData = ISettingsIcon | IPreparedSettings;
+
+export default async function (): Promise {
try {
const db = database.active;
const settingsParams = Object.keys(settings).filter(key => !loginSettings.includes(key));
// RC 0.60.0
const result = await fetch(
- `${this.sdk.client.host}/api/v1/settings.public?query={"_id":{"$in":${JSON.stringify(settingsParams)}}}&count=${
+ `${sdk.current.client.host}/api/v1/settings.public?query={"_id":{"$in":${JSON.stringify(settingsParams)}}}&count=${
settingsParams.length
}`
).then(response => response.json());
@@ -151,33 +155,32 @@ export default async function () {
if (!result.success) {
return;
}
- const data = result.settings || [];
- const filteredSettings = this._prepareSettings(data);
+ const data: IData[] = result.settings || [];
+ const filteredSettings: IPreparedSettings[] = RocketChat._prepareSettings(data);
const filteredSettingsIds = filteredSettings.map(s => s._id);
- reduxStore.dispatch(addSettings(this.parseSettings(filteredSettings)));
+ reduxStore.dispatch(addSettings(RocketChat.parseSettings(filteredSettings)));
// filter server info
const serverInfo = filteredSettings.filter(i1 => serverInfoKeys.includes(i1._id));
- const iconSetting = data.find(item => item._id === 'Assets_favicon_512');
+ const iconSetting = data.find(icon => icon._id === 'Assets_favicon_512');
try {
- await serverInfoUpdate(serverInfo, iconSetting);
+ await serverInfoUpdate(serverInfo, iconSetting as ISettingsIcon);
} catch {
// Server not found
}
- await db.action(async () => {
+ await db.write(async () => {
const settingsCollection = db.get('settings');
const allSettingsRecords = await settingsCollection.query(Q.where('id', Q.oneOf(filteredSettingsIds))).fetch();
// filter settings
- let settingsToCreate = filteredSettings.filter(i1 => !allSettingsRecords.find(i2 => i1._id === i2.id));
- let settingsToUpdate = allSettingsRecords.filter(i1 => filteredSettings.find(i2 => i1.id === i2._id));
-
+ const settingsToCreate = filteredSettings.filter(i1 => !allSettingsRecords.find(i2 => i1._id === i2.id));
+ const settingsToUpdate = allSettingsRecords.filter(i1 => filteredSettings.find(i2 => i1.id === i2._id));
// Create
- settingsToCreate = settingsToCreate.map(setting =>
+ const settingsToCreateMapped = settingsToCreate.map(setting =>
settingsCollection.prepareCreate(
- protectedFunction(s => {
+ protectedFunction((s: any) => {
s._raw = sanitizedRaw({ id: setting._id }, settingsCollection.schema);
Object.assign(s, setting);
})
@@ -185,16 +188,16 @@ export default async function () {
);
// Update
- settingsToUpdate = settingsToUpdate.map(setting => {
+ const settingsToUpdateMapped = settingsToUpdate.map(setting => {
const newSetting = filteredSettings.find(s => s._id === setting.id);
return setting.prepareUpdate(
- protectedFunction(s => {
+ protectedFunction((s: any) => {
Object.assign(s, newSetting);
})
);
});
- const allRecords = [...settingsToCreate, ...settingsToUpdate];
+ const allRecords = [...settingsToCreateMapped, ...settingsToUpdateMapped];
try {
await db.batch(...allRecords);
diff --git a/app/lib/methods/getSingleMessage.js b/app/lib/methods/getSingleMessage.ts
similarity index 86%
rename from app/lib/methods/getSingleMessage.js
rename to app/lib/methods/getSingleMessage.ts
index 4c9b32a68..4d395a0db 100644
--- a/app/lib/methods/getSingleMessage.js
+++ b/app/lib/methods/getSingleMessage.ts
@@ -1,6 +1,6 @@
import RocketChat from '../rocketchat';
-const getSingleMessage = messageId =>
+const getSingleMessage = (messageId: string) =>
new Promise(async (resolve, reject) => {
try {
const result = await RocketChat.getSingleMessage(messageId);
diff --git a/app/lib/methods/getSlashCommands.js b/app/lib/methods/getSlashCommands.js
deleted file mode 100644
index 3b65c40eb..000000000
--- a/app/lib/methods/getSlashCommands.js
+++ /dev/null
@@ -1,71 +0,0 @@
-import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
-
-import database from '../database';
-import log from '../../utils/log';
-import protectedFunction from './helpers/protectedFunction';
-
-export default function () {
- const db = database.active;
- return new Promise(async resolve => {
- try {
- // RC 0.60.2
- const result = await this.sdk.get('commands.list');
-
- if (!result.success) {
- console.log(result);
- return resolve();
- }
-
- const { commands } = result;
-
- if (commands && commands.length) {
- await db.action(async () => {
- const slashCommandsCollection = db.get('slash_commands');
- const allSlashCommandsRecords = await slashCommandsCollection.query().fetch();
-
- // filter slash commands
- let slashCommandsToCreate = commands.filter(i1 => !allSlashCommandsRecords.find(i2 => i1.command === i2.id));
- let slashCommandsToUpdate = allSlashCommandsRecords.filter(i1 => commands.find(i2 => i1.id === i2.command));
- let slashCommandsToDelete = allSlashCommandsRecords.filter(
- i1 => !slashCommandsToCreate.find(i2 => i2.command === i1.id) && !slashCommandsToUpdate.find(i2 => i2.id === i1.id)
- );
-
- // Create
- slashCommandsToCreate = slashCommandsToCreate.map(command =>
- slashCommandsCollection.prepareCreate(
- protectedFunction(s => {
- s._raw = sanitizedRaw({ id: command.command }, slashCommandsCollection.schema);
- Object.assign(s, command);
- })
- )
- );
-
- // Update
- slashCommandsToUpdate = slashCommandsToUpdate.map(command => {
- const newCommand = commands.find(s => s.command === command.id);
- return command.prepareUpdate(
- protectedFunction(s => {
- Object.assign(s, newCommand);
- })
- );
- });
-
- // Delete
- slashCommandsToDelete = slashCommandsToDelete.map(command => command.prepareDestroyPermanently());
-
- const allRecords = [...slashCommandsToCreate, ...slashCommandsToUpdate, ...slashCommandsToDelete];
-
- try {
- await db.batch(...allRecords);
- } catch (e) {
- log(e);
- }
- return allRecords.length;
- });
- }
- } catch (e) {
- log(e);
- return resolve();
- }
- });
-}
diff --git a/app/lib/methods/getSlashCommands.ts b/app/lib/methods/getSlashCommands.ts
new file mode 100644
index 000000000..ed9f37783
--- /dev/null
+++ b/app/lib/methods/getSlashCommands.ts
@@ -0,0 +1,78 @@
+import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
+
+import database from '../database';
+import log from '../../utils/log';
+import protectedFunction from './helpers/protectedFunction';
+import { ISlashCommandResult, TSlashCommandModel } from '../../definitions';
+import sdk from '../rocketchat/services/sdk';
+
+export default function getSlashCommands() {
+ const db = database.active;
+ return new Promise(async resolve => {
+ try {
+ // RC 0.60.2
+ // @ts-ignore
+ const result = await sdk.get('commands.list');
+
+ if (!result.success) {
+ return resolve();
+ }
+ // @ts-ignore
+ const { commands } = result;
+ if (commands && commands.length) {
+ await db.write(async () => {
+ const slashCommandsCollection = db.get('slash_commands');
+ const allSlashCommandsRecords = await slashCommandsCollection.query().fetch();
+
+ // filter slash commands
+ const filteredSlashCommandsToCreate = commands.filter(
+ (i1: ISlashCommandResult) => !allSlashCommandsRecords.find(i2 => i1.command === i2.id)
+ );
+ const filteredSlashCommandsToUpdate = allSlashCommandsRecords.filter(i1 =>
+ commands.find((i2: ISlashCommandResult) => i1.id === i2.command)
+ );
+ const filteredSlashCommandsToDelete = allSlashCommandsRecords.filter(
+ i1 =>
+ !filteredSlashCommandsToCreate.find((i2: ISlashCommandResult) => i2.command === i1.id) &&
+ !filteredSlashCommandsToUpdate.find(i2 => i2.id === i1.id)
+ );
+
+ // Create
+ const slashCommandsToCreate = filteredSlashCommandsToCreate.map((command: ISlashCommandResult) =>
+ slashCommandsCollection.prepareCreate(
+ protectedFunction((s: TSlashCommandModel) => {
+ s._raw = sanitizedRaw({ id: command.command }, slashCommandsCollection.schema);
+ Object.assign(s, command);
+ })
+ )
+ );
+
+ // Update
+ const slashCommandsToUpdate = filteredSlashCommandsToUpdate.map(command => {
+ const newCommand = commands.find((s: ISlashCommandResult) => s.command === command.id);
+ return command.prepareUpdate(
+ protectedFunction((s: TSlashCommandModel) => {
+ Object.assign(s, newCommand);
+ })
+ );
+ });
+
+ // Delete
+ const slashCommandsToDelete = filteredSlashCommandsToDelete.map(command => command.prepareDestroyPermanently());
+
+ const allRecords = [...slashCommandsToCreate, ...slashCommandsToUpdate, ...slashCommandsToDelete];
+
+ try {
+ await db.batch(...allRecords);
+ } catch (e) {
+ log(e);
+ }
+ return allRecords.length;
+ });
+ }
+ } catch (e) {
+ log(e);
+ return resolve();
+ }
+ });
+}
diff --git a/app/lib/methods/getUsersPresence.js b/app/lib/methods/getUsersPresence.ts
similarity index 55%
rename from app/lib/methods/getUsersPresence.js
rename to app/lib/methods/getUsersPresence.ts
index 07a7b985e..afc885caa 100644
--- a/app/lib/methods/getUsersPresence.js
+++ b/app/lib/methods/getUsersPresence.ts
@@ -1,46 +1,49 @@
import { InteractionManager } from 'react-native';
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
-import { compareServerVersion, methods } from '../utils';
-import reduxStore from '../createStore';
+import { IActiveUsers } from '../../reducers/activeUsers';
+import { compareServerVersion } from '../utils';
+import { store as reduxStore } from '../auxStore';
import { setActiveUsers } from '../../actions/activeUsers';
import { setUser } from '../../actions/login';
import database from '../database';
+import { IRocketChat, IUser } from '../../definitions';
+import sdk from '../rocketchat/services/sdk';
-export function subscribeUsersPresence() {
- const serverVersion = reduxStore.getState().server.version;
+export function subscribeUsersPresence(this: IRocketChat) {
+ const serverVersion = reduxStore.getState().server.version as string;
// if server is lower than 1.1.0
- if (compareServerVersion(serverVersion, '1.1.0', methods.lowerThan)) {
+ if (compareServerVersion(serverVersion, 'lowerThan', '1.1.0')) {
if (this.activeUsersSubTimeout) {
clearTimeout(this.activeUsersSubTimeout);
this.activeUsersSubTimeout = false;
}
this.activeUsersSubTimeout = setTimeout(() => {
- this.sdk.subscribe('activeUsers');
+ sdk.subscribe('activeUsers');
}, 5000);
- } else {
- this.sdk.subscribe('stream-notify-logged', 'user-status');
+ } else if (compareServerVersion(serverVersion, 'lowerThan', '4.1.0')) {
+ sdk.subscribe('stream-notify-logged', 'user-status');
}
// RC 0.49.1
- this.sdk.subscribe('stream-notify-logged', 'updateAvatar');
+ sdk.subscribe('stream-notify-logged', 'updateAvatar');
// RC 0.58.0
- this.sdk.subscribe('stream-notify-logged', 'Users:NameChanged');
+ sdk.subscribe('stream-notify-logged', 'Users:NameChanged');
}
-let ids = [];
+let ids: string[] = [];
export default async function getUsersPresence() {
- const serverVersion = reduxStore.getState().server.version;
+ const serverVersion = reduxStore.getState().server.version as string;
const { user: loggedUser } = reduxStore.getState().login;
// if server is greather than or equal 1.1.0
- if (compareServerVersion(serverVersion, '1.1.0', methods.greaterThanOrEqualTo)) {
+ if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '1.1.0')) {
let params = {};
// if server is greather than or equal 3.0.0
- if (compareServerVersion(serverVersion, '3.0.0', methods.greaterThanOrEqualTo)) {
+ if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '3.0.0')) {
// if not have any id
if (!ids.length) {
return;
@@ -51,12 +54,17 @@ export default async function getUsersPresence() {
try {
// RC 1.1.0
- const result = await this.sdk.get('users.presence', params);
+ const result = (await sdk.get('users.presence' as any, params as any)) as any;
+
+ if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '4.1.0')) {
+ sdk.subscribeRaw('stream-user-presence', ['', { added: ids }]);
+ }
+
if (result.success) {
const { users } = result;
- const activeUsers = ids.reduce((ret, id) => {
- const user = users.find(u => u._id === id) ?? { _id: id, status: 'offline' };
+ const activeUsers = ids.reduce((ret: IActiveUsers, id) => {
+ const user = users.find((u: IUser) => u._id === id) ?? { _id: id, status: 'offline' };
const { _id, status, statusText } = user;
if (loggedUser && loggedUser.id === _id) {
@@ -73,17 +81,17 @@ export default async function getUsersPresence() {
const db = database.active;
const userCollection = db.get('users');
- users.forEach(async user => {
+ users.forEach(async (user: IUser) => {
try {
const userRecord = await userCollection.find(user._id);
- await db.action(async () => {
+ await db.write(async () => {
await userRecord.update(u => {
Object.assign(u, user);
});
});
} catch (e) {
// User not found
- await db.action(async () => {
+ await db.write(async () => {
await userCollection.create(u => {
u._raw = sanitizedRaw({ id: user._id }, userCollection.schema);
Object.assign(u, user);
@@ -98,15 +106,11 @@ export default async function getUsersPresence() {
}
}
-let usersTimer = null;
-export function getUserPresence(uid) {
- const auth = reduxStore.getState().login.isAuthenticated;
-
+let usersTimer: number | null = null;
+export function getUserPresence(uid: string) {
if (!usersTimer) {
usersTimer = setTimeout(() => {
- if (auth && ids.length) {
- getUsersPresence.call(this);
- }
+ getUsersPresence();
usersTimer = null;
}, 2000);
}
diff --git a/app/lib/methods/helpers/buildMessage.js b/app/lib/methods/helpers/buildMessage.ts
similarity index 66%
rename from app/lib/methods/helpers/buildMessage.js
rename to app/lib/methods/helpers/buildMessage.ts
index e0c09e4a3..44c30ffa2 100644
--- a/app/lib/methods/helpers/buildMessage.js
+++ b/app/lib/methods/helpers/buildMessage.ts
@@ -1,7 +1,8 @@
+import { IMessage } from '../../../definitions';
import messagesStatus from '../../../constants/messagesStatus';
import normalizeMessage from './normalizeMessage';
-export default message => {
+export default (message: IMessage): IMessage => {
message.status = messagesStatus.SENT;
return normalizeMessage(message);
};
diff --git a/app/lib/methods/helpers/getFileUrlFromMessage.js b/app/lib/methods/helpers/getFileUrlFromMessage.ts
similarity index 80%
rename from app/lib/methods/helpers/getFileUrlFromMessage.js
rename to app/lib/methods/helpers/getFileUrlFromMessage.ts
index c5a34ba04..ea3fb64ff 100644
--- a/app/lib/methods/helpers/getFileUrlFromMessage.js
+++ b/app/lib/methods/helpers/getFileUrlFromMessage.ts
@@ -1,4 +1,4 @@
-export default function (message) {
+export default function (message: { type: string; url: string }) {
if (/image/.test(message.type)) {
return { image_url: message.url };
}
diff --git a/app/lib/methods/helpers/mergeSubscriptionsRooms.js b/app/lib/methods/helpers/mergeSubscriptionsRooms.js
index 95a81001c..e4541a426 100644
--- a/app/lib/methods/helpers/mergeSubscriptionsRooms.js
+++ b/app/lib/methods/helpers/mergeSubscriptionsRooms.js
@@ -1,8 +1,8 @@
import EJSON from 'ejson';
import { Encryption } from '../../encryption';
-import reduxStore from '../../createStore';
-import { compareServerVersion, methods } from '../../utils';
+import { store as reduxStore } from '../../auxStore';
+import { compareServerVersion } from '../../utils';
import findSubscriptionsRooms from './findSubscriptionsRooms';
import normalizeMessage from './normalizeMessage';
// TODO: delete and update
@@ -28,7 +28,7 @@ export const merge = (subscription, room) => {
subscription.usernames = room.usernames;
subscription.uids = room.uids;
}
- if (compareServerVersion(serverVersion, '3.7.0', methods.lowerThan)) {
+ if (compareServerVersion(serverVersion, 'lowerThan', '3.7.0')) {
const updatedAt = room?._updatedAt ? new Date(room._updatedAt) : null;
const lastMessageTs = subscription?.lastMessage?.ts ? new Date(subscription.lastMessage.ts) : null;
subscription.roomUpdatedAt = Math.max(updatedAt, lastMessageTs);
diff --git a/app/lib/methods/helpers/parseQuery.js b/app/lib/methods/helpers/parseQuery.js
deleted file mode 100644
index 30f1bb3c6..000000000
--- a/app/lib/methods/helpers/parseQuery.js
+++ /dev/null
@@ -1,7 +0,0 @@
-export default function (query) {
- return (/^[?#]/.test(query) ? query.slice(1) : query).split('&').reduce((params, param) => {
- const [key, value] = param.split('=');
- params[key] = value ? decodeURIComponent(value.replace(/\+/g, ' ')) : '';
- return params;
- }, {});
-}
diff --git a/app/lib/methods/helpers/parseQuery.ts b/app/lib/methods/helpers/parseQuery.ts
new file mode 100644
index 000000000..c6e2bb5fd
--- /dev/null
+++ b/app/lib/methods/helpers/parseQuery.ts
@@ -0,0 +1,18 @@
+/**
+ *
+ * @example
+ * parseQuery("host=open.rocket.chat&path=channel/general/thread/meRK2nfjR99MjLn55")
+ * // the return will be
+ * {
+ * host: "open.rocket.chat",
+ * path: "channel/general/thread/meRK2nfjR99MjLn55"
+ * }
+ */
+
+export default function (query: string) {
+ return (/^[?#]/.test(query) ? query.slice(1) : query).split('&').reduce((params: { [key: string]: string }, param) => {
+ const [key, value] = param.split('=');
+ params[key] = value ? decodeURIComponent(value.replace(/\+/g, ' ')) : '';
+ return params;
+ }, {});
+}
diff --git a/app/lib/methods/helpers/parseUrls.js b/app/lib/methods/helpers/parseUrls.ts
similarity index 75%
rename from app/lib/methods/helpers/parseUrls.js
rename to app/lib/methods/helpers/parseUrls.ts
index 1d3dcff45..7ec2fa5ea 100644
--- a/app/lib/methods/helpers/parseUrls.js
+++ b/app/lib/methods/helpers/parseUrls.ts
@@ -1,8 +1,10 @@
-export default urls =>
+import { IUrl, IUrlFromServer } from '../../../definitions';
+
+export default (urls: IUrlFromServer[]): IUrl[] =>
urls
- .filter(url => url.meta && !url.ignoreParse)
- .map((url, index) => {
- const tmp = {};
+ .filter((url: IUrlFromServer) => url.meta && !url.ignoreParse)
+ .map((url: IUrlFromServer, index) => {
+ const tmp: IUrl = {} as any;
const { meta } = url;
tmp._id = index;
tmp.title = meta.ogTitle || meta.twitterTitle || meta.title || meta.pageTitle || meta.oembedTitle;
diff --git a/app/lib/methods/loadMessagesForRoom.js b/app/lib/methods/loadMessagesForRoom.ts
similarity index 62%
rename from app/lib/methods/loadMessagesForRoom.js
rename to app/lib/methods/loadMessagesForRoom.ts
index 4a11bcc8c..db64a05ca 100644
--- a/app/lib/methods/loadMessagesForRoom.js
+++ b/app/lib/methods/loadMessagesForRoom.ts
@@ -5,32 +5,41 @@ import log from '../../utils/log';
import { getMessageById } from '../database/services/Message';
import { generateLoadMoreId } from '../utils';
import updateMessages from './updateMessages';
+import { IMessage, TMessageModel } from '../../definitions';
+import sdk from '../rocketchat/services/sdk';
+import roomTypeToApiType, { RoomTypes } from '../rocketchat/methods/roomTypeToApiType';
const COUNT = 50;
-async function load({ rid: roomId, latest, t }) {
- let params = { roomId, count: COUNT };
+async function load({ rid: roomId, latest, t }: { rid: string; latest?: string; t: RoomTypes }) {
+ let params = { roomId, count: COUNT } as { roomId: string; count: number; latest?: string };
if (latest) {
params = { ...params, latest: new Date(latest).toISOString() };
}
- const apiType = this.roomTypeToApiType(t);
+ const apiType = roomTypeToApiType(t);
if (!apiType) {
return [];
}
// RC 0.48.0
- const data = await this.sdk.get(`${apiType}.history`, params);
+ // @ts-ignore
+ const data: any = await sdk.get(`${apiType}.history`, params);
if (!data || data.status === 'error') {
return [];
}
return data.messages;
}
-export default function loadMessagesForRoom(args) {
+export default function loadMessagesForRoom(args: {
+ rid: string;
+ t: RoomTypes;
+ latest: string;
+ loaderItem: TMessageModel;
+}): Promise {
return new Promise(async (resolve, reject) => {
try {
- const data = await load.call(this, args);
+ const data = await load(args);
if (data?.length) {
const lastMessage = data[data.length - 1];
const lastMessageRecord = await getMessageById(lastMessage._id);
@@ -46,9 +55,8 @@ export default function loadMessagesForRoom(args) {
}
await updateMessages({ rid: args.rid, update: data, loaderItem: args.loaderItem });
return resolve(data);
- } else {
- return resolve([]);
}
+ return resolve([]);
} catch (e) {
log(e);
reject(e);
diff --git a/app/lib/methods/loadMissedMessages.js b/app/lib/methods/loadMissedMessages.js
deleted file mode 100644
index 7b2e8c978..000000000
--- a/app/lib/methods/loadMissedMessages.js
+++ /dev/null
@@ -1,44 +0,0 @@
-import database from '../database';
-import log from '../../utils/log';
-import updateMessages from './updateMessages';
-
-const getLastUpdate = async rid => {
- try {
- const db = database.active;
- const subsCollection = db.get('subscriptions');
- const sub = await subsCollection.find(rid);
- return sub.lastOpen.toISOString();
- } catch (e) {
- // Do nothing
- }
- return null;
-};
-
-async function load({ rid: roomId, lastOpen }) {
- let lastUpdate;
- if (lastOpen) {
- lastUpdate = new Date(lastOpen).toISOString();
- } else {
- lastUpdate = await getLastUpdate(roomId);
- }
- // RC 0.60.0
- const { result } = await this.sdk.get('chat.syncMessages', { roomId, lastUpdate });
- return result;
-}
-
-export default function loadMissedMessages(args) {
- return new Promise(async (resolve, reject) => {
- try {
- const data = await load.call(this, { rid: args.rid, lastOpen: args.lastOpen });
-
- if (data) {
- const { updated, deleted } = data;
- await updateMessages({ rid: args.rid, update: updated, remove: deleted });
- }
- resolve();
- } catch (e) {
- log(e);
- reject(e);
- }
- });
-}
diff --git a/app/lib/methods/loadMissedMessages.ts b/app/lib/methods/loadMissedMessages.ts
new file mode 100644
index 000000000..e4578a867
--- /dev/null
+++ b/app/lib/methods/loadMissedMessages.ts
@@ -0,0 +1,47 @@
+import { ILastMessage } from '../../definitions';
+import log from '../../utils/log';
+import database from '../database';
+import sdk from '../rocketchat/services/sdk';
+import updateMessages from './updateMessages';
+
+const getLastUpdate = async (rid: string) => {
+ try {
+ const db = database.active;
+ const subsCollection = db.get('subscriptions');
+ const sub = await subsCollection.find(rid);
+ return sub.lastOpen?.toISOString();
+ } catch (e) {
+ // Do nothing
+ }
+ return null;
+};
+
+async function load({ rid: roomId, lastOpen }: { rid: string; lastOpen: string }) {
+ let lastUpdate;
+ if (lastOpen) {
+ lastUpdate = new Date(lastOpen).toISOString();
+ } else {
+ lastUpdate = await getLastUpdate(roomId);
+ }
+ // RC 0.60.0
+ // @ts-ignore // this method dont have type
+ const { result } = await sdk.get('chat.syncMessages', { roomId, lastUpdate });
+ return result;
+}
+
+export default function loadMissedMessages(args: { rid: string; lastOpen: string }): Promise {
+ return new Promise(async (resolve, reject) => {
+ try {
+ const data = await load({ rid: args.rid, lastOpen: args.lastOpen });
+ if (data) {
+ const { updated, deleted }: { updated: ILastMessage[]; deleted: ILastMessage[] } = data;
+ // @ts-ignore // TODO: remove loaderItem obligatoriness
+ await updateMessages({ rid: args.rid, update: updated, remove: deleted });
+ }
+ resolve();
+ } catch (e) {
+ log(e);
+ reject(e);
+ }
+ });
+}
diff --git a/app/lib/methods/loadNextMessages.js b/app/lib/methods/loadNextMessages.ts
similarity index 74%
rename from app/lib/methods/loadNextMessages.js
rename to app/lib/methods/loadNextMessages.ts
index 3fe728512..74f91d1f2 100644
--- a/app/lib/methods/loadNextMessages.js
+++ b/app/lib/methods/loadNextMessages.ts
@@ -7,13 +7,22 @@ import { getMessageById } from '../database/services/Message';
import { MESSAGE_TYPE_LOAD_NEXT_CHUNK } from '../../constants/messageTypeLoad';
import { generateLoadMoreId } from '../utils';
import updateMessages from './updateMessages';
+import { IMessage, TMessageModel } from '../../definitions';
+import RocketChat from '../rocketchat';
const COUNT = 50;
-export default function loadNextMessages(args) {
+interface ILoadNextMessages {
+ rid: string;
+ ts: string;
+ tmid: string;
+ loaderItem: TMessageModel;
+}
+
+export default function loadNextMessages(args: ILoadNextMessages): Promise {
return new Promise(async (resolve, reject) => {
try {
- const data = await this.methodCallWrapper('loadNextMessages', args.rid, args.ts, COUNT);
+ const data = await RocketChat.methodCallWrapper('loadNextMessages', args.rid, args.ts, COUNT);
let messages = EJSON.fromJSONValue(data?.messages);
messages = orderBy(messages, 'ts');
if (messages?.length) {
@@ -31,9 +40,8 @@ export default function loadNextMessages(args) {
}
await updateMessages({ rid: args.rid, update: messages, loaderItem: args.loaderItem });
return resolve(messages);
- } else {
- return resolve([]);
}
+ return resolve([]);
} catch (e) {
log(e);
reject(e);
diff --git a/app/lib/methods/logout.js b/app/lib/methods/logout.ts
similarity index 72%
rename from app/lib/methods/logout.js
rename to app/lib/methods/logout.ts
index c108a9965..fefd1769b 100644
--- a/app/lib/methods/logout.js
+++ b/app/lib/methods/logout.ts
@@ -1,5 +1,6 @@
import * as FileSystem from 'expo-file-system';
import { Rocketchat as RocketchatClient } from '@rocket.chat/sdk';
+import Model from '@nozbe/watermelondb/Model';
import { getDeviceToken } from '../../notifications/push';
import { extractHostname } from '../../utils/server';
@@ -7,34 +8,38 @@ import { BASIC_AUTH_KEY } from '../../utils/fetch';
import database, { getDatabase } from '../database';
import RocketChat from '../rocketchat';
import { useSsl } from '../../utils/url';
+import log from '../../utils/log';
import { E2E_PRIVATE_KEY, E2E_PUBLIC_KEY, E2E_RANDOM_PASSWORD_KEY } from '../encryption/constants';
import UserPreferences from '../userPreferences';
+import { ICertificate, IRocketChat } from '../../definitions';
-async function removeServerKeys({ server, userId }) {
+async function removeServerKeys({ server, userId }: { server: string; userId: string | null }) {
await UserPreferences.removeItem(`${RocketChat.TOKEN_KEY}-${server}`);
- await UserPreferences.removeItem(`${RocketChat.TOKEN_KEY}-${userId}`);
+ if (userId) {
+ await UserPreferences.removeItem(`${RocketChat.TOKEN_KEY}-${userId}`);
+ }
await UserPreferences.removeItem(`${BASIC_AUTH_KEY}-${server}`);
await UserPreferences.removeItem(`${server}-${E2E_PUBLIC_KEY}`);
await UserPreferences.removeItem(`${server}-${E2E_PRIVATE_KEY}`);
await UserPreferences.removeItem(`${server}-${E2E_RANDOM_PASSWORD_KEY}`);
}
-async function removeSharedCredentials({ server }) {
+async function removeSharedCredentials({ server }: { server: string }) {
// clear certificate for server - SSL Pinning
try {
- const certificate = await UserPreferences.getMapAsync(extractHostname(server));
- if (certificate && certificate.path) {
+ const certificate = (await UserPreferences.getMapAsync(extractHostname(server))) as ICertificate | null;
+ if (certificate?.path) {
await UserPreferences.removeItem(extractHostname(server));
await FileSystem.deleteAsync(certificate.path);
}
} catch (e) {
- console.log('removeSharedCredentials', e);
+ log(e);
}
}
-async function removeServerData({ server }) {
+async function removeServerData({ server }: { server: string }) {
try {
- const batch = [];
+ const batch: Model[] = [];
const serversDB = database.servers;
const userId = await UserPreferences.getStringAsync(`${RocketChat.TOKEN_KEY}-${server}`);
@@ -47,11 +52,11 @@ async function removeServerData({ server }) {
const serverRecord = await serverCollection.find(server);
batch.push(serverRecord.prepareDestroyPermanently());
- await serversDB.action(() => serversDB.batch(...batch));
+ await serversDB.write(() => serversDB.batch(...batch));
await removeSharedCredentials({ server });
- await removeServerKeys({ server });
+ await removeServerKeys({ server, userId });
} catch (e) {
- console.log('removeServerData', e);
+ log(e);
}
}
@@ -59,16 +64,16 @@ async function removeCurrentServer() {
await UserPreferences.removeItem(RocketChat.CURRENT_SERVER);
}
-async function removeServerDatabase({ server }) {
+async function removeServerDatabase({ server }: { server: string }) {
try {
const db = getDatabase(server);
- await db.action(() => db.unsafeResetDatabase());
+ await db.write(() => db.unsafeResetDatabase());
} catch (e) {
- console.log(e);
+ log(e);
}
}
-export async function removeServer({ server }) {
+export async function removeServer({ server }: { server: string }): Promise {
try {
const userId = await UserPreferences.getStringAsync(`${RocketChat.TOKEN_KEY}-${server}`);
if (userId) {
@@ -88,11 +93,11 @@ export async function removeServer({ server }) {
await removeServerData({ server });
await removeServerDatabase({ server });
} catch (e) {
- console.log('removeServer', e);
+ log(e);
}
}
-export default async function logout({ server }) {
+export default async function logout(this: IRocketChat, { server }: { server: string }): Promise {
if (this.roomsSub) {
this.roomsSub.stop();
this.roomsSub = null;
@@ -106,14 +111,14 @@ export default async function logout({ server }) {
try {
await this.removePushToken();
} catch (e) {
- console.log('removePushToken', e);
+ log(e);
}
try {
// RC 0.60.0
await this.sdk.logout();
} catch (e) {
- console.log('logout', e);
+ log(e);
}
if (this.sdk) {
diff --git a/app/lib/methods/readMessages.js b/app/lib/methods/readMessages.ts
similarity index 61%
rename from app/lib/methods/readMessages.js
rename to app/lib/methods/readMessages.ts
index 7d2d58de1..344ce9ebb 100644
--- a/app/lib/methods/readMessages.js
+++ b/app/lib/methods/readMessages.ts
@@ -1,7 +1,9 @@
import database from '../database';
import log from '../../utils/log';
+import { TSubscriptionModel } from '../../definitions';
+import { IRocketChat } from '../../definitions/IRocketChat';
-export default async function readMessages(rid, ls, updateLastOpen = false) {
+export default async function readMessages(this: IRocketChat, rid: string, ls: Date, updateLastOpen = false): Promise {
try {
const db = database.active;
const subscription = await db.get('subscriptions').find(rid);
@@ -9,9 +11,9 @@ export default async function readMessages(rid, ls, updateLastOpen = false) {
// RC 0.61.0
await this.sdk.post('subscriptions.read', { rid });
- await db.action(async () => {
+ await db.write(async () => {
try {
- await subscription.update(s => {
+ await subscription.update((s: TSubscriptionModel) => {
s.open = true;
s.alert = false;
s.unread = 0;
diff --git a/app/lib/methods/sendFileMessage.js b/app/lib/methods/sendFileMessage.ts
similarity index 71%
rename from app/lib/methods/sendFileMessage.js
rename to app/lib/methods/sendFileMessage.ts
index 35d2815ca..cc11606b6 100644
--- a/app/lib/methods/sendFileMessage.js
+++ b/app/lib/methods/sendFileMessage.ts
@@ -1,18 +1,22 @@
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
import { settings as RocketChatSettings } from '@rocket.chat/sdk';
+import { FetchBlobResponse, StatefulPromise } from 'rn-fetch-blob';
+import isEmpty from 'lodash/isEmpty';
import FileUpload from '../../utils/fileUpload';
import database from '../database';
import log from '../../utils/log';
+import { IUpload, IUser, TUploadModel } from '../../definitions';
+import { IFileUpload } from '../../utils/fileUpload/interfaces';
-const uploadQueue = {};
+const uploadQueue: { [index: string]: StatefulPromise } = {};
-export function isUploadActive(path) {
+export function isUploadActive(path: string): boolean {
return !!uploadQueue[path];
}
-export async function cancelUpload(item) {
- if (uploadQueue[item.path]) {
+export async function cancelUpload(item: TUploadModel): Promise {
+ if (!isEmpty(uploadQueue[item.path])) {
try {
await uploadQueue[item.path].cancel();
} catch {
@@ -20,7 +24,7 @@ export async function cancelUpload(item) {
}
try {
const db = database.active;
- await db.action(async () => {
+ await db.write(async () => {
await item.destroyPermanently();
});
} catch (e) {
@@ -30,7 +34,13 @@ export async function cancelUpload(item) {
}
}
-export function sendFileMessage(rid, fileInfo, tmid, server, user) {
+export function sendFileMessage(
+ rid: string,
+ fileInfo: IUpload,
+ tmid: string,
+ server: string,
+ user: IUser
+): Promise {
return new Promise(async (resolve, reject) => {
try {
const { id, token } = user;
@@ -41,16 +51,18 @@ export function sendFileMessage(rid, fileInfo, tmid, server, user) {
const db = database.active;
const uploadsCollection = db.get('uploads');
- let uploadRecord;
+ let uploadRecord: TUploadModel;
try {
uploadRecord = await uploadsCollection.find(fileInfo.path);
} catch (error) {
try {
- await db.action(async () => {
+ await db.write(async () => {
uploadRecord = await uploadsCollection.create(u => {
u._raw = sanitizedRaw({ id: fileInfo.path }, uploadsCollection.schema);
Object.assign(u, fileInfo);
- u.subscription.id = rid;
+ if (u.subscription) {
+ u.subscription.id = rid;
+ }
});
});
} catch (e) {
@@ -58,7 +70,7 @@ export function sendFileMessage(rid, fileInfo, tmid, server, user) {
}
}
- const formData = [];
+ const formData: IFileUpload[] = [];
formData.push({
name: 'file',
type: fileInfo.type,
@@ -89,9 +101,9 @@ export function sendFileMessage(rid, fileInfo, tmid, server, user) {
uploadQueue[fileInfo.path] = FileUpload.fetch('POST', uploadUrl, headers, formData);
- uploadQueue[fileInfo.path].uploadProgress(async (loaded, total) => {
+ uploadQueue[fileInfo.path].uploadProgress(async (loaded: number, total: number) => {
try {
- await db.action(async () => {
+ await db.write(async () => {
await uploadRecord.update(u => {
u.progress = Math.floor((loaded / total) * 100);
});
@@ -105,7 +117,7 @@ export function sendFileMessage(rid, fileInfo, tmid, server, user) {
if (response.respInfo.status >= 200 && response.respInfo.status < 400) {
// If response is all good...
try {
- await db.action(async () => {
+ await db.write(async () => {
await uploadRecord.destroyPermanently();
});
resolve(response);
@@ -114,7 +126,7 @@ export function sendFileMessage(rid, fileInfo, tmid, server, user) {
}
} else {
try {
- await db.action(async () => {
+ await db.write(async () => {
await uploadRecord.update(u => {
u.error = true;
});
@@ -132,7 +144,7 @@ export function sendFileMessage(rid, fileInfo, tmid, server, user) {
uploadQueue[fileInfo.path].catch(async error => {
try {
- await db.action(async () => {
+ await db.write(async () => {
await uploadRecord.update(u => {
u.error = true;
});
diff --git a/app/lib/methods/subscriptions/room.js b/app/lib/methods/subscriptions/room.js
index 33ebb7df5..3aa0a206d 100644
--- a/app/lib/methods/subscriptions/room.js
+++ b/app/lib/methods/subscriptions/room.js
@@ -6,6 +6,9 @@ import log from '../../../utils/log';
import protectedFunction from '../helpers/protectedFunction';
import buildMessage from '../helpers/buildMessage';
import database from '../../database';
+import { getMessageById } from '../../database/services/Message';
+import { getThreadById } from '../../database/services/Thread';
+import { getThreadMessageById } from '../../database/services/ThreadMessage';
import reduxStore from '../../createStore';
import { addUserTyping, clearUserTyping, removeUserTyping } from '../../../actions/usersTyping';
import debounce from '../../../utils/debounce';
@@ -170,75 +173,81 @@ export default class RoomSubscription {
// Create or update message
try {
- const messageRecord = await msgCollection.find(message._id);
- if (!messageRecord._hasPendingUpdate) {
- const update = messageRecord.prepareUpdate(
+ let operation = null;
+ const messageRecord = await getMessageById(message._id);
+ if (messageRecord) {
+ operation = messageRecord.prepareUpdate(
protectedFunction(m => {
Object.assign(m, message);
})
);
- this._messagesBatch[message._id] = update;
+ } else {
+ operation = msgCollection.prepareCreate(
+ protectedFunction(m => {
+ m._raw = sanitizedRaw({ id: message._id }, msgCollection.schema);
+ m.subscription.id = this.rid;
+ Object.assign(m, message);
+ })
+ );
}
- } catch {
- const create = msgCollection.prepareCreate(
- protectedFunction(m => {
- m._raw = sanitizedRaw({ id: message._id }, msgCollection.schema);
- m.subscription.id = this.rid;
- Object.assign(m, message);
- })
- );
- this._messagesBatch[message._id] = create;
+ this._messagesBatch[message._id] = operation;
+ } catch (e) {
+ log(e);
}
// Create or update thread
if (message.tlm) {
try {
- const threadRecord = await threadsCollection.find(message._id);
- if (!threadRecord._hasPendingUpdate) {
- const updateThread = threadRecord.prepareUpdate(
+ let operation = null;
+ const threadRecord = await getThreadById(message._id);
+ if (threadRecord) {
+ operation = threadRecord.prepareUpdate(
protectedFunction(t => {
Object.assign(t, message);
})
);
- this._threadsBatch[message._id] = updateThread;
+ } else {
+ operation = threadsCollection.prepareCreate(
+ protectedFunction(t => {
+ t._raw = sanitizedRaw({ id: message._id }, threadsCollection.schema);
+ t.subscription.id = this.rid;
+ Object.assign(t, message);
+ })
+ );
}
- } catch {
- const createThread = threadsCollection.prepareCreate(
- protectedFunction(t => {
- t._raw = sanitizedRaw({ id: message._id }, threadsCollection.schema);
- t.subscription.id = this.rid;
- Object.assign(t, message);
- })
- );
- this._threadsBatch[message._id] = createThread;
+ this._threadsBatch[message._id] = operation;
+ } catch (e) {
+ log(e);
}
}
// Create or update thread message
if (message.tmid) {
try {
- const threadMessageRecord = await threadMessagesCollection.find(message._id);
- if (!threadMessageRecord._hasPendingUpdate) {
- const updateThreadMessage = threadMessageRecord.prepareUpdate(
+ let operation = null;
+ const threadMessageRecord = await getThreadMessageById(message._id);
+ if (threadMessageRecord) {
+ operation = threadMessageRecord.prepareUpdate(
protectedFunction(tm => {
Object.assign(tm, message);
tm.rid = message.tmid;
delete tm.tmid;
})
);
- this._threadMessagesBatch[message._id] = updateThreadMessage;
+ } else {
+ operation = threadMessagesCollection.prepareCreate(
+ protectedFunction(tm => {
+ tm._raw = sanitizedRaw({ id: message._id }, threadMessagesCollection.schema);
+ Object.assign(tm, message);
+ tm.subscription.id = this.rid;
+ tm.rid = message.tmid;
+ delete tm.tmid;
+ })
+ );
}
- } catch {
- const createThreadMessage = threadMessagesCollection.prepareCreate(
- protectedFunction(tm => {
- tm._raw = sanitizedRaw({ id: message._id }, threadMessagesCollection.schema);
- Object.assign(tm, message);
- tm.subscription.id = this.rid;
- tm.rid = message.tmid;
- delete tm.tmid;
- })
- );
- this._threadMessagesBatch[message._id] = createThreadMessage;
+ this._threadMessagesBatch[message._id] = operation;
+ } catch (e) {
+ log(e);
}
}
diff --git a/app/lib/methods/subscriptions/rooms.js b/app/lib/methods/subscriptions/rooms.js
index c2fc9fcdf..9e1cf8519 100644
--- a/app/lib/methods/subscriptions/rooms.js
+++ b/app/lib/methods/subscriptions/rooms.js
@@ -1,5 +1,6 @@
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
import { InteractionManager } from 'react-native';
+import EJSON from 'ejson';
import database from '../../database';
import { merge } from '../helpers/mergeSubscriptionsRooms';
@@ -7,7 +8,7 @@ import protectedFunction from '../helpers/protectedFunction';
import messagesStatus from '../../../constants/messagesStatus';
import log from '../../../utils/log';
import random from '../../../utils/random';
-import store from '../../createStore';
+import { store } from '../../auxStore';
import { handlePayloadUserInteraction } from '../actions';
import buildMessage from '../helpers/buildMessage';
import RocketChat from '../../rocketchat';
@@ -17,6 +18,7 @@ import { setUser } from '../../../actions/login';
import { INAPP_NOTIFICATION_EMITTER } from '../../../containers/InAppNotification';
import { Encryption } from '../../encryption';
import { E2E_MESSAGE_TYPE } from '../../encryption/constants';
+import updateMessages from '../updateMessages';
const removeListener = listener => listener.stop();
@@ -309,32 +311,18 @@ export default function subscribeRooms() {
}
}
if (/message/.test(ev)) {
- const [args] = ddpMessage.fields.args;
- const _id = random(17);
- const message = {
- _id,
- rid: args.rid,
- msg: args.msg,
- blocks: args.blocks,
- ts: new Date(),
- _updatedAt: new Date(),
- status: messagesStatus.SENT,
- u: {
- _id,
- username: 'rocket.cat'
- }
- };
try {
- const msgCollection = db.get('messages');
- await db.action(async () => {
- await msgCollection.create(
- protectedFunction(m => {
- m._raw = sanitizedRaw({ id: message._id }, msgCollection.schema);
- m.subscription.id = args.rid;
- Object.assign(m, message);
- })
- );
- });
+ const [args] = ddpMessage.fields.args;
+ const _id = random(17);
+ const message = {
+ u: {
+ _id,
+ username: 'rocket.cat',
+ name: 'Rocket Cat'
+ },
+ ...buildMessage(EJSON.fromJSONValue(args))
+ };
+ await updateMessages({ rid: args.rid, update: [message] });
} catch (e) {
log(e);
}
diff --git a/app/lib/methods/updateMessages.js b/app/lib/methods/updateMessages.js
deleted file mode 100644
index c39066b20..000000000
--- a/app/lib/methods/updateMessages.js
+++ /dev/null
@@ -1,174 +0,0 @@
-import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
-import { Q } from '@nozbe/watermelondb';
-
-import log from '../../utils/log';
-import database from '../database';
-import { Encryption } from '../encryption';
-import { MESSAGE_TYPE_ANY_LOAD } from '../../constants/messageTypeLoad';
-import { generateLoadMoreId } from '../utils';
-import protectedFunction from './helpers/protectedFunction';
-import buildMessage from './helpers/buildMessage';
-
-export default function updateMessages({ rid, update = [], remove = [], loaderItem }) {
- try {
- if (!((update && update.length) || (remove && remove.length))) {
- return;
- }
- const db = database.active;
- return db.action(async () => {
- // Decrypt these messages
- update = await Encryption.decryptMessages(update);
- const subCollection = db.get('subscriptions');
- let sub;
- try {
- sub = await subCollection.find(rid);
- } catch (error) {
- sub = { id: rid };
- console.log('updateMessages: subscription not found');
- }
-
- const messagesIds = [...update.map(m => m._id), ...remove.map(m => m._id)];
- const msgCollection = db.get('messages');
- const threadCollection = db.get('threads');
- const threadMessagesCollection = db.get('thread_messages');
- const allMessagesRecords = await msgCollection
- .query(Q.where('rid', rid), Q.or(Q.where('id', Q.oneOf(messagesIds)), Q.where('t', Q.oneOf(MESSAGE_TYPE_ANY_LOAD))))
- .fetch();
- const allThreadsRecords = await threadCollection.query(Q.where('rid', rid), Q.where('id', Q.oneOf(messagesIds))).fetch();
- const allThreadMessagesRecords = await threadMessagesCollection
- .query(Q.where('subscription_id', rid), Q.where('id', Q.oneOf(messagesIds)))
- .fetch();
-
- update = update.map(m => buildMessage(m));
-
- // filter messages
- let msgsToCreate = update.filter(i1 => !allMessagesRecords.find(i2 => i1._id === i2.id));
- let msgsToUpdate = allMessagesRecords.filter(i1 => update.find(i2 => i1.id === i2._id));
-
- // filter threads
- const allThreads = update.filter(m => m.tlm);
- let threadsToCreate = allThreads.filter(i1 => !allThreadsRecords.find(i2 => i1._id === i2.id));
- let threadsToUpdate = allThreadsRecords.filter(i1 => allThreads.find(i2 => i1.id === i2._id));
-
- // filter thread messages
- const allThreadMessages = update.filter(m => m.tmid);
- let threadMessagesToCreate = allThreadMessages.filter(i1 => !allThreadMessagesRecords.find(i2 => i1._id === i2.id));
- let threadMessagesToUpdate = allThreadMessagesRecords.filter(i1 => allThreadMessages.find(i2 => i1.id === i2._id));
-
- // filter loaders to delete
- let loadersToDelete = allMessagesRecords.filter(i1 => update.find(i2 => i1.id === generateLoadMoreId(i2._id)));
-
- // Create
- msgsToCreate = msgsToCreate.map(message =>
- msgCollection.prepareCreate(
- protectedFunction(m => {
- m._raw = sanitizedRaw({ id: message._id }, msgCollection.schema);
- m.subscription.id = sub.id;
- Object.assign(m, message);
- })
- )
- );
- threadsToCreate = threadsToCreate.map(thread =>
- threadCollection.prepareCreate(
- protectedFunction(t => {
- t._raw = sanitizedRaw({ id: thread._id }, threadCollection.schema);
- t.subscription.id = sub.id;
- Object.assign(t, thread);
- })
- )
- );
- threadMessagesToCreate = threadMessagesToCreate.map(threadMessage =>
- threadMessagesCollection.prepareCreate(
- protectedFunction(tm => {
- tm._raw = sanitizedRaw({ id: threadMessage._id }, threadMessagesCollection.schema);
- Object.assign(tm, threadMessage);
- tm.subscription.id = sub.id;
- tm.rid = threadMessage.tmid;
- delete threadMessage.tmid;
- })
- )
- );
-
- // Update
- msgsToUpdate = msgsToUpdate.map(message => {
- const newMessage = update.find(m => m._id === message.id);
- if (message._hasPendingUpdate) {
- console.log(message);
- return;
- }
- return message.prepareUpdate(
- protectedFunction(m => {
- Object.assign(m, newMessage);
- })
- );
- });
- threadsToUpdate = threadsToUpdate.map(thread => {
- if (thread._hasPendingUpdate) {
- console.log(thread);
- return;
- }
- const newThread = allThreads.find(t => t._id === thread.id);
- return thread.prepareUpdate(
- protectedFunction(t => {
- Object.assign(t, newThread);
- })
- );
- });
- threadMessagesToUpdate = threadMessagesToUpdate.map(threadMessage => {
- if (threadMessage._hasPendingUpdate) {
- console.log(threadMessage);
- return;
- }
- const newThreadMessage = allThreadMessages.find(t => t._id === threadMessage.id);
- return threadMessage.prepareUpdate(
- protectedFunction(tm => {
- Object.assign(tm, newThreadMessage);
- tm.rid = threadMessage.tmid;
- delete threadMessage.tmid;
- })
- );
- });
-
- // Delete
- let msgsToDelete = [];
- let threadsToDelete = [];
- let threadMessagesToDelete = [];
- if (remove && remove.length) {
- msgsToDelete = allMessagesRecords.filter(i1 => remove.find(i2 => i1.id === i2._id));
- msgsToDelete = msgsToDelete.map(m => m.prepareDestroyPermanently());
- threadsToDelete = allThreadsRecords.filter(i1 => remove.find(i2 => i1.id === i2._id));
- threadsToDelete = threadsToDelete.map(t => t.prepareDestroyPermanently());
- threadMessagesToDelete = allThreadMessagesRecords.filter(i1 => remove.find(i2 => i1.id === i2._id));
- threadMessagesToDelete = threadMessagesToDelete.map(tm => tm.prepareDestroyPermanently());
- }
-
- // Delete loaders
- loadersToDelete = loadersToDelete.map(m => m.prepareDestroyPermanently());
- if (loaderItem) {
- loadersToDelete.push(loaderItem.prepareDestroyPermanently());
- }
-
- const allRecords = [
- ...msgsToCreate,
- ...msgsToUpdate,
- ...msgsToDelete,
- ...threadsToCreate,
- ...threadsToUpdate,
- ...threadsToDelete,
- ...threadMessagesToCreate,
- ...threadMessagesToUpdate,
- ...threadMessagesToDelete,
- ...loadersToDelete
- ];
-
- try {
- await db.batch(...allRecords);
- } catch (e) {
- log(e);
- }
- return allRecords.length;
- });
- } catch (e) {
- log(e);
- }
-}
diff --git a/app/lib/methods/updateMessages.ts b/app/lib/methods/updateMessages.ts
new file mode 100644
index 000000000..dd1f535cb
--- /dev/null
+++ b/app/lib/methods/updateMessages.ts
@@ -0,0 +1,187 @@
+import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
+import { Q } from '@nozbe/watermelondb';
+
+import database from '../database';
+import { Encryption } from '../encryption';
+import { MESSAGE_TYPE_ANY_LOAD } from '../../constants/messageTypeLoad';
+import { generateLoadMoreId } from '../utils';
+import protectedFunction from './helpers/protectedFunction';
+import buildMessage from './helpers/buildMessage';
+import { IMessage, TMessageModel, TThreadMessageModel, TThreadModel } from '../../definitions';
+import { getSubscriptionByRoomId } from '../database/services/Subscription';
+
+interface IUpdateMessages {
+ rid: string;
+ update: IMessage[];
+ remove?: IMessage[];
+ loaderItem?: TMessageModel;
+}
+
+export default async function updateMessages({
+ rid,
+ update = [],
+ remove = [],
+ loaderItem
+}: IUpdateMessages): Promise {
+ if (!((update && update.length) || (remove && remove.length))) {
+ return Promise.resolve(0);
+ }
+
+ const sub = await getSubscriptionByRoomId(rid);
+ if (!sub) {
+ throw new Error('updateMessages: subscription not found');
+ }
+
+ const db = database.active;
+ return db.write(async () => {
+ // Decrypt these messages
+ update = await Encryption.decryptMessages(update);
+
+ const messagesIds: string[] = [...update.map(m => m._id), ...remove.map(m => m._id)];
+ const msgCollection = db.get('messages');
+ const threadCollection = db.get('threads');
+ const threadMessagesCollection = db.get('thread_messages');
+ const allMessagesRecords = await msgCollection
+ .query(Q.where('rid', rid), Q.or(Q.where('id', Q.oneOf(messagesIds)), Q.where('t', Q.oneOf(MESSAGE_TYPE_ANY_LOAD))))
+ .fetch();
+ const allThreadsRecords = await threadCollection.query(Q.where('rid', rid), Q.where('id', Q.oneOf(messagesIds))).fetch();
+ const allThreadMessagesRecords = await threadMessagesCollection
+ .query(Q.where('subscription_id', rid), Q.where('id', Q.oneOf(messagesIds)))
+ .fetch();
+
+ update = update.map(m => buildMessage(m));
+
+ // filter loaders to delete
+ let loadersToDelete: TMessageModel[] = allMessagesRecords.filter(i1 =>
+ update.find(i2 => i1.id === generateLoadMoreId(i2._id))
+ );
+
+ // Delete
+ let msgsToDelete: TMessageModel[] = [];
+ let threadsToDelete: TThreadModel[] = [];
+ let threadMessagesToDelete: TThreadMessageModel[] = [];
+ if (remove && remove.length) {
+ msgsToDelete = allMessagesRecords.filter(i1 => remove.find(i2 => i1.id === i2._id));
+ msgsToDelete = msgsToDelete.map(m => m.prepareDestroyPermanently());
+ threadsToDelete = allThreadsRecords.filter(i1 => remove.find(i2 => i1.id === i2._id));
+ threadsToDelete = threadsToDelete.map(t => t.prepareDestroyPermanently());
+ threadMessagesToDelete = allThreadMessagesRecords.filter(i1 => remove.find(i2 => i1.id === i2._id));
+ threadMessagesToDelete = threadMessagesToDelete.map(tm => tm.prepareDestroyPermanently());
+ }
+
+ // Delete loaders
+ loadersToDelete = loadersToDelete.map(m => m.prepareDestroyPermanently());
+ if (loaderItem) {
+ loadersToDelete.push(loaderItem.prepareDestroyPermanently());
+ }
+
+ // filter messages
+ const filteredMsgsToCreate = update.filter(i1 => !allMessagesRecords.find(i2 => i1._id === i2.id));
+ const filteredMsgsToUpdate = allMessagesRecords.filter(i1 => update.find(i2 => i1.id === i2._id));
+
+ // filter threads
+ const allThreads = update.filter(m => m.tlm);
+ const filteredThreadsToCreate = allThreads.filter(i1 => !allThreadsRecords.find(i2 => i1._id === i2.id));
+ const filteredThreadsToUpdate = allThreadsRecords.filter(i1 => allThreads.find(i2 => i1.id === i2._id));
+
+ // filter thread messages
+ const allThreadMessages = update.filter(m => m.tmid);
+ const filteredThreadMessagesToCreate = allThreadMessages.filter(i1 => !allThreadMessagesRecords.find(i2 => i1._id === i2.id));
+ const filteredThreadMessagesToUpdate = allThreadMessagesRecords.filter(i1 => allThreadMessages.find(i2 => i1.id === i2._id));
+
+ // Create
+ const msgsToCreate = filteredMsgsToCreate.map(message =>
+ msgCollection.prepareCreate(
+ protectedFunction((m: TMessageModel) => {
+ m._raw = sanitizedRaw({ id: message._id }, msgCollection.schema);
+ if (m.subscription) {
+ m.subscription.id = sub.id;
+ }
+ Object.assign(m, message);
+ })
+ )
+ );
+ const threadsToCreate = filteredThreadsToCreate.map(thread =>
+ threadCollection.prepareCreate(
+ protectedFunction((t: TThreadModel) => {
+ t._raw = sanitizedRaw({ id: thread._id }, threadCollection.schema);
+ t.subscription.id = sub.id;
+ Object.assign(t, thread);
+ })
+ )
+ );
+ const threadMessagesToCreate = filteredThreadMessagesToCreate.map(threadMessage =>
+ threadMessagesCollection.prepareCreate(
+ protectedFunction((tm: TThreadMessageModel) => {
+ tm._raw = sanitizedRaw({ id: threadMessage._id }, threadMessagesCollection.schema);
+ Object.assign(tm, threadMessage);
+ if (tm.subscription) {
+ tm.subscription.id = sub.id;
+ }
+ if (threadMessage.tmid) {
+ tm.rid = threadMessage.tmid;
+ }
+ delete threadMessage.tmid;
+ })
+ )
+ );
+
+ // Update
+ const msgsToUpdate = filteredMsgsToUpdate.map(message => {
+ const newMessage = update.find(m => m._id === message.id);
+ try {
+ return message.prepareUpdate(
+ protectedFunction((m: TMessageModel) => {
+ Object.assign(m, newMessage);
+ })
+ );
+ } catch {
+ return null;
+ }
+ });
+ const threadsToUpdate = filteredThreadsToUpdate.map(thread => {
+ const newThread = allThreads.find(t => t._id === thread.id);
+ try {
+ return thread.prepareUpdate(
+ protectedFunction((t: TThreadModel) => {
+ Object.assign(t, newThread);
+ })
+ );
+ } catch {
+ return null;
+ }
+ });
+ const threadMessagesToUpdate = filteredThreadMessagesToUpdate.map(threadMessage => {
+ const newThreadMessage = allThreadMessages.find(t => t._id === threadMessage.id);
+ try {
+ return threadMessage.prepareUpdate(
+ protectedFunction((tm: TThreadMessageModel) => {
+ Object.assign(tm, newThreadMessage);
+ if (threadMessage.tmid) {
+ tm.rid = threadMessage.tmid;
+ }
+ delete threadMessage.tmid;
+ })
+ );
+ } catch {
+ return null;
+ }
+ });
+
+ const allRecords = [
+ ...msgsToDelete,
+ ...threadsToDelete,
+ ...threadMessagesToDelete,
+ ...loadersToDelete,
+ ...msgsToCreate,
+ ...msgsToUpdate,
+ ...threadsToCreate,
+ ...threadsToUpdate,
+ ...threadMessagesToCreate,
+ ...threadMessagesToUpdate
+ ];
+
+ await db.batch(...allRecords);
+ return allRecords.length;
+ });
+}
diff --git a/app/lib/rocketchat/index.ts b/app/lib/rocketchat/index.ts
new file mode 100644
index 000000000..0a63687ae
--- /dev/null
+++ b/app/lib/rocketchat/index.ts
@@ -0,0 +1,4 @@
+import RocketChat, { THEME_PREFERENCES_KEY, CRASH_REPORT_KEY, ANALYTICS_EVENTS_KEY } from './rocketchat';
+
+export { THEME_PREFERENCES_KEY, CRASH_REPORT_KEY, ANALYTICS_EVENTS_KEY };
+export default RocketChat;
diff --git a/app/lib/rocketchat/methods/clearCache.ts b/app/lib/rocketchat/methods/clearCache.ts
new file mode 100644
index 000000000..41fc4f62d
--- /dev/null
+++ b/app/lib/rocketchat/methods/clearCache.ts
@@ -0,0 +1,23 @@
+import database from '../../database';
+
+export default async function clearCache({ server }: { server: string }): Promise {
+ try {
+ const serversDB = database.servers;
+ await serversDB.write(async () => {
+ const serverCollection = serversDB.get('servers');
+ const serverRecord = await serverCollection.find(server);
+ await serverRecord.update(s => {
+ s.roomsUpdatedAt = null;
+ });
+ });
+ } catch (e) {
+ // Do nothing
+ }
+
+ try {
+ const db = database.active;
+ await db.write(() => db.unsafeResetDatabase());
+ } catch (e) {
+ // Do nothing
+ }
+}
diff --git a/app/lib/rocketchat/methods/getPermalinkMessage.ts b/app/lib/rocketchat/methods/getPermalinkMessage.ts
new file mode 100644
index 000000000..9bc96bdd8
--- /dev/null
+++ b/app/lib/rocketchat/methods/getPermalinkMessage.ts
@@ -0,0 +1,25 @@
+import log from '../../../utils/log';
+import { TMessageModel, TSubscriptionModel } from '../../../definitions';
+import reduxStore from '../../createStore';
+import getRoom from './getRoom';
+import isGroupChat from './isGroupChat';
+
+type TRoomType = 'p' | 'c' | 'd';
+
+export default async function getPermalinkMessage(message: TMessageModel): Promise {
+ if (!message.subscription) return null;
+ let room: TSubscriptionModel;
+ try {
+ room = await getRoom(message.subscription.id);
+ } catch (e) {
+ log(e);
+ return null;
+ }
+ const { server } = reduxStore.getState().server;
+ const roomType = {
+ p: 'group',
+ c: 'channel',
+ d: 'direct'
+ }[room.t as TRoomType];
+ return `${server}/${roomType}/${isGroupChat(room) ? room.rid : room.name}?msg=${message.id}`;
+}
diff --git a/app/lib/rocketchat/methods/getRoom.ts b/app/lib/rocketchat/methods/getRoom.ts
new file mode 100644
index 000000000..849a8a6c7
--- /dev/null
+++ b/app/lib/rocketchat/methods/getRoom.ts
@@ -0,0 +1,12 @@
+import { TSubscriptionModel } from '../../../definitions';
+import database from '../../database';
+
+export default async function getRoom(rid: string): Promise {
+ try {
+ const db = database.active;
+ const room = await db.get('subscriptions').find(rid);
+ return Promise.resolve(room);
+ } catch (error) {
+ return Promise.reject(new Error('Room not found'));
+ }
+}
diff --git a/app/lib/rocketchat/methods/isGroupChat.ts b/app/lib/rocketchat/methods/isGroupChat.ts
new file mode 100644
index 000000000..558091b60
--- /dev/null
+++ b/app/lib/rocketchat/methods/isGroupChat.ts
@@ -0,0 +1,5 @@
+import { ISubscription, TSubscriptionModel } from '../../../definitions';
+
+export default function isGroupChat(room: ISubscription | TSubscriptionModel): boolean {
+ return ((room.uids && room.uids.length > 2) || (room.usernames && room.usernames.length > 2)) ?? false;
+}
diff --git a/app/lib/rocketchat/methods/roomTypeToApiType.ts b/app/lib/rocketchat/methods/roomTypeToApiType.ts
new file mode 100644
index 000000000..ecc0d16c2
--- /dev/null
+++ b/app/lib/rocketchat/methods/roomTypeToApiType.ts
@@ -0,0 +1,14 @@
+const types = {
+ c: 'channels',
+ d: 'im',
+ p: 'groups',
+ l: 'channels'
+};
+
+// TODO: refactor this
+export type RoomTypes = keyof typeof types;
+type ApiTypes = typeof types[RoomTypes];
+
+const roomTypeToApiType = (t: RoomTypes): ApiTypes => types[t];
+
+export default roomTypeToApiType;
diff --git a/app/lib/rocketchat.js b/app/lib/rocketchat/rocketchat.js
similarity index 55%
rename from app/lib/rocketchat.js
rename to app/lib/rocketchat/rocketchat.js
index ac923025f..b2171eacd 100644
--- a/app/lib/rocketchat.js
+++ b/app/lib/rocketchat/rocketchat.js
@@ -1,64 +1,71 @@
-import { InteractionManager } from 'react-native';
-import EJSON from 'ejson';
-import { settings as RocketChatSettings, Rocketchat as RocketchatClient } from '@rocket.chat/sdk';
import { Q } from '@nozbe/watermelondb';
-import AsyncStorage from '@react-native-community/async-storage';
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
+import AsyncStorage from '@react-native-community/async-storage';
+import { Rocketchat as RocketchatClient, settings as RocketChatSettings } from '@rocket.chat/sdk';
+import { InteractionManager } from 'react-native';
import RNFetchBlob from 'rn-fetch-blob';
-import isEmpty from 'lodash/isEmpty';
-
-import defaultSettings from '../constants/settings';
-import log from '../utils/log';
-import { getBundleId, isIOS } from '../utils/deviceInfo';
-import fetch from '../utils/fetch';
-import SSLPinning from '../utils/sslPinning';
-import { encryptionInit } from '../actions/encryption';
-import { loginRequest, setLoginServices, setUser } from '../actions/login';
-import { connectRequest, connectSuccess, disconnect } from '../actions/connect';
-import { shareSelectServer, shareSetSettings, shareSetUser } from '../actions/share';
-import { getDeviceToken } from '../notifications/push';
-import { setActiveUsers } from '../actions/activeUsers';
-import I18n from '../i18n';
-import { twoFactor } from '../utils/twoFactor';
-import { selectServerFailure } from '../actions/server';
-import { useSsl } from '../utils/url';
-import EventEmitter from '../utils/events';
-import { updatePermission } from '../actions/permissions';
-import { TEAM_TYPE } from '../definitions/ITeam';
-import { updateSettings } from '../actions/settings';
-import { compareServerVersion, methods } from './utils';
-import reduxStore from './createStore';
-import database from './database';
-import subscribeRooms from './methods/subscriptions/rooms';
-import getUsersPresence, { getUserPresence, subscribeUsersPresence } from './methods/getUsersPresence';
-import protectedFunction from './methods/helpers/protectedFunction';
-import readMessages from './methods/readMessages';
-import getSettings, { getLoginSettings, setSettings, subscribeSettings } from './methods/getSettings';
-import getRooms from './methods/getRooms';
-import { getPermissions, setPermissions } from './methods/getPermissions';
-import { getCustomEmojis, setCustomEmojis } from './methods/getCustomEmojis';
+import { setActiveUsers } from '../../actions/activeUsers';
+import { connectRequest, connectSuccess, disconnect } from '../../actions/connect';
+import { encryptionInit } from '../../actions/encryption';
+import { loginRequest, setLoginServices, setUser } from '../../actions/login';
+import { updatePermission } from '../../actions/permissions';
+import { selectServerFailure } from '../../actions/server';
+import { updateSettings } from '../../actions/settings';
+import { shareSelectServer, shareSetSettings, shareSetUser } from '../../actions/share';
+import defaultSettings from '../../constants/settings';
+import I18n from '../../i18n';
+import { getDeviceToken } from '../../notifications/push';
+import { getBundleId, isIOS } from '../../utils/deviceInfo';
+import EventEmitter from '../../utils/events';
+import fetch from '../../utils/fetch';
+import log from '../../utils/log';
+import SSLPinning from '../../utils/sslPinning';
+import { twoFactor } from '../../utils/twoFactor';
+import { useSsl } from '../../utils/url';
+import database from '../database';
+import { sanitizeLikeString } from '../database/utils';
+import { Encryption } from '../encryption';
+import triggerBlockAction, { triggerCancel, triggerSubmitView } from '../methods/actions';
+import callJitsi, { callJitsiWithoutServer } from '../methods/callJitsi';
+import canOpenRoom from '../methods/canOpenRoom';
import {
getEnterpriseModules,
hasLicense,
isOmnichannelModuleAvailable,
setEnterpriseModules
-} from './methods/enterpriseModules';
-import getSlashCommands from './methods/getSlashCommands';
-import { getRoles, onRolesChanged, setRoles } from './methods/getRoles';
-import canOpenRoom from './methods/canOpenRoom';
-import triggerBlockAction, { triggerCancel, triggerSubmitView } from './methods/actions';
-import loadMessagesForRoom from './methods/loadMessagesForRoom';
-import loadSurroundingMessages from './methods/loadSurroundingMessages';
-import loadNextMessages from './methods/loadNextMessages';
-import loadMissedMessages from './methods/loadMissedMessages';
-import loadThreadMessages from './methods/loadThreadMessages';
-import sendMessage, { resendMessage } from './methods/sendMessage';
-import { cancelUpload, isUploadActive, sendFileMessage } from './methods/sendFileMessage';
-import callJitsi, { callJitsiWithoutServer } from './methods/callJitsi';
-import logout, { removeServer } from './methods/logout';
-import UserPreferences from './userPreferences';
-import { Encryption } from './encryption';
-import { sanitizeLikeString } from './database/utils';
+} from '../methods/enterpriseModules';
+import { getCustomEmojis, setCustomEmojis } from '../methods/getCustomEmojis';
+import { getPermissions, setPermissions } from '../methods/getPermissions';
+import { getRoles, onRolesChanged, setRoles } from '../methods/getRoles';
+import getRooms from '../methods/getRooms';
+import getSettings, { getLoginSettings, setSettings, subscribeSettings } from '../methods/getSettings';
+import getSlashCommands from '../methods/getSlashCommands';
+import protectedFunction from '../methods/helpers/protectedFunction';
+import loadMessagesForRoom from '../methods/loadMessagesForRoom';
+import loadMissedMessages from '../methods/loadMissedMessages';
+import loadNextMessages from '../methods/loadNextMessages';
+import loadSurroundingMessages from '../methods/loadSurroundingMessages';
+import loadThreadMessages from '../methods/loadThreadMessages';
+import logout, { removeServer } from '../methods/logout';
+import readMessages from '../methods/readMessages';
+import { cancelUpload, isUploadActive, sendFileMessage } from '../methods/sendFileMessage';
+import sendMessage, { resendMessage } from '../methods/sendMessage';
+import subscribeRooms from '../methods/subscriptions/rooms';
+import UserPreferences from '../userPreferences';
+import { compareServerVersion } from '../utils';
+import { getUserPresence, subscribeUsersPresence } from '../methods/getUsersPresence';
+import { store as reduxStore } from '../auxStore';
+// Methods
+import clearCache from './methods/clearCache';
+import getPermalinkMessage from './methods/getPermalinkMessage';
+import getRoom from './methods/getRoom';
+import isGroupChat from './methods/isGroupChat';
+import roomTypeToApiType from './methods/roomTypeToApiType';
+import getUserInfo from './services/getUserInfo';
+// Services
+import sdk from './services/sdk';
+import toggleFavorite from './services/toggleFavorite';
+import * as restAPis from './services/restApi';
const TOKEN_KEY = 'reactnativemeteor_usertoken';
const CURRENT_SERVER = 'currentServer';
@@ -75,6 +82,7 @@ const RocketChat = {
TOKEN_KEY,
CURRENT_SERVER,
CERTIFICATE_KEY,
+ ...restAPis,
callJitsi,
callJitsiWithoutServer,
async subscribeRooms() {
@@ -93,19 +101,6 @@ const RocketChat = {
}
},
canOpenRoom,
- createChannel({ name, users, type, readOnly, broadcast, encrypted, teamId }) {
- const params = {
- name,
- members: users,
- readOnly,
- extraData: {
- broadcast,
- encrypted,
- ...(teamId && { teamId })
- }
- };
- return this.post(type ? 'groups.create' : 'channels.create', params);
- },
async getWebsocketInfo({ server }) {
const sdk = new RocketchatClient({ host: server, protocol: 'ddp', useSsl: useSsl(server) });
@@ -138,7 +133,7 @@ const RocketChat = {
message: I18n.t('Not_RC_Server', { contact: I18n.t('Contact_your_server_admin') })
};
}
- if (compareServerVersion(jsonRes.version, MIN_ROCKETCHAT_VERSION, methods.lowerThan)) {
+ if (compareServerVersion(jsonRes.version, 'lowerThan', MIN_ROCKETCHAT_VERSION)) {
return {
success: false,
message: I18n.t('Invalid_server_version', {
@@ -186,8 +181,7 @@ const RocketChat = {
return this?.sdk?.checkAndReopen();
},
disconnect() {
- this.sdk?.disconnect?.();
- this.sdk = null;
+ this.sdk = sdk.disconnect();
},
connect({ server, user, logoutOnError = false }) {
return new Promise(resolve => {
@@ -235,12 +229,7 @@ const RocketChat = {
EventEmitter.emit('INQUIRY_UNSUBSCRIBE');
- if (this.code) {
- this.code = null;
- }
-
- // The app can't reconnect if reopen interval is 5s while in development
- this.sdk = new RocketchatClient({ host: server, protocol: 'ddp', useSsl: useSsl(server), reopen: __DEV__ ? 20000 : 5000 });
+ this.sdk = sdk.initialize(server);
this.getSettings();
this.sdk
@@ -263,7 +252,6 @@ const RocketChat = {
}
reduxStore.dispatch(connectSuccess());
const { server: currentServer } = reduxStore.getState().server;
- const { user } = reduxStore.getState().login;
if (user?.token && server === currentServer) {
reduxStore.dispatch(loginRequest({ resume: user.token }, logoutOnError));
}
@@ -309,10 +297,26 @@ const RocketChat = {
protectedFunction(ddpMessage => onRolesChanged(ddpMessage))
);
+ // RC 4.1
+ this.sdk.onStreamData('stream-user-presence', ddpMessage => {
+ const userStatus = ddpMessage.fields.args[0];
+ const { uid } = ddpMessage.fields;
+ const [, status, statusText] = userStatus;
+ const newStatus = { status: STATUSES[status], statusText };
+ reduxStore.dispatch(setActiveUsers({ [uid]: newStatus }));
+
+ const { user: loggedUser } = reduxStore.getState().login;
+ if (loggedUser && loggedUser.id === uid) {
+ reduxStore.dispatch(setUser(newStatus));
+ }
+ });
+
this.notifyLoggedListener = this.sdk.onStreamData(
'stream-notify-logged',
protectedFunction(async ddpMessage => {
const { eventName } = ddpMessage.fields;
+
+ // `user-status` event is deprecated after RC 4.1 in favor of `stream-user-presence/${uid}`
if (/user-status/.test(eventName)) {
this.activeUsers = this.activeUsers || {};
if (!this._setUserTimer) {
@@ -400,12 +404,8 @@ const RocketChat = {
// Do nothing
}
- if (this.shareSDK) {
- this.shareSDK.disconnect();
- this.shareSDK = null;
- }
-
- this.shareSDK = new RocketchatClient({ host: server, protocol: 'ddp', useSsl: useSsl(server) });
+ this.shareSDK = sdk.disconnect();
+ this.shareSDK = sdk.initialize(server);
// set Server
const currentServer = { server };
@@ -458,10 +458,7 @@ const RocketChat = {
}
},
closeShareExtension() {
- if (this.shareSDK) {
- this.shareSDK.disconnect();
- this.shareSDK = null;
- }
+ this.shareSDK = sdk.disconnect();
database.share = null;
reduxStore.dispatch(shareSelectServer({}));
@@ -483,30 +480,6 @@ const RocketChat = {
}
return result;
},
- e2eSetUserPublicAndPrivateKeys(public_key, private_key) {
- // RC 2.2.0
- return this.post('e2e.setUserPublicAndPrivateKeys', { public_key, private_key });
- },
- e2eRequestSubscriptionKeys() {
- // RC 0.72.0
- return this.methodCallWrapper('e2e.requestSubscriptionKeys');
- },
- e2eGetUsersOfRoomWithoutKey(rid) {
- // RC 0.70.0
- return this.sdk.get('e2e.getUsersOfRoomWithoutKey', { rid });
- },
- e2eSetRoomKeyID(rid, keyID) {
- // RC 0.70.0
- return this.post('e2e.setRoomKeyID', { rid, keyID });
- },
- e2eUpdateGroupKey(uid, rid, key) {
- // RC 0.70.0
- return this.post('e2e.updateGroupKey', { uid, rid, key });
- },
- e2eRequestRoomKey(rid, e2eKeyId) {
- // RC 0.70.0
- return this.methodCallWrapper('stream-notify-room-users', `${rid}/e2ekeyRequest`, rid, e2eKeyId);
- },
e2eResetOwnKey() {
this.unsubscribeRooms();
@@ -514,25 +487,6 @@ const RocketChat = {
return this.methodCallWrapper('e2e.resetOwnE2EKey');
},
- updateJitsiTimeout(roomId) {
- // RC 0.74.0
- return this.post('video-conference/jitsi.update-timeout', { roomId });
- },
-
- register(credentials) {
- // RC 0.50.0
- return this.post('users.register', credentials, false);
- },
-
- forgotPassword(email) {
- // RC 0.64.0
- return this.post('users.forgotPassword', { email }, false);
- },
-
- sendConfirmationEmail(email) {
- return this.methodCallWrapper('sendConfirmationEmail', email);
- },
-
loginTOTP(params, loginEmailPassword, isFromWebView = false) {
return new Promise(async (resolve, reject) => {
try {
@@ -549,7 +503,7 @@ const RocketChat = {
// Force normalized params for 2FA starting RC 3.9.0.
const serverVersion = reduxStore.getState().server.version;
- if (compareServerVersion(serverVersion, '3.9.0', methods.greaterThanOrEqualTo)) {
+ if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '3.9.0')) {
const user = params.user ?? params.username;
const password = params.password ?? params.ldapPass ?? params.crowdPassword;
params = { user, password };
@@ -636,27 +590,7 @@ const RocketChat = {
return this.sdk.post('users.removeOtherTokens', { userId });
},
removeServer,
- async clearCache({ server }) {
- try {
- const serversDB = database.servers;
- await serversDB.action(async () => {
- const serverCollection = serversDB.get('servers');
- const serverRecord = await serverCollection.find(server);
- await serverRecord.update(s => {
- s.roomsUpdatedAt = null;
- });
- });
- } catch (e) {
- // Do nothing
- }
-
- try {
- const db = database.active;
- await db.action(() => db.unsafeResetDatabase());
- } catch (e) {
- // Do nothing
- }
- },
+ clearCache,
registerPushToken() {
return new Promise(async resolve => {
const token = getDeviceToken();
@@ -781,17 +715,6 @@ const RocketChat = {
// return [];
}
},
-
- spotlight(search, usernames, type) {
- // RC 0.51.0
- return this.methodCallWrapper('spotlight', search, usernames, type);
- },
-
- createDirectMessage(username) {
- // RC 0.59.0
- return this.post('im.create', { username });
- },
-
createGroupChat() {
const { users } = reduxStore.getState().selectedUsers;
const usernames = users.map(u => u.name).join(',');
@@ -799,104 +722,6 @@ const RocketChat = {
// RC 3.1.0
return this.post('im.create', { usernames });
},
-
- createDiscussion({ prid, pmid, t_name, reply, users, encrypted }) {
- // RC 1.0.0
- return this.post('rooms.createDiscussion', {
- prid,
- pmid,
- t_name,
- reply,
- users,
- encrypted
- });
- },
- createTeam({ name, users, type, readOnly, broadcast, encrypted }) {
- const params = {
- name,
- users,
- type: type ? TEAM_TYPE.PRIVATE : TEAM_TYPE.PUBLIC,
- room: {
- readOnly,
- extraData: {
- broadcast,
- encrypted
- }
- }
- };
- // RC 3.13.0
- return this.post('teams.create', params);
- },
- addRoomsToTeam({ teamId, rooms }) {
- // RC 3.13.0
- return this.post('teams.addRooms', { teamId, rooms });
- },
- removeTeamRoom({ roomId, teamId }) {
- // RC 3.13.0
- return this.post('teams.removeRoom', { roomId, teamId });
- },
- leaveTeam({ teamId, rooms }) {
- // RC 3.13.0
- return this.post('teams.leave', {
- teamId,
- // RC 4.2.0
- ...(rooms?.length && { rooms })
- });
- },
- removeTeamMember({ teamId, userId, rooms }) {
- // RC 3.13.0
- return this.post('teams.removeMember', {
- teamId,
- userId,
- // RC 4.2.0
- ...(rooms?.length && { rooms })
- });
- },
- updateTeamRoom({ roomId, isDefault }) {
- // RC 3.13.0
- return this.post('teams.updateRoom', { roomId, isDefault });
- },
- deleteTeam({ teamId, roomsToRemove }) {
- // RC 3.13.0
- return this.post('teams.delete', { teamId, roomsToRemove });
- },
- teamListRoomsOfUser({ teamId, userId }) {
- // RC 3.13.0
- return this.sdk.get('teams.listRoomsOfUser', { teamId, userId });
- },
- getTeamInfo({ teamId }) {
- // RC 3.13.0
- return this.sdk.get('teams.info', { teamId });
- },
- convertChannelToTeam({ rid, name, type }) {
- const params = {
- ...(type === 'c'
- ? {
- channelId: rid,
- channelName: name
- }
- : {
- roomId: rid,
- roomName: name
- })
- };
- return this.sdk.post(type === 'c' ? 'channels.convertToTeam' : 'groups.convertToTeam', params);
- },
- convertTeamToChannel({ teamId, selected }) {
- const params = {
- teamId,
- ...(selected.length && { roomsToRemove: selected })
- };
- return this.sdk.post('teams.convertToChannel', params);
- },
- joinRoom(roomId, joinCode, type) {
- // TODO: join code
- // RC 0.48.0
- if (type === 'p') {
- return this.methodCallWrapper('joinRoom', roomId);
- }
- return this.post('channels.join', { roomId, joinCode });
- },
triggerBlockAction,
triggerSubmitView,
triggerCancel,
@@ -935,62 +760,13 @@ const RocketChat = {
return setting;
});
},
- deleteMessage(messageId, rid) {
- // RC 0.48.0
- return this.post('chat.delete', { msgId: messageId, roomId: rid });
- },
async editMessage(message) {
const { rid, msg } = await Encryption.encryptMessage(message);
// RC 0.49.0
return this.post('chat.update', { roomId: rid, msgId: message.id, text: msg });
},
- markAsUnread({ messageId }) {
- return this.post('subscriptions.unread', { firstUnreadMessage: { _id: messageId } });
- },
- toggleStarMessage(messageId, starred) {
- if (starred) {
- // RC 0.59.0
- return this.post('chat.unStarMessage', { messageId });
- }
- // RC 0.59.0
- return this.post('chat.starMessage', { messageId });
- },
- togglePinMessage(messageId, pinned) {
- if (pinned) {
- // RC 0.59.0
- return this.post('chat.unPinMessage', { messageId });
- }
- // RC 0.59.0
- return this.post('chat.pinMessage', { messageId });
- },
- reportMessage(messageId) {
- return this.post('chat.reportMessage', { messageId, description: 'Message reported by user' });
- },
- async getRoom(rid) {
- try {
- const db = database.active;
- const room = await db.get('subscriptions').find(rid);
- return Promise.resolve(room);
- } catch (error) {
- return Promise.reject(new Error('Room not found'));
- }
- },
- async getPermalinkMessage(message) {
- let room;
- try {
- room = await RocketChat.getRoom(message.subscription.id);
- } catch (e) {
- log(e);
- return null;
- }
- const { server } = reduxStore.getState().server;
- const roomType = {
- p: 'group',
- c: 'channel',
- d: 'direct'
- }[room.t];
- return `${server}/${roomType}/${this.isGroupChat(room) ? room.rid : room.name}?msg=${message.id}`;
- },
+ getRoom,
+ getPermalinkMessage,
getPermalinkChannel(channel) {
const { server } = reduxStore.getState().server;
const roomType = {
@@ -1001,16 +777,19 @@ const RocketChat = {
return `${server}/${roomType}/${channel.name}`;
},
subscribe(...args) {
- return this.sdk.subscribe(...args);
+ return sdk.subscribe(...args);
+ },
+ subscribeRaw(...args) {
+ return sdk.subscribeRaw(...args);
},
subscribeRoom(...args) {
- return this.sdk.subscribeRoom(...args);
+ return sdk.subscribeRoom(...args);
},
unsubscribe(subscription) {
- return this.sdk.unsubscribe(subscription);
+ return sdk.unsubscribe(subscription);
},
onStreamData(...args) {
- return this.sdk.onStreamData(...args);
+ return sdk.onStreamData(...args);
},
emitTyping(room, typing = true) {
const { login, settings } = reduxStore.getState();
@@ -1019,37 +798,10 @@ const RocketChat = {
const name = UI_Use_Real_Name ? user.name : user.username;
return this.methodCall('stream-notify-room', `${room}/typing`, name, typing);
},
- setUserPresenceAway() {
- return this.methodCall('UserPresence:away');
- },
- setUserPresenceOnline() {
- return this.methodCall('UserPresence:online');
- },
- setUserPreferences(userId, data) {
- // RC 0.62.0
- return this.sdk.post('users.setPreferences', { userId, data });
- },
- setUserStatus(status, message) {
- // RC 1.2.0
- return this.post('users.setStatus', { status, message });
- },
- setReaction(emoji, messageId) {
- // RC 0.62.2
- return this.post('chat.react', { emoji, messageId });
- },
- toggleFavorite(roomId, favorite) {
- // RC 0.64.0
- return this.post('rooms.favorite', { roomId, favorite });
- },
- toggleRead(read, roomId) {
- if (read) {
- return this.post('subscriptions.unread', { roomId });
- }
- return this.post('subscriptions.read', { rid: roomId });
- },
+ toggleFavorite,
async getRoomMembers({ rid, allUsers, roomType, type, filter, skip = 0, limit = 10 }) {
const serverVersion = reduxStore.getState().server.version;
- if (compareServerVersion(serverVersion, '3.16.0', methods.greaterThanOrEqualTo)) {
+ if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '3.16.0')) {
const params = {
roomId: rid,
offset: skip,
@@ -1066,124 +818,9 @@ const RocketChat = {
return result?.records;
},
methodCallWrapper(method, ...params) {
- const { API_Use_REST_For_DDP_Calls } = reduxStore.getState().settings;
- const { user } = reduxStore.getState().login;
- if (API_Use_REST_For_DDP_Calls) {
- const url = isEmpty(user) ? 'method.callAnon' : 'method.call';
- return this.post(`${url}/${method}`, {
- message: EJSON.stringify({ method, params })
- });
- }
- const parsedParams = params.map(param => {
- if (param instanceof Date) {
- return { $date: new Date(param).getTime() };
- }
- return param;
- });
- return this.methodCall(method, ...parsedParams);
+ return sdk.methodCallWrapper(method, ...params);
},
-
- getUserRoles() {
- // RC 0.27.0
- return this.methodCallWrapper('getUserRoles');
- },
- getRoomCounters(roomId, t) {
- // RC 0.65.0
- return this.sdk.get(`${this.roomTypeToApiType(t)}.counters`, { roomId });
- },
- getChannelInfo(roomId) {
- // RC 0.48.0
- return this.sdk.get('channels.info', { roomId });
- },
- getUserInfo(userId) {
- // RC 0.48.0
- return this.sdk.get('users.info', { userId });
- },
- getUserPreferences(userId) {
- // RC 0.62.0
- return this.sdk.get('users.getPreferences', { userId });
- },
- getRoomInfo(roomId) {
- // RC 0.72.0
- return this.sdk.get('rooms.info', { roomId });
- },
-
- getVisitorInfo(visitorId) {
- // RC 2.3.0
- return this.sdk.get('livechat/visitors.info', { visitorId });
- },
- getTeamListRoom({ teamId, count, offset, type, filter }) {
- const params = {
- teamId,
- count,
- offset,
- type
- };
-
- if (filter) {
- params.filter = filter;
- }
- // RC 3.13.0
- return this.sdk.get('teams.listRooms', params);
- },
- closeLivechat(rid, comment) {
- // RC 0.29.0
- return this.methodCallWrapper('livechat:closeRoom', rid, comment, { clientAction: true });
- },
- editLivechat(userData, roomData) {
- // RC 0.55.0
- return this.methodCallWrapper('livechat:saveInfo', userData, roomData);
- },
- returnLivechat(rid) {
- // RC 0.72.0
- return this.methodCallWrapper('livechat:returnAsInquiry', rid);
- },
- forwardLivechat(transferData) {
- // RC 0.36.0
- return this.methodCallWrapper('livechat:transfer', transferData);
- },
- getDepartmentInfo(departmentId) {
- // RC 2.2.0
- return this.sdk.get(`livechat/department/${departmentId}?includeAgents=false`);
- },
- getDepartments() {
- // RC 2.2.0
- return this.sdk.get('livechat/department');
- },
- usersAutoComplete(selector) {
- // RC 2.4.0
- return this.sdk.get('users.autocomplete', { selector });
- },
- getRoutingConfig() {
- // RC 2.0.0
- return this.methodCallWrapper('livechat:getRoutingConfig');
- },
- getTagsList() {
- // RC 2.0.0
- return this.methodCallWrapper('livechat:getTagsList');
- },
- getAgentDepartments(uid) {
- // RC 2.4.0
- return this.sdk.get(`livechat/agents/${uid}/departments?enabledDepartmentsOnly=true`);
- },
- getCustomFields() {
- // RC 2.2.0
- return this.sdk.get('livechat/custom-fields');
- },
-
- getListCannedResponse({ scope = '', departmentId = '', offset = 0, count = 25, text = '' }) {
- const params = {
- offset,
- count,
- ...(departmentId && { departmentId }),
- ...(text && { text }),
- ...(scope && { scope })
- };
-
- // RC 3.17.0
- return this.sdk.get('canned-responses', params);
- },
-
+ getUserInfo,
getUidDirectMessage(room) {
const { id: userId } = reduxStore.getState().login.user;
@@ -1211,166 +848,24 @@ const RocketChat = {
isUnread = isUnread && (item.unread > 0 || item.alert === true); // either its unread count > 0 or its alert
return !isUnread;
},
-
- isGroupChat(room) {
- return (room.uids && room.uids.length > 2) || (room.usernames && room.usernames.length > 2);
- },
-
- toggleBlockUser(rid, blocked, block) {
- if (block) {
- // RC 0.49.0
- return this.methodCallWrapper('blockUser', { rid, blocked });
- }
- // RC 0.49.0
- return this.methodCallWrapper('unblockUser', { rid, blocked });
- },
- leaveRoom(roomId, t) {
- // RC 0.48.0
- return this.post(`${this.roomTypeToApiType(t)}.leave`, { roomId });
- },
- deleteRoom(roomId, t) {
- // RC 0.49.0
- return this.post(`${this.roomTypeToApiType(t)}.delete`, { roomId });
- },
- toggleMuteUserInRoom(rid, username, mute) {
- if (mute) {
- // RC 0.51.0
- return this.methodCallWrapper('muteUserInRoom', { rid, username });
- }
- // RC 0.51.0
- return this.methodCallWrapper('unmuteUserInRoom', { rid, username });
- },
- toggleRoomOwner({ roomId, t, userId, isOwner }) {
- if (isOwner) {
- // RC 0.49.4
- return this.post(`${this.roomTypeToApiType(t)}.addOwner`, { roomId, userId });
- }
- // RC 0.49.4
- return this.post(`${this.roomTypeToApiType(t)}.removeOwner`, { roomId, userId });
- },
- toggleRoomLeader({ roomId, t, userId, isLeader }) {
- if (isLeader) {
- // RC 0.58.0
- return this.post(`${this.roomTypeToApiType(t)}.addLeader`, { roomId, userId });
- }
- // RC 0.58.0
- return this.post(`${this.roomTypeToApiType(t)}.removeLeader`, { roomId, userId });
- },
- toggleRoomModerator({ roomId, t, userId, isModerator }) {
- if (isModerator) {
- // RC 0.49.4
- return this.post(`${this.roomTypeToApiType(t)}.addModerator`, { roomId, userId });
- }
- // RC 0.49.4
- return this.post(`${this.roomTypeToApiType(t)}.removeModerator`, { roomId, userId });
- },
- removeUserFromRoom({ roomId, t, userId }) {
- // RC 0.48.0
- return this.post(`${this.roomTypeToApiType(t)}.kick`, { roomId, userId });
- },
- ignoreUser({ rid, userId, ignore }) {
- return this.sdk.get('chat.ignoreUser', { rid, userId, ignore });
- },
- toggleArchiveRoom(roomId, t, archive) {
- if (archive) {
- // RC 0.48.0
- return this.post(`${this.roomTypeToApiType(t)}.archive`, { roomId });
- }
- // RC 0.48.0
- return this.post(`${this.roomTypeToApiType(t)}.unarchive`, { roomId });
- },
- hideRoom(roomId, t) {
- return this.post(`${this.roomTypeToApiType(t)}.close`, { roomId });
- },
- saveRoomSettings(rid, params) {
- // RC 0.55.0
- return this.methodCallWrapper('saveRoomSettings', rid, params);
- },
+ isGroupChat,
post(...args) {
- return new Promise(async (resolve, reject) => {
- const isMethodCall = args[0]?.startsWith('method.call/');
- try {
- 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);
- } catch (e) {
- const errorType = isMethodCall ? e?.error : e?.data?.errorType;
- const totpInvalid = 'totp-invalid';
- const totpRequired = 'totp-required';
- if ([totpInvalid, totpRequired].includes(errorType)) {
- const { details } = isMethodCall ? e : e?.data;
- try {
- await twoFactor({ method: details?.method, invalid: errorType === totpInvalid });
- return resolve(this.post(...args));
- } catch {
- // twoFactor was canceled
- return resolve({});
- }
- } else {
- reject(e);
- }
- }
- });
+ return sdk.post(...args);
},
methodCall(...args) {
- return new Promise(async (resolve, reject) => {
- try {
- const result = await this.sdk?.methodCall(...args, this.code || '');
- return resolve(result);
- } catch (e) {
- if (e.error && (e.error === 'totp-required' || e.error === 'totp-invalid')) {
- const { details } = e;
- try {
- this.code = await twoFactor({ method: details?.method, invalid: e.error === 'totp-invalid' });
- return resolve(this.methodCall(...args));
- } catch {
- // twoFactor was canceled
- return resolve({});
- }
- } else {
- reject(e);
- }
- }
- });
+ return sdk.methodCall(...args);
},
sendEmailCode() {
const { username } = reduxStore.getState().login.user;
// RC 3.1.0
return this.post('users.2fa.sendEmailCode', { emailOrUsername: username });
},
- saveUserProfile(data, customFields) {
- // RC 0.62.2
- return this.post('users.updateOwnBasicInfo', { data, customFields });
- },
- saveUserPreferences(data) {
- // RC 0.62.0
- return this.post('users.setPreferences', { data });
- },
- saveNotificationSettings(roomId, notifications) {
- // RC 0.63.0
- return this.post('rooms.saveNotification', { roomId, notifications });
- },
addUsersToRoom(rid) {
let { users } = reduxStore.getState().selectedUsers;
users = users.map(u => u.name);
// RC 0.51.0
return this.methodCallWrapper('addUsersToRoom', { rid, users });
},
- getSingleMessage(msgId) {
- // RC 0.47.0
- return this.sdk.get('chat.getMessage', { msgId });
- },
hasRole(role) {
const shareUser = reduxStore.getState().share.user;
const loginUser = reduxStore.getState().login.user;
@@ -1379,10 +874,6 @@ const RocketChat = {
return userRoles.indexOf(r => r === role) > -1;
},
- getRoomRoles(roomId, type) {
- // RC 0.65.0
- return this.sdk.get(`${this.roomTypeToApiType(type)}.roles`, { roomId });
- },
/**
* Permissions: array of permissions' roles from redux. Example: [['owner', 'admin'], ['leader']]
* Returns an array of boolean for each permission from permissions arg
@@ -1414,18 +905,6 @@ const RocketChat = {
log(e);
}
},
- getAvatarSuggestion() {
- // RC 0.51.0
- return this.methodCallWrapper('getAvatarSuggestion');
- },
- resetAvatar(userId) {
- // RC 0.55.0
- return this.post('users.resetAvatar', { userId });
- },
- setAvatarFromService({ data, contentType = '', service = null }) {
- // RC 0.51.0
- return this.methodCallWrapper('setAvatarFromService', data, contentType, service);
- },
async getAllowCrashReport() {
const allowCrashReport = await AsyncStorage.getItem(CRASH_REPORT_KEY);
if (allowCrashReport === null) {
@@ -1502,116 +981,15 @@ const RocketChat = {
const availableOAuth = ['facebook', 'github', 'gitlab', 'google', 'linkedin', 'meteor-developer', 'twitter', 'wordpress'];
return availableOAuth.includes(authName) ? 'oauth' : 'not_supported';
},
- getUsernameSuggestion() {
- // RC 0.65.0
- return this.sdk.get('users.getUsernameSuggestion');
- },
- roomTypeToApiType(t) {
- const types = {
- c: 'channels',
- d: 'im',
- p: 'groups',
- l: 'channels'
- };
- return types[t];
- },
- getFiles(roomId, type, offset) {
- // RC 0.59.0
- return this.sdk.get(`${this.roomTypeToApiType(type)}.files`, {
- roomId,
- offset,
- sort: { uploadedAt: -1 }
- });
- },
- getMessages(roomId, type, query, offset) {
- // RC 0.59.0
- return this.sdk.get(`${this.roomTypeToApiType(type)}.messages`, {
- roomId,
- query,
- offset,
- sort: { ts: -1 }
- });
- },
-
- getReadReceipts(messageId) {
- return this.sdk.get('chat.getMessageReadReceipts', {
- messageId
- });
- },
- searchMessages(roomId, searchText, count, offset) {
- // RC 0.60.0
- return this.sdk.get('chat.search', {
- roomId,
- searchText,
- count,
- offset
- });
- },
- toggleFollowMessage(mid, follow) {
- // RC 1.0
- if (follow) {
- return this.post('chat.followMessage', { mid });
- }
- return this.post('chat.unfollowMessage', { mid });
- },
- getThreadsList({ rid, count, offset, text }) {
- const params = {
- rid,
- count,
- offset,
- sort: { ts: -1 }
- };
- if (text) {
- params.text = text;
- }
-
- // RC 1.0
- return this.sdk.get('chat.getThreadsList', params);
- },
- getSyncThreadsList({ rid, updatedSince }) {
- // RC 1.0
- return this.sdk.get('chat.syncThreadsList', {
- rid,
- updatedSince
- });
- },
+ roomTypeToApiType,
readThreads(tmid) {
const serverVersion = reduxStore.getState().server.version;
- if (compareServerVersion(serverVersion, '3.4.0', methods.greaterThanOrEqualTo)) {
+ if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '3.4.0')) {
// RC 3.4.0
return this.methodCallWrapper('readThreads', tmid);
}
return Promise.resolve();
},
- runSlashCommand(command, roomId, params, triggerId, tmid) {
- // RC 0.60.2
- return this.post('commands.run', {
- command,
- roomId,
- params,
- triggerId,
- tmid
- });
- },
- getCommandPreview(command, roomId, params) {
- // RC 0.65.0
- return this.sdk.get('commands.preview', {
- command,
- roomId,
- params
- });
- },
- executeCommandPreview(command, params, roomId, previewItem, triggerId, tmid) {
- // RC 0.65.0
- return this.post('commands.preview', {
- command,
- params,
- roomId,
- previewItem,
- triggerId,
- tmid
- });
- },
_setUser(ddpMessage) {
this.activeUsers = this.activeUsers || {};
const { user } = reduxStore.getState().login;
@@ -1624,15 +1002,18 @@ const RocketChat = {
reduxStore.dispatch(setUser({ status: { status: 'offline' } }));
}
- if (!this._setUserTimer) {
- this._setUserTimer = setTimeout(() => {
- const activeUsersBatch = this.activeUsers;
- InteractionManager.runAfterInteractions(() => {
- reduxStore.dispatch(setActiveUsers(activeUsersBatch));
- });
- this._setUserTimer = null;
- return (this.activeUsers = {});
- }, 10000);
+ const serverVersion = reduxStore.getState().server.version;
+ if (compareServerVersion(serverVersion, 'lowerThan', '4.1.0')) {
+ if (!this._setUserTimer) {
+ this._setUserTimer = setTimeout(() => {
+ const activeUsersBatch = this.activeUsers;
+ InteractionManager.runAfterInteractions(() => {
+ reduxStore.dispatch(setActiveUsers(activeUsersBatch));
+ });
+ this._setUserTimer = null;
+ return (this.activeUsers = {});
+ }, 10000);
+ }
}
if (!ddpMessage.fields) {
@@ -1641,18 +1022,8 @@ const RocketChat = {
this.activeUsers[ddpMessage.id] = { status: ddpMessage.fields.status };
}
},
- getUsersPresence,
getUserPresence,
subscribeUsersPresence,
- getDirectory({ query, count, offset, sort }) {
- // RC 1.0
- return this.sdk.get('directory', {
- query,
- count,
- offset,
- sort
- });
- },
canAutoTranslate() {
try {
const { AutoTranslate_Enabled } = reduxStore.getState().settings;
@@ -1667,15 +1038,6 @@ const RocketChat = {
return false;
}
},
- saveAutoTranslate({ rid, field, value, options }) {
- return this.methodCallWrapper('autoTranslate.saveSettings', rid, field, value, options);
- },
- getSupportedLanguagesAutoTranslate() {
- return this.methodCallWrapper('autoTranslate.getSupportedLanguages', 'en');
- },
- translateMessage(message, targetLanguage) {
- return this.methodCallWrapper('autoTranslate.translateMessage', message, targetLanguage);
- },
getSenderName(sender) {
const { UI_Use_Real_Name: useRealName } = reduxStore.getState().settings;
return useRealName ? sender.name : sender.username;
@@ -1700,19 +1062,6 @@ const RocketChat = {
return room.uids?.length + room.usernames?.join();
}
return room.prid ? room.fname : room.name;
- },
-
- findOrCreateInvite({ rid, days, maxUses }) {
- // RC 2.4.0
- return this.post('findOrCreateInvite', { rid, days, maxUses });
- },
- validateInviteToken(token) {
- // RC 2.4.0
- return this.post('validateInviteToken', { token });
- },
- useInviteToken(token) {
- // RC 2.4.0
- return this.post('useInviteToken', { token });
}
};
diff --git a/app/lib/rocketchat/services/getServerTimeSync.ts b/app/lib/rocketchat/services/getServerTimeSync.ts
new file mode 100644
index 000000000..da50c07ed
--- /dev/null
+++ b/app/lib/rocketchat/services/getServerTimeSync.ts
@@ -0,0 +1,16 @@
+import RNFetchBlob from 'rn-fetch-blob';
+
+export const getServerTimeSync = async (server: string) => {
+ try {
+ const response = await Promise.race([
+ RNFetchBlob.fetch('GET', `${server}/_timesync`),
+ new Promise(res => setTimeout(res, 2000))
+ ]);
+ if (response?.data) {
+ return parseInt(response.data);
+ }
+ return null;
+ } catch {
+ return null;
+ }
+};
diff --git a/app/lib/rocketchat/services/getUserInfo.ts b/app/lib/rocketchat/services/getUserInfo.ts
new file mode 100644
index 000000000..1da955456
--- /dev/null
+++ b/app/lib/rocketchat/services/getUserInfo.ts
@@ -0,0 +1,6 @@
+import sdk from './sdk';
+
+export default function getUserInfo(userId: string) {
+ // RC 0.48.0
+ return sdk.get('users.info', { userId });
+}
diff --git a/app/lib/rocketchat/services/restApi.ts b/app/lib/rocketchat/services/restApi.ts
new file mode 100644
index 000000000..3fc518123
--- /dev/null
+++ b/app/lib/rocketchat/services/restApi.ts
@@ -0,0 +1,813 @@
+import sdk from './sdk';
+import { TEAM_TYPE } from '../../../definitions/ITeam';
+import roomTypeToApiType, { RoomTypes } from '../methods/roomTypeToApiType';
+
+export const createChannel = ({
+ name,
+ users,
+ type,
+ readOnly,
+ broadcast,
+ encrypted,
+ teamId
+}: {
+ name: string;
+ users: string[];
+ type: boolean;
+ readOnly: boolean;
+ broadcast: boolean;
+ encrypted: boolean;
+ teamId: string;
+}): any => {
+ const params = {
+ name,
+ members: users,
+ readOnly,
+ extraData: {
+ broadcast,
+ encrypted,
+ ...(teamId && { teamId })
+ }
+ };
+ // TODO: missing definitions from server
+ // @ts-ignore
+ return sdk.post(type ? 'groups.create' : 'channels.create', params);
+};
+
+export const e2eSetUserPublicAndPrivateKeys = (public_key: string, private_key: string): any =>
+ // RC 2.2.0
+ // TODO: missing definitions from server
+ // @ts-ignore
+ sdk.post('e2e.setUserPublicAndPrivateKeys', { public_key, private_key });
+
+export const e2eRequestSubscriptionKeys = (): any =>
+ // RC 0.72.0
+ sdk.methodCallWrapper('e2e.requestSubscriptionKeys');
+
+export const e2eGetUsersOfRoomWithoutKey = (rid: string): any =>
+ // RC 0.70.0
+ // TODO: missing definitions from server
+ // @ts-ignore
+ sdk.get('e2e.getUsersOfRoomWithoutKey', { rid });
+
+export const e2eSetRoomKeyID = (rid: string, keyID: string): any =>
+ // RC 0.70.0
+ // TODO: missing definitions from server
+ // @ts-ignore
+ sdk.post('e2e.setRoomKeyID', { rid, keyID });
+
+export const e2eUpdateGroupKey = (uid: string, rid: string, key: string): any =>
+ // RC 0.70.0
+ // TODO: missing definitions from server
+ // @ts-ignore
+ sdk.post('e2e.updateGroupKey', { uid, rid, key });
+
+export const e2eRequestRoomKey = (rid: string, e2eKeyId: string) =>
+ // RC 0.70.0
+ sdk.methodCallWrapper('stream-notify-room-users', `${rid}/e2ekeyRequest`, rid, e2eKeyId);
+
+export const updateJitsiTimeout = (roomId: string): any =>
+ // RC 0.74.0
+ // TODO: missing definitions from server
+ // @ts-ignore
+ sdk.post('video-conference/jitsi.update-timeout', { roomId });
+
+export const register = (credentials: any): any =>
+ // RC 0.50.0
+ // TODO: missing definitions from server
+ // @ts-ignore
+ sdk.post('users.register', credentials);
+
+export const forgotPassword = (email: string): any =>
+ // RC 0.64.0
+ // TODO: missing definitions from server
+ // @ts-ignore
+ sdk.post('users.forgotPassword', { email });
+
+export const sendConfirmationEmail = (email: string) => sdk.methodCallWrapper('sendConfirmationEmail', email);
+
+export const spotlight = (search: string, usernames: string, type: { users: boolean; rooms: boolean }) =>
+ // RC 0.51.0
+ sdk.methodCallWrapper('spotlight', search, usernames, type);
+
+export const createDirectMessage = (username: string) =>
+ // RC 0.59.0
+ sdk.post('im.create', { username });
+
+export const createDiscussion = ({
+ prid,
+ pmid,
+ t_name,
+ reply,
+ users,
+ encrypted
+}: {
+ prid: string;
+ pmid?: string;
+ t_name: string;
+ reply?: string;
+ users?: string[];
+ encrypted?: boolean;
+}) =>
+ // RC 1.0.0
+ sdk.post('rooms.createDiscussion', {
+ prid,
+ pmid,
+ t_name,
+ reply,
+ users,
+ encrypted
+ });
+
+export const getDiscussions = ({
+ roomId,
+ offset,
+ count,
+ text
+}: {
+ roomId: string | undefined;
+ text?: string | undefined;
+ offset: number;
+ count: number;
+}) => {
+ const params = {
+ roomId,
+ offset,
+ count,
+ ...(text && { text })
+ };
+ // RC 2.4.0
+ return sdk.get('chat.getDiscussions', params);
+};
+
+export const createTeam = ({
+ name,
+ users,
+ type,
+ readOnly,
+ broadcast,
+ encrypted
+}: {
+ name: string;
+ users: string[];
+ type: boolean;
+ readOnly: boolean;
+ broadcast: boolean;
+ encrypted: boolean;
+}): any => {
+ const params = {
+ name,
+ users,
+ type: type ? TEAM_TYPE.PRIVATE : TEAM_TYPE.PUBLIC,
+ room: {
+ readOnly,
+ extraData: {
+ broadcast,
+ encrypted
+ }
+ }
+ };
+ // RC 3.13.0
+ // TODO: missing definitions from server
+ // @ts-ignore
+ return sdk.post('teams.create', params);
+};
+export const addRoomsToTeam = ({ teamId, rooms }: { teamId: string; rooms: string[] }): any =>
+ // RC 3.13.0
+ // TODO: missing definitions from server
+ // @ts-ignore
+ sdk.post('teams.addRooms', { teamId, rooms });
+
+export const removeTeamRoom = ({ roomId, teamId }: { roomId: string; teamId: string }) =>
+ // RC 3.13.0
+ sdk.post('teams.removeRoom', { roomId, teamId });
+
+export const leaveTeam = ({ teamId, rooms }: { teamId: string; rooms: string[] }): any =>
+ // RC 3.13.0
+ // TODO: missing definitions from server
+ // @ts-ignore
+ sdk.post('teams.leave', {
+ teamId,
+ // RC 4.2.0
+ ...(rooms?.length && { rooms })
+ });
+
+export const removeTeamMember = ({ teamId, userId, rooms }: { teamId: string; userId: string; rooms: string[] }): any =>
+ // RC 3.13.0
+ // TODO: missing definitions from server
+ // @ts-ignore
+ sdk.post('teams.removeMember', {
+ teamId,
+ userId,
+ // RC 4.2.0
+ ...(rooms?.length && { rooms })
+ });
+
+export const updateTeamRoom = ({ roomId, isDefault }: { roomId: string; isDefault: boolean }): any =>
+ // RC 3.13.0
+ // TODO: missing definitions from server
+ // @ts-ignore
+ sdk.post('teams.updateRoom', { roomId, isDefault });
+
+export const deleteTeam = ({ teamId, roomsToRemove }: { teamId: string; roomsToRemove: string[] }): any =>
+ // RC 3.13.0
+ // TODO: missing definitions from server
+ // @ts-ignore
+ sdk.post('teams.delete', { teamId, roomsToRemove });
+
+export const teamListRoomsOfUser = ({ teamId, userId }: { teamId: string; userId: string }): any =>
+ // RC 3.13.0
+ // TODO: missing definitions from server
+ // @ts-ignore
+ sdk.get('teams.listRoomsOfUser', { teamId, userId });
+
+export const getTeamInfo = ({ teamId }: { teamId: string }): any =>
+ // RC 3.13.0
+ // TODO: missing definitions from server
+ // @ts-ignore
+ sdk.get('teams.info', { teamId });
+
+export const convertChannelToTeam = ({ rid, name, type }: { rid: string; name: string; type: 'c' | 'p' }): any => {
+ const params = {
+ ...(type === 'c'
+ ? {
+ channelId: rid,
+ channelName: name
+ }
+ : {
+ roomId: rid,
+ roomName: name
+ })
+ };
+ // TODO: missing definitions from server
+ // @ts-ignore
+ return sdk.post(type === 'c' ? 'channels.convertToTeam' : 'groups.convertToTeam', params);
+};
+
+export const convertTeamToChannel = ({ teamId, selected }: { teamId: string; selected: string[] }): any => {
+ const params = {
+ teamId,
+ ...(selected.length && { roomsToRemove: selected })
+ };
+ // TODO: missing definitions from server
+ // @ts-ignore
+ return sdk.post('teams.convertToChannel', params);
+};
+
+export const joinRoom = (roomId: string, joinCode: string, type: 'c' | 'p'): any => {
+ // TODO: join code
+ // RC 0.48.0
+ if (type === 'p') {
+ return sdk.methodCallWrapper('joinRoom', roomId);
+ }
+ // TODO: missing definitions from server
+ // @ts-ignore
+ return sdk.post('channels.join', { roomId, joinCode });
+};
+
+export const deleteMessage = (messageId: string, rid: string): any =>
+ // RC 0.48.0
+ // TODO: missing definitions from server
+ // @ts-ignore
+ sdk.post('chat.delete', { msgId: messageId, roomId: rid });
+
+export const markAsUnread = ({ messageId }: { messageId: string }): any =>
+ // RC 0.65.0
+ // TODO: missing definitions from server
+ // @ts-ignore
+ sdk.post('subscriptions.unread', { firstUnreadMessage: { _id: messageId } });
+
+export const toggleStarMessage = (messageId: string, starred: boolean): any => {
+ if (starred) {
+ // RC 0.59.0
+ // TODO: missing definitions from server
+ // @ts-ignore
+ return sdk.post('chat.unStarMessage', { messageId });
+ }
+ // RC 0.59.0
+ // TODO: missing definitions from server
+ // @ts-ignore
+ return sdk.post('chat.starMessage', { messageId });
+};
+
+export const togglePinMessage = (messageId: string, pinned: boolean): any => {
+ if (pinned) {
+ // RC 0.59.0
+ // TODO: missing definitions from server
+ // @ts-ignore
+ return sdk.post('chat.unPinMessage', { messageId });
+ }
+ // RC 0.59.0
+ // TODO: missing definitions from server
+ // @ts-ignore
+ return sdk.post('chat.pinMessage', { messageId });
+};
+
+export const reportMessage = (messageId: string): any =>
+ // RC 0.64.0
+ // TODO: missing definitions from server
+ // @ts-ignore
+ sdk.post('chat.reportMessage', { messageId, description: 'Message reported by user' });
+
+export const setUserPreferences = (userId: string, data: any): any =>
+ // RC 0.62.0
+ // TODO: missing definitions from server
+ // @ts-ignore
+ sdk.post('users.setPreferences', { userId, data });
+
+export const setUserStatus = (status?: string, message?: string): any =>
+ // RC 1.2.0
+ // TODO: missing definitions from server
+ // @ts-ignore
+ sdk.post('users.setStatus', { status, message });
+
+export const setReaction = (emoji: string, messageId: string): any =>
+ // RC 0.62.2
+ // TODO: missing definitions from server
+ // @ts-ignore
+ sdk.post('chat.react', { emoji, messageId });
+
+export const toggleRead = (read: boolean, roomId: string): any => {
+ if (read) {
+ // TODO: missing definitions from server
+ // @ts-ignore
+ return sdk.post('subscriptions.unread', { roomId });
+ }
+ // TODO: missing definitions from server
+ // @ts-ignore
+ return sdk.post('subscriptions.read', { rid: roomId });
+};
+
+export const getUserRoles = () =>
+ // RC 0.27.0
+ sdk.methodCallWrapper('getUserRoles');
+
+export const getRoomCounters = (roomId: string, t: RoomTypes): any =>
+ // RC 0.65.0
+ // TODO: missing definitions from server
+ // @ts-ignore
+ sdk.get(`${roomTypeToApiType(t)}.counters`, { roomId });
+
+export const getChannelInfo = (roomId: string): any =>
+ // RC 0.48.0
+ // TODO: missing definitions from server
+ // @ts-ignore
+ sdk.get('channels.info', { roomId });
+
+export const getUserPreferences = (userId: string): any =>
+ // RC 0.62.0
+ // TODO: missing definitions from server
+ // @ts-ignore
+ sdk.get('users.getPreferences', { userId });
+
+export const getRoomInfo = (roomId: string) =>
+ // RC 0.72.0
+ sdk.get('rooms.info', { roomId });
+
+export const getVisitorInfo = (visitorId: string) =>
+ // RC 2.3.0
+ sdk.get('livechat/visitors.info', { visitorId });
+
+export const setUserPresenceAway = () => sdk.methodCall('UserPresence:away');
+
+export const setUserPresenceOnline = () => sdk.methodCall('UserPresence:online');
+
+export const getTeamListRoom = ({
+ teamId,
+ count,
+ offset,
+ type,
+ filter
+}: {
+ teamId: string;
+ count: number;
+ offset: number;
+ type: string;
+ filter: any;
+}): any => {
+ const params: any = {
+ teamId,
+ count,
+ offset,
+ type
+ };
+
+ if (filter) {
+ params.filter = filter;
+ }
+ // RC 3.13.0
+ // TODO: missing definitions from server
+ // @ts-ignore
+ return sdk.get('teams.listRooms', params);
+};
+
+export const closeLivechat = (rid: string, comment: string) =>
+ // RC 0.29.0
+ sdk.methodCallWrapper('livechat:closeRoom', rid, comment, { clientAction: true });
+
+export const editLivechat = (userData: any, roomData: any) =>
+ // RC 0.55.0
+ sdk.methodCallWrapper('livechat:saveInfo', userData, roomData);
+
+export const returnLivechat = (rid: string) =>
+ // RC 0.72.0
+ sdk.methodCallWrapper('livechat:returnAsInquiry', rid);
+
+export const forwardLivechat = (transferData: any) =>
+ // RC 0.36.0
+ sdk.methodCallWrapper('livechat:transfer', transferData);
+
+export const getDepartmentInfo = (departmentId: string): any =>
+ // RC 2.2.0
+ sdk.get(`livechat/department/${departmentId}?includeAgents=false`);
+
+export const getDepartments = (args?: { count: number; offset: number; text: string }) => {
+ let params;
+ if (args) {
+ params = {
+ text: args.text,
+ count: args.count,
+ offset: args.offset
+ };
+ }
+ // RC 2.2.0
+ return sdk.get('livechat/department', params);
+};
+
+export const usersAutoComplete = (selector: any) =>
+ // RC 2.4.0
+ sdk.get('users.autocomplete', { selector });
+
+export const getRoutingConfig = () =>
+ // RC 2.0.0
+ sdk.methodCallWrapper('livechat:getRoutingConfig');
+
+export const getTagsList = () =>
+ // RC 2.0.0
+ sdk.methodCallWrapper('livechat:getTagsList');
+
+export const getAgentDepartments = (uid: string): any =>
+ // RC 2.4.0
+ // TODO: missing definitions from server
+ // @ts-ignore
+ sdk.get(`livechat/agents/${uid}/departments?enabledDepartmentsOnly=true`);
+
+export const getCustomFields = () =>
+ // RC 2.2.0
+ sdk.get('livechat/custom-fields');
+
+export const getListCannedResponse = ({ scope = '', departmentId = '', offset = 0, count = 25, text = '' }): any => {
+ const params = {
+ offset,
+ count,
+ ...(departmentId && { departmentId }),
+ ...(text && { text }),
+ ...(scope && { scope })
+ };
+
+ // RC 3.17.0
+ // TODO: missing definitions from server
+ // @ts-ignore
+ return sdk.get('canned-responses', params);
+};
+
+export const toggleBlockUser = (rid: string, blocked: string, block: boolean) => {
+ if (block) {
+ // RC 0.49.0
+ return sdk.methodCallWrapper('blockUser', { rid, blocked });
+ }
+ // RC 0.49.0
+ return sdk.methodCallWrapper('unblockUser', { rid, blocked });
+};
+
+export const leaveRoom = (roomId: string, t: RoomTypes): any =>
+ // RC 0.48.0
+ // TODO: missing definitions from server
+ // @ts-ignore
+ sdk.post(`${roomTypeToApiType(t)}.leave`, { roomId });
+
+export const deleteRoom = (roomId: string, t: RoomTypes): any =>
+ // RC 0.49.0
+ // TODO: missing definitions from server
+ // @ts-ignore
+ sdk.post(`${roomTypeToApiType(t)}.delete`, { roomId });
+
+export const toggleMuteUserInRoom = (rid: string, username: string, mute: boolean) => {
+ if (mute) {
+ // RC 0.51.0
+ return sdk.methodCallWrapper('muteUserInRoom', { rid, username });
+ }
+ // RC 0.51.0
+ return sdk.methodCallWrapper('unmuteUserInRoom', { rid, username });
+};
+
+export const toggleRoomOwner = ({
+ roomId,
+ t,
+ userId,
+ isOwner
+}: {
+ roomId: string;
+ t: RoomTypes;
+ userId: string;
+ isOwner: boolean;
+}): any => {
+ if (isOwner) {
+ // RC 0.49.4
+ // TODO: missing definitions from server
+ // @ts-ignore
+ return sdk.post(`${roomTypeToApiType(t)}.addOwner`, { roomId, userId });
+ }
+ // RC 0.49.4
+ // TODO: missing definitions from server
+ // @ts-ignore
+ return sdk.post(`${roomTypeToApiType(t)}.removeOwner`, { roomId, userId });
+};
+
+export const toggleRoomLeader = ({
+ roomId,
+ t,
+ userId,
+ isLeader
+}: {
+ roomId: string;
+ t: RoomTypes;
+ userId: string;
+ isLeader: boolean;
+}): any => {
+ if (isLeader) {
+ // RC 0.58.0
+ // TODO: missing definitions from server
+ // @ts-ignore
+ return sdk.post(`${roomTypeToApiType(t)}.addLeader`, { roomId, userId });
+ }
+ // RC 0.58.0
+ // TODO: missing definitions from server
+ // @ts-ignore
+ return sdk.post(`${roomTypeToApiType(t)}.removeLeader`, { roomId, userId });
+};
+
+export const toggleRoomModerator = ({
+ roomId,
+ t,
+ userId,
+ isModerator
+}: {
+ roomId: string;
+ t: RoomTypes;
+ userId: string;
+ isModerator: boolean;
+}): any => {
+ if (isModerator) {
+ // RC 0.49.4
+ // TODO: missing definitions from server
+ // @ts-ignore
+ return sdk.post(`${roomTypeToApiType(t)}.addModerator`, { roomId, userId });
+ }
+ // RC 0.49.4
+ // TODO: missing definitions from server
+ // @ts-ignore
+ return sdk.post(`${roomTypeToApiType(t)}.removeModerator`, { roomId, userId });
+};
+
+export const removeUserFromRoom = ({ roomId, t, userId }: { roomId: string; t: RoomTypes; userId: string }): any =>
+ // RC 0.48.0
+ // TODO: missing definitions from server
+ // @ts-ignore
+ sdk.post(`${roomTypeToApiType(t)}.kick`, { roomId, userId });
+
+export const ignoreUser = ({ rid, userId, ignore }: { rid: string; userId: string; ignore: boolean }): any =>
+ // RC 0.64.0
+ // TODO: missing definitions from server
+ // @ts-ignore
+ sdk.get('chat.ignoreUser', { rid, userId, ignore });
+
+export const toggleArchiveRoom = (roomId: string, t: RoomTypes, archive: boolean): any => {
+ if (archive) {
+ // RC 0.48.0
+ // TODO: missing definitions from server
+ // @ts-ignore
+ return sdk.post(`${roomTypeToApiType(t)}.archive`, { roomId });
+ }
+ // RC 0.48.0
+ // TODO: missing definitions from server
+ // @ts-ignore
+ return sdk.post(`${roomTypeToApiType(t)}.unarchive`, { roomId });
+};
+
+export const hideRoom = (roomId: string, t: RoomTypes): any =>
+ // RC 0.48.0
+ // TODO: missing definitions from server
+ // @ts-ignore
+ sdk.post(`${roomTypeToApiType(t)}.close`, { roomId });
+
+export const saveRoomSettings = (rid: string, params: any) =>
+ // RC 0.55.0
+ sdk.methodCallWrapper('saveRoomSettings', rid, params);
+
+export const saveUserProfile = (data: any, customFields?: any): any =>
+ // RC 0.62.2
+ // TODO: missing definitions from server
+ // @ts-ignore
+ sdk.post('users.updateOwnBasicInfo', { data, customFields });
+
+export const saveUserPreferences = (data: any): any =>
+ // RC 0.62.0
+ // TODO: missing definitions from server
+ // @ts-ignore
+ sdk.post('users.setPreferences', { data });
+
+export const saveNotificationSettings = (roomId: string, notifications: any): any =>
+ // RC 0.63.0
+ // TODO: missing definitions from server
+ // @ts-ignore
+ sdk.post('rooms.saveNotification', { roomId, notifications });
+
+export const getSingleMessage = (msgId: string) =>
+ // RC 0.47.0
+ sdk.get('chat.getMessage', { msgId });
+
+export const getRoomRoles = (roomId: string, type: RoomTypes): any =>
+ // RC 0.65.0
+ // TODO: missing definitions from server
+ // @ts-ignore
+ sdk.get(`${roomTypeToApiType(type)}.roles`, { roomId });
+
+export const getAvatarSuggestion = () =>
+ // RC 0.51.0
+ sdk.methodCallWrapper('getAvatarSuggestion');
+
+export const resetAvatar = (userId: string): any =>
+ // RC 0.55.0
+ // TODO: missing definitions from server
+ // @ts-ignore
+ sdk.post('users.resetAvatar', { userId });
+
+export const setAvatarFromService = ({
+ data,
+ contentType = '',
+ service = null
+}: {
+ data: any;
+ contentType?: string;
+ service?: string | null;
+}) =>
+ // RC 0.51.0
+ sdk.methodCallWrapper('setAvatarFromService', data, contentType, service);
+
+export const getUsernameSuggestion = (): any =>
+ // RC 0.65.0
+ // TODO: missing definitions from server
+ // @ts-ignore
+ sdk.get('users.getUsernameSuggestion');
+
+export const getFiles = (roomId: string, type: RoomTypes, offset: number): any =>
+ // RC 0.59.0
+ // TODO: missing definitions from server
+ // @ts-ignore
+ sdk.get(`${roomTypeToApiType(type)}.files`, {
+ roomId,
+ offset,
+ sort: { uploadedAt: -1 }
+ });
+
+export const getMessages = (roomId: string, type: RoomTypes, query: any, offset: number): any =>
+ // RC 0.59.0
+ // TODO: missing definitions from server
+ // @ts-ignore
+ sdk.get(`${roomTypeToApiType(type)}.messages`, {
+ roomId,
+ query,
+ offset,
+ sort: { ts: -1 }
+ });
+
+export const getReadReceipts = (messageId: string): any =>
+ // RC 0.63.0
+ // TODO: missing definitions from server
+ // @ts-ignore
+ sdk.get('chat.getMessageReadReceipts', {
+ messageId
+ });
+
+export const searchMessages = (roomId: string, searchText: string, count: number, offset: number): any =>
+ // RC 0.60.0
+ // TODO: missing definitions from server
+ // @ts-ignore
+ sdk.get('chat.search', {
+ roomId,
+ searchText,
+ count,
+ offset
+ });
+
+export const toggleFollowMessage = (mid: string, follow: boolean) => {
+ // RC 1.0
+ if (follow) {
+ return sdk.post('chat.followMessage', { mid });
+ }
+ return sdk.post('chat.unfollowMessage', { mid });
+};
+
+export const getThreadsList = ({ rid, count, offset, text }: { rid: string; count: number; offset: number; text?: string }) => {
+ const params: any = {
+ rid,
+ count,
+ offset,
+ sort: { ts: -1 }
+ };
+ if (text) {
+ params.text = text;
+ }
+
+ // RC 1.0
+ return sdk.get('chat.getThreadsList', params);
+};
+
+export const getSyncThreadsList = ({ rid, updatedSince }: { rid: string; updatedSince: string }): any =>
+ // RC 1.0
+ // TODO: missing definitions from server
+ // @ts-ignore
+ sdk.get('chat.syncThreadsList', {
+ rid,
+ updatedSince
+ });
+
+export const runSlashCommand = (command: string, roomId: string, params: any, triggerId?: string, tmid?: string): any =>
+ // RC 0.60.2
+ // TODO: missing definitions from server
+ // @ts-ignore
+ sdk.post('commands.run', {
+ command,
+ roomId,
+ params,
+ triggerId,
+ tmid
+ });
+
+export const getCommandPreview = (command: string, roomId: string, params: any): any =>
+ // RC 0.65.0
+ // TODO: missing definitions from server
+ // @ts-ignore
+ sdk.get('commands.preview', {
+ command,
+ roomId,
+ params
+ });
+
+export const executeCommandPreview = (
+ command: string,
+ params: any,
+ roomId: string,
+ previewItem: any,
+ triggerId: string,
+ tmid?: string
+): any =>
+ // RC 0.65.0
+ // TODO: missing definitions from server
+ // @ts-ignore
+ sdk.post('commands.preview', {
+ command,
+ params,
+ roomId,
+ previewItem,
+ triggerId,
+ tmid
+ });
+
+export const getDirectory = ({ query, count, offset, sort }: { query: any; count: number; offset: number; sort: any }): any =>
+ // RC 1.0
+ // TODO: missing definitions from server
+ // @ts-ignore
+ sdk.get('directory', {
+ query,
+ count,
+ offset,
+ sort
+ });
+
+export const saveAutoTranslate = ({ rid, field, value, options }: { rid: string; field: string; value: any; options: any }) =>
+ sdk.methodCallWrapper('autoTranslate.saveSettings', rid, field, value, options);
+
+export const getSupportedLanguagesAutoTranslate = () => sdk.methodCallWrapper('autoTranslate.getSupportedLanguages', 'en');
+
+export const translateMessage = (message: any, targetLanguage: string) =>
+ sdk.methodCallWrapper('autoTranslate.translateMessage', message, targetLanguage);
+
+export const findOrCreateInvite = ({ rid, days, maxUses }: { rid: string; days: number; maxUses: number }): any =>
+ // RC 2.4.0
+ // TODO: missing definitions from server
+ // @ts-ignore
+ sdk.post('findOrCreateInvite', { rid, days, maxUses });
+
+export const validateInviteToken = (token: string): any =>
+ // RC 2.4.0
+ // TODO: missing definitions from server
+ // @ts-ignore
+ sdk.post('validateInviteToken', { token });
+
+export const useInviteToken = (token: string): any =>
+ // RC 2.4.0
+ // TODO: missing definitions from server
+ // @ts-ignore
+ sdk.post('useInviteToken', { token });
diff --git a/app/lib/rocketchat/services/sdk.ts b/app/lib/rocketchat/services/sdk.ts
new file mode 100644
index 000000000..41c124b11
--- /dev/null
+++ b/app/lib/rocketchat/services/sdk.ts
@@ -0,0 +1,165 @@
+import { Rocketchat } from '@rocket.chat/sdk';
+import EJSON from 'ejson';
+import isEmpty from 'lodash/isEmpty';
+
+import { twoFactor } from '../../../utils/twoFactor';
+import { useSsl } from '../../../utils/url';
+import reduxStore from '../../createStore';
+import { Serialized, MatchPathPattern, OperationParams, PathFor, ResultFor } from '../../../definitions/rest/helpers';
+
+class Sdk {
+ private sdk: typeof Rocketchat;
+ private code: any;
+
+ // TODO: We need to stop returning the SDK after all methods are dehydrated
+ initialize(server: string) {
+ this.code = null;
+
+ // The app can't reconnect if reopen interval is 5s while in development
+ this.sdk = new Rocketchat({ host: server, protocol: 'ddp', useSsl: useSsl(server), reopen: __DEV__ ? 20000 : 5000 });
+ return this.sdk;
+ }
+
+ get current() {
+ return this.sdk;
+ }
+
+ /**
+ * TODO: evaluate the need for assigning "null" to this.sdk
+ * I'm returning "null" because we need to remove both instances of this.sdk here and on rocketchat.js
+ */
+ disconnect() {
+ if (this.sdk) {
+ this.sdk.disconnect();
+ this.sdk = null;
+ }
+ return null;
+ }
+
+ get>(
+ endpoint: TPath,
+ params: void extends OperationParams<'GET', MatchPathPattern>
+ ? void
+ : Serialized>> = undefined as void extends OperationParams<
+ 'GET',
+ MatchPathPattern
+ >
+ ? void
+ : Serialized>>
+ ): Promise>>> {
+ return this.sdk.get(endpoint, params);
+ }
+
+ post>(
+ endpoint: TPath,
+ params: void extends OperationParams<'POST', MatchPathPattern>
+ ? void
+ : Serialized>> = undefined as void extends OperationParams<
+ 'POST',
+ MatchPathPattern
+ >
+ ? void
+ : Serialized>>
+ ): Promise>> {
+ return new Promise(async (resolve, reject) => {
+ const isMethodCall = endpoint?.startsWith('method.call/');
+ try {
+ const result = await this.sdk.post(endpoint, params);
+
+ /**
+ * 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);
+ } catch (e: any) {
+ const errorType = isMethodCall ? e?.error : e?.data?.errorType;
+ const totpInvalid = 'totp-invalid';
+ const totpRequired = 'totp-required';
+ if ([totpInvalid, totpRequired].includes(errorType)) {
+ const { details } = isMethodCall ? e : e?.data;
+ try {
+ await twoFactor({ method: details?.method, invalid: errorType === totpInvalid });
+ return resolve(this.post(endpoint, params));
+ } catch {
+ // twoFactor was canceled
+ return resolve({} as any);
+ }
+ } else {
+ reject(e);
+ }
+ }
+ });
+ }
+
+ methodCall(...args: any[]): Promise {
+ return new Promise(async (resolve, reject) => {
+ try {
+ const result = await this.sdk?.methodCall(...args, this.code || '');
+ return resolve(result);
+ } catch (e: any) {
+ if (e.error && (e.error === 'totp-required' || e.error === 'totp-invalid')) {
+ const { details } = e;
+ try {
+ this.code = await twoFactor({ method: details?.method, invalid: e.error === 'totp-invalid' });
+ return resolve(this.methodCall(...args));
+ } catch {
+ // twoFactor was canceled
+ return resolve({});
+ }
+ } else {
+ reject(e);
+ }
+ }
+ });
+ }
+
+ methodCallWrapper(method: string, ...params: any[]): Promise {
+ const { API_Use_REST_For_DDP_Calls } = reduxStore.getState().settings;
+ const { user } = reduxStore.getState().login;
+ if (API_Use_REST_For_DDP_Calls) {
+ const url = isEmpty(user) ? 'method.callAnon' : 'method.call';
+ // @ts-ignore
+ return this.post(`${url}/${method}`, {
+ message: EJSON.stringify({ method, params })
+ });
+ }
+ const parsedParams = params.map(param => {
+ if (param instanceof Date) {
+ return { $date: new Date(param).getTime() };
+ }
+ return param;
+ });
+ return this.methodCall(method, ...parsedParams);
+ }
+
+ subscribe(...args: any[]) {
+ return this.sdk.subscribe(...args);
+ }
+
+ subscribeRaw(...args: any[]) {
+ return this.sdk.subscribeRaw(...args);
+ }
+
+ subscribeRoom(...args: any[]) {
+ return this.sdk.subscribeRoom(...args);
+ }
+
+ unsubscribe(subscription: any[]) {
+ return this.sdk.unsubscribe(subscription);
+ }
+
+ onStreamData(...args: any[]) {
+ return this.sdk.onStreamData(...args);
+ }
+}
+
+const sdk = new Sdk();
+
+export default sdk;
diff --git a/app/lib/rocketchat/services/toggleFavorite.ts b/app/lib/rocketchat/services/toggleFavorite.ts
new file mode 100644
index 000000000..ed3049634
--- /dev/null
+++ b/app/lib/rocketchat/services/toggleFavorite.ts
@@ -0,0 +1,6 @@
+import sdk from './sdk';
+
+// RC 0.64.0
+const toggleFavorite = (roomId: string, favorite: boolean) => sdk.post('rooms.favorite', { roomId, favorite });
+
+export default toggleFavorite;
diff --git a/app/lib/utils.js b/app/lib/utils.js
deleted file mode 100644
index 5776a3389..000000000
--- a/app/lib/utils.js
+++ /dev/null
@@ -1,23 +0,0 @@
-import { coerce, gt, gte, lt, lte } from 'semver';
-
-export const formatAttachmentUrl = (attachmentUrl, userId, token, server) => {
- if (attachmentUrl.startsWith('http')) {
- if (attachmentUrl.includes('rc_token')) {
- return encodeURI(attachmentUrl);
- }
- return encodeURI(`${attachmentUrl}?rc_uid=${userId}&rc_token=${token}`);
- }
- return encodeURI(`${server}${attachmentUrl}?rc_uid=${userId}&rc_token=${token}`);
-};
-
-export const methods = {
- lowerThan: lt,
- lowerThanOrEqualTo: lte,
- greaterThan: gt,
- greaterThanOrEqualTo: gte
-};
-
-export const compareServerVersion = (currentServerVersion, versionToCompare, func) =>
- currentServerVersion && func(coerce(currentServerVersion), versionToCompare);
-
-export const generateLoadMoreId = id => `load-more-${id}`;
diff --git a/app/lib/utils.ts b/app/lib/utils.ts
new file mode 100644
index 000000000..bece818b2
--- /dev/null
+++ b/app/lib/utils.ts
@@ -0,0 +1,27 @@
+import { coerce, gt, gte, lt, lte, SemVer } from 'semver';
+
+export const formatAttachmentUrl = (attachmentUrl: string, userId: string, token: string, server: string): string => {
+ if (attachmentUrl.startsWith('http')) {
+ if (attachmentUrl.includes('rc_token')) {
+ return encodeURI(attachmentUrl);
+ }
+ return encodeURI(`${attachmentUrl}?rc_uid=${userId}&rc_token=${token}`);
+ }
+ return encodeURI(`${server}${attachmentUrl}?rc_uid=${userId}&rc_token=${token}`);
+};
+
+const methods = {
+ lowerThan: lt,
+ lowerThanOrEqualTo: lte,
+ greaterThan: gt,
+ greaterThanOrEqualTo: gte
+};
+
+export const compareServerVersion = (
+ currentServerVersion: string,
+ method: keyof typeof methods,
+ versionToCompare: string
+): boolean =>
+ (currentServerVersion && methods[method](coerce(currentServerVersion) as string | SemVer, versionToCompare)) as boolean;
+
+export const generateLoadMoreId = (id: string): string => `load-more-${id}`;
diff --git a/app/notifications/push/index.ts b/app/notifications/push/index.ts
index 09fa86027..749517642 100644
--- a/app/notifications/push/index.ts
+++ b/app/notifications/push/index.ts
@@ -1,6 +1,6 @@
import EJSON from 'ejson';
-import store from '../../lib/createStore';
+import { store } from '../../lib/auxStore';
import { deepLinkingOpen } from '../../actions/deepLinking';
import { isFDroidBuild } from '../../constants/environment';
import PushNotification from './push';
diff --git a/app/notifications/push/push.ios.ts b/app/notifications/push/push.ios.ts
index a6b1118c5..b9713af2a 100644
--- a/app/notifications/push/push.ios.ts
+++ b/app/notifications/push/push.ios.ts
@@ -2,7 +2,7 @@
// TODO BUMP LIB VERSION
import NotificationsIOS, { NotificationAction, NotificationCategory, Notification } from 'react-native-notifications';
-import reduxStore from '../../lib/createStore';
+import { store as reduxStore } from '../../lib/auxStore';
import I18n from '../../i18n';
import { INotification } from '../../definitions/INotification';
diff --git a/app/presentation/RoomItem/LastMessage.tsx b/app/presentation/RoomItem/LastMessage.tsx
index 1f0a0a486..b21f0a6f3 100644
--- a/app/presentation/RoomItem/LastMessage.tsx
+++ b/app/presentation/RoomItem/LastMessage.tsx
@@ -3,7 +3,7 @@ import { dequal } from 'dequal';
import I18n from '../../i18n';
import styles from './styles';
-import Markdown from '../../containers/markdown';
+import { MarkdownPreview } from '../../containers/markdown';
import { themes } from '../../constants/colors';
import { E2E_MESSAGE_TYPE, E2E_STATUS } from '../../lib/encryption/constants';
@@ -65,8 +65,7 @@ const arePropsEqual = (oldProps: any, newProps: any) => dequal(oldProps, newProp
const LastMessage = React.memo(
({ lastMessage, type, showLastMessage, username, alert, useRealName, theme }: ILastMessage) => (
- // @ts-ignore
-
),
diff --git a/app/presentation/TextInput.tsx b/app/presentation/TextInput.tsx
index 4d575fa8f..6b6e61a9e 100644
--- a/app/presentation/TextInput.tsx
+++ b/app/presentation/TextInput.tsx
@@ -15,7 +15,7 @@ interface IThemedTextInput extends IRCTextInputProps {
theme: string;
}
-const ThemedTextInput = React.forwardRef(({ style, theme, ...props }: IThemedTextInput, ref: any) => (
+const ThemedTextInput = React.forwardRef(({ style, theme, ...props }, ref) => (
{
expect(state).toEqual(initialState);
});
it('should return modified store after action', () => {
- const activeUsers: IActiveUsers = { any: { status: 'online', statusText: 'any' } };
+ const activeUsers: IActiveUsers = { any: { status: UserStatus.ONLINE, statusText: 'any' } };
mockedStore.dispatch(setActiveUsers(activeUsers));
const state = mockedStore.getState().activeUsers;
expect(state).toEqual({ ...activeUsers });
});
+ it('should return initial state after dispatching clear', () => {
+ const previousState = mockedStore.getState().activeUsers;
+ expect(previousState).not.toBe(initialState);
+ mockedStore.dispatch(clearActiveUsers());
+ const state = mockedStore.getState().activeUsers;
+ expect(state).toEqual(initialState);
+ });
});
diff --git a/app/reducers/activeUsers.ts b/app/reducers/activeUsers.ts
index 9877a5ceb..34a55ce6d 100644
--- a/app/reducers/activeUsers.ts
+++ b/app/reducers/activeUsers.ts
@@ -1,10 +1,10 @@
+import { ACTIVE_USERS } from '../actions/actionsTypes';
import { TApplicationActions } from '../definitions';
-import { SET_ACTIVE_USERS } from '../actions/actionsTypes';
+import { UserStatus } from '../definitions/UserStatus';
-type TUserStatus = 'online' | 'offline';
export interface IActiveUser {
- status: TUserStatus;
- statusText?: string;
+ status: UserStatus;
+ statusText: string;
}
export interface IActiveUsers {
@@ -15,11 +15,13 @@ export const initialState: IActiveUsers = {};
export default function activeUsers(state = initialState, action: TApplicationActions): IActiveUsers {
switch (action.type) {
- case SET_ACTIVE_USERS:
+ case ACTIVE_USERS.SET:
return {
...state,
...action.activeUsers
};
+ case ACTIVE_USERS.CLEAR:
+ return initialState;
default:
return state;
}
diff --git a/app/reducers/app.test.ts b/app/reducers/app.test.ts
new file mode 100644
index 000000000..a4910ab0f
--- /dev/null
+++ b/app/reducers/app.test.ts
@@ -0,0 +1,44 @@
+import { appStart, appInit, setMasterDetail } from '../actions/app';
+import { initialState } from './app';
+import { mockedStore } from './mockedStore';
+import { RootEnum } from '../definitions';
+import { APP_STATE } from '../actions/actionsTypes';
+
+describe('test reducer', () => {
+ it('should return initial state', () => {
+ const state = mockedStore.getState().app;
+ expect(state).toEqual(initialState);
+ });
+
+ it('should return root state after dispatch appStart action', () => {
+ mockedStore.dispatch(appStart({ root: RootEnum.ROOT_INSIDE }));
+ const { root } = mockedStore.getState().app;
+ expect(root).toEqual(RootEnum.ROOT_INSIDE);
+ });
+
+ it('should return ready state after dispatch appInit action', () => {
+ mockedStore.dispatch(appInit());
+ const { ready } = mockedStore.getState().app;
+ expect(ready).toEqual(false);
+ });
+
+ it('should return ready state after dispatch setMasterDetail action', () => {
+ mockedStore.dispatch(setMasterDetail(false));
+ const { isMasterDetail } = mockedStore.getState().app;
+ expect(isMasterDetail).toEqual(false);
+ });
+
+ it('should return correct state after app go to foreground', () => {
+ mockedStore.dispatch({ type: APP_STATE.FOREGROUND });
+ const { foreground, background } = mockedStore.getState().app;
+ expect(foreground).toEqual(true);
+ expect(background).toEqual(false);
+ });
+
+ it('should return correct state after app go to background', () => {
+ mockedStore.dispatch({ type: APP_STATE.BACKGROUND });
+ const { foreground, background } = mockedStore.getState().app;
+ expect(foreground).toEqual(false);
+ expect(background).toEqual(true);
+ });
+});
diff --git a/app/reducers/app.js b/app/reducers/app.ts
similarity index 65%
rename from app/reducers/app.js
rename to app/reducers/app.ts
index 4ed04dcd7..5cd948973 100644
--- a/app/reducers/app.js
+++ b/app/reducers/app.ts
@@ -1,15 +1,26 @@
+import { TActionApp } from '../actions/app';
+import { RootEnum } from '../definitions';
import { APP, APP_STATE } from '../actions/actionsTypes';
-const initialState = {
- root: null,
+export interface IApp {
+ root?: RootEnum;
+ isMasterDetail: boolean;
+ text?: string;
+ ready: boolean;
+ foreground: boolean;
+ background: boolean;
+}
+
+export const initialState: IApp = {
+ root: undefined,
isMasterDetail: false,
- text: null,
+ text: undefined,
ready: false,
foreground: true,
background: false
};
-export default function app(state = initialState, action) {
+export default function app(state = initialState, action: TActionApp): IApp {
switch (action.type) {
case APP_STATE.FOREGROUND:
return {
diff --git a/app/reducers/connect.test.ts b/app/reducers/connect.test.ts
new file mode 100644
index 000000000..241abf51b
--- /dev/null
+++ b/app/reducers/connect.test.ts
@@ -0,0 +1,28 @@
+import { connectRequest, connectSuccess, disconnect } from '../actions/connect';
+import { initialState } from './connect';
+import { mockedStore } from './mockedStore';
+
+describe('test reducer', () => {
+ it('should return initial state', () => {
+ const { meteor } = mockedStore.getState();
+ expect(meteor).toEqual(initialState);
+ });
+
+ it('should return correct meteor state after dispatch connectRequest action', () => {
+ mockedStore.dispatch(connectRequest());
+ const { meteor } = mockedStore.getState();
+ expect(meteor).toEqual({ connecting: true, connected: false });
+ });
+
+ it('should return correct meteor state after dispatch connectSuccess action', () => {
+ mockedStore.dispatch(connectSuccess());
+ const { meteor } = mockedStore.getState();
+ expect(meteor).toEqual({ connecting: false, connected: true });
+ });
+
+ it('should return correct meteor state after dispatch disconnect action', () => {
+ mockedStore.dispatch(disconnect());
+ const { meteor } = mockedStore.getState();
+ expect(meteor).toEqual(initialState);
+ });
+});
diff --git a/app/reducers/connect.js b/app/reducers/connect.ts
similarity index 63%
rename from app/reducers/connect.js
rename to app/reducers/connect.ts
index a8ef2bea1..beffc58bd 100644
--- a/app/reducers/connect.js
+++ b/app/reducers/connect.ts
@@ -1,11 +1,18 @@
+import { Action } from 'redux';
+
import { METEOR } from '../actions/actionsTypes';
-const initialState = {
+export interface IConnect {
+ connecting: boolean;
+ connected: boolean;
+}
+
+export const initialState: IConnect = {
connecting: false,
connected: false
};
-export default function connect(state = initialState, action) {
+export default function connect(state = initialState, action: Action): IConnect {
switch (action.type) {
case METEOR.REQUEST:
return {
diff --git a/app/reducers/createChannel.js b/app/reducers/createChannel.js
deleted file mode 100644
index 60e13f007..000000000
--- a/app/reducers/createChannel.js
+++ /dev/null
@@ -1,36 +0,0 @@
-import { CREATE_CHANNEL } from '../actions/actionsTypes';
-
-const initialState = {
- isFetching: false,
- failure: false,
- result: {},
- error: {}
-};
-
-export default function (state = initialState, action) {
- switch (action.type) {
- case CREATE_CHANNEL.REQUEST:
- return {
- ...state,
- isFetching: true,
- failure: false,
- error: {}
- };
- case CREATE_CHANNEL.SUCCESS:
- return {
- ...state,
- isFetching: false,
- failure: false,
- result: action.data
- };
- case CREATE_CHANNEL.FAILURE:
- return {
- ...state,
- isFetching: false,
- failure: true,
- error: action.err
- };
- default:
- return state;
- }
-}
diff --git a/app/reducers/createChannel.test.ts b/app/reducers/createChannel.test.ts
new file mode 100644
index 000000000..ed51eb45d
--- /dev/null
+++ b/app/reducers/createChannel.test.ts
@@ -0,0 +1,44 @@
+import { createChannelRequest, createChannelSuccess, createChannelFailure } from '../actions/createChannel';
+import { initialState } from './createChannel';
+import { mockedStore } from './mockedStore';
+
+describe('test reducer', () => {
+ const data = {
+ name: 'test',
+ users: ['diego', 'karla'],
+ type: true,
+ readOnly: true,
+ broadcast: true,
+ encrypted: true,
+ isTeam: true,
+ teamId: 'xxx'
+ };
+
+ it('should return initial state', () => {
+ const { createChannel } = mockedStore.getState();
+ expect(createChannel).toEqual(initialState);
+ });
+
+ it('should return correct createChannel state after dispatch createChannelRequest action', () => {
+ mockedStore.dispatch(createChannelRequest(data));
+ const { createChannel } = mockedStore.getState();
+ expect(createChannel).toEqual({ isFetching: true, failure: false, error: {}, result: {} });
+ });
+
+ it('should return correct createChannel state after dispatch createChannelSuccess action', () => {
+ mockedStore.dispatch(createChannelSuccess(data));
+ const { createChannel } = mockedStore.getState();
+ expect(createChannel).toEqual({ isFetching: false, failure: false, result: { ...data }, error: {} });
+ });
+
+ it('should return correct createChannel state after dispatch createChannelFailure action', () => {
+ mockedStore.dispatch(createChannelFailure({ err: true }, true));
+ const { createChannel } = mockedStore.getState();
+ expect(createChannel).toEqual({
+ isFetching: false,
+ failure: true,
+ result: { ...data },
+ error: { err: true }
+ });
+ });
+});
diff --git a/app/reducers/createChannel.ts b/app/reducers/createChannel.ts
new file mode 100644
index 000000000..713a59f4e
--- /dev/null
+++ b/app/reducers/createChannel.ts
@@ -0,0 +1,61 @@
+import { TApplicationActions } from '../definitions';
+import { CREATE_CHANNEL } from '../actions/actionsTypes';
+
+interface ICreateChannelResult {
+ name: string;
+ users: string[];
+ teamId: string;
+ type: boolean;
+ readOnly: boolean;
+ encrypted: boolean;
+ broadcast: boolean;
+ isTeam: boolean;
+}
+
+interface ICreateChannelResultOnlyGroup {
+ group: boolean;
+}
+
+export type TCreateChannelResult = ICreateChannelResult | ICreateChannelResultOnlyGroup;
+
+export interface ICreateChannel {
+ isFetching: boolean;
+ failure: boolean;
+ result: TCreateChannelResult | {};
+ error: any;
+}
+
+export const initialState: ICreateChannel = {
+ isFetching: false,
+ failure: false,
+ result: {},
+ error: {}
+};
+
+export default function (state = initialState, action: TApplicationActions): ICreateChannel {
+ switch (action.type) {
+ case CREATE_CHANNEL.REQUEST:
+ return {
+ ...state,
+ isFetching: true,
+ failure: false,
+ error: {}
+ };
+ case CREATE_CHANNEL.SUCCESS:
+ return {
+ ...state,
+ isFetching: false,
+ failure: false,
+ result: action.data
+ };
+ case CREATE_CHANNEL.FAILURE:
+ return {
+ ...state,
+ isFetching: false,
+ failure: true,
+ error: action.err
+ };
+ default:
+ return state;
+ }
+}
diff --git a/app/reducers/createDiscussion.test.ts b/app/reducers/createDiscussion.test.ts
new file mode 100644
index 000000000..0291cbb37
--- /dev/null
+++ b/app/reducers/createDiscussion.test.ts
@@ -0,0 +1,33 @@
+import { createDiscussionRequest, createDiscussionSuccess, createDiscussionFailure } from '../actions/createDiscussion';
+import { initialState } from './createDiscussion';
+import { mockedStore } from './mockedStore';
+
+describe('test reducer', () => {
+ it('should return initial state', () => {
+ const { createDiscussion } = mockedStore.getState();
+ expect(createDiscussion).toEqual(initialState);
+ });
+
+ it('should return correct createDiscussion state after dispatch createDiscussionRequest action', () => {
+ mockedStore.dispatch(createDiscussionRequest({}));
+ const { createDiscussion } = mockedStore.getState();
+ expect(createDiscussion).toEqual({ isFetching: true, failure: false, error: {}, result: {} });
+ });
+
+ it('should return correct createDiscussion state after dispatch createDiscussionSuccess action', () => {
+ mockedStore.dispatch(createDiscussionSuccess({ data: true }));
+ const { createDiscussion } = mockedStore.getState();
+ expect(createDiscussion).toEqual({ isFetching: false, failure: false, result: { data: true }, error: {} });
+ });
+
+ it('should return correct createDiscussion state after dispatch createDiscussionFailure action', () => {
+ mockedStore.dispatch(createDiscussionFailure({ err: true }));
+ const { createDiscussion } = mockedStore.getState();
+ expect(createDiscussion).toEqual({
+ isFetching: false,
+ failure: true,
+ result: { data: true },
+ error: { err: true }
+ });
+ });
+});
diff --git a/app/reducers/createDiscussion.js b/app/reducers/createDiscussion.ts
similarity index 62%
rename from app/reducers/createDiscussion.js
rename to app/reducers/createDiscussion.ts
index 04081d157..4b1bada91 100644
--- a/app/reducers/createDiscussion.js
+++ b/app/reducers/createDiscussion.ts
@@ -1,13 +1,21 @@
+import { TApplicationActions } from '../definitions';
import { CREATE_DISCUSSION } from '../actions/actionsTypes';
-const initialState = {
+export interface ICreateDiscussion {
+ isFetching: boolean;
+ failure: boolean;
+ result: Record;
+ error: Record;
+}
+
+export const initialState: ICreateDiscussion = {
isFetching: false,
failure: false,
result: {},
error: {}
};
-export default function (state = initialState, action) {
+export default function (state = initialState, action: TApplicationActions): ICreateDiscussion {
switch (action.type) {
case CREATE_DISCUSSION.REQUEST:
return {
diff --git a/app/reducers/customEmojis.js b/app/reducers/customEmojis.js
deleted file mode 100644
index fbdaeab80..000000000
--- a/app/reducers/customEmojis.js
+++ /dev/null
@@ -1,14 +0,0 @@
-import { SET_CUSTOM_EMOJIS } from '../actions/actionsTypes';
-
-const initialState = {
- customEmojis: {}
-};
-
-export default function customEmojis(state = initialState, action) {
- switch (action.type) {
- case SET_CUSTOM_EMOJIS:
- return action.emojis;
- default:
- return state;
- }
-}
diff --git a/app/reducers/customEmojis.test.ts b/app/reducers/customEmojis.test.ts
new file mode 100644
index 000000000..3f507f04c
--- /dev/null
+++ b/app/reducers/customEmojis.test.ts
@@ -0,0 +1,16 @@
+import { setCustomEmojis } from '../actions/customEmojis';
+import { ICustomEmojis, initialState } from './customEmojis';
+import { mockedStore } from './mockedStore';
+
+describe('test reducer', () => {
+ it('should return initial state', () => {
+ const state = mockedStore.getState().customEmojis;
+ expect(state).toEqual(initialState);
+ });
+ it('should return modified store after action', () => {
+ const emojis: ICustomEmojis = { dog: { name: 'dog', extension: 'jpg' }, cat: { name: 'cat', extension: 'jpg' } };
+ mockedStore.dispatch(setCustomEmojis(emojis));
+ const state = mockedStore.getState().customEmojis;
+ expect(state).toEqual(emojis);
+ });
+});
diff --git a/app/reducers/customEmojis.ts b/app/reducers/customEmojis.ts
new file mode 100644
index 000000000..859d634d4
--- /dev/null
+++ b/app/reducers/customEmojis.ts
@@ -0,0 +1,23 @@
+import { SET_CUSTOM_EMOJIS } from '../actions/actionsTypes';
+import { TApplicationActions } from '../definitions';
+
+// There are at least three interfaces for emoji, but none of them includes only this data.
+interface IEmoji {
+ name: string;
+ extension: string;
+}
+
+export interface ICustomEmojis {
+ [key: string]: IEmoji;
+}
+
+export const initialState: ICustomEmojis = {};
+
+export default function customEmojis(state = initialState, action: TApplicationActions): ICustomEmojis {
+ switch (action.type) {
+ case SET_CUSTOM_EMOJIS:
+ return action.emojis;
+ default:
+ return state;
+ }
+}
diff --git a/app/reducers/encryption.test.ts b/app/reducers/encryption.test.ts
new file mode 100644
index 000000000..96abe2a7a
--- /dev/null
+++ b/app/reducers/encryption.test.ts
@@ -0,0 +1,28 @@
+import { encryptionSet, encryptionInit, encryptionSetBanner } from '../actions/encryption';
+import { mockedStore } from './mockedStore';
+import { initialState } from './encryption';
+
+describe('test encryption reducer', () => {
+ it('should return initial state', () => {
+ const state = mockedStore.getState().encryption;
+ expect(state).toEqual(initialState);
+ });
+
+ it('should return modified store after encryptionSet', () => {
+ mockedStore.dispatch(encryptionSet(true, 'BANNER'));
+ const state = mockedStore.getState().encryption;
+ expect(state).toEqual({ banner: 'BANNER', enabled: true });
+ });
+
+ it('should return empty store after encryptionInit', () => {
+ mockedStore.dispatch(encryptionInit());
+ const state = mockedStore.getState().encryption;
+ expect(state).toEqual({ banner: '', enabled: false });
+ });
+
+ it('should return initial state after encryptionSetBanner', () => {
+ mockedStore.dispatch(encryptionSetBanner('BANNER_NEW'));
+ const state = mockedStore.getState().encryption;
+ expect(state).toEqual({ banner: 'BANNER_NEW', enabled: false });
+ });
+});
diff --git a/app/reducers/encryption.js b/app/reducers/encryption.ts
similarity index 54%
rename from app/reducers/encryption.js
rename to app/reducers/encryption.ts
index 0145ae2d1..061dc7c94 100644
--- a/app/reducers/encryption.js
+++ b/app/reducers/encryption.ts
@@ -1,11 +1,18 @@
import { ENCRYPTION } from '../actions/actionsTypes';
+import { TApplicationActions } from '../definitions';
-const initialState = {
+export type IBanner = string;
+export interface IEncryption {
+ enabled: boolean;
+ banner: IBanner;
+}
+
+export const initialState: IEncryption = {
enabled: false,
- banner: null
+ banner: ''
};
-export default function encryption(state = initialState, action) {
+export default function encryption(state = initialState, action: TApplicationActions): IEncryption {
switch (action.type) {
case ENCRYPTION.SET:
return {
diff --git a/app/reducers/enterpriseModules.js b/app/reducers/enterpriseModules.js
deleted file mode 100644
index 2f1a7ac9a..000000000
--- a/app/reducers/enterpriseModules.js
+++ /dev/null
@@ -1,14 +0,0 @@
-import { ENTERPRISE_MODULES } from '../actions/actionsTypes';
-
-const initialState = [];
-
-export default (state = initialState, action) => {
- switch (action.type) {
- case ENTERPRISE_MODULES.SET:
- return action.payload;
- case ENTERPRISE_MODULES.CLEAR:
- return initialState;
- default:
- return state;
- }
-};
diff --git a/app/reducers/enterpriseModules.test.ts b/app/reducers/enterpriseModules.test.ts
new file mode 100644
index 000000000..7daf14167
--- /dev/null
+++ b/app/reducers/enterpriseModules.test.ts
@@ -0,0 +1,23 @@
+import { clearEnterpriseModules, setEnterpriseModules } from '../actions/enterpriseModules';
+import { mockedStore } from './mockedStore';
+import { IEnterpriseModules, initialState } from './enterpriseModules';
+
+describe('test enterpriseModules reducer', () => {
+ it('should return initial state', () => {
+ const state = mockedStore.getState().enterpriseModules;
+ expect(state).toEqual(initialState);
+ });
+
+ it('should return modified store after setEnterpriseModules', () => {
+ const modules = ['omnichannel-mobile-enterprise', 'livechat-enterprise'] as IEnterpriseModules[];
+ mockedStore.dispatch(setEnterpriseModules(modules));
+ const state = mockedStore.getState().enterpriseModules;
+ expect(state).toEqual(modules);
+ });
+
+ it('should return empty store after clearEnterpriseModules', () => {
+ mockedStore.dispatch(clearEnterpriseModules());
+ const state = mockedStore.getState().enterpriseModules;
+ expect(state).toEqual([]);
+ });
+});
diff --git a/app/reducers/enterpriseModules.ts b/app/reducers/enterpriseModules.ts
new file mode 100644
index 000000000..78616e246
--- /dev/null
+++ b/app/reducers/enterpriseModules.ts
@@ -0,0 +1,17 @@
+import { TActionEnterpriseModules } from '../actions/enterpriseModules';
+import { ENTERPRISE_MODULES } from '../actions/actionsTypes';
+
+export type IEnterpriseModules = 'omnichannel-mobile-enterprise' | 'livechat-enterprise';
+
+export const initialState: IEnterpriseModules[] = [];
+
+export default (state = initialState, action: TActionEnterpriseModules): IEnterpriseModules[] => {
+ switch (action.type) {
+ case ENTERPRISE_MODULES.SET:
+ return action.payload;
+ case ENTERPRISE_MODULES.CLEAR:
+ return initialState;
+ default:
+ return state;
+ }
+};
diff --git a/app/reducers/inviteLinks.test.ts b/app/reducers/inviteLinks.test.ts
new file mode 100644
index 000000000..ed1a34a46
--- /dev/null
+++ b/app/reducers/inviteLinks.test.ts
@@ -0,0 +1,72 @@
+import {
+ inviteLinksClear,
+ inviteLinksFailure,
+ inviteLinksRequest,
+ inviteLinksSetInvite,
+ inviteLinksSetParams,
+ inviteLinksSetToken,
+ inviteLinksSuccess
+} from '../actions/inviteLinks';
+import { initialState } from './inviteLinks';
+import { mockedStore } from './mockedStore';
+
+describe('test roles reducer', () => {
+ const invite = {
+ _id: 'nZestg',
+ days: 1,
+ maxUses: 0,
+ createdAt: '2022-01-17T20:32:44.695Z',
+ expires: '2022-01-18T20:32:44.695Z',
+ uses: 0,
+ _updatedAt: '2022-01-17T20:32:44.695Z',
+ url: 'https://go.rocket.chat/invite?host=open.rocket.chat&path=invite%2FnZestg',
+ success: true,
+ token: ''
+ };
+ it('should return initial state', () => {
+ const state = mockedStore.getState().inviteLinks;
+ expect(state).toEqual(initialState);
+ });
+
+ it('should return initialState after call inviteLinksFailure', () => {
+ mockedStore.dispatch(inviteLinksFailure());
+ const state = mockedStore.getState().inviteLinks;
+ expect(state).toEqual(initialState);
+ });
+
+ it('should return initialState after call inviteLinksSuccess', () => {
+ mockedStore.dispatch(inviteLinksSuccess());
+ const state = mockedStore.getState().inviteLinks;
+ expect(state).toEqual(initialState);
+ });
+
+ it('should return correctly token after call inviteLinksSetToken', () => {
+ mockedStore.dispatch(inviteLinksSetToken('xxx'));
+ const { token } = mockedStore.getState().inviteLinks;
+ expect(token).toEqual('xxx');
+ });
+
+ it('should return correctly invite value after call inviteLinksSetInvite', () => {
+ mockedStore.dispatch(inviteLinksSetInvite(invite));
+ const state = mockedStore.getState().inviteLinks;
+ expect(state.invite).toEqual(invite);
+ });
+
+ it('should return modified store after call inviteLinksSetParams', () => {
+ mockedStore.dispatch(inviteLinksSetParams({ token: 'nZestg' }));
+ const { token } = mockedStore.getState().inviteLinks;
+ expect(token).toEqual('nZestg');
+ });
+
+ it('should return initialState after call inviteLinksClear', () => {
+ mockedStore.dispatch(inviteLinksClear());
+ const state = mockedStore.getState().inviteLinks;
+ expect(state).toEqual(initialState);
+ });
+
+ it('should return actual state after call inviteLinksRequest', () => {
+ mockedStore.dispatch(inviteLinksRequest('xxx'));
+ const state = mockedStore.getState().inviteLinks;
+ expect(state).toEqual(initialState);
+ });
+});
diff --git a/app/reducers/inviteLinks.js b/app/reducers/inviteLinks.ts
similarity index 53%
rename from app/reducers/inviteLinks.js
rename to app/reducers/inviteLinks.ts
index 2126c4896..9ccd36e76 100644
--- a/app/reducers/inviteLinks.js
+++ b/app/reducers/inviteLinks.ts
@@ -1,18 +1,26 @@
+import { TActionInviteLinks } from '../actions/inviteLinks';
import { INVITE_LINKS } from '../actions/actionsTypes';
-const initialState = {
+export type TInvite = { url: string; expires: string; maxUses: number; uses: number; [x: string]: any };
+
+export interface IInviteLinks {
+ token: string;
+ days: number;
+ maxUses: number;
+ invite: TInvite;
+}
+
+export const initialState: IInviteLinks = {
token: '',
days: 1,
maxUses: 0,
- invite: {}
+ invite: { url: '', expires: '', maxUses: 0, uses: 0 }
};
-export default (state = initialState, action) => {
+export default (state = initialState, action: TActionInviteLinks): IInviteLinks => {
switch (action.type) {
case INVITE_LINKS.SET_TOKEN:
- return {
- token: action.token
- };
+ return { ...state, token: action.token };
case INVITE_LINKS.SET_PARAMS:
return {
...state,
diff --git a/app/reducers/login.test.ts b/app/reducers/login.test.ts
new file mode 100644
index 000000000..abab2ebaf
--- /dev/null
+++ b/app/reducers/login.test.ts
@@ -0,0 +1,109 @@
+import {
+ loginFailure,
+ loginRequest,
+ loginSuccess,
+ logout,
+ setLocalAuthenticated,
+ setLoginServices,
+ setUser
+} from '../actions/login';
+import { UserStatus } from '../definitions/UserStatus';
+import { initialState } from './login';
+import { mockedStore } from './mockedStore';
+
+describe('test selectedUsers reducer', () => {
+ it('should return initial state', () => {
+ const state = mockedStore.getState().login;
+ expect(state).toEqual(initialState);
+ });
+
+ it('should return modified store after loginRequest', () => {
+ mockedStore.dispatch(loginRequest({ user: 'carlitos@email.com', password: '123456' }));
+ const state = mockedStore.getState().login;
+ expect(state).toEqual({ ...initialState, isFetching: true, isAuthenticated: false, failure: false, error: {} });
+ });
+
+ it('should return modified store after loginFailure', () => {
+ mockedStore.dispatch(loginFailure({ error: 'error' }));
+ const state = mockedStore.getState().login.error.error;
+ expect(state).toEqual('error');
+ });
+
+ it('should return modified store after loginSuccess', () => {
+ const user = {
+ id: 'ajhsiahsa',
+ token: 'asdasdasdas',
+ username: 'carlitos',
+ name: 'Carlitos',
+ customFields: {
+ phonenumber: ''
+ },
+ emails: [
+ {
+ address: 'carlitos@email.com',
+ verified: true
+ }
+ ],
+ roles: ['user'],
+ isFromWebView: false,
+ showMessageInMainThread: false,
+ enableMessageParserEarlyAdoption: false,
+ status: UserStatus.ONLINE,
+ statusText: 'online'
+ };
+ mockedStore.dispatch(loginSuccess(user));
+ const state = mockedStore.getState().login.user;
+ expect(state).toEqual(user);
+ });
+
+ it('should return modified store after setUser', () => {
+ const user = {
+ id: 'ajhsiahsa',
+ token: 'asdasdasdas',
+ username: 'carlito',
+ name: 'Carlitos',
+ customFields: {
+ phonenumber: ''
+ },
+ emails: [
+ {
+ address: 'carlitos@email.com',
+ verified: true
+ }
+ ],
+ roles: ['user'],
+ isFromWebView: false,
+ showMessageInMainThread: false,
+ enableMessageParserEarlyAdoption: false
+ };
+ mockedStore.dispatch(setUser(user));
+ const state = mockedStore.getState().login.user.username;
+ expect(state).toEqual(user.username);
+ });
+
+ // TODO PREFERENCE REDUCER WITH EMPTY PREFERENCE - NON USED?
+ // it('should return modified store after setPreference', () => {
+ // mockedStore.dispatch(setPreference({ showAvatar: true }));
+ // const state = mockedStore.getState().login;
+ // console.log(state);
+ // expect(state).toEqual('error');
+ // });
+
+ it('should return modified store after setLocalAuthenticated', () => {
+ mockedStore.dispatch(setLocalAuthenticated(true));
+ const state = mockedStore.getState().login.isLocalAuthenticated;
+ expect(state).toEqual(true);
+ });
+
+ it('should return modified store after setLoginServices', () => {
+ mockedStore.dispatch(setLoginServices({ facebook: { clientId: 'xxx' } }));
+ const state = mockedStore.getState().login.services.facebook.clientId;
+ expect(state).toEqual('xxx');
+ });
+
+ it('should return modified store after logout', () => {
+ mockedStore.dispatch(logout());
+ const state = mockedStore.getState().login;
+ expect(state).toEqual(initialState);
+ });
+});
diff --git a/app/reducers/login.js b/app/reducers/login.ts
similarity index 50%
rename from app/reducers/login.js
rename to app/reducers/login.ts
index 664718c4c..2969d0c28 100644
--- a/app/reducers/login.js
+++ b/app/reducers/login.ts
@@ -1,15 +1,47 @@
+import { UserStatus } from '../definitions/UserStatus';
import * as types from '../actions/actionsTypes';
+import { TActionsLogin } from '../actions/login';
+import { IUser } from '../definitions';
-const initialState = {
+export interface IUserLogin {
+ id: string;
+ token: string;
+ username: string;
+ name: string;
+ language?: string;
+ status: UserStatus;
+ statusText: string;
+ roles: string[];
+ avatarETag?: string;
+ isFromWebView: boolean;
+ showMessageInMainThread: boolean;
+ enableMessageParserEarlyAdoption: boolean;
+ emails: Record[];
+ customFields: Record;
+ settings?: Record;
+}
+
+export interface ILogin {
+ user: Partial;
+ isLocalAuthenticated: boolean;
+ isAuthenticated: boolean;
+ isFetching: boolean;
+ error: Record;
+ services: Record;
+ failure: boolean;
+}
+
+export const initialState: ILogin = {
isLocalAuthenticated: true,
isAuthenticated: false,
isFetching: false,
user: {},
error: {},
- services: {}
+ services: {},
+ failure: false
};
-export default function login(state = initialState, action) {
+export default function login(state = initialState, action: TActionsLogin): ILogin {
switch (action.type) {
case types.APP.INIT:
return initialState;
@@ -60,13 +92,14 @@ export default function login(state = initialState, action) {
...state,
user: {
...state.user,
- settings: {
- ...state.user.settings,
- preferences: {
- ...state.user.settings.preferences,
- ...action.preference
- }
- }
+ settings: state.user?.settings
+ ? {
+ ...state.user?.settings,
+ preferences: state.user?.settings?.preferences
+ ? { ...state.user.settings.preferences, ...action.preference }
+ : { ...action.preference }
+ }
+ : { profile: {}, preferences: {} }
}
};
case types.LOGIN.SET_LOCAL_AUTHENTICATED:
diff --git a/app/reducers/permissions.js b/app/reducers/permissions.js
deleted file mode 100644
index 034c51420..000000000
--- a/app/reducers/permissions.js
+++ /dev/null
@@ -1,17 +0,0 @@
-import { PERMISSIONS } from '../actions/actionsTypes';
-
-const initialState = {};
-
-export default function permissions(state = initialState, action) {
- switch (action.type) {
- case PERMISSIONS.SET:
- return action.permissions;
- case PERMISSIONS.UPDATE:
- return {
- ...state,
- [action.payload.id]: action.payload.roles
- };
- default:
- return state;
- }
-}
diff --git a/app/reducers/permissions.test.ts b/app/reducers/permissions.test.ts
new file mode 100644
index 000000000..0d6d8b287
--- /dev/null
+++ b/app/reducers/permissions.test.ts
@@ -0,0 +1,23 @@
+import { setPermissions, updatePermission } from '../actions/permissions';
+import { mockedStore } from './mockedStore';
+import { initialState, IPermissionsState } from './permissions';
+
+describe('test permissions reducer', () => {
+ it('should return initial state', () => {
+ const state = mockedStore.getState().permissions;
+ expect(state).toEqual(initialState);
+ });
+
+ it('should return modified store after setPermissions', () => {
+ const permissions: IPermissionsState = { 'add-user-to-any-c-room': ['admin'], 'add-team-channel': ['user'] };
+ mockedStore.dispatch(setPermissions(permissions));
+ const state = mockedStore.getState().permissions;
+ expect(state).toEqual(permissions);
+ });
+
+ it('should return empty store after remove user', () => {
+ mockedStore.dispatch(updatePermission('add-team-channel', ['owner']));
+ const state = mockedStore.getState().permissions;
+ expect(state['add-team-channel']).toEqual(['owner']);
+ });
+});
diff --git a/app/reducers/permissions.ts b/app/reducers/permissions.ts
new file mode 100644
index 000000000..d2887a555
--- /dev/null
+++ b/app/reducers/permissions.ts
@@ -0,0 +1,25 @@
+import { PERMISSIONS } from '../actions/actionsTypes';
+import { TActionPermissions } from '../actions/permissions';
+import { SUPPORTED_PERMISSIONS } from '../lib/methods/getPermissions';
+
+export type TSupportedPermissions = typeof SUPPORTED_PERMISSIONS[number];
+
+export type IPermissionsState = {
+ [K in TSupportedPermissions]?: string[];
+};
+
+export const initialState: IPermissionsState = {};
+
+export default function permissions(state = initialState, action: TActionPermissions): IPermissionsState {
+ switch (action.type) {
+ case PERMISSIONS.SET:
+ return action.permissions;
+ case PERMISSIONS.UPDATE:
+ return {
+ ...state,
+ [action.payload.id]: action.payload.roles
+ };
+ default:
+ return state;
+ }
+}
diff --git a/app/reducers/roles.test.ts b/app/reducers/roles.test.ts
new file mode 100644
index 000000000..8348a8712
--- /dev/null
+++ b/app/reducers/roles.test.ts
@@ -0,0 +1,35 @@
+import { setRoles, updateRoles, removeRoles } from '../actions/roles';
+import { mockedStore } from './mockedStore';
+import { initialState } from './roles';
+
+describe('test roles reducer', () => {
+ it('should return initial state', () => {
+ const state = mockedStore.getState().roles;
+ expect(state).toEqual(initialState);
+ });
+
+ it('should return modified store after call setRoles action', () => {
+ const roles = { admin: 'enabled', user: 'enabled', dog: 'carlitos' };
+ mockedStore.dispatch(setRoles(roles));
+ const state = mockedStore.getState().roles;
+ expect(state.admin).toEqual('enabled');
+ expect(state.user).toEqual('enabled');
+ expect(state.dog).toEqual('carlitos');
+ });
+
+ it('should return modified store after call updateRoles action', () => {
+ mockedStore.dispatch(updateRoles('admin', 'disabled'));
+ const state = mockedStore.getState().roles;
+ expect(state.admin).toEqual('disabled');
+ expect(state.user).toEqual('enabled');
+ expect(state.dog).toEqual('carlitos');
+ });
+
+ it('should return modified store after call removeRoles action', () => {
+ mockedStore.dispatch(removeRoles('dog'));
+ const state = mockedStore.getState().roles;
+ expect(state.admin).toEqual('disabled');
+ expect(state.user).toEqual('enabled');
+ expect(state.dog).toEqual(undefined);
+ });
+});
diff --git a/app/reducers/roles.js b/app/reducers/roles.ts
similarity index 63%
rename from app/reducers/roles.js
rename to app/reducers/roles.ts
index 93cbffcb0..fc4f11248 100644
--- a/app/reducers/roles.js
+++ b/app/reducers/roles.ts
@@ -1,8 +1,11 @@
import { ROLES } from '../actions/actionsTypes';
+import { IActionRoles } from '../actions/roles';
-const initialState = {};
+export type IRoles = Record;
-export default function permissions(state = initialState, action) {
+export const initialState: IRoles = {};
+
+export default function roles(state = initialState, action: IActionRoles): IRoles {
switch (action.type) {
case ROLES.SET:
return action.roles;
diff --git a/app/reducers/room.test.ts b/app/reducers/room.test.ts
new file mode 100644
index 000000000..7ffba8e2a
--- /dev/null
+++ b/app/reducers/room.test.ts
@@ -0,0 +1,58 @@
+import { closeRoom, deleteRoom, forwardRoom, leaveRoom, removedRoom, subscribeRoom, unsubscribeRoom } from '../actions/room';
+import { ERoomType } from '../definitions/ERoomType';
+import { mockedStore } from './mockedStore';
+import { initialState } from './room';
+
+describe('test room reducer', () => {
+ it('should return initial state', () => {
+ const state = mockedStore.getState().room;
+ expect(state).toEqual(initialState);
+ });
+
+ it('should return modified store after subscribeRoom', () => {
+ mockedStore.dispatch(subscribeRoom('GENERAL'));
+ const state = mockedStore.getState().room;
+ expect(state.rooms).toEqual(['GENERAL']);
+ });
+
+ it('should return empty store after remove unsubscribeRoom', () => {
+ mockedStore.dispatch(unsubscribeRoom('GENERAL'));
+ const state = mockedStore.getState().room;
+ expect(state.rooms).toEqual([]);
+ });
+
+ it('should return initial state after leaveRoom', () => {
+ mockedStore.dispatch(leaveRoom(ERoomType.c, { rid: ERoomType.c }));
+ const { rid, isDeleting } = mockedStore.getState().room;
+ expect(rid).toEqual(ERoomType.c);
+ expect(isDeleting).toEqual(true);
+ });
+
+ it('should return initial state after deleteRoom', () => {
+ mockedStore.dispatch(deleteRoom(ERoomType.l, { rid: ERoomType.l }));
+ const { rid, isDeleting } = mockedStore.getState().room;
+ expect(rid).toEqual(ERoomType.l);
+ expect(isDeleting).toEqual(true);
+ });
+
+ it('should return initial state after closeRoom', () => {
+ mockedStore.dispatch(closeRoom('CLOSING'));
+ const { rid, isDeleting } = mockedStore.getState().room;
+ expect(rid).toEqual('CLOSING');
+ expect(isDeleting).toEqual(true);
+ });
+
+ it('should return initial state after forwardRoom', () => {
+ const transferData = { roomId: 'FORWARDING' };
+ mockedStore.dispatch(forwardRoom('FORWARDING', transferData));
+ const { rid, isDeleting } = mockedStore.getState().room;
+ expect(rid).toEqual('FORWARDING');
+ expect(isDeleting).toEqual(true);
+ });
+
+ it('should return loading after call removedRoom', () => {
+ mockedStore.dispatch(removedRoom());
+ const { isDeleting } = mockedStore.getState().room;
+ expect(isDeleting).toEqual(false);
+ });
+});
diff --git a/app/reducers/room.js b/app/reducers/room.ts
similarity index 73%
rename from app/reducers/room.js
rename to app/reducers/room.ts
index 086e43f5c..e5b9815b4 100644
--- a/app/reducers/room.js
+++ b/app/reducers/room.ts
@@ -1,12 +1,21 @@
+import { TActionsRoom } from '../actions/room';
import { ROOM } from '../actions/actionsTypes';
-const initialState = {
- rid: null,
+export type IRoomRecord = string[];
+
+export interface IRoom {
+ rid: string;
+ isDeleting: boolean;
+ rooms: IRoomRecord;
+}
+
+export const initialState: IRoom = {
+ rid: '',
isDeleting: false,
rooms: []
};
-export default function (state = initialState, action) {
+export default function (state = initialState, action: TActionsRoom): IRoom {
switch (action.type) {
case ROOM.SUBSCRIBE:
return {
diff --git a/app/reducers/rooms.test.ts b/app/reducers/rooms.test.ts
new file mode 100644
index 000000000..4bf1312ec
--- /dev/null
+++ b/app/reducers/rooms.test.ts
@@ -0,0 +1,77 @@
+import {
+ closeSearchHeader,
+ closeServerDropdown,
+ openSearchHeader,
+ roomsFailure,
+ roomsRefresh,
+ roomsRequest,
+ roomsSuccess,
+ setSearch,
+ toggleServerDropdown
+} from '../actions/rooms';
+import { mockedStore } from './mockedStore';
+import { initialState } from './rooms';
+
+describe('test selectedUsers reducer', () => {
+ it('should return initial state', () => {
+ const state = mockedStore.getState().rooms;
+ expect(state).toEqual(initialState);
+ });
+
+ it('should return modified store after call roomsRequest', () => {
+ mockedStore.dispatch(roomsRequest());
+ const state = mockedStore.getState().rooms;
+ const manipulated = { ...initialState, isFetching: true, failure: false, errorMessage: {} };
+ expect(state).toEqual(manipulated);
+ });
+
+ it('should return modified store after call roomsSuccess', () => {
+ mockedStore.dispatch(roomsSuccess());
+ const state = mockedStore.getState().rooms;
+ const manipulated = { ...initialState, isFetching: false, refreshing: false };
+ expect(state).toEqual(manipulated);
+ });
+
+ it('should return modified store after call roomsRefresh', () => {
+ mockedStore.dispatch(roomsRefresh());
+ const state = mockedStore.getState().rooms;
+ const manipulated = { ...initialState, isFetching: true, refreshing: true };
+ expect(state).toEqual(manipulated);
+ });
+
+ it('should return modified store after call setSearch', () => {
+ mockedStore.dispatch(setSearch('dog'));
+ const state = mockedStore.getState().rooms;
+ expect(state.searchText).toEqual('dog');
+ });
+
+ it('should return modified store after call closeServerDropdown', () => {
+ mockedStore.dispatch(closeServerDropdown());
+ const state = mockedStore.getState().rooms;
+ expect(state.closeServerDropdown).toEqual(!initialState.closeServerDropdown);
+ });
+
+ it('should return modified store after call toggleServerDropdown', () => {
+ mockedStore.dispatch(toggleServerDropdown());
+ const state = mockedStore.getState().rooms;
+ expect(state.showServerDropdown).toEqual(!initialState.showServerDropdown);
+ });
+
+ it('should return modified store after call openSearchHeader', () => {
+ mockedStore.dispatch(openSearchHeader());
+ const state = mockedStore.getState().rooms;
+ expect(state.showSearchHeader).toEqual(true);
+ });
+
+ it('should return modified store after call closeSearchHeader', () => {
+ mockedStore.dispatch(closeSearchHeader());
+ const state = mockedStore.getState().rooms;
+ expect(state.showSearchHeader).toEqual(false);
+ });
+
+ it('should return modified store after call roomsFailure', () => {
+ mockedStore.dispatch(roomsFailure('error'));
+ const state = mockedStore.getState().rooms;
+ expect(state.errorMessage).toEqual('error');
+ });
+});
diff --git a/app/reducers/rooms.js b/app/reducers/rooms.ts
similarity index 57%
rename from app/reducers/rooms.js
rename to app/reducers/rooms.ts
index 660f5f1e7..1a6705086 100644
--- a/app/reducers/rooms.js
+++ b/app/reducers/rooms.ts
@@ -1,6 +1,18 @@
-import * as types from '../actions/actionsTypes';
+import { IRoomsAction } from '../actions/rooms';
+import { ROOMS } from '../actions/actionsTypes';
-const initialState = {
+export interface IRooms {
+ isFetching: boolean;
+ refreshing: boolean;
+ failure: boolean;
+ errorMessage: Record | string;
+ searchText: string;
+ showServerDropdown: boolean;
+ closeServerDropdown: boolean;
+ showSearchHeader: boolean;
+}
+
+export const initialState: IRooms = {
isFetching: false,
refreshing: false,
failure: false,
@@ -11,22 +23,22 @@ const initialState = {
showSearchHeader: false
};
-export default function login(state = initialState, action) {
+export default function rooms(state = initialState, action: IRoomsAction): IRooms {
switch (action.type) {
- case types.ROOMS.REQUEST:
+ case ROOMS.REQUEST:
return {
...state,
isFetching: true,
failure: false,
errorMessage: {}
};
- case types.ROOMS.SUCCESS:
+ case ROOMS.SUCCESS:
return {
...state,
isFetching: false,
refreshing: false
};
- case types.ROOMS.FAILURE:
+ case ROOMS.FAILURE:
return {
...state,
isFetching: false,
@@ -34,33 +46,33 @@ export default function login(state = initialState, action) {
failure: true,
errorMessage: action.err
};
- case types.ROOMS.REFRESH:
+ case ROOMS.REFRESH:
return {
...state,
isFetching: true,
refreshing: true
};
- case types.ROOMS.SET_SEARCH:
+ case ROOMS.SET_SEARCH:
return {
...state,
searchText: action.searchText
};
- case types.ROOMS.CLOSE_SERVER_DROPDOWN:
+ case ROOMS.CLOSE_SERVER_DROPDOWN:
return {
...state,
closeServerDropdown: !state.closeServerDropdown
};
- case types.ROOMS.TOGGLE_SERVER_DROPDOWN:
+ case ROOMS.TOGGLE_SERVER_DROPDOWN:
return {
...state,
showServerDropdown: !state.showServerDropdown
};
- case types.ROOMS.OPEN_SEARCH_HEADER:
+ case ROOMS.OPEN_SEARCH_HEADER:
return {
...state,
showSearchHeader: true
};
- case types.ROOMS.CLOSE_SEARCH_HEADER:
+ case ROOMS.CLOSE_SEARCH_HEADER:
return {
...state,
showSearchHeader: false
diff --git a/app/reducers/server.test.ts b/app/reducers/server.test.ts
new file mode 100644
index 000000000..0b0f8fd40
--- /dev/null
+++ b/app/reducers/server.test.ts
@@ -0,0 +1,68 @@
+import {
+ selectServerRequest,
+ serverRequest,
+ selectServerSuccess,
+ serverInitAdd,
+ serverFailure,
+ serverFinishAdd,
+ selectServerFailure
+} from '../actions/server';
+import { mockedStore } from './mockedStore';
+import { initialState } from './server';
+
+describe('test server reducer', () => {
+ it('should return initial state', () => {
+ const state = mockedStore.getState().server;
+ expect(state).toEqual(initialState);
+ });
+
+ it('should return modified store after serverRequest', () => {
+ const server = 'https://open.rocket.chat/';
+ mockedStore.dispatch(serverRequest(server));
+ const state = mockedStore.getState().server;
+ const manipulated = { ...initialState, connecting: true, failure: false };
+ expect(state).toEqual(manipulated);
+ });
+
+ it('should return modified store after selectServerFailure', () => {
+ mockedStore.dispatch(selectServerFailure());
+ const state = mockedStore.getState().server;
+ const manipulated = { ...initialState, connecting: false, connected: false, loading: false, changingServer: false };
+ expect(state).toEqual(manipulated);
+ });
+
+ it('should return modified store after selectServer', () => {
+ const server = 'https://open.rocket.chat/';
+ mockedStore.dispatch(selectServerRequest(server));
+ const state = mockedStore.getState().server.server;
+ expect(state).toEqual(server);
+ });
+
+ it('should return modified store after selectServerSucess', () => {
+ const server = 'https://open.rocket.chat/';
+ const version = '4.1.0';
+ mockedStore.dispatch(selectServerSuccess(server, version));
+ const state = mockedStore.getState().server;
+ const manipulated = { ...initialState, server, version, connected: true, loading: false };
+ expect(state).toEqual(manipulated);
+ });
+
+ it('should return modified store after serverRequestInitAdd', () => {
+ const previousServer = 'https://mobile.rocket.chat';
+ mockedStore.dispatch(serverInitAdd(previousServer));
+ const state = mockedStore.getState().server.previousServer;
+ expect(state).toEqual(previousServer);
+ });
+
+ it('should return modified store after serverFinishAdd', () => {
+ mockedStore.dispatch(serverFinishAdd());
+ const state = mockedStore.getState().server.previousServer;
+ expect(state).toEqual(null);
+ });
+
+ it('should return modified store after serverRequestFailure', () => {
+ mockedStore.dispatch(serverFailure('error'));
+ const state = mockedStore.getState().server;
+ expect(state.failure).toEqual(true);
+ });
+});
diff --git a/app/reducers/server.js b/app/reducers/server.ts
similarity index 76%
rename from app/reducers/server.js
rename to app/reducers/server.ts
index 14c7bbfdf..02a7ffede 100644
--- a/app/reducers/server.js
+++ b/app/reducers/server.ts
@@ -1,6 +1,18 @@
+import { TActionServer } from '../actions/server';
import { SERVER } from '../actions/actionsTypes';
-const initialState = {
+export interface IServer {
+ connecting: boolean;
+ connected: boolean;
+ failure: boolean;
+ server: string;
+ version: string | null;
+ loading: boolean;
+ previousServer: string | null;
+ changingServer: boolean;
+}
+
+export const initialState: IServer = {
connecting: false,
connected: false,
failure: false,
@@ -11,7 +23,7 @@ const initialState = {
changingServer: false
};
-export default function server(state = initialState, action) {
+export default function server(state = initialState, action: TActionServer): IServer {
switch (action.type) {
case SERVER.REQUEST:
return {
diff --git a/app/reducers/settings.test.ts b/app/reducers/settings.test.ts
new file mode 100644
index 000000000..da93ce3cc
--- /dev/null
+++ b/app/reducers/settings.test.ts
@@ -0,0 +1,31 @@
+import { addSettings, clearSettings, updateSettings } from '../actions/settings';
+import { mockedStore } from './mockedStore';
+import { initialState } from './settings';
+
+describe('test settings reducer', () => {
+ it('should return initial state', () => {
+ const state = mockedStore.getState().settings;
+ expect(state).toEqual(initialState);
+ });
+
+ const settings = { API_Use_REST_For_DDP_Calls: true, FileUpload_MaxFileSize: 600857600, Jitsi_URL_Room_Prefix: 'RocketChat' };
+
+ it('should return modified store after call addSettings action', () => {
+ mockedStore.dispatch(addSettings(settings));
+ const state = mockedStore.getState().settings;
+ expect(state).toEqual(settings);
+ });
+
+ it('should return correctly settings after call updateSettings action', () => {
+ const id = 'Jitsi_URL_Room_Prefix';
+ mockedStore.dispatch(updateSettings(id, 'ChatRocket'));
+ const state = mockedStore.getState().settings;
+ expect(state[id]).toEqual('ChatRocket');
+ });
+
+ it('should return initial state after clearSettings', () => {
+ mockedStore.dispatch(clearSettings());
+ const state = mockedStore.getState().settings;
+ expect(state).toEqual({});
+ });
+});
diff --git a/app/reducers/settings.js b/app/reducers/settings.ts
similarity index 54%
rename from app/reducers/settings.js
rename to app/reducers/settings.ts
index 6e9ab5005..028431ed0 100644
--- a/app/reducers/settings.js
+++ b/app/reducers/settings.ts
@@ -1,8 +1,13 @@
+import { IActionSettings } from '../actions/settings';
import { SETTINGS } from '../actions/actionsTypes';
-const initialState = {};
+export type TSettings = string | number | boolean;
-export default (state = initialState, action) => {
+export type ISettings = Record;
+
+export const initialState: ISettings = {};
+
+export default (state = initialState, action: IActionSettings): ISettings => {
switch (action.type) {
case SETTINGS.ADD:
return {
diff --git a/app/reducers/share.js b/app/reducers/share.js
deleted file mode 100644
index 4f2a22a1a..000000000
--- a/app/reducers/share.js
+++ /dev/null
@@ -1,29 +0,0 @@
-import { SHARE } from '../actions/actionsTypes';
-
-const initialState = {
- user: {},
- server: {},
- settings: {}
-};
-
-export default function share(state = initialState, action) {
- switch (action.type) {
- case SHARE.SELECT_SERVER:
- return {
- ...state,
- server: action.server
- };
- case SHARE.SET_USER:
- return {
- ...state,
- user: action.user
- };
- case SHARE.SET_SETTINGS:
- return {
- ...state,
- settings: action.settings
- };
- default:
- return state;
- }
-}
diff --git a/app/reducers/share.test.ts b/app/reducers/share.test.ts
new file mode 100644
index 000000000..851e7ad26
--- /dev/null
+++ b/app/reducers/share.test.ts
@@ -0,0 +1,41 @@
+import { shareSelectServer, shareSetSettings, shareSetUser } from '../actions/share';
+import { mockedStore } from './mockedStore';
+import { initialState } from './share';
+
+describe('test share reducer', () => {
+ it('should return initial state', () => {
+ const state = mockedStore.getState().share;
+ expect(state).toEqual(initialState);
+ });
+
+ it('should return modified store after shareSelectServer', () => {
+ const server = {
+ server: 'https://open.rocket.chat',
+ version: '4.4.0'
+ };
+ mockedStore.dispatch(shareSelectServer(server));
+ const state = mockedStore.getState().share.server;
+ expect(state).toEqual(server);
+ });
+
+ it('should return modified store after shareSetSettings', () => {
+ const settings = {
+ Admin: false
+ };
+ mockedStore.dispatch(shareSetSettings(settings));
+ const state = mockedStore.getState().share.settings;
+ expect(state).toEqual(settings);
+ });
+
+ it('should return modified store after shareSetUser', () => {
+ const user = {
+ id: 'dig-joy',
+ token: 'token',
+ username: 'rocket.chat',
+ roles: ['admin']
+ };
+ mockedStore.dispatch(shareSetUser(user));
+ const state = mockedStore.getState().share.user;
+ expect(state).toEqual(user);
+ });
+});
diff --git a/app/reducers/share.ts b/app/reducers/share.ts
new file mode 100644
index 000000000..03904ec46
--- /dev/null
+++ b/app/reducers/share.ts
@@ -0,0 +1,50 @@
+import { TActionsShare } from '../actions/share';
+import { SHARE } from '../actions/actionsTypes';
+
+export interface IShareServer {
+ server: string;
+ version: string;
+}
+
+export type TShareSettings = Record;
+
+export interface IShareUser {
+ id: string;
+ token: string;
+ username: string;
+ roles: string[];
+}
+
+export interface IShare {
+ user: IShareUser;
+ server: IShareServer;
+ settings: TShareSettings;
+}
+
+export const initialState: IShare = {
+ user: {} as IShareUser,
+ server: {} as IShareServer,
+ settings: {}
+};
+
+export default function share(state = initialState, action: TActionsShare): IShare {
+ switch (action.type) {
+ case SHARE.SELECT_SERVER:
+ return {
+ ...state,
+ server: action.server
+ };
+ case SHARE.SET_USER:
+ return {
+ ...state,
+ user: action.user
+ };
+ case SHARE.SET_SETTINGS:
+ return {
+ ...state,
+ settings: action.settings
+ };
+ default:
+ return state;
+ }
+}
diff --git a/app/reducers/sortPreferences.test.ts b/app/reducers/sortPreferences.test.ts
new file mode 100644
index 000000000..5de29933d
--- /dev/null
+++ b/app/reducers/sortPreferences.test.ts
@@ -0,0 +1,35 @@
+import { IPreferences } from '../definitions';
+import { setAllPreferences, setPreference } from '../actions/sortPreferences';
+import { mockedStore } from './mockedStore';
+import { initialState } from './sortPreferences';
+import { DisplayMode, SortBy } from '../constants/constantDisplayMode';
+
+describe('test sortPreferences reducer', () => {
+ it('should return initial state', () => {
+ const state = mockedStore.getState().sortPreferences;
+ expect(state).toEqual(initialState);
+ });
+
+ it('should return correctly value after call setPreference action', () => {
+ const preferences: IPreferences = {
+ displayMode: DisplayMode.Condensed,
+ groupByType: true,
+ showAvatar: true,
+ showFavorites: true,
+ showUnread: true,
+ sortBy: SortBy.Activity
+ };
+ mockedStore.dispatch(setAllPreferences(preferences));
+ const state = mockedStore.getState().sortPreferences;
+ expect(state).toEqual(preferences);
+ });
+
+ it('should return correctly value after call setPreference action', () => {
+ const preference: Partial = {
+ displayMode: DisplayMode.Expanded
+ };
+ mockedStore.dispatch(setPreference(preference));
+ const { displayMode } = mockedStore.getState().sortPreferences;
+ expect(displayMode).toEqual(DisplayMode.Expanded);
+ });
+});
diff --git a/app/reducers/sortPreferences.js b/app/reducers/sortPreferences.ts
similarity index 72%
rename from app/reducers/sortPreferences.js
rename to app/reducers/sortPreferences.ts
index 4ad9e797d..2083e8f7a 100644
--- a/app/reducers/sortPreferences.js
+++ b/app/reducers/sortPreferences.ts
@@ -1,7 +1,8 @@
import { SORT_PREFERENCES } from '../actions/actionsTypes';
import { DisplayMode, SortBy } from '../constants/constantDisplayMode';
+import { IPreferences, TApplicationActions } from '../definitions';
-const initialState = {
+export const initialState: IPreferences = {
sortBy: SortBy.Activity,
groupByType: false,
showFavorites: false,
@@ -10,7 +11,7 @@ const initialState = {
displayMode: DisplayMode.Expanded
};
-export default (state = initialState, action) => {
+export default (state = initialState, action: TApplicationActions): IPreferences => {
switch (action.type) {
case SORT_PREFERENCES.SET_ALL:
return {
diff --git a/app/reducers/usersTyping.test.ts b/app/reducers/usersTyping.test.ts
new file mode 100644
index 000000000..26e527882
--- /dev/null
+++ b/app/reducers/usersTyping.test.ts
@@ -0,0 +1,30 @@
+import { addUserTyping, removeUserTyping, clearUserTyping } from '../actions/usersTyping';
+import { mockedStore } from './mockedStore';
+import { initialState } from './usersTyping';
+
+describe('test usersTyping reducer', () => {
+ it('should return initial state', () => {
+ const state = mockedStore.getState().usersTyping;
+ expect(state).toEqual(initialState);
+ });
+
+ it('should return modified store after addUserTyping', () => {
+ mockedStore.dispatch(addUserTyping('diego'));
+ mockedStore.dispatch(addUserTyping('carlos'));
+ mockedStore.dispatch(addUserTyping('maria'));
+ const state = mockedStore.getState().usersTyping;
+ expect(state).toEqual(['diego', 'carlos', 'maria']);
+ });
+
+ it('should return modified store after removeUserTyping', () => {
+ mockedStore.dispatch(removeUserTyping('diego'));
+ const state = mockedStore.getState().usersTyping;
+ expect(state).toEqual(['carlos', 'maria']);
+ });
+
+ it('should return initial state after reset', () => {
+ mockedStore.dispatch(clearUserTyping());
+ const state = mockedStore.getState().usersTyping;
+ expect(state).toEqual(initialState);
+ });
+});
diff --git a/app/reducers/usersTyping.js b/app/reducers/usersTyping.ts
similarity index 72%
rename from app/reducers/usersTyping.js
rename to app/reducers/usersTyping.ts
index acecab632..ecfbbb77d 100644
--- a/app/reducers/usersTyping.js
+++ b/app/reducers/usersTyping.ts
@@ -1,8 +1,11 @@
import { USERS_TYPING } from '../actions/actionsTypes';
+import { TApplicationActions } from '../definitions';
-const initialState = [];
+export type IUsersTyping = string[];
-export default function usersTyping(state = initialState, action) {
+export const initialState: IUsersTyping = [];
+
+export default function usersTyping(state = initialState, action: TApplicationActions): IUsersTyping {
switch (action.type) {
case USERS_TYPING.ADD:
if (state.findIndex(item => item === action.username) === -1) {
diff --git a/app/sagas/deepLinking.js b/app/sagas/deepLinking.js
index 240771b3e..900e6a145 100644
--- a/app/sagas/deepLinking.js
+++ b/app/sagas/deepLinking.js
@@ -8,11 +8,12 @@ import { inviteLinksRequest, inviteLinksSetToken } from '../actions/inviteLinks'
import database from '../lib/database';
import RocketChat from '../lib/rocketchat';
import EventEmitter from '../utils/events';
-import { ROOT_INSIDE, ROOT_OUTSIDE, appInit, appStart } from '../actions/app';
+import { appInit, appStart } from '../actions/app';
import { localAuthenticate } from '../utils/localAuthentication';
import { goRoom } from '../utils/goRoom';
import { loginRequest } from '../actions/login';
import log from '../utils/log';
+import { RootEnum } from '../definitions';
const roomTypes = {
channel: 'c',
@@ -41,7 +42,7 @@ const popToRoot = function popToRoot({ isMasterDetail }) {
};
const navigate = function* navigate({ params }) {
- yield put(appStart({ root: ROOT_INSIDE }));
+ yield put(appStart({ root: RootEnum.ROOT_INSIDE }));
if (params.path || params.rid) {
let type;
let name;
@@ -192,7 +193,7 @@ const handleOpen = function* handleOpen({ params }) {
yield fallbackNavigation();
return;
}
- yield put(appStart({ root: ROOT_OUTSIDE }));
+ yield put(appStart({ root: RootEnum.ROOT_OUTSIDE }));
yield put(serverInitAdd(server));
yield delay(1000);
EventEmitter.emit('NewServer', { server: host });
diff --git a/app/sagas/init.js b/app/sagas/init.js
index af42c4faa..3671e83f4 100644
--- a/app/sagas/init.js
+++ b/app/sagas/init.js
@@ -1,6 +1,7 @@
import { put, takeLatest } from 'redux-saga/effects';
import RNBootSplash from 'react-native-bootsplash';
+import { BIOMETRY_ENABLED_KEY } from '../constants/localAuthentication';
import UserPreferences from '../lib/userPreferences';
import { selectServerRequest } from '../actions/server';
import { setAllPreferences } from '../actions/sortPreferences';
@@ -9,20 +10,35 @@ import RocketChat from '../lib/rocketchat';
import log from '../utils/log';
import database from '../lib/database';
import { localAuthenticate } from '../utils/localAuthentication';
-import { ROOT_OUTSIDE, appReady, appStart } from '../actions/app';
+import { appReady, appStart } from '../actions/app';
+import { RootEnum } from '../definitions';
export const initLocalSettings = function* initLocalSettings() {
const sortPreferences = yield RocketChat.getSortPreferences();
yield put(setAllPreferences(sortPreferences));
};
+const BIOMETRY_MIGRATION_KEY = 'kBiometryMigration';
+
const restore = function* restore() {
try {
const server = yield UserPreferences.getStringAsync(RocketChat.CURRENT_SERVER);
let userId = yield UserPreferences.getStringAsync(`${RocketChat.TOKEN_KEY}-${server}`);
+ // Migration biometry setting from WatermelonDB to MMKV
+ // TODO: remove it after a few versions
+ const hasMigratedBiometry = yield UserPreferences.getBoolAsync(BIOMETRY_MIGRATION_KEY);
+ if (!hasMigratedBiometry) {
+ const serversDB = database.servers;
+ const serversCollection = serversDB.get('servers');
+ const servers = yield serversCollection.query().fetch();
+ const isBiometryEnabled = servers.some(server => !!server.biometry);
+ yield UserPreferences.setBoolAsync(BIOMETRY_ENABLED_KEY, isBiometryEnabled);
+ yield UserPreferences.setBoolAsync(BIOMETRY_MIGRATION_KEY, true);
+ }
+
if (!server) {
- yield put(appStart({ root: ROOT_OUTSIDE }));
+ yield put(appStart({ root: RootEnum.ROOT_OUTSIDE }));
} else if (!userId) {
const serversDB = database.servers;
const serversCollection = serversDB.get('servers');
@@ -38,7 +54,7 @@ const restore = function* restore() {
}
}
}
- yield put(appStart({ root: ROOT_OUTSIDE }));
+ yield put(appStart({ root: RootEnum.ROOT_OUTSIDE }));
} else {
const serversDB = database.servers;
const serverCollections = serversDB.get('servers');
@@ -56,7 +72,7 @@ const restore = function* restore() {
yield put(appReady({}));
} catch (e) {
log(e);
- yield put(appStart({ root: ROOT_OUTSIDE }));
+ yield put(appStart({ root: RootEnum.ROOT_OUTSIDE }));
}
};
diff --git a/app/sagas/login.js b/app/sagas/login.js
index 1d8f688f2..346e3be15 100644
--- a/app/sagas/login.js
+++ b/app/sagas/login.js
@@ -3,7 +3,7 @@ import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
import { Q } from '@nozbe/watermelondb';
import * as types from '../actions/actionsTypes';
-import { ROOT_INSIDE, ROOT_LOADING, ROOT_OUTSIDE, ROOT_SET_USERNAME, appStart } from '../actions/app';
+import { appStart } from '../actions/app';
import { selectServerRequest, serverFinishAdd } from '../actions/server';
import { loginFailure, loginSuccess, logout, setUser } from '../actions/login';
import { roomsRequest } from '../actions/rooms';
@@ -15,11 +15,11 @@ import EventEmitter from '../utils/events';
import { inviteLinksRequest } from '../actions/inviteLinks';
import { showErrorAlert } from '../utils/info';
import { localAuthenticate } from '../utils/localAuthentication';
-import { setActiveUsers } from '../actions/activeUsers';
import { encryptionInit, encryptionStop } from '../actions/encryption';
import UserPreferences from '../lib/userPreferences';
import { inquiryRequest, inquiryReset } from '../ee/omnichannel/actions/inquiry';
import { isOmnichannelStatusAvailable } from '../ee/omnichannel/lib';
+import { RootEnum } from '../definitions';
const getServer = state => state.server.server;
const loginWithPasswordCall = args => RocketChat.loginWithPassword(args);
@@ -38,7 +38,7 @@ const handleLoginRequest = function* handleLoginRequest({ credentials, logoutOnE
if (!result.username) {
yield put(serverFinishAdd());
yield put(setUser(result));
- yield put(appStart({ root: ROOT_SET_USERNAME }));
+ yield put(appStart({ root: RootEnum.ROOT_SET_USERNAME }));
} else {
const server = yield select(getServer);
yield localAuthenticate(server);
@@ -99,7 +99,6 @@ const registerPushToken = function* registerPushToken() {
};
const fetchUsersPresence = function* fetchUserPresence() {
- yield RocketChat.getUsersPresence();
RocketChat.subscribeUsersPresence();
};
@@ -167,7 +166,7 @@ const handleLoginSuccess = function* handleLoginSuccess({ user }) {
yield put(setUser(user));
EventEmitter.emit('connected');
- yield put(appStart({ root: ROOT_INSIDE }));
+ yield put(appStart({ root: RootEnum.ROOT_INSIDE }));
const inviteLinkToken = yield select(state => state.inviteLinks.token);
if (inviteLinkToken) {
yield put(inviteLinksRequest(inviteLinkToken));
@@ -179,7 +178,7 @@ const handleLoginSuccess = function* handleLoginSuccess({ user }) {
const handleLogout = function* handleLogout({ forcedByServer }) {
yield put(encryptionStop());
- yield put(appStart({ root: ROOT_LOADING, text: I18n.t('Logging_out') }));
+ yield put(appStart({ root: RootEnum.ROOT_LOADING, text: I18n.t('Logging_out') }));
const server = yield select(getServer);
if (server) {
try {
@@ -187,7 +186,7 @@ const handleLogout = function* handleLogout({ forcedByServer }) {
// if the user was logged out by the server
if (forcedByServer) {
- yield put(appStart({ root: ROOT_OUTSIDE }));
+ yield put(appStart({ root: RootEnum.ROOT_OUTSIDE }));
showErrorAlert(I18n.t('Logged_out_by_server'), I18n.t('Oops'));
yield delay(300);
EventEmitter.emit('NewServer', { server });
@@ -209,10 +208,10 @@ const handleLogout = function* handleLogout({ forcedByServer }) {
}
}
// if there's no servers, go outside
- yield put(appStart({ root: ROOT_OUTSIDE }));
+ yield put(appStart({ root: RootEnum.ROOT_OUTSIDE }));
}
} catch (e) {
- yield put(appStart({ root: ROOT_OUTSIDE }));
+ yield put(appStart({ root: RootEnum.ROOT_OUTSIDE }));
log(e);
}
}
@@ -221,11 +220,6 @@ const handleLogout = function* handleLogout({ forcedByServer }) {
const handleSetUser = function* handleSetUser({ user }) {
setLanguage(user?.language);
- if (user && user.status) {
- const userId = yield select(state => state.login.user.id);
- yield put(setActiveUsers({ [userId]: user }));
- }
-
if (user?.statusLivechat && RocketChat.isOmnichannelModuleAvailable()) {
if (isOmnichannelStatusAvailable(user)) {
yield put(inquiryRequest());
diff --git a/app/sagas/selectServer.js b/app/sagas/selectServer.js
index 5aacd2b11..c67af42d1 100644
--- a/app/sagas/selectServer.js
+++ b/app/sagas/selectServer.js
@@ -10,16 +10,18 @@ import { SERVER } from '../actions/actionsTypes';
import { selectServerFailure, selectServerRequest, selectServerSuccess, serverFailure } from '../actions/server';
import { clearSettings } from '../actions/settings';
import { setUser } from '../actions/login';
+import { clearActiveUsers } from '../actions/activeUsers';
import RocketChat from '../lib/rocketchat';
import database from '../lib/database';
import log, { logServerVersion } from '../utils/log';
import I18n from '../i18n';
import { BASIC_AUTH_KEY, setBasicAuth } from '../utils/fetch';
-import { ROOT_INSIDE, ROOT_OUTSIDE, appStart } from '../actions/app';
+import { appStart } from '../actions/app';
import UserPreferences from '../lib/userPreferences';
import { encryptionStop } from '../actions/encryption';
import SSLPinning from '../utils/sslPinning';
import { inquiryReset } from '../ee/omnichannel/actions/inquiry';
+import { RootEnum } from '../definitions';
const getServerInfo = function* getServerInfo({ server, raiseError = true }) {
try {
@@ -72,6 +74,7 @@ const handleSelectServer = function* handleSelectServer({ server, version, fetch
yield put(inquiryReset());
yield put(encryptionStop());
+ yield put(clearActiveUsers());
const serversDB = database.servers;
yield UserPreferences.setStringAsync(RocketChat.CURRENT_SERVER, server);
const userId = yield UserPreferences.getStringAsync(`${RocketChat.TOKEN_KEY}-${server}`);
@@ -111,10 +114,10 @@ const handleSelectServer = function* handleSelectServer({ server, version, fetch
yield put(clearSettings());
yield RocketChat.connect({ server, user, logoutOnError: true });
yield put(setUser(user));
- yield put(appStart({ root: ROOT_INSIDE }));
+ yield put(appStart({ root: RootEnum.ROOT_INSIDE }));
} else {
yield RocketChat.connect({ server });
- yield put(appStart({ root: ROOT_OUTSIDE }));
+ yield put(appStart({ root: RootEnum.ROOT_OUTSIDE }));
}
// We can't use yield here because fetch of Settings & Custom Emojis is slower
diff --git a/app/sagas/state.js b/app/sagas/state.js
index b9afeb4dd..8f122b4d8 100644
--- a/app/sagas/state.js
+++ b/app/sagas/state.js
@@ -5,11 +5,11 @@ import { setBadgeCount } from '../notifications/push';
import log from '../utils/log';
import { localAuthenticate, saveLastLocalAuthenticationSession } from '../utils/localAuthentication';
import { APP_STATE } from '../actions/actionsTypes';
-import { ROOT_OUTSIDE } from '../actions/app';
+import { RootEnum } from '../definitions';
const appHasComeBackToForeground = function* appHasComeBackToForeground() {
const appRoot = yield select(state => state.app.root);
- if (appRoot === ROOT_OUTSIDE) {
+ if (appRoot === RootEnum.ROOT_OUTSIDE) {
return;
}
const login = yield select(state => state.login);
@@ -29,7 +29,7 @@ const appHasComeBackToForeground = function* appHasComeBackToForeground() {
const appHasComeBackToBackground = function* appHasComeBackToBackground() {
const appRoot = yield select(state => state.app.root);
- if (appRoot === ROOT_OUTSIDE) {
+ if (appRoot === RootEnum.ROOT_OUTSIDE) {
return;
}
try {
diff --git a/app/selectors/login.js b/app/selectors/login.js
deleted file mode 100644
index 03e3a2ecb..000000000
--- a/app/selectors/login.js
+++ /dev/null
@@ -1,19 +0,0 @@
-import { createSelector } from 'reselect';
-import isEmpty from 'lodash/isEmpty';
-
-const getUser = state => {
- if (!isEmpty(state.share?.user)) {
- return state.share.user;
- }
- return state.login?.user;
-};
-const getLoginServices = state => state.login.services || {};
-const getShowFormLoginSetting = state => state.settings.Accounts_ShowFormLogin || false;
-const getIframeEnabledSetting = state => state.settings.Accounts_iframe_enabled || false;
-
-export const getUserSelector = createSelector([getUser], user => user);
-
-export const getShowLoginButton = createSelector(
- [getLoginServices, getShowFormLoginSetting, getIframeEnabledSetting],
- (loginServices, showFormLogin, iframeEnabled) => showFormLogin || Object.values(loginServices).length || iframeEnabled
-);
diff --git a/app/selectors/login.ts b/app/selectors/login.ts
new file mode 100644
index 000000000..2b634d6ab
--- /dev/null
+++ b/app/selectors/login.ts
@@ -0,0 +1,33 @@
+import { createSelector } from 'reselect';
+import isEmpty from 'lodash/isEmpty';
+
+import { IApplicationState, IUser } from '../definitions';
+
+interface IServices {
+ facebook: { clientId: string };
+ github: { clientId: string };
+ gitlab: { clientId: string };
+ google: { clientId: string };
+ linkedin: { clientId: string };
+ 'meteor-developer': { clientId: string };
+ wordpress: { clientId: string; serverURL: string };
+}
+
+const getUser = (state: IApplicationState): Partial => {
+ if (!isEmpty(state.share?.user)) {
+ return state.share.user;
+ }
+ return state.login?.user;
+};
+const getLoginServices = (state: IApplicationState) => (state.login.services as IServices) || {};
+const getShowFormLoginSetting = (state: IApplicationState) => (state.settings.Accounts_ShowFormLogin as boolean) || false;
+const getIframeEnabledSetting = (state: IApplicationState) => (state.settings.Accounts_iframe_enabled as boolean) || false;
+
+// TODO: we need to change 42 files to fix a correct type, i believe is better to do this later
+export const getUserSelector = createSelector([getUser], user => user) as any;
+
+export const getShowLoginButton = createSelector(
+ [getLoginServices, getShowFormLoginSetting, getIframeEnabledSetting],
+ (loginServices, showFormLogin, iframeEnabled) =>
+ (showFormLogin || Object.values(loginServices).length || iframeEnabled) as boolean
+);
diff --git a/app/share.tsx b/app/share.tsx
index 4af2d6026..8a756d91a 100644
--- a/app/share.tsx
+++ b/app/share.tsx
@@ -9,6 +9,7 @@ import { defaultTheme, newThemeState, subscribeTheme, unsubscribeTheme } from '.
import UserPreferences from './lib/userPreferences';
import Navigation from './lib/ShareNavigation';
import store from './lib/createStore';
+import { initStore } from './lib/auxStore';
import { supportSystemTheme } from './utils/deviceInfo';
import { defaultHeader, getActiveRouteName, navigationTheme, themedHeader } from './utils/navigation';
import RocketChat, { THEME_PREFERENCES_KEY } from './lib/rocketchat';
@@ -28,6 +29,8 @@ import { DimensionsContext } from './dimensions';
import debounce from './utils/debounce';
import { ShareInsideStackParamList, ShareOutsideStackParamList, ShareAppStackParamList } from './definitions/navigationTypes';
+initStore(store);
+
interface IDimensions {
width: number;
height: number;
diff --git a/app/stacks/InsideStack.tsx b/app/stacks/InsideStack.tsx
index fce844a4a..ec3ae318e 100644
--- a/app/stacks/InsideStack.tsx
+++ b/app/stacks/InsideStack.tsx
@@ -66,6 +66,7 @@ import QueueListView from '../ee/omnichannel/views/QueueListView';
import AddChannelTeamView from '../views/AddChannelTeamView';
import AddExistingChannelView from '../views/AddExistingChannelView';
import SelectListView from '../views/SelectListView';
+import DiscussionsView from '../views/DiscussionsView';
import {
AdminPanelStackParamList,
ChatsStackParamList,
@@ -92,7 +93,8 @@ const ChatsStackNavigator = () => {
-
+
+
{
+
IOptionsField[]) | ((term?: string) => Promise);
+ value?: string;
+ onSearch?: (text?: string) => Promise;
+ onEndReached?: (text: string, offset?: number) => Promise;
+ total?: number;
goBack?: boolean;
onChangeValue: Function;
};
@@ -133,12 +140,7 @@ export type ChatsStackParamList = {
rid: string;
};
CannedResponseDetail: {
- cannedResponse: {
- shortcut: string;
- text: string;
- scopeName: string;
- tags: string[];
- };
+ cannedResponse: ICannedResponse;
room: ISubscription;
};
};
diff --git a/app/utils/avatar.ts b/app/utils/avatar.ts
index 7e4b28195..a2045f7d8 100644
--- a/app/utils/avatar.ts
+++ b/app/utils/avatar.ts
@@ -1,4 +1,4 @@
-import { compareServerVersion, methods } from '../lib/utils';
+import { compareServerVersion } from '../lib/utils';
import { SubscriptionType } from '../definitions/ISubscription';
import { IAvatar } from '../containers/Avatar/interfaces';
@@ -19,7 +19,7 @@ export const avatarURL = ({
let room;
if (type === SubscriptionType.DIRECT) {
room = text;
- } else if (rid && !compareServerVersion(serverVersion, '3.6.0', methods.lowerThan)) {
+ } else if (rid && !compareServerVersion(serverVersion, 'lowerThan', '3.6.0')) {
room = `room/${rid}`;
} else {
room = `@${text}`;
diff --git a/app/utils/base64-js/index.ts b/app/utils/base64-js/index.ts
index 71fac91ce..7a77af0f2 100644
--- a/app/utils/base64-js/index.ts
+++ b/app/utils/base64-js/index.ts
@@ -37,7 +37,7 @@ const getLens = (b64: string) => {
};
// base64 is 4/3 + up to two characters of the original data
-export const byteLength = (b64: string) => {
+export const byteLength = (b64: string): number => {
const lens = getLens(b64);
const validLen = lens[0];
const placeHoldersLen = lens[1];
@@ -47,7 +47,7 @@ export const byteLength = (b64: string) => {
const _byteLength = (b64: string, validLen: number, placeHoldersLen: number) =>
((validLen + placeHoldersLen) * 3) / 4 - placeHoldersLen;
-export const toByteArray = (b64: string) => {
+export const toByteArray = (b64: string): any[] | Uint8Array => {
let tmp;
const lens = getLens(b64);
const validLen = lens[0];
@@ -106,7 +106,7 @@ const encodeChunk = (uint8: number[] | Uint8Array, start: number, end: number) =
return output.join('');
};
-export const fromByteArray = (uint8: number[] | Uint8Array) => {
+export const fromByteArray = (uint8: number[] | Uint8Array): string => {
let tmp;
const len = uint8.length;
const extraBytes = len % 3; // if we have 1 byte left, pad 2 bytes
diff --git a/app/utils/deferred.js b/app/utils/deferred.js
deleted file mode 100644
index 0c0046e55..000000000
--- a/app/utils/deferred.js
+++ /dev/null
@@ -1,14 +0,0 @@
-// https://developer.mozilla.org/en-US/docs/Mozilla/JavaScript_code_modules/Promise.jsm/Deferred
-export default class Deferred {
- constructor() {
- const promise = new Promise((resolve, reject) => {
- this.resolve = resolve;
- this.reject = reject;
- });
-
- promise.resolve = this.resolve;
- promise.reject = this.reject;
-
- return promise;
- }
-}
diff --git a/app/utils/deferred.ts b/app/utils/deferred.ts
new file mode 100644
index 000000000..28e4f29c4
--- /dev/null
+++ b/app/utils/deferred.ts
@@ -0,0 +1,41 @@
+export default class Deferred {
+ [Symbol.toStringTag]: 'Promise';
+
+ private promise: Promise;
+ private _resolve: (value?: unknown) => void;
+ private _reject: (reason?: any) => void;
+
+ constructor() {
+ this._resolve = () => {};
+ this._reject = () => {};
+ this.promise = new Promise((resolve, reject) => {
+ this._resolve = resolve as (value?: unknown) => void;
+ this._reject = reject;
+ });
+ }
+
+ public then(
+ onfulfilled?: ((value: unknown) => TResult1 | PromiseLike) | undefined | null,
+ onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null
+ ): Promise {
+ return this.promise.then(onfulfilled, onrejected);
+ }
+
+ public catch(
+ onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null
+ ): Promise {
+ return this.promise.catch(onrejected);
+ }
+
+ public finally(onfinally?: (() => void) | null | undefined): Promise {
+ return this.promise.finally(onfinally);
+ }
+
+ public resolve(value?: unknown): void {
+ this._resolve(value);
+ }
+
+ public reject(reason?: any): void {
+ this._reject(reason);
+ }
+}
diff --git a/app/utils/fileUpload/index.ios.ts b/app/utils/fileUpload/index.ios.ts
index ae5cfabc2..96c2ae355 100644
--- a/app/utils/fileUpload/index.ios.ts
+++ b/app/utils/fileUpload/index.ios.ts
@@ -43,6 +43,7 @@ class FileUpload {
upload.formData.append(item.name, {
// @ts-ignore
uri: item.uri,
+ // @ts-ignore
type: item.type,
name: item.filename
});
diff --git a/app/utils/fileUpload/interfaces.ts b/app/utils/fileUpload/interfaces.ts
index a3002f727..91b0d7d46 100644
--- a/app/utils/fileUpload/interfaces.ts
+++ b/app/utils/fileUpload/interfaces.ts
@@ -1,7 +1,7 @@
export interface IFileUpload {
name: string;
uri?: string;
- type: string;
- filename: string;
- data: any;
+ type?: string;
+ filename?: string;
+ data?: any;
}
diff --git a/app/utils/goRoom.ts b/app/utils/goRoom.ts
index dc8a31882..0a49bb215 100644
--- a/app/utils/goRoom.ts
+++ b/app/utils/goRoom.ts
@@ -31,9 +31,8 @@ const navigate = ({
};
interface IItem extends Partial {
- rid: string;
- name: string;
- t: SubscriptionType;
+ search?: boolean; // comes from spotlight
+ username?: string;
}
export const goRoom = async ({
@@ -45,17 +44,19 @@ export const goRoom = async ({
isMasterDetail: boolean;
navigationMethod?: any;
jumpToMessageId?: string;
+ usedCannedResponse?: string;
}): Promise => {
- if (item.t === 'd' && item.search) {
+ if (item.t === SubscriptionType.DIRECT && item?.search) {
// if user is using the search we need first to join/create room
try {
const { username } = item;
+ // @ts-ignore
const result = await RocketChat.createDirectMessage(username);
if (result.success) {
return navigate({
item: {
rid: result.room._id,
- name: username!,
+ name: username || '',
t: SubscriptionType.DIRECT
},
isMasterDetail,
diff --git a/app/utils/isReadOnly.ts b/app/utils/isReadOnly.ts
index d94b73c49..6afc2d962 100644
--- a/app/utils/isReadOnly.ts
+++ b/app/utils/isReadOnly.ts
@@ -1,5 +1,5 @@
import RocketChat from '../lib/rocketchat';
-import reduxStore from '../lib/createStore';
+import { store as reduxStore } from '../lib/auxStore';
import { ISubscription } from '../definitions/ISubscription';
const canPostReadOnly = async ({ rid }: { rid: string }) => {
diff --git a/app/utils/localAuthentication.ts b/app/utils/localAuthentication.ts
index f43599633..141268b4d 100644
--- a/app/utils/localAuthentication.ts
+++ b/app/utils/localAuthentication.ts
@@ -1,14 +1,16 @@
import * as LocalAuthentication from 'expo-local-authentication';
-import moment from 'moment';
import RNBootSplash from 'react-native-bootsplash';
import AsyncStorage from '@react-native-community/async-storage';
import { sha256 } from 'js-sha256';
+import moment from 'moment';
import UserPreferences from '../lib/userPreferences';
-import store from '../lib/createStore';
+import { store } from '../lib/auxStore';
import database from '../lib/database';
+import { getServerTimeSync } from '../lib/rocketchat/services/getServerTimeSync';
import {
ATTEMPTS_KEY,
+ BIOMETRY_ENABLED_KEY,
CHANGE_PASSCODE_EMITTER,
LOCAL_AUTHENTICATE_EMITTER,
LOCKED_OUT_TIMER_KEY,
@@ -20,16 +22,25 @@ import { TServerModel } from '../definitions/IServer';
import EventEmitter from './events';
import { isIOS } from './deviceInfo';
-export const saveLastLocalAuthenticationSession = async (server: string, serverRecord?: TServerModel): Promise => {
+export const saveLastLocalAuthenticationSession = async (
+ server: string,
+ serverRecord?: TServerModel,
+ timesync?: number | null
+): Promise => {
+ if (!timesync) {
+ timesync = new Date().getTime();
+ }
+
const serversDB = database.servers;
const serversCollection = serversDB.get('servers');
await serversDB.write(async () => {
try {
if (!serverRecord) {
- serverRecord = (await serversCollection.find(server)) as TServerModel;
+ serverRecord = await serversCollection.find(server);
}
+ const time = timesync || 0;
await serverRecord.update(record => {
- record.lastLocalAuthenticatedSession = new Date();
+ record.lastLocalAuthenticatedSession = new Date(time);
});
} catch (e) {
// Do nothing
@@ -72,32 +83,18 @@ export const biometryAuth = (force?: boolean): Promise {
- const serversDB = database.servers;
-
+const checkBiometry = async () => {
const result = await biometryAuth(true);
- await serversDB.write(async () => {
- try {
- await serverRecord.update(record => {
- record.biometry = !!result?.success;
- });
- } catch {
- // Do nothing
- }
- });
+ const isBiometryEnabled = !!result?.success;
+ await UserPreferences.setBoolAsync(BIOMETRY_ENABLED_KEY, isBiometryEnabled);
+ return isBiometryEnabled;
};
-export const checkHasPasscode = async ({
- force = true,
- serverRecord
-}: {
- force?: boolean;
- serverRecord: TServerModel;
-}): Promise<{ newPasscode?: boolean } | void> => {
+export const checkHasPasscode = async ({ force = true }: { force?: boolean }): Promise<{ newPasscode?: boolean } | void> => {
const storedPasscode = await UserPreferences.getStringAsync(PASSCODE_KEY);
if (!storedPasscode) {
await changePasscode({ force });
- await checkBiometry(serverRecord);
+ await checkBiometry();
return Promise.resolve({ newPasscode: true });
}
return Promise.resolve();
@@ -116,6 +113,9 @@ export const localAuthenticate = async (server: string): Promise => {
// if screen lock is enabled
if (serverRecord?.autoLock) {
+ // Get time from server
+ const timesync = await getServerTimeSync(server);
+
// Make sure splash screen has been hidden
try {
await RNBootSplash.hide();
@@ -124,22 +124,23 @@ export const localAuthenticate = async (server: string): Promise => {
}
// Check if the app has passcode
- const result = await checkHasPasscode({ serverRecord });
+ const result = await checkHasPasscode({});
// `checkHasPasscode` results newPasscode = true if a passcode has been set
if (!result?.newPasscode) {
// diff to last authenticated session
- const diffToLastSession = moment().diff(serverRecord?.lastLocalAuthenticatedSession, 'seconds');
+ const diffToLastSession = moment(timesync).diff(serverRecord?.lastLocalAuthenticatedSession, 'seconds');
- // if last authenticated session is older than configured auto lock time, authentication is required
- if (diffToLastSession >= serverRecord.autoLockTime!) {
+ // if it was not possible to get `timesync` from server or the last authenticated session is older than the configured auto lock time, authentication is required
+ if (!timesync || (serverRecord?.autoLockTime && diffToLastSession >= serverRecord.autoLockTime)) {
// set isLocalAuthenticated to false
store.dispatch(setLocalAuthenticated(false));
- let hasBiometry = false;
+ // let hasBiometry = false;
+ let hasBiometry = (await UserPreferences.getBoolAsync(BIOMETRY_ENABLED_KEY)) ?? false;
// if biometry is enabled on the app
- if (serverRecord.biometry) {
+ if (hasBiometry) {
const isEnrolled = await LocalAuthentication.isEnrolledAsync();
hasBiometry = isEnrolled;
}
@@ -153,7 +154,7 @@ export const localAuthenticate = async (server: string): Promise => {
}
await resetAttempts();
- await saveLastLocalAuthenticationSession(server, serverRecord);
+ await saveLastLocalAuthenticationSession(server, serverRecord, timesync);
}
};
diff --git a/app/utils/log/index.ts b/app/utils/log/index.ts
index d52bd0e94..bebbf147a 100644
--- a/app/utils/log/index.ts
+++ b/app/utils/log/index.ts
@@ -77,6 +77,6 @@ export default (e: any): void => {
crashlytics().recordError(e);
}
} else {
- console.log(e);
+ console.error(e);
}
};
diff --git a/app/utils/messageTypes.ts b/app/utils/messageTypes.ts
index 4eefa614f..436856346 100644
--- a/app/utils/messageTypes.ts
+++ b/app/utils/messageTypes.ts
@@ -58,5 +58,33 @@ export const MessageTypeValues = [
{
value: 'room_unarchived',
text: 'Message_HideType_room_unarchived'
+ },
+ {
+ value: 'removed-user-from-team',
+ text: 'Message_HideType_removed_user_from_team'
+ },
+ {
+ value: 'added-user-to-team',
+ text: 'Message_HideType_added_user_to_team'
+ },
+ {
+ value: 'user-added-room-to-team',
+ text: 'Message_HideType_user_added_room_to_team'
+ },
+ {
+ value: 'user-converted-to-channel',
+ text: 'Message_HideType_user_converted_to_channel'
+ },
+ {
+ value: 'user-converted-to-team',
+ text: 'Message_HideType_user_converted_to_team'
+ },
+ {
+ value: 'user-deleted-room-from-team',
+ text: 'Message_HideType_user_deleted_room_from_team'
+ },
+ {
+ value: 'user-removed-room-from-team',
+ text: 'Message_HideType_user_removed_room_from_team'
}
];
diff --git a/app/utils/sslPinning.ts b/app/utils/sslPinning.ts
index 42245c98a..27b100228 100644
--- a/app/utils/sslPinning.ts
+++ b/app/utils/sslPinning.ts
@@ -5,6 +5,7 @@ import * as FileSystem from 'expo-file-system';
import UserPreferences from '../lib/userPreferences';
import I18n from '../i18n';
import { extractHostname } from './server';
+import { ICertificate } from '../definitions';
const { SSLPinning } = NativeModules;
const { documentDirectory } = FileSystem;
@@ -13,11 +14,6 @@ const extractFileScheme = (path: string) => path.replace('file://', ''); // file
const getPath = (name: string) => `${documentDirectory}/${name}`;
-interface ICertificate {
- path: string;
- password: string;
-}
-
const persistCertificate = async (name: string, password: string) => {
const certificatePath = getPath(name);
const certificate: ICertificate = {
diff --git a/app/views/AddExistingChannelView.tsx b/app/views/AddExistingChannelView.tsx
index 98fe4d2ba..69af6b57b 100644
--- a/app/views/AddExistingChannelView.tsx
+++ b/app/views/AddExistingChannelView.tsx
@@ -22,11 +22,11 @@ import { goRoom } from '../utils/goRoom';
import { showErrorAlert } from '../utils/info';
import debounce from '../utils/debounce';
import { ChatsStackParamList } from '../stacks/types';
-import { IRoom } from '../definitions/IRoom';
+import { TSubscriptionModel, SubscriptionType } from '../definitions';
interface IAddExistingChannelViewState {
- search: Array;
- channels: Array;
+ search: TSubscriptionModel[];
+ channels: TSubscriptionModel[];
selected: string[];
loading: boolean;
}
@@ -42,12 +42,12 @@ interface IAddExistingChannelViewProps {
const QUERY_SIZE = 50;
class AddExistingChannelView extends React.Component {
- private teamId?: string;
+ private teamId: string;
constructor(props: IAddExistingChannelViewProps) {
super(props);
this.query();
- this.teamId = props.route?.params?.teamId;
+ this.teamId = props.route?.params?.teamId ?? '';
this.state = {
search: [],
channels: [],
@@ -83,7 +83,7 @@ class AddExistingChannelView extends React.Component) => {
+ const asyncFilter = async (channelsArray: TSubscriptionModel[]) => {
const results = await Promise.all(
- channelsArray.map(async (channel: IRoom) => {
+ channelsArray.map(async channel => {
if (channel.prid) {
return false;
}
@@ -136,6 +136,8 @@ class AddExistingChannelView extends React.Component {
+ renderItem = ({ item }: { item: TSubscriptionModel }) => {
const isChecked = this.isChecked(item.rid);
// TODO: reuse logic inside RoomTypeIcon
- const icon = item.t === 'p' && !item.teamId ? 'channel-private' : 'channel-public';
+ const icon = item.t === SubscriptionType.DIRECT && !item?.teamId ? 'channel-private' : 'channel-public';
return (
{
});
private mounted: boolean;
- private rid: string | undefined;
+ private rid: string;
private roomObservable: any;
private subscription: any;
constructor(props: IAutoTranslateViewProps) {
super(props);
this.mounted = false;
- this.rid = props.route.params?.rid;
+ this.rid = props.route.params?.rid ?? '';
const room = props.route.params?.room;
if (room && room.observe) {
diff --git a/app/views/CannedResponseDetail.js b/app/views/CannedResponseDetail.tsx
similarity index 80%
rename from app/views/CannedResponseDetail.js
rename to app/views/CannedResponseDetail.tsx
index 67002bbdb..8c42b7aff 100644
--- a/app/views/CannedResponseDetail.js
+++ b/app/views/CannedResponseDetail.tsx
@@ -1,5 +1,6 @@
import React, { useEffect } from 'react';
-import PropTypes from 'prop-types';
+import { StackNavigationProp } from '@react-navigation/stack';
+import { RouteProp } from '@react-navigation/native';
import { StyleSheet, Text, View, ScrollView } from 'react-native';
import { useSelector } from 'react-redux';
@@ -13,6 +14,8 @@ import Navigation from '../lib/Navigation';
import { goRoom } from '../utils/goRoom';
import { themes } from '../constants/colors';
import Markdown from '../containers/markdown';
+import { ICannedResponse } from '../definitions/ICannedResponse';
+import { ChatsStackParamList } from '../stacks/types';
import sharedStyles from './Styles';
const styles = StyleSheet.create({
@@ -68,27 +71,34 @@ const styles = StyleSheet.create({
}
});
-const Item = ({ label, content, theme, testID }) =>
+interface IItem {
+ label: string;
+ content?: string;
+ theme: string;
+ testID?: string;
+}
+
+const Item = ({ label, content, theme, testID }: IItem) =>
content ? (
{label}
+ {/* @ts-ignore */}
) : null;
-Item.propTypes = {
- label: PropTypes.string,
- content: PropTypes.string,
- theme: PropTypes.string,
- testID: PropTypes.string
-};
-const CannedResponseDetail = ({ navigation, route }) => {
+interface ICannedResponseDetailProps {
+ navigation: StackNavigationProp;
+ route: RouteProp;
+}
+
+const CannedResponseDetail = ({ navigation, route }: ICannedResponseDetailProps): JSX.Element => {
const { cannedResponse } = route?.params;
const { theme } = useTheme();
- const { isMasterDetail } = useSelector(state => state.app);
- const { rooms } = useSelector(state => state.room);
+ const { isMasterDetail } = useSelector((state: any) => state.app);
+ const { rooms } = useSelector((state: any) => state.room);
useEffect(() => {
navigation.setOptions({
@@ -96,15 +106,14 @@ const CannedResponseDetail = ({ navigation, route }) => {
});
}, []);
- const navigateToRoom = item => {
+ const navigateToRoom = (item: ICannedResponse) => {
const { room } = route.params;
- const { name, username } = room;
+ const { name } = room;
const params = {
rid: room.rid,
name: RocketChat.getRoomTitle({
t: room.t,
- fname: name,
- name: username
+ fname: name
}),
t: room.t,
roomUserId: RocketChat.getUidDirectMessage(room),
@@ -115,7 +124,7 @@ const CannedResponseDetail = ({ navigation, route }) => {
// if it's on master detail layout, we close the modal and replace RoomView
if (isMasterDetail) {
Navigation.navigate('DrawerNavigator');
- goRoom({ item: params, isMasterDetail, usedCannedResponse: item.text });
+ goRoom({ item: params, isMasterDetail });
} else {
let navigate = navigation.push;
// if this is a room focused
@@ -163,9 +172,4 @@ const CannedResponseDetail = ({ navigation, route }) => {
);
};
-CannedResponseDetail.propTypes = {
- navigation: PropTypes.object,
- route: PropTypes.object
-};
-
export default CannedResponseDetail;
diff --git a/app/views/CannedResponsesListView/CannedResponseItem.js b/app/views/CannedResponsesListView/CannedResponseItem.tsx
similarity index 77%
rename from app/views/CannedResponsesListView/CannedResponseItem.js
rename to app/views/CannedResponsesListView/CannedResponseItem.tsx
index cc5f3c39a..d81e0e366 100644
--- a/app/views/CannedResponsesListView/CannedResponseItem.js
+++ b/app/views/CannedResponsesListView/CannedResponseItem.tsx
@@ -1,14 +1,31 @@
import React from 'react';
-import PropTypes from 'prop-types';
import { View, Text } from 'react-native';
-
import Touchable from 'react-native-platform-touchable';
+
import { themes } from '../../constants/colors';
import Button from '../../containers/Button';
import I18n from '../../i18n';
import styles from './styles';
-const CannedResponseItem = ({ theme, onPressDetail, shortcut, scope, onPressUse, text, tags }) => (
+interface ICannedResponseItem {
+ theme: string;
+ onPressDetail: () => void;
+ shortcut: string;
+ scope: string;
+ onPressUse: () => void;
+ text: string;
+ tags: string[];
+}
+
+const CannedResponseItem = ({
+ theme,
+ onPressDetail = () => {},
+ shortcut,
+ scope,
+ onPressUse = () => {},
+ text,
+ tags
+}: ICannedResponseItem): JSX.Element => (
<>
@@ -43,19 +60,4 @@ const CannedResponseItem = ({ theme, onPressDetail, shortcut, scope, onPressUse,
);
-CannedResponseItem.propTypes = {
- theme: PropTypes.string,
- onPressDetail: PropTypes.func,
- shortcut: PropTypes.string,
- scope: PropTypes.string,
- onPressUse: PropTypes.func,
- text: PropTypes.string,
- tags: PropTypes.array
-};
-
-CannedResponseItem.defaultProps = {
- onPressDetail: () => {},
- onPressUse: () => {}
-};
-
export default CannedResponseItem;
diff --git a/app/views/CannedResponsesListView/Dropdown/DropdownItem.js b/app/views/CannedResponsesListView/Dropdown/DropdownItem.js
deleted file mode 100644
index 85b9aa703..000000000
--- a/app/views/CannedResponsesListView/Dropdown/DropdownItem.js
+++ /dev/null
@@ -1,44 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import { StyleSheet, Text, View } from 'react-native';
-
-import { themes } from '../../../constants/colors';
-import { withTheme } from '../../../theme';
-import Touch from '../../../utils/touch';
-import { CustomIcon } from '../../../lib/Icons';
-import sharedStyles from '../../Styles';
-
-export const ROW_HEIGHT = 44;
-
-const styles = StyleSheet.create({
- container: {
- paddingVertical: 11,
- height: ROW_HEIGHT,
- paddingHorizontal: 16,
- flexDirection: 'row',
- alignItems: 'center'
- },
- text: {
- flex: 1,
- fontSize: 16,
- ...sharedStyles.textRegular
- }
-});
-
-const DropdownItem = React.memo(({ theme, onPress, iconName, text }) => (
-
-
- {text}
- {iconName ? : null}
-
-
-));
-
-DropdownItem.propTypes = {
- text: PropTypes.string,
- iconName: PropTypes.string,
- theme: PropTypes.string,
- onPress: PropTypes.func
-};
-
-export default withTheme(DropdownItem);
diff --git a/app/views/CannedResponsesListView/Dropdown/DropdownItem.tsx b/app/views/CannedResponsesListView/Dropdown/DropdownItem.tsx
new file mode 100644
index 000000000..ebae90378
--- /dev/null
+++ b/app/views/CannedResponsesListView/Dropdown/DropdownItem.tsx
@@ -0,0 +1,46 @@
+import React from 'react';
+import { StyleSheet, Text, View } from 'react-native';
+
+import { themes } from '../../../constants/colors';
+import { useTheme } from '../../../theme';
+import Touch from '../../../utils/touch';
+import { CustomIcon } from '../../../lib/Icons';
+import sharedStyles from '../../Styles';
+
+export const ROW_HEIGHT = 44;
+
+const styles = StyleSheet.create({
+ container: {
+ paddingVertical: 11,
+ height: ROW_HEIGHT,
+ paddingHorizontal: 16,
+ flexDirection: 'row',
+ alignItems: 'center'
+ },
+ text: {
+ flex: 1,
+ fontSize: 16,
+ ...sharedStyles.textRegular
+ }
+});
+
+interface IDropdownItem {
+ text: string;
+ iconName: string | null;
+ onPress: () => void;
+}
+
+const DropdownItem = React.memo(({ onPress, iconName, text }: IDropdownItem) => {
+ const { theme } = useTheme();
+
+ return (
+
+
+ {text}
+ {iconName ? : null}
+
+
+ );
+});
+
+export default DropdownItem;
diff --git a/app/views/CannedResponsesListView/Dropdown/DropdownItemFilter.js b/app/views/CannedResponsesListView/Dropdown/DropdownItemFilter.tsx
similarity index 57%
rename from app/views/CannedResponsesListView/Dropdown/DropdownItemFilter.js
rename to app/views/CannedResponsesListView/Dropdown/DropdownItemFilter.tsx
index d4e457805..a2d68c528 100644
--- a/app/views/CannedResponsesListView/Dropdown/DropdownItemFilter.js
+++ b/app/views/CannedResponsesListView/Dropdown/DropdownItemFilter.tsx
@@ -1,9 +1,15 @@
import React from 'react';
-import PropTypes from 'prop-types';
+import { IDepartment } from '../../../definitions/ICannedResponse';
import DropdownItem from './DropdownItem';
-const DropdownItemFilter = ({ currentDepartment, value, onPress }) => (
+interface IDropdownItemFilter {
+ currentDepartment: IDepartment;
+ value: IDepartment;
+ onPress: (value: IDepartment) => void;
+}
+
+const DropdownItemFilter = ({ currentDepartment, value, onPress }: IDropdownItemFilter): JSX.Element => (
(
/>
);
-DropdownItemFilter.propTypes = {
- currentDepartment: PropTypes.object,
- value: PropTypes.string,
- onPress: PropTypes.func
-};
-
export default DropdownItemFilter;
diff --git a/app/views/CannedResponsesListView/Dropdown/DropdownItemHeader.js b/app/views/CannedResponsesListView/Dropdown/DropdownItemHeader.js
deleted file mode 100644
index 4f1f2b68f..000000000
--- a/app/views/CannedResponsesListView/Dropdown/DropdownItemHeader.js
+++ /dev/null
@@ -1,15 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-
-import DropdownItem from './DropdownItem';
-
-const DropdownItemHeader = ({ department, onPress }) => (
-
-);
-
-DropdownItemHeader.propTypes = {
- department: PropTypes.object,
- onPress: PropTypes.func
-};
-
-export default DropdownItemHeader;
diff --git a/app/views/CannedResponsesListView/Dropdown/DropdownItemHeader.tsx b/app/views/CannedResponsesListView/Dropdown/DropdownItemHeader.tsx
new file mode 100644
index 000000000..ecfa95e8a
--- /dev/null
+++ b/app/views/CannedResponsesListView/Dropdown/DropdownItemHeader.tsx
@@ -0,0 +1,15 @@
+import React from 'react';
+
+import { IDepartment } from '../../../definitions/ICannedResponse';
+import DropdownItem from './DropdownItem';
+
+interface IDropdownItemHeader {
+ department: IDepartment;
+ onPress: () => void;
+}
+
+const DropdownItemHeader = ({ department, onPress }: IDropdownItemHeader): JSX.Element => (
+
+);
+
+export default DropdownItemHeader;
diff --git a/app/views/CannedResponsesListView/Dropdown/index.js b/app/views/CannedResponsesListView/Dropdown/index.tsx
similarity index 72%
rename from app/views/CannedResponsesListView/Dropdown/index.js
rename to app/views/CannedResponsesListView/Dropdown/index.tsx
index e2735e5c5..d723bc4c8 100644
--- a/app/views/CannedResponsesListView/Dropdown/index.js
+++ b/app/views/CannedResponsesListView/Dropdown/index.tsx
@@ -1,31 +1,30 @@
import React from 'react';
-import PropTypes from 'prop-types';
import { Animated, Easing, FlatList, TouchableWithoutFeedback } from 'react-native';
import { withSafeAreaInsets } from 'react-native-safe-area-context';
import styles from '../styles';
import { themes } from '../../../constants/colors';
import { withTheme } from '../../../theme';
-import { headerHeight } from '../../../containers/Header';
import * as List from '../../../containers/List';
+import { IDepartment } from '../../../definitions/ICannedResponse';
import DropdownItemFilter from './DropdownItemFilter';
import DropdownItemHeader from './DropdownItemHeader';
import { ROW_HEIGHT } from './DropdownItem';
const ANIMATION_DURATION = 200;
-class Dropdown extends React.Component {
- static propTypes = {
- isMasterDetail: PropTypes.bool,
- theme: PropTypes.string,
- insets: PropTypes.object,
- currentDepartment: PropTypes.object,
- onClose: PropTypes.func,
- onDepartmentSelected: PropTypes.func,
- departments: PropTypes.array
- };
+interface IDropdownProps {
+ theme?: string;
+ currentDepartment: IDepartment;
+ onClose: () => void;
+ onDepartmentSelected: (value: IDepartment) => void;
+ departments: IDepartment[];
+}
- constructor(props) {
+class Dropdown extends React.Component {
+ private animatedValue: Animated.Value;
+
+ constructor(props: IDropdownProps) {
super(props);
this.animatedValue = new Animated.Value(0);
}
@@ -50,16 +49,15 @@ class Dropdown extends React.Component {
};
render() {
- const { isMasterDetail, insets, theme, currentDepartment, onDepartmentSelected, departments } = this.props;
- const statusBarHeight = insets?.top ?? 0;
- const heightDestination = isMasterDetail ? headerHeight + statusBarHeight : 0;
+ const { theme, currentDepartment, onDepartmentSelected, departments } = this.props;
+ const heightDestination = 0;
const translateY = this.animatedValue.interpolate({
inputRange: [0, 1],
outputRange: [-300, heightDestination] // approximated height of the component when closed/open
});
const backdropOpacity = this.animatedValue.interpolate({
inputRange: [0, 1],
- outputRange: [0, themes[theme].backdropOpacity]
+ outputRange: [0, themes[theme!].backdropOpacity]
});
const maxRows = 5;
@@ -70,7 +68,7 @@ class Dropdown extends React.Component {
style={[
styles.backdrop,
{
- backgroundColor: themes[theme].backdropColor,
+ backgroundColor: themes[theme!].backdropColor,
opacity: backdropOpacity,
top: heightDestination
}
@@ -82,8 +80,8 @@ class Dropdown extends React.Component {
styles.dropdownContainer,
{
transform: [{ translateY }],
- backgroundColor: themes[theme].backgroundColor,
- borderColor: themes[theme].separatorColor
+ backgroundColor: themes[theme!].backgroundColor,
+ borderColor: themes[theme!].separatorColor
}
]}>
diff --git a/app/views/CannedResponsesListView/__snapshots__/CannedResponseItem.stories.storyshot b/app/views/CannedResponsesListView/__snapshots__/CannedResponseItem.stories.storyshot
new file mode 100644
index 000000000..29c25024c
--- /dev/null
+++ b/app/views/CannedResponsesListView/__snapshots__/CannedResponseItem.stories.storyshot
@@ -0,0 +1,3 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Storyshots CannedResponseItem Itens 1`] = `"[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"focusable\\":true,\\"style\\":{\\"minHeight\\":117,\\"maxHeight\\":141,\\"padding\\":16,\\"backgroundColor\\":\\"#ffffff\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flexDirection\\":\\"row\\",\\"height\\":36}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"flex\\":1,\\"fontSize\\":14,\\"paddingTop\\":0,\\"paddingBottom\\":0,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#0d0e12\\"}]},\\"children\\":[\\"!\\",\\"!FAQ4\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"flex\\":1,\\"fontSize\\":12,\\"paddingTop\\":0,\\"paddingBottom\\":0,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\"},{\\"color\\":\\"#6C727A\\"}]},\\"children\\":[\\"Private\\"]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Use\\",\\"focusable\\":true,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":28,\\"borderRadius\\":2,\\"marginBottom\\":12,\\"backgroundColor\\":\\"#f3f4f5\\",\\"width\\":56,\\"marginLeft\\":8,\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"fontSize\\":16,\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#0d0e12\\"},{\\"fontSize\\":12},null],\\"accessibilityLabel\\":\\"Use\\"},\\"children\\":[\\"Use\\"]}]}]},{\\"type\\":\\"Text\\",\\"props\\":{\\"ellipsizeMode\\":\\"tail\\",\\"numberOfLines\\":2,\\"style\\":[{\\"marginTop\\":8,\\"fontSize\\":14,\\"paddingTop\\":0,\\"paddingBottom\\":0,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\"},{\\"color\\":\\"#6C727A\\"}]},\\"children\\":[\\"“\\",\\"ZCVXZVXCZVZXVZXCVZXCVXZCVZX\\",\\"”\\"]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flexDirection\\":\\"row\\",\\"overflow\\":\\"hidden\\"}},\\"children\\":null}]},{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"focusable\\":true,\\"style\\":{\\"minHeight\\":117,\\"maxHeight\\":141,\\"padding\\":16,\\"backgroundColor\\":\\"#ffffff\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flexDirection\\":\\"row\\",\\"height\\":36}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flex\\":1}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"flex\\":1,\\"fontSize\\":14,\\"paddingTop\\":0,\\"paddingBottom\\":0,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#0d0e12\\"}]},\\"children\\":[\\"!\\",\\"test4mobilePrivate\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"flex\\":1,\\"fontSize\\":12,\\"paddingTop\\":0,\\"paddingBottom\\":0,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\"},{\\"color\\":\\"#6C727A\\"}]},\\"children\\":[\\"Private\\"]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Use\\",\\"focusable\\":true,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":28,\\"borderRadius\\":2,\\"marginBottom\\":12,\\"backgroundColor\\":\\"#f3f4f5\\",\\"width\\":56,\\"marginLeft\\":8,\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"fontSize\\":16,\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#0d0e12\\"},{\\"fontSize\\":12},null],\\"accessibilityLabel\\":\\"Use\\"},\\"children\\":[\\"Use\\"]}]}]},{\\"type\\":\\"Text\\",\\"props\\":{\\"ellipsizeMode\\":\\"tail\\",\\"numberOfLines\\":2,\\"style\\":[{\\"marginTop\\":8,\\"fontSize\\":14,\\"paddingTop\\":0,\\"paddingBottom\\":0,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\"},{\\"color\\":\\"#6C727A\\"}]},\\"children\\":[\\"“\\",\\"test for mobile private\\",\\"”\\"]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flexDirection\\":\\"row\\",\\"overflow\\":\\"hidden\\"}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"borderRadius\\":4,\\"marginRight\\":4,\\"marginTop\\":8,\\"height\\":16},{\\"backgroundColor\\":\\"#E6E6E7\\"}]},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"fontSize\\":12,\\"paddingTop\\":0,\\"paddingBottom\\":0,\\"paddingHorizontal\\":4,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\"},{\\"color\\":\\"#6C727A\\"}]},\\"children\\":[\\"HQ\\"]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"borderRadius\\":4,\\"marginRight\\":4,\\"marginTop\\":8,\\"height\\":16},{\\"backgroundColor\\":\\"#E6E6E7\\"}]},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"fontSize\\":12,\\"paddingTop\\":0,\\"paddingBottom\\":0,\\"paddingHorizontal\\":4,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\"},{\\"color\\":\\"#6C727A\\"}]},\\"children\\":[\\"Closed\\"]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"borderRadius\\":4,\\"marginRight\\":4,\\"marginTop\\":8,\\"height\\":16},{\\"backgroundColor\\":\\"#E6E6E7\\"}]},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"fontSize\\":12,\\"paddingTop\\":0,\\"paddingBottom\\":0,\\"paddingHorizontal\\":4,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\"},{\\"color\\":\\"#6C727A\\"}]},\\"children\\":[\\"HQ\\"]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"borderRadius\\":4,\\"marginRight\\":4,\\"marginTop\\":8,\\"height\\":16},{\\"backgroundColor\\":\\"#E6E6E7\\"}]},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"fontSize\\":12,\\"paddingTop\\":0,\\"paddingBottom\\":0,\\"paddingHorizontal\\":4,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\"},{\\"color\\":\\"#6C727A\\"}]},\\"children\\":[\\"Problem in Product Y\\"]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"borderRadius\\":4,\\"marginRight\\":4,\\"marginTop\\":8,\\"height\\":16},{\\"backgroundColor\\":\\"#E6E6E7\\"}]},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"fontSize\\":12,\\"paddingTop\\":0,\\"paddingBottom\\":0,\\"paddingHorizontal\\":4,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\"},{\\"color\\":\\"#6C727A\\"}]},\\"children\\":[\\"HQ\\"]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"borderRadius\\":4,\\"marginRight\\":4,\\"marginTop\\":8,\\"height\\":16},{\\"backgroundColor\\":\\"#E6E6E7\\"}]},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"fontSize\\":12,\\"paddingTop\\":0,\\"paddingBottom\\":0,\\"paddingHorizontal\\":4,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\"},{\\"color\\":\\"#6C727A\\"}]},\\"children\\":[\\"Closed\\"]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"borderRadius\\":4,\\"marginRight\\":4,\\"marginTop\\":8,\\"height\\":16},{\\"backgroundColor\\":\\"#E6E6E7\\"}]},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"fontSize\\":12,\\"paddingTop\\":0,\\"paddingBottom\\":0,\\"paddingHorizontal\\":4,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"System\\",\\"fontWeight\\":\\"400\\"},{\\"color\\":\\"#6C727A\\"}]},\\"children\\":[\\"Problem in Product Y\\"]}]}]}]}]"`;
diff --git a/app/views/CannedResponsesListView/index.js b/app/views/CannedResponsesListView/index.tsx
similarity index 82%
rename from app/views/CannedResponsesListView/index.js
rename to app/views/CannedResponsesListView/index.tsx
index f9f515deb..47ec73c65 100644
--- a/app/views/CannedResponsesListView/index.js
+++ b/app/views/CannedResponsesListView/index.tsx
@@ -1,9 +1,9 @@
import React, { useEffect, useState, useCallback } from 'react';
-import PropTypes from 'prop-types';
import { FlatList } from 'react-native';
import { useSelector } from 'react-redux';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
-import { HeaderBackButton } from '@react-navigation/stack';
+import { RouteProp } from '@react-navigation/native';
+import { HeaderBackButton, StackNavigationOptions, StackNavigationProp } from '@react-navigation/stack';
import database from '../../lib/database';
import I18n from '../../i18n';
@@ -26,6 +26,9 @@ import CannedResponseItem from './CannedResponseItem';
import Dropdown from './Dropdown';
import DropdownItemHeader from './Dropdown/DropdownItemHeader';
import styles from './styles';
+import { ICannedResponse, IDepartment } from '../../definitions/ICannedResponse';
+import { ChatsStackParamList } from '../../stacks/types';
+import { ISubscription } from '../../definitions/ISubscription';
const COUNT = 25;
@@ -42,14 +45,19 @@ const fixedScopes = [
_id: 'user',
name: I18n.t('Private')
}
-];
+] as IDepartment[];
-const CannedResponsesListView = ({ navigation, route }) => {
- const [room, setRoom] = useState(null);
+interface ICannedResponsesListViewProps {
+ navigation: StackNavigationProp;
+ route: RouteProp;
+}
- const [cannedResponses, setCannedResponses] = useState([]);
- const [cannedResponsesScopeName, setCannedResponsesScopeName] = useState([]);
- const [departments, setDepartments] = useState([]);
+const CannedResponsesListView = ({ navigation, route }: ICannedResponsesListViewProps): JSX.Element => {
+ const [room, setRoom] = useState(null);
+
+ const [cannedResponses, setCannedResponses] = useState([]);
+ const [cannedResponsesScopeName, setCannedResponsesScopeName] = useState([]);
+ const [departments, setDepartments] = useState([]);
// states used by the filter in Header and Dropdown
const [isSearching, setIsSearching] = useState(false);
@@ -65,8 +73,8 @@ const CannedResponsesListView = ({ navigation, route }) => {
const insets = useSafeAreaInsets();
const { theme } = useTheme();
- const { isMasterDetail } = useSelector(state => state.app);
- const { rooms } = useSelector(state => state.room);
+ const { isMasterDetail } = useSelector((state: any) => state.app);
+ const { rooms } = useSelector((state: any) => state.room);
const getRoomFromDb = async () => {
const { rid } = route.params;
@@ -83,7 +91,7 @@ const CannedResponsesListView = ({ navigation, route }) => {
const getDepartments = debounce(async () => {
try {
- const res = await RocketChat.getDepartments();
+ const res: any = await RocketChat.getDepartments();
if (res.success) {
setDepartments([...fixedScopes, ...res.departments]);
}
@@ -93,21 +101,22 @@ const CannedResponsesListView = ({ navigation, route }) => {
}
}, 300);
- const goToDetail = item => {
- navigation.navigate('CannedResponseDetail', { cannedResponse: item, room });
+ const goToDetail = (item: ICannedResponse) => {
+ if (room) {
+ navigation.navigate('CannedResponseDetail', { cannedResponse: item, room });
+ }
};
- const navigateToRoom = item => {
+ const navigateToRoom = (item: ICannedResponse) => {
if (!room) {
return;
}
- const { name, username } = room;
+ const { name } = room;
const params = {
rid: room.rid,
name: RocketChat.getRoomTitle({
t: room.t,
- fname: name,
- name: username
+ fname: name
}),
t: room.t,
roomUserId: RocketChat.getUidDirectMessage(room),
@@ -118,7 +127,7 @@ const CannedResponsesListView = ({ navigation, route }) => {
// if it's on master detail layout, we close the modal and replace RoomView
if (isMasterDetail) {
Navigation.navigate('DrawerNavigator');
- goRoom({ item: params, isMasterDetail, usedCannedResponse: item.text });
+ goRoom({ item: params, isMasterDetail });
} else {
let navigate = navigation.push;
// if this is a room focused
@@ -130,7 +139,17 @@ const CannedResponsesListView = ({ navigation, route }) => {
}
};
- const getListCannedResponse = async ({ text, department, depId, debounced }) => {
+ const getListCannedResponse = async ({
+ text,
+ department,
+ depId,
+ debounced
+ }: {
+ text: string;
+ department: string;
+ depId: string;
+ debounced: boolean;
+ }) => {
try {
const res = await RocketChat.getListCannedResponse({
text,
@@ -188,13 +207,13 @@ const CannedResponsesListView = ({ navigation, route }) => {
setOffset(0);
};
- const onChangeText = text => {
+ const onChangeText = (text: string) => {
newSearch();
setSearchText(text);
searchCallback(text, scope, departmentId);
};
- const onDepartmentSelect = value => {
+ const onDepartmentSelect = (value: IDepartment) => {
let department = '';
let depId = '';
@@ -225,7 +244,7 @@ const CannedResponsesListView = ({ navigation, route }) => {
await getListCannedResponse({ text: searchText, department: scope, depId: departmentId, debounced: false });
};
- const getHeader = () => {
+ const getHeader = (): StackNavigationOptions => {
if (isSearching) {
const headerTitlePosition = getHeaderTitlePosition({ insets, numIconsRight: 1 });
return {
@@ -235,13 +254,13 @@ const CannedResponsesListView = ({ navigation, route }) => {
{
- onChangeText();
+ onChangeText('');
setIsSearching(false);
}}
/>
),
- headerTitle: () => ,
+ headerTitle: () => ,
headerTitleContainerStyle: {
left: headerTitlePosition.left,
right: headerTitlePosition.right
@@ -250,7 +269,7 @@ const CannedResponsesListView = ({ navigation, route }) => {
};
}
- const options = {
+ const options: StackNavigationOptions = {
headerLeft: () => (
navigation.pop()} tintColor={themes[theme].headerTintColor} />
),
@@ -355,9 +374,4 @@ const CannedResponsesListView = ({ navigation, route }) => {
);
};
-CannedResponsesListView.propTypes = {
- navigation: PropTypes.object,
- route: PropTypes.object
-};
-
export default CannedResponsesListView;
diff --git a/app/views/CannedResponsesListView/styles.js b/app/views/CannedResponsesListView/styles.ts
similarity index 97%
rename from app/views/CannedResponsesListView/styles.js
rename to app/views/CannedResponsesListView/styles.ts
index d9b8f580c..f0e971867 100644
--- a/app/views/CannedResponsesListView/styles.js
+++ b/app/views/CannedResponsesListView/styles.ts
@@ -13,7 +13,7 @@ export default StyleSheet.create({
borderBottomWidth: StyleSheet.hairlineWidth
},
backdrop: {
- ...StyleSheet.absoluteFill
+ ...StyleSheet.absoluteFillObject
},
wrapCannedItem: {
minHeight: 117,
diff --git a/app/views/CreateChannelView.tsx b/app/views/CreateChannelView.tsx
index e8d719ab4..43df75fbf 100644
--- a/app/views/CreateChannelView.tsx
+++ b/app/views/CreateChannelView.tsx
@@ -1,16 +1,13 @@
import React from 'react';
import { connect } from 'react-redux';
-import { Dispatch } from 'redux';
-import { StackNavigationProp } from '@react-navigation/stack';
-import { RouteProp } from '@react-navigation/native';
import { FlatList, ScrollView, StyleSheet, Switch, Text, View, SwitchProps } from 'react-native';
import { dequal } from 'dequal';
import * as List from '../containers/List';
import TextInput from '../presentation/TextInput';
import Loading from '../containers/Loading';
-import { createChannelRequest as createChannelRequestAction } from '../actions/createChannel';
-import { removeUser as removeUserAction } from '../actions/selectedUsers';
+import { createChannelRequest } from '../actions/createChannel';
+import { removeUser } from '../actions/selectedUsers';
import KeyboardView from '../presentation/KeyboardView';
import scrollPersistTaps from '../utils/scrollPersistTaps';
import I18n from '../i18n';
@@ -26,6 +23,7 @@ import SafeAreaView from '../containers/SafeAreaView';
import RocketChat from '../lib/rocketchat';
import sharedStyles from './Styles';
import { ChatsStackParamList } from '../stacks/types';
+import { IApplicationState, IBaseScreen } from '../definitions';
const styles = StyleSheet.create({
container: {
@@ -75,12 +73,6 @@ interface IOtherUser {
fname: string;
}
-interface ICreateFunction extends Omit {
- name: string;
- users: string[];
- teamId: string;
-}
-
interface ICreateChannelViewState {
channelName: string;
type: boolean;
@@ -91,12 +83,8 @@ interface ICreateChannelViewState {
permissions: boolean[];
}
-interface ICreateChannelViewProps {
- navigation: StackNavigationProp;
- route: RouteProp;
+interface ICreateChannelViewProps extends IBaseScreen {
baseUrl: string;
- create: (data: ICreateFunction) => void;
- removeUser: (user: IOtherUser) => void;
error: object;
failure: boolean;
isFetching: boolean;
@@ -107,10 +95,9 @@ interface ICreateChannelViewProps {
token: string;
roles: string[];
};
- theme: string;
teamId: string;
- createPublicChannelPermission: string[];
- createPrivateChannelPermission: string[];
+ createPublicChannelPermission: string[] | undefined;
+ createPrivateChannelPermission: string[] | undefined;
}
interface ISwitch extends SwitchProps {
@@ -223,7 +210,7 @@ class CreateChannelView extends React.Component {
const { channelName, type, readOnly, broadcast, encrypted, isTeam } = this.state;
- const { users: usersProps, isFetching, create } = this.props;
+ const { users: usersProps, isFetching, dispatch } = this.props;
if (!channelName.trim() || isFetching) {
return;
@@ -233,7 +220,7 @@ class CreateChannelView extends React.Component user.name);
// create channel or team
- create({
+ const data = {
name: channelName,
users,
type,
@@ -242,15 +229,15 @@ class CreateChannelView extends React.Component {
logEvent(events.CR_REMOVE_USER);
- const { removeUser } = this.props;
- removeUser(user);
+ const { dispatch } = this.props;
+ dispatch(removeUser(user));
};
renderSwitch = ({ id, value, label, onValueChange, disabled = false }: ISwitch) => {
@@ -434,7 +421,7 @@ class CreateChannelView extends React.Component ({
+const mapStateToProps = (state: IApplicationState) => ({
baseUrl: state.server.server,
isFetching: state.createChannel.isFetching,
encryptionEnabled: state.encryption.enabled,
@@ -444,9 +431,4 @@ const mapStateToProps = (state: any) => ({
createPrivateChannelPermission: state.permissions['create-p']
});
-const mapDispatchToProps = (dispatch: Dispatch) => ({
- create: (data: ICreateFunction) => dispatch(createChannelRequestAction(data)),
- removeUser: (user: IOtherUser) => dispatch(removeUserAction(user))
-});
-
-export default connect(mapStateToProps, mapDispatchToProps)(withTheme(CreateChannelView));
+export default connect(mapStateToProps)(withTheme(CreateChannelView));
diff --git a/app/views/CreateDiscussionView/SelectChannel.tsx b/app/views/CreateDiscussionView/SelectChannel.tsx
index 41f7ab0b1..d779dc3af 100644
--- a/app/views/CreateDiscussionView/SelectChannel.tsx
+++ b/app/views/CreateDiscussionView/SelectChannel.tsx
@@ -7,6 +7,7 @@ import RocketChat from '../../lib/rocketchat';
import I18n from '../../i18n';
import { MultiSelect } from '../../containers/UIKit/MultiSelect';
import { themes } from '../../constants/colors';
+import { TSubscriptionModel } from '../../definitions/ISubscription';
import styles from './styles';
import { ICreateDiscussionViewSelectChannel } from './interfaces';
@@ -20,7 +21,7 @@ const SelectChannel = ({
serverVersion,
theme
}: ICreateDiscussionViewSelectChannel): JSX.Element => {
- const [channels, setChannels] = useState([]);
+ const [channels, setChannels] = useState([]);
const getChannels = debounce(async (keyword = '') => {
try {
diff --git a/app/views/CreateDiscussionView/SelectUsers.tsx b/app/views/CreateDiscussionView/SelectUsers.tsx
index d63c5ae6a..6ef6e25df 100644
--- a/app/views/CreateDiscussionView/SelectUsers.tsx
+++ b/app/views/CreateDiscussionView/SelectUsers.tsx
@@ -38,11 +38,11 @@ const SelectUsers = ({
const res = await RocketChat.search({ text: keyword, filterRooms: false });
let items = [
...users.filter((u: IUser) => selected.includes(u.name)),
- ...res.filter((r: IUser) => !users.find((u: IUser) => u.name === r.name))
+ ...res.filter(r => !users.find((u: IUser) => u.name === r.name))
];
const records = await usersCollection.query(Q.where('username', Q.oneOf(items.map(u => u.name)))).fetch();
items = items.map(item => {
- const index = records.findIndex((r: IUser) => r.username === item.name);
+ const index = records.findIndex(r => r.username === item.name);
if (index > -1) {
const record = records[index];
return {
diff --git a/app/views/CreateDiscussionView/index.tsx b/app/views/CreateDiscussionView/index.tsx
index 53d741d2d..34cebd894 100644
--- a/app/views/CreateDiscussionView/index.tsx
+++ b/app/views/CreateDiscussionView/index.tsx
@@ -24,7 +24,8 @@ import { E2E_ROOM_TYPES } from '../../lib/encryption/constants';
import styles from './styles';
import SelectUsers from './SelectUsers';
import SelectChannel from './SelectChannel';
-import { ICreateChannelViewProps } from './interfaces';
+import { ICreateChannelViewProps, IResult, IError } from './interfaces';
+import { IApplicationState } from '../../definitions';
class CreateChannelView extends React.Component {
private channel: any;
@@ -102,7 +103,7 @@ class CreateChannelView extends React.Component {
users,
encrypted
} = this.state;
- const { create } = this.props;
+ const { dispatch } = this.props;
const params: any = {
prid: prid || rid,
@@ -115,7 +116,7 @@ class CreateChannelView extends React.Component {
params.encrypted = encrypted ?? false;
}
- create(params);
+ dispatch(createDiscussionRequest(params));
};
valid = () => {
@@ -203,21 +204,17 @@ class CreateChannelView extends React.Component {
}
}
-const mapStateToProps = (state: any) => ({
+const mapStateToProps = (state: IApplicationState) => ({
user: getUserSelector(state),
server: state.server.server,
- error: state.createDiscussion.error,
+ error: state.createDiscussion.error as IError,
failure: state.createDiscussion.failure,
loading: state.createDiscussion.isFetching,
- result: state.createDiscussion.result,
- blockUnauthenticatedAccess: state.settings.Accounts_AvatarBlockUnauthenticatedAccess ?? true,
- serverVersion: state.server.version,
+ result: state.createDiscussion.result as IResult,
+ blockUnauthenticatedAccess: !!state.settings.Accounts_AvatarBlockUnauthenticatedAccess ?? true,
+ serverVersion: state.server.version as string,
isMasterDetail: state.app.isMasterDetail,
encryptionEnabled: state.encryption.enabled
});
-const mapDispatchToProps = (dispatch: any) => ({
- create: (data: any) => dispatch(createDiscussionRequest(data))
-});
-
-export default connect(mapStateToProps, mapDispatchToProps)(withTheme(CreateChannelView));
+export default connect(mapStateToProps)(withTheme(CreateChannelView));
diff --git a/app/views/CreateDiscussionView/interfaces.ts b/app/views/CreateDiscussionView/interfaces.ts
index 6009881c1..cde570d5a 100644
--- a/app/views/CreateDiscussionView/interfaces.ts
+++ b/app/views/CreateDiscussionView/interfaces.ts
@@ -1,12 +1,17 @@
-import { RouteProp } from '@react-navigation/core';
-import { StackNavigationProp } from '@react-navigation/stack';
-
import { NewMessageStackParamList } from '../../stacks/types';
import { SubscriptionType } from '../../definitions/ISubscription';
+import { IBaseScreen } from '../../definitions';
-export interface ICreateChannelViewProps {
- navigation: StackNavigationProp;
- route: RouteProp;
+export interface IResult {
+ rid: string;
+ t: SubscriptionType;
+ prid: string;
+}
+
+export interface IError {
+ reason: string;
+}
+export interface ICreateChannelViewProps extends IBaseScreen {
server: string;
user: {
id: string;
@@ -14,16 +19,9 @@ export interface ICreateChannelViewProps {
};
create: Function;
loading: boolean;
- result: {
- rid: string;
- t: SubscriptionType;
- prid: string;
- };
+ result: IResult;
failure: boolean;
- error: {
- reason: string;
- };
- theme: string;
+ error: IError;
isMasterDetail: boolean;
blockUnauthenticatedAccess: boolean;
serverVersion: string;
diff --git a/app/views/DirectoryView/index.tsx b/app/views/DirectoryView/index.tsx
index 2213497be..b4b0689bd 100644
--- a/app/views/DirectoryView/index.tsx
+++ b/app/views/DirectoryView/index.tsx
@@ -157,7 +157,7 @@ class DirectoryView extends React.Component {
this.goRoom({ rid: result.room._id, name: item.username, t: 'd' });
}
} else if (['p', 'c'].includes(item.t) && !item.teamMain) {
- const { room } = await RocketChat.getRoomInfo(item._id);
+ const { room }: any = await RocketChat.getRoomInfo(item._id);
this.goRoom({
rid: item._id,
name: item.name,
diff --git a/app/views/DiscussionsView/DiscussionDetails.tsx b/app/views/DiscussionsView/DiscussionDetails.tsx
new file mode 100644
index 000000000..45635558f
--- /dev/null
+++ b/app/views/DiscussionsView/DiscussionDetails.tsx
@@ -0,0 +1,67 @@
+import React from 'react';
+import { StyleSheet, Text, View } from 'react-native';
+
+import { TThreadModel } from '../../definitions/IThread';
+import { CustomIcon } from '../../lib/Icons';
+import { themes } from '../../constants/colors';
+import sharedStyles from '../Styles';
+import { useTheme } from '../../theme';
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ marginTop: 8,
+ flexDirection: 'row',
+ alignItems: 'center'
+ },
+ detailsContainer: {
+ flex: 1,
+ flexDirection: 'row'
+ },
+ detailContainer: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ marginRight: 8
+ },
+ detailText: {
+ fontSize: 10,
+ marginLeft: 2,
+ ...sharedStyles.textSemibold
+ }
+});
+
+interface IDiscussionDetails {
+ item: TThreadModel;
+ date: string;
+}
+
+const DiscussionDetails = ({ item, date }: IDiscussionDetails): JSX.Element => {
+ const { theme } = useTheme();
+ let { dcount } = item;
+
+ if (dcount && dcount >= 1000) {
+ dcount = '+999';
+ }
+
+ return (
+
+
+
+
+
+ {dcount}
+
+
+
+
+
+
+ {date}
+
+
+
+
+ );
+};
+
+export default DiscussionDetails;
diff --git a/app/views/DiscussionsView/Item.stories.js b/app/views/DiscussionsView/Item.stories.js
new file mode 100644
index 000000000..f909600db
--- /dev/null
+++ b/app/views/DiscussionsView/Item.stories.js
@@ -0,0 +1,96 @@
+/* eslint-disable import/no-extraneous-dependencies, import/no-unresolved, import/extensions, react/prop-types */
+import React from 'react';
+import { storiesOf } from '@storybook/react-native';
+import { ScrollView } from 'react-native';
+import { Provider } from 'react-redux';
+
+import * as List from '../../containers/List';
+import { themes } from '../../constants/colors';
+import { ThemeContext } from '../../theme';
+import { store } from '../../../storybook/stories';
+import Item from './Item';
+
+const author = {
+ _id: 'userid',
+ username: 'rocket.cat',
+ name: 'Rocket Cat'
+};
+const baseUrl = 'https://open.rocket.chat';
+const date = new Date(2020, 10, 10, 10);
+const longText =
+ 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.';
+const defaultItem = {
+ msg: 'Message content',
+ tcount: 1,
+ replies: [1],
+ ts: date,
+ tlm: date,
+ u: author,
+ attachments: []
+};
+
+const BaseItem = ({ item, ...props }) => (
+ - alert('pressed')}
+ {...props}
+ />
+);
+
+const listDecorator = story => (
+
+
+ {story()}
+
+
+);
+
+const stories = storiesOf('Discussions.Item', module)
+ .addDecorator(listDecorator)
+ .addDecorator(story => {story()});
+
+stories.add('content', () => (
+ <>
+
+
+
+
+
+
+
+
+
+ >
+));
+
+const ThemeStory = ({ theme }) => (
+
+
+
+);
+
+stories.add('themes', () => (
+ <>
+
+
+
+ >
+));
diff --git a/app/views/DiscussionsView/Item.tsx b/app/views/DiscussionsView/Item.tsx
new file mode 100644
index 000000000..578b92be4
--- /dev/null
+++ b/app/views/DiscussionsView/Item.tsx
@@ -0,0 +1,93 @@
+import React from 'react';
+import { StyleSheet, Text, View } from 'react-native';
+import Touchable from 'react-native-platform-touchable';
+import moment from 'moment';
+
+import { useTheme } from '../../theme';
+import Avatar from '../../containers/Avatar';
+import sharedStyles from '../Styles';
+import { themes } from '../../constants/colors';
+import { MarkdownPreview } from '../../containers/markdown';
+import { formatDateThreads, makeThreadName } from '../../utils/room';
+import DiscussionDetails from './DiscussionDetails';
+import { TThreadModel } from '../../definitions/IThread';
+
+const styles = StyleSheet.create({
+ container: {
+ flexDirection: 'row',
+ padding: 16
+ },
+ contentContainer: {
+ flexDirection: 'column',
+ flex: 1
+ },
+ titleContainer: {
+ flexDirection: 'row',
+ marginBottom: 2,
+ justifyContent: 'space-between'
+ },
+ title: {
+ flexShrink: 1,
+ fontSize: 18,
+ ...sharedStyles.textMedium
+ },
+ time: {
+ fontSize: 14,
+ marginLeft: 4,
+ ...sharedStyles.textRegular
+ },
+ avatar: {
+ marginRight: 8
+ },
+ messageContainer: {
+ flexDirection: 'row'
+ },
+ markdown: {
+ flex: 1
+ }
+});
+
+interface IItem {
+ item: TThreadModel;
+ onPress: {
+ (...args: any[]): void;
+ stop(): void;
+ };
+}
+
+const Item = ({ item, onPress }: IItem): JSX.Element => {
+ const { theme } = useTheme();
+ const username = item?.u?.username;
+ let messageTime = '';
+ let messageDate = '';
+
+ if (item?.ts) {
+ messageTime = moment(item.ts).format('LT');
+ messageDate = formatDateThreads(item.ts);
+ }
+
+ return (
+ onPress(item)}
+ testID={`discussions-view-${item.msg}`}
+ style={{ backgroundColor: themes[theme].backgroundColor }}>
+
+
+
+